| OLD | NEW |
| 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 "os/exec" | 8 "os/exec" |
| 9 | 9 |
| 10 "github.com/luci/luci-go/common/errors" | 10 "github.com/luci/luci-go/common/errors" |
| 11 "github.com/luci/luci-go/common/logging" |
| 11 | 12 |
| 12 "golang.org/x/net/context" | 13 "golang.org/x/net/context" |
| 13 ) | 14 ) |
| 14 | 15 |
| 15 // LookPathResult is the result of LookPathFunc. | 16 // LookPathResult is the result of LookPathFunc. |
| 16 type LookPathResult struct { | 17 type LookPathResult struct { |
| 17 // Path, if not zero, is the absolute path to the identified target | 18 // Path, if not zero, is the absolute path to the identified target |
| 18 // executable. | 19 // executable. |
| 19 Path string | 20 Path string |
| 20 | 21 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 35 // | 36 // |
| 36 // A nil LookPathFunc will use exec.LookPath. | 37 // A nil LookPathFunc will use exec.LookPath. |
| 37 type LookPathFunc func(c context.Context, target string) (*LookPathResult, error
) | 38 type LookPathFunc func(c context.Context, target string) (*LookPathResult, error
) |
| 38 | 39 |
| 39 // Find attempts to find a Python interpreter matching the supplied version | 40 // Find attempts to find a Python interpreter matching the supplied version |
| 40 // using PATH. | 41 // using PATH. |
| 41 // | 42 // |
| 42 // In order to accommodate multiple configurations on operating systems, Find | 43 // In order to accommodate multiple configurations on operating systems, Find |
| 43 // will attempt to identify versions that appear on the path | 44 // will attempt to identify versions that appear on the path |
| 44 func Find(c context.Context, vers Version, lookPath LookPathFunc) (*Interpreter,
error) { | 45 func Find(c context.Context, vers Version, lookPath LookPathFunc) (*Interpreter,
error) { |
| 45 if lookPath == nil { | |
| 46 lookPath = func(c context.Context, target string) (*LookPathResu
lt, error) { | |
| 47 v, err := exec.LookPath(target) | |
| 48 if err != nil { | |
| 49 return nil, err | |
| 50 } | |
| 51 return &LookPathResult{Path: v}, nil | |
| 52 } | |
| 53 } | |
| 54 | |
| 55 // pythonM.m, pythonM, python | 46 // pythonM.m, pythonM, python |
| 56 searches := make([]string, 0, 3) | 47 searches := make([]string, 0, 3) |
| 57 pv := vers | 48 pv := vers |
| 58 pv.Patch = 0 | 49 pv.Patch = 0 |
| 59 if pv.Minor > 0 { | 50 if pv.Minor > 0 { |
| 60 searches = append(searches, pv.PythonBase()) | 51 searches = append(searches, pv.PythonBase()) |
| 61 pv.Minor = 0 | 52 pv.Minor = 0 |
| 62 } | 53 } |
| 63 if pv.Major > 0 { | 54 if pv.Major > 0 { |
| 64 searches = append(searches, pv.PythonBase()) | 55 searches = append(searches, pv.PythonBase()) |
| 65 pv.Major = 0 | 56 pv.Major = 0 |
| 66 } | 57 } |
| 67 searches = append(searches, pv.PythonBase()) | 58 searches = append(searches, pv.PythonBase()) |
| 68 | 59 |
| 69 » for _, s := range searches { | 60 » lookErrs := errors.NewLazyMultiError(len(searches)) |
| 70 » » lpr, err := lookPath(c, s) | 61 » for i, s := range searches { |
| 71 » » if err != nil { | 62 » » interp, err := findInterpreter(c, s, vers, lookPath) |
| 72 » » » if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNo
tFound { | 63 » » if err == nil { |
| 73 » » » » // Not found is okay. | 64 » » » return interp, nil |
| 74 » » » » continue | |
| 75 » » » } | |
| 76 » » » return nil, errors.Annotate(err).Reason("failed to searc
h PATH for: %(interp)q"). | |
| 77 » » » » D("interp", s). | |
| 78 » » » » Err() | |
| 79 } | 65 } |
| 80 | 66 |
| 81 » » i := Interpreter{ | 67 » » logging.WithError(err).Debugf(c, "Could not find Python for: %q.
", s) |
| 82 » » » Python: lpr.Path, | 68 » » lookErrs.Assign(i, err) |
| 83 » » } | |
| 84 | |
| 85 » » // If our LookPathResult included a target version, install that
into the | |
| 86 » » // Interpreter, allowing it to use this cached value when GetVer
sion is | |
| 87 » » // called instead of needing to perform an additional lookup. | |
| 88 » » // | |
| 89 » » // Note that our LookPathResult may not populate Version, in whi
ch case we | |
| 90 » » // will not pre-cache it. | |
| 91 » » if !lpr.Version.IsZero() { | |
| 92 » » » i.cachedVersion = &lpr.Version | |
| 93 » » } | |
| 94 » » if err := i.Normalize(); err != nil { | |
| 95 » » » return nil, err | |
| 96 » » } | |
| 97 | |
| 98 » » iv, err := i.GetVersion(c) | |
| 99 » » if err != nil { | |
| 100 » » » return nil, errors.Annotate(err).Reason("failed to get v
ersion for: %(interp)q"). | |
| 101 » » » » D("interp", i.Python). | |
| 102 » » » » Err() | |
| 103 » » } | |
| 104 » » if vers.IsSatisfiedBy(iv) { | |
| 105 » » » return &i, nil | |
| 106 » » } | |
| 107 } | 69 } |
| 108 | 70 |
| 109 » return nil, errors.New("no Python found") | 71 » // No Python interpreter could be identified. |
| 72 » return nil, errors.Annotate(lookErrs.Get()).Reason("no Python found").Er
r() |
| 110 } | 73 } |
| 74 |
| 75 func findInterpreter(c context.Context, name string, vers Version, lookPath Look
PathFunc) (*Interpreter, error) { |
| 76 if lookPath == nil { |
| 77 lookPath = osExecLookPath |
| 78 } |
| 79 lpr, err := lookPath(c, name) |
| 80 if err != nil { |
| 81 return nil, errors.Annotate(err).Reason("could not find executab
le for: %(name)q"). |
| 82 D("name", name). |
| 83 Err() |
| 84 } |
| 85 |
| 86 i := Interpreter{ |
| 87 Python: lpr.Path, |
| 88 } |
| 89 |
| 90 // If our LookPathResult included a target version, install that into th
e |
| 91 // Interpreter, allowing it to use this cached value when GetVersion is |
| 92 // called instead of needing to perform an additional lookup. |
| 93 // |
| 94 // Note that our LookPathResult may not populate Version, in which case
we |
| 95 // will not pre-cache it. |
| 96 if !lpr.Version.IsZero() { |
| 97 i.cachedVersion = &lpr.Version |
| 98 } |
| 99 if err := i.Normalize(); err != nil { |
| 100 return nil, err |
| 101 } |
| 102 |
| 103 iv, err := i.GetVersion(c) |
| 104 if err != nil { |
| 105 return nil, errors.Annotate(err).Reason("failed to get version f
or: %(interp)q"). |
| 106 D("interp", i.Python). |
| 107 Err() |
| 108 } |
| 109 if !vers.IsSatisfiedBy(iv) { |
| 110 return nil, errors.Reason("interpreter %(interp)q version %(inte
rpVersion)q does not satisfy %(version)q"). |
| 111 D("interp", i.Python). |
| 112 D("interpVersion", iv). |
| 113 D("version", vers). |
| 114 Err() |
| 115 } |
| 116 |
| 117 return &i, nil |
| 118 } |
| 119 |
| 120 func osExecLookPath(c context.Context, target string) (*LookPathResult, error) { |
| 121 v, err := exec.LookPath(target) |
| 122 if err != nil { |
| 123 return nil, err |
| 124 } |
| 125 return &LookPathResult{Path: v}, nil |
| 126 } |
| OLD | NEW |