Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(288)

Unified Diff: vpython/application/probe.go

Issue 2925723004: [vpython] Implement smart probing. (Closed)
Patch Set: sentinel text Created 3 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « vpython/application/application.go ('k') | vpython/python/find.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: vpython/application/probe.go
diff --git a/vpython/application/probe.go b/vpython/application/probe.go
new file mode 100644
index 0000000000000000000000000000000000000000..a12713e5bc986c3c3a4e95fbcf29219fd6dfe1b0
--- /dev/null
+++ b/vpython/application/probe.go
@@ -0,0 +1,137 @@
+// Copyright 2017 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package application
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ "golang.org/x/net/context"
+
+ "github.com/luci/luci-go/vpython/python"
+
+ "github.com/luci/luci-go/common/errors"
+ "github.com/luci/luci-go/common/logging"
+ "github.com/luci/luci-go/common/system/environ"
+ "github.com/luci/luci-go/common/system/exitcode"
+ "github.com/luci/luci-go/common/system/prober"
+)
+
+const (
+ // CheckWrapperENV is an environment variable that is used to detect if a
+ // given binary is a "vpython" wrapper instance. The "vpython" Main function
+ // will immediately exit with a non-zero return code if this environment
+ // variable is set.
+ //
+ // See checkWrapper for more details.
+ checkWrapperENV = "VPYTHON_CHECK_WRAPPER"
+
+ // checkWrapperSentinel is text that will be output by a "vpython" instance if
+ // checkWrapperENV is set. If an application exits with a non-zero error code
+ // and outputs this text to STDOUT, then it is considered a "vpython" wrapper.
+ checkWrapperSentinel = checkWrapperENV + ": vpython wrapper check, exiting with non-zero return code"
+)
+
+func wrapperCheck(env environ.Env) bool {
+ if env.GetEmpty(checkWrapperENV) != "" {
+ fmt.Fprintln(os.Stdout, checkWrapperSentinel)
+ return true
+ }
+ return false
+}
+
+type lookPath struct {
+ // probeBase is a base prober whose Self and SelfStat fields will be used
+ // during a lookPathImpl call. This ensures that we only have to call
+ // ResolveSelf once for any given application.
+ probeBase prober.Probe
+
+ env environ.Env
+ lastPath string
+ lastOutput string
+}
+
+func (lp *lookPath) look(c context.Context, target string) (*python.LookPathResult, error) {
+ // Construct our probe, derived it from our "probeBase" and tailored to the
+ // specific "target" we're looking for.
+ probe := lp.probeBase
+ probe.Target = target
+ probe.CheckWrapper = lp.checkWrapper
+
+ var result python.LookPathResult
+ var err error
+ if result.Path, err = probe.Locate(c, "", lp.env.Clone()); err != nil {
+ return nil, err
+ }
+
+ // During probing, we try and capture the output of "--version". If we do,
+ // and if we confirm that the "path" returned by the probe matches "lastPath",
+ // return this version along with the resolution.
+ if result.Path == lp.lastPath {
+ var err error
+ if result.Version, err = python.ParseVersionOutput(lp.lastOutput); err == nil {
+ logging.Debugf(c, "Detected Python version %q from probe candidate [%s]", result.Version, result.Path)
+ } else {
+ logging.Debugf(c, "Failed to parse version from probe candidate [%s]: %s", result.Path, err)
+ }
+ }
+
+ return &result, nil
+}
+
+// checkWrapper is a prober.CheckWrapperFunc that detects if a given path is a
+// "vpython" instance.
+//
+// It does this by running it "path --version" with CheckWrapperENV set. If
+// the target is a "vpython" wrapper, it will immediately exit with a non-zero
+// value (see Main). If it is not a valid Python program, its behavior is
+// undefined. If it is a valid Pytohn instance, it will exit with a Python
+// version string.
+//
+// As a side effect, the text output of the last version probe will be stored
+// in lp.lastOutput. The "look" function can pass this value on to
+// the LookPathResult.
+func (lp *lookPath) checkWrapper(c context.Context, path string, env environ.Env) (isWrapper bool, err error) {
+ env.Set(checkWrapperENV, "1")
+
+ cmd := exec.CommandContext(c, path, "--version")
+ cmd.Env = env.Sorted()
+
+ output, err := cmd.CombinedOutput()
+ rc, ok := exitcode.Get(err)
+ if !ok {
+ err = errors.Annotate(err).Reason("failed to check if %(path)q is a wrapper").
+ D("path", path).
+ Err()
+ return
+ }
+
+ // Retain the last output, so our "look" can reference it to extract the
+ // version string.
+ lp.lastPath = path
+ lp.lastOutput = string(output)
+
+ if rc != 0 {
+ // The target exited with a non-zero error code. It is a wrapper if it
+ // printed the sentinel text.
+ isWrapper = strings.TrimSpace(lp.lastOutput) == checkWrapperSentinel
+ if isWrapper {
+ // The target was successfully identified as a wrapper. Clear "err", which
+ // is non-nil b/c of the non-zero exit code, to indicate a successful
+ // wrapper check.
+ err = nil
+ return
+ }
+
+ // The target returned non-zero, but didn't identify as a wrapper. It is
+ // likely something that happens to be named the same thing as the target,
+ // which is an error.
+ err = errors.Annotate(err).Reason("wrapper check returned non-zero").Err()
+ }
+
+ return
+}
« no previous file with comments | « vpython/application/application.go ('k') | vpython/python/find.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698