| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package application |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "os" |
| 10 "os/exec" |
| 11 "strings" |
| 12 |
| 13 "golang.org/x/net/context" |
| 14 |
| 15 "github.com/luci/luci-go/vpython/python" |
| 16 |
| 17 "github.com/luci/luci-go/common/errors" |
| 18 "github.com/luci/luci-go/common/logging" |
| 19 "github.com/luci/luci-go/common/system/environ" |
| 20 "github.com/luci/luci-go/common/system/exitcode" |
| 21 "github.com/luci/luci-go/common/system/prober" |
| 22 ) |
| 23 |
| 24 const ( |
| 25 // CheckWrapperENV is an environment variable that is used to detect if
a |
| 26 // given binary is a "vpython" wrapper instance. The "vpython" Main func
tion |
| 27 // will immediately exit with a non-zero return code if this environment |
| 28 // variable is set. |
| 29 // |
| 30 // See checkWrapper for more details. |
| 31 checkWrapperENV = "VPYTHON_CHECK_WRAPPER" |
| 32 |
| 33 // checkWrapperSentinel is text that will be output by a "vpython" insta
nce if |
| 34 // checkWrapperENV is set. If an application exits with a non-zero error
code |
| 35 // and outputs this text to STDOUT, then it is considered a "vpython" wr
apper. |
| 36 checkWrapperSentinel = checkWrapperENV + ": vpython wrapper check, exiti
ng with non-zero return code" |
| 37 ) |
| 38 |
| 39 func wrapperCheck(env environ.Env) bool { |
| 40 if env.GetEmpty(checkWrapperENV) != "" { |
| 41 fmt.Fprintln(os.Stdout, checkWrapperSentinel) |
| 42 return true |
| 43 } |
| 44 return false |
| 45 } |
| 46 |
| 47 type lookPath struct { |
| 48 // probeBase is a base prober whose Self and SelfStat fields will be use
d |
| 49 // during a lookPathImpl call. This ensures that we only have to call |
| 50 // ResolveSelf once for any given application. |
| 51 probeBase prober.Probe |
| 52 |
| 53 env environ.Env |
| 54 lastPath string |
| 55 lastOutput string |
| 56 } |
| 57 |
| 58 func (lp *lookPath) look(c context.Context, target string) (*python.LookPathResu
lt, error) { |
| 59 // Construct our probe, derived it from our "probeBase" and tailored to
the |
| 60 // specific "target" we're looking for. |
| 61 probe := lp.probeBase |
| 62 probe.Target = target |
| 63 probe.CheckWrapper = lp.checkWrapper |
| 64 |
| 65 var result python.LookPathResult |
| 66 var err error |
| 67 if result.Path, err = probe.Locate(c, "", lp.env.Clone()); err != nil { |
| 68 return nil, err |
| 69 } |
| 70 |
| 71 // During probing, we try and capture the output of "--version". If we d
o, |
| 72 // and if we confirm that the "path" returned by the probe matches "last
Path", |
| 73 // return this version along with the resolution. |
| 74 if result.Path == lp.lastPath { |
| 75 var err error |
| 76 if result.Version, err = python.ParseVersionOutput(lp.lastOutput
); err == nil { |
| 77 logging.Debugf(c, "Detected Python version %q from probe
candidate [%s]", result.Version, result.Path) |
| 78 } else { |
| 79 logging.Debugf(c, "Failed to parse version from probe ca
ndidate [%s]: %s", result.Path, err) |
| 80 } |
| 81 } |
| 82 |
| 83 return &result, nil |
| 84 } |
| 85 |
| 86 // checkWrapper is a prober.CheckWrapperFunc that detects if a given path is a |
| 87 // "vpython" instance. |
| 88 // |
| 89 // It does this by running it "path --version" with CheckWrapperENV set. If |
| 90 // the target is a "vpython" wrapper, it will immediately exit with a non-zero |
| 91 // value (see Main). If it is not a valid Python program, its behavior is |
| 92 // undefined. If it is a valid Pytohn instance, it will exit with a Python |
| 93 // version string. |
| 94 // |
| 95 // As a side effect, the text output of the last version probe will be stored |
| 96 // in lp.lastOutput. The "look" function can pass this value on to |
| 97 // the LookPathResult. |
| 98 func (lp *lookPath) checkWrapper(c context.Context, path string, env environ.Env
) (isWrapper bool, err error) { |
| 99 env.Set(checkWrapperENV, "1") |
| 100 |
| 101 cmd := exec.CommandContext(c, path, "--version") |
| 102 cmd.Env = env.Sorted() |
| 103 |
| 104 output, err := cmd.CombinedOutput() |
| 105 rc, ok := exitcode.Get(err) |
| 106 if !ok { |
| 107 err = errors.Annotate(err).Reason("failed to check if %(path)q i
s a wrapper"). |
| 108 D("path", path). |
| 109 Err() |
| 110 return |
| 111 } |
| 112 |
| 113 // Retain the last output, so our "look" can reference it to extract the |
| 114 // version string. |
| 115 lp.lastPath = path |
| 116 lp.lastOutput = string(output) |
| 117 |
| 118 if rc != 0 { |
| 119 // The target exited with a non-zero error code. It is a wrapper
if it |
| 120 // printed the sentinel text. |
| 121 isWrapper = strings.TrimSpace(lp.lastOutput) == checkWrapperSent
inel |
| 122 if isWrapper { |
| 123 // The target was successfully identified as a wrapper.
Clear "err", which |
| 124 // is non-nil b/c of the non-zero exit code, to indicate
a successful |
| 125 // wrapper check. |
| 126 err = nil |
| 127 return |
| 128 } |
| 129 |
| 130 // The target returned non-zero, but didn't identify as a wrappe
r. It is |
| 131 // likely something that happens to be named the same thing as t
he target, |
| 132 // which is an error. |
| 133 err = errors.Annotate(err).Reason("wrapper check returned non-ze
ro").Err() |
| 134 } |
| 135 |
| 136 return |
| 137 } |
| OLD | NEW |