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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « vpython/application/application.go ('k') | vpython/python/find.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« 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