| // Copyright 2017 The LUCI Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package vpython |
| |
| import ( |
| "context" |
| "os" |
| "os/exec" |
| "os/signal" |
| |
| "go.chromium.org/luci/vpython/python" |
| "go.chromium.org/luci/vpython/venv" |
| |
| "go.chromium.org/luci/common/logging" |
| "go.chromium.org/luci/common/system/environ" |
| "go.chromium.org/luci/common/system/exitcode" |
| ) |
| |
| // systemSpecificLaunch launches the process described by "cmd" while ensuring |
| // that the VirtualEnv lock is held throughout its duration (best effort). |
| // |
| // On Windows, we don't forward signals. Forwarding signals on Windows is |
| // nuanced. For now, we won't, since sending them via Python is similarly |
| // nuanced and not commonly done. |
| // |
| // For more discussion, see: |
| // https://github.com/golang/go/issues/6720 |
| // |
| // On Windows, we launch it as a child process and interpret any signal that we |
| // receive as terminal, cancelling the child. |
| func systemSpecificLaunch(c context.Context, ve *venv.Env, cl *python.CommandLine, env environ.Env, dir string) error { |
| return Exec(c, ve.Interpreter(), cl, env, dir, nil) |
| } |
| |
| func execImpl(c context.Context, argv []string, env environ.Env, dir string, setupFn func() error) error { |
| cmd := exec.Cmd{ |
| Path: argv[0], |
| Args: argv, |
| Env: env.Sorted(), |
| Dir: dir, |
| Stdin: os.Stdin, |
| Stdout: os.Stdout, |
| Stderr: os.Stderr, |
| } |
| |
| // At this point, ANY ERROR will be fatal (panic). We assume that each |
| // operation may permanently alter our runtime environment. |
| if setupFn != nil { |
| if err := setupFn(); err != nil { |
| panic(err) |
| } |
| } |
| |
| ch := make(chan os.Signal) |
| signal.Notify(ch, os.Interrupt) |
| go func() { |
| <-ch |
| logging.Debugf(c, "os.Interrupt recieved, restoring signal handler.") |
| signal.Stop(ch) |
| // Due to the nature of os.Interrupt (either CTRL_C_EVENT or |
| // CTRL_BREAK_EVENT), they're sent to the entire process group. Since we |
| // haven't created a separate group for `cmd`, we don't need to relay the |
| // signal (since `cmd` would have gotten it as well). |
| }() |
| |
| err := cmd.Run() |
| if rc, has := exitcode.Get(err); has { |
| // The process had an exit code (includes err==nil, 0). |
| logging.Debugf(c, "Python subprocess has terminated: %v", err) |
| os.Exit(rc) |
| panic("must not return") |
| } |
| panic(err) |
| } |