go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/vpython/spec/resolve.go (about) 1 // Copyright 2017 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package spec 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/logging" 25 "go.chromium.org/luci/common/system/filesystem" 26 27 "go.chromium.org/luci/vpython/api/vpython" 28 "go.chromium.org/luci/vpython/python" 29 ) 30 31 // IsUserError is tagged into errors caused by bad user inputs (e.g. modules or 32 // scripts which don't exist). 33 var IsUserError = errors.BoolTag{ 34 Key: errors.NewTagKey("this error occurred due to a user input."), 35 } 36 37 // ResolveSpec resolves the configured environment specification. The resulting 38 // spec is installed into o's EnvConfig.Spec field. 39 func ResolveSpec(c context.Context, l *Loader, target python.Target, workDir string) (*vpython.Spec, error) { 40 // If there's no target, then we're dropping to an interactive shell 41 _, interactive := target.(python.NoTarget) 42 43 // Reading script from stdin or executing code from command line args are 44 // the same as no script in that we don't have a source file to key off 45 // of to find the spec, so resolve from CWD 46 // 47 // Executing code from command line args. 48 _, isCommandTarget := target.(python.CommandTarget) 49 // 50 // Reading script from stdin. 51 script, isScriptTarget := target.(python.ScriptTarget) 52 loadFromStdin := isScriptTarget && (script.Path == "-") 53 54 // If we're loading a module, then we could attempt to find the module and 55 // start the search there. But resolving the module path in full generality 56 // would be slow and/or complicated. Perhaps we'll revisit in the future, 57 // but for now let's just start the search in the CWD, as this is at least 58 // a subset of the paths we should search. 59 _, isModuleTarget := target.(python.ModuleTarget) 60 61 // We're either dropping to interactive mode, reading a script from stdin or 62 // command-line, or loading a module. Regardless, try to resolve the spec 63 // from the CWD. 64 if interactive || isCommandTarget || loadFromStdin || isModuleTarget { 65 spec, path, err := l.LoadForScript(c, workDir, false) 66 if err != nil { 67 return nil, errors.Annotate(err, "failed to load spec for script: %s", target).Err() 68 } 69 if spec != nil { 70 relpath, err := filepath.Rel(workDir, path) 71 if err != nil { 72 return nil, errors.Annotate(err, "failed to get relative path for %s", path).Err() 73 } 74 75 if interactive { 76 fmt.Fprintf(os.Stderr, "Starting interactive mode, loading vpython spec from %s\n", relpath) 77 } 78 79 if loadFromStdin { 80 fmt.Fprintf(os.Stderr, "Reading from stdin, loading vpython spec from %s\n", relpath) 81 } 82 83 return spec, nil 84 } 85 } 86 87 // If we're running a Python script, assert that the target script exists. 88 // Additionally, track whether it's a file or a module (directory). 89 isModule := false 90 if isScriptTarget { 91 logging.Debugf(c, "Resolved Python target script: %s", target) 92 93 // Resolve to absolute script path. 94 if err := filesystem.AbsPath(&script.Path); err != nil { 95 return nil, errors.Annotate(err, "failed to get absolute path of: %s", target).Err() 96 } 97 98 // Confirm that the script path actually exists. 99 st, err := os.Stat(script.Path) 100 if err != nil { 101 return nil, IsUserError.Apply(err) 102 } 103 104 // If the script is a directory, then we assume that we're doing a module 105 // invocation (__main__.py). 106 isModule = st.IsDir() 107 } 108 109 // If it's a script, try resolving from filesystem first. 110 if isScriptTarget { 111 spec, _, err := l.LoadForScript(c, script.Path, isModule) 112 if err != nil { 113 kind := "script" 114 if isModule { 115 kind = "module" 116 } 117 return nil, errors.Annotate(err, "failed to load spec for %s: %s", kind, target).Err() 118 } 119 if spec != nil { 120 return spec, nil 121 } 122 } 123 124 // If standard resolution doesn't yield a spec, fall back on our default spec. 125 logging.Infof(c, "Unable to resolve specification path. Using default specification.") 126 return nil, nil 127 }