blob: bf1bc54d0dec5dca1ad364361d18d20999296e4d [file] [log] [blame]
// 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)
}