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

Side by Side Diff: vpython/python/interpreter.go

Issue 2905943002: [vpython] Incorporate interpreter path/hash. (Closed)
Patch Set: 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/python/find.go ('k') | vpython/python/python_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2017 The LUCI Authors. All rights reserved. 1 // Copyright 2017 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0 2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file. 3 // that can be found in the LICENSE file.
4 4
5 package python 5 package python
6 6
7 import ( 7 import (
8 "crypto/sha256"
9 "encoding/hex"
10 "io"
11 "os"
8 "os/exec" 12 "os/exec"
13 "path/filepath"
9 "strings" 14 "strings"
10 "sync" 15 "sync"
11 16
12 "github.com/luci/luci-go/common/errors" 17 "github.com/luci/luci-go/common/errors"
18 "github.com/luci/luci-go/common/system/filesystem"
13 19
14 "golang.org/x/net/context" 20 "golang.org/x/net/context"
15 ) 21 )
16 22
17 type runnerFunc func(cmd *exec.Cmd, capture bool) (string, error) 23 type runnerFunc func(cmd *exec.Cmd, capture bool) (string, error)
18 24
19 // Interpreter represents a system Python interpreter. It exposes the ability 25 // Interpreter represents a system Python interpreter. It exposes the ability
20 // to use common functionality of that interpreter. 26 // to use common functionality of that interpreter.
21 type Interpreter struct { 27 type Interpreter struct {
22 // Python is the path to the system Python interpreter. 28 // Python is the path to the system Python interpreter.
23 Python string 29 Python string
24 30
25 // cachedVersion is the cached Version for this interpreter. It is popul ated 31 // cachedVersion is the cached Version for this interpreter. It is popul ated
26 // on the first GetVersion call. 32 // on the first GetVersion call.
27 cachedVersion *Version 33 cachedVersion *Version
28 cachedVersionMu sync.Mutex 34 cachedVersionMu sync.Mutex
29 35
36 // cachedHash is the cached SHA256 hash string of the interpreter's bina ry
37 // contents. It is populated once, protected by cachedHashOnce.
38 cachedHash string
39 cachedHashErr error
40 cachedHashOnce sync.Once
41
30 // testCommandHook, if not nil, is called on generated Command results p rior 42 // testCommandHook, if not nil, is called on generated Command results p rior
31 // to returning them. 43 // to returning them.
32 testCommandHook func(*exec.Cmd) 44 testCommandHook func(*exec.Cmd)
33 } 45 }
34 46
47 // Normalize normalizes the Interpreter configuration by resolving relative
48 // paths into absolute paths and evaluating symlnks.
49 func (i *Interpreter) Normalize() error {
50 if err := filesystem.AbsPath(&i.Python); err != nil {
51 return err
52 }
53 resolved, err := filepath.EvalSymlinks(i.Python)
54 if err != nil {
55 return errors.Annotate(err).Reason("could not evaluate symlinks for: %(path)q").
56 D("path", i.Python).
57 Err()
58 }
59 i.Python = resolved
60 return nil
61 }
62
35 // IsolatedCommand returns a configurable exec.Cmd structure bound to this 63 // IsolatedCommand returns a configurable exec.Cmd structure bound to this
36 // Interpreter. 64 // Interpreter.
37 // 65 //
38 // The supplied arguments have several Python isolation flags prepended to them 66 // The supplied arguments have several Python isolation flags prepended to them
39 // to remove environmental factors such as: 67 // to remove environmental factors such as:
40 // - The user's "site.py". 68 // - The user's "site.py".
41 // - The current PYTHONPATH environment variable. 69 // - The current PYTHONPATH environment variable.
42 // - Compiled ".pyc/.pyo" files. 70 // - Compiled ".pyc/.pyo" files.
43 func (i *Interpreter) IsolatedCommand(c context.Context, args ...string) *exec.C md { 71 func (i *Interpreter) IsolatedCommand(c context.Context, args ...string) *exec.C md {
44 // Isolate the supplied arguments. 72 // Isolate the supplied arguments.
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
78 106
79 if v, err = parseVersionOutput(strings.TrimSpace(string(out))); err != n il { 107 if v, err = parseVersionOutput(strings.TrimSpace(string(out))); err != n il {
80 err = errors.Annotate(err).Err() 108 err = errors.Annotate(err).Err()
81 return 109 return
82 } 110 }
83 111
84 i.cachedVersion = &v 112 i.cachedVersion = &v
85 return 113 return
86 } 114 }
87 115
116 // Hash returns the SHA256 hash string of this interpreter.
117 //
118 // The hash value is cached; if called multiple times, the cached value will
119 // be returned.
120 func (i *Interpreter) Hash() (string, error) {
121 hashInterpreter := func(path string) (string, error) {
122 fd, err := os.Open(i.Python)
123 if err != nil {
124 return "", errors.Annotate(err).Reason("failed to open i nterpreter").Err()
125 }
126 defer fd.Close()
127
128 hash := sha256.New()
129 if _, err := io.Copy(hash, fd); err != nil {
130 return "", errors.Annotate(err).Reason("failed to read [ %(path)s] for hashing").
131 D("path", path).
132 Err()
133 }
134
135 return hex.EncodeToString(hash.Sum(nil)), nil
136 }
137
138 i.cachedHashOnce.Do(func() {
139 i.cachedHash, i.cachedHashErr = hashInterpreter(i.Python)
140 })
141 return i.cachedHash, i.cachedHashErr
142 }
143
88 func parseVersionOutput(output string) (Version, error) { 144 func parseVersionOutput(output string) (Version, error) {
89 // Expected output: 145 // Expected output:
90 // Python X.Y.Z 146 // Python X.Y.Z
91 parts := strings.SplitN(output, " ", 2) 147 parts := strings.SplitN(output, " ", 2)
92 if len(parts) != 2 || parts[0] != "Python" { 148 if len(parts) != 2 || parts[0] != "Python" {
93 return Version{}, errors.Reason("unknown version output"). 149 return Version{}, errors.Reason("unknown version output").
94 D("output", output). 150 D("output", output).
95 Err() 151 Err()
96 } 152 }
97 153
98 v, err := ParseVersion(parts[1]) 154 v, err := ParseVersion(parts[1])
99 if err != nil { 155 if err != nil {
100 err = errors.Annotate(err).Reason("failed to parse version from: %(value)q"). 156 err = errors.Annotate(err).Reason("failed to parse version from: %(value)q").
101 D("value", parts[1]). 157 D("value", parts[1]).
102 Err() 158 Err()
103 return v, err 159 return v, err
104 } 160 }
105 return v, nil 161 return v, nil
106 } 162 }
OLDNEW
« no previous file with comments | « vpython/python/find.go ('k') | vpython/python/python_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698