Chromium Code Reviews| 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 application | 5 package application |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "flag" | 8 "flag" |
| 9 "fmt" | 9 "fmt" |
| 10 "io/ioutil" | 10 "io/ioutil" |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 23 "github.com/luci/luci-go/vpython/venv" | 23 "github.com/luci/luci-go/vpython/venv" |
| 24 | 24 |
| 25 cipdVersion "github.com/luci/luci-go/cipd/version" | 25 cipdVersion "github.com/luci/luci-go/cipd/version" |
| 26 "github.com/luci/luci-go/common/cli" | 26 "github.com/luci/luci-go/common/cli" |
| 27 "github.com/luci/luci-go/common/errors" | 27 "github.com/luci/luci-go/common/errors" |
| 28 "github.com/luci/luci-go/common/logging" | 28 "github.com/luci/luci-go/common/logging" |
| 29 "github.com/luci/luci-go/common/logging/gologger" | 29 "github.com/luci/luci-go/common/logging/gologger" |
| 30 "github.com/luci/luci-go/common/system/environ" | 30 "github.com/luci/luci-go/common/system/environ" |
| 31 "github.com/luci/luci-go/common/system/exitcode" | 31 "github.com/luci/luci-go/common/system/exitcode" |
| 32 "github.com/luci/luci-go/common/system/filesystem" | 32 "github.com/luci/luci-go/common/system/filesystem" |
| 33 "github.com/luci/luci-go/common/system/prober" | |
| 33 ) | 34 ) |
| 34 | 35 |
| 35 const ( | 36 const ( |
| 36 // VirtualEnvRootENV is an environment variable that, if set, will be us ed | 37 // VirtualEnvRootENV is an environment variable that, if set, will be us ed |
| 37 // as the default VirtualEnv root. | 38 // as the default VirtualEnv root. |
| 38 // | 39 // |
| 39 // This value overrides the default (~/.vpython), but can be overridden by the | 40 // This value overrides the default (~/.vpython), but can be overridden by the |
| 40 // "-root" flag. | 41 // "-root" flag. |
| 41 // | 42 // |
| 42 // Like "-root", if this value is present but empty, a tempdir will be u sed | 43 // Like "-root", if this value is present but empty, a tempdir will be u sed |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 58 type VerificationFunc func(context.Context, string, venv.PackageLoader, *vpython API.Environment) | 59 type VerificationFunc func(context.Context, string, venv.PackageLoader, *vpython API.Environment) |
| 59 | 60 |
| 60 // Config is an application's default configuration. | 61 // Config is an application's default configuration. |
| 61 type Config struct { | 62 type Config struct { |
| 62 // PackageLoader is the package loader to use. | 63 // PackageLoader is the package loader to use. |
| 63 PackageLoader venv.PackageLoader | 64 PackageLoader venv.PackageLoader |
| 64 | 65 |
| 65 // VENVPackage is the VirtualEnv package to use for bootstrap generation . | 66 // VENVPackage is the VirtualEnv package to use for bootstrap generation . |
| 66 VENVPackage vpythonAPI.Spec_Package | 67 VENVPackage vpythonAPI.Spec_Package |
| 67 | 68 |
| 69 // RelativePathOverride is a series of forward-slash-delimited paths to | |
| 70 // directories relative to the "vpython" executable that will be checked | |
| 71 // for Python targets prior to checking PATH. This allows bundles (e.g., CIPD) | |
| 72 // that include both the wrapper and a real implementation, to force the | |
| 73 // wrapper to use the bundled implementation if present. | |
| 74 // | |
| 75 // See "github.com/luci/luci-go/common/wrapper/prober.Probe"'s | |
| 76 // RelativePathOverride member for more information. | |
| 77 RelativePathOverride []string | |
| 78 | |
| 68 // PruneThreshold, if > 0, is the maximum age of a VirtualEnv before it | 79 // PruneThreshold, if > 0, is the maximum age of a VirtualEnv before it |
| 69 // becomes candidate for pruning. If <= 0, no pruning will be performed. | 80 // becomes candidate for pruning. If <= 0, no pruning will be performed. |
| 70 // | 81 // |
| 71 // See venv.Config's PruneThreshold. | 82 // See venv.Config's PruneThreshold. |
| 72 PruneThreshold time.Duration | 83 PruneThreshold time.Duration |
| 73 // MaxPrunesPerSweep, if > 0, is the maximum number of VirtualEnv that s hould | 84 // MaxPrunesPerSweep, if > 0, is the maximum number of VirtualEnv that s hould |
| 74 // be pruned passively. If <= 0, no limit will be applied. | 85 // be pruned passively. If <= 0, no limit will be applied. |
| 75 // | 86 // |
| 76 // See venv.Config's MaxPrunesPerSweep. | 87 // See venv.Config's MaxPrunesPerSweep. |
| 77 MaxPrunesPerSweep int | 88 MaxPrunesPerSweep int |
| (...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 138 fs.StringVar(&a.opts.EnvConfig.BaseDir, "root", a.opts.EnvConfig.BaseDir , | 149 fs.StringVar(&a.opts.EnvConfig.BaseDir, "root", a.opts.EnvConfig.BaseDir , |
| 139 "Path to virtual environment root directory. Default is the work ing directory. "+ | 150 "Path to virtual environment root directory. Default is the work ing directory. "+ |
| 140 "If explicitly set to empty string, a temporary director y will be used and cleaned up "+ | 151 "If explicitly set to empty string, a temporary director y will be used and cleaned up "+ |
| 141 "on completion.") | 152 "on completion.") |
| 142 fs.StringVar(&a.specPath, "spec", a.specPath, | 153 fs.StringVar(&a.specPath, "spec", a.specPath, |
| 143 "Path to environment specification file to load. Default probes for one.") | 154 "Path to environment specification file to load. Default probes for one.") |
| 144 | 155 |
| 145 a.logConfig.AddFlags(fs) | 156 a.logConfig.AddFlags(fs) |
| 146 } | 157 } |
| 147 | 158 |
| 148 func (a *application) mainImpl(c context.Context, args []string) error { | 159 func (a *application) mainImpl(c context.Context, argv0 string, args []string) e rror { |
| 149 // Determine our VirtualEnv base directory. | 160 // Determine our VirtualEnv base directory. |
| 150 if v, ok := a.opts.Environ.Get(VirtualEnvRootENV); ok { | 161 if v, ok := a.opts.Environ.Get(VirtualEnvRootENV); ok { |
| 151 a.opts.EnvConfig.BaseDir = v | 162 a.opts.EnvConfig.BaseDir = v |
| 152 } else { | 163 } else { |
| 153 hdir, err := homedir.Dir() | 164 hdir, err := homedir.Dir() |
| 154 if err != nil { | 165 if err != nil { |
| 155 return errors.Annotate(err).Reason("failed to get user h ome directory").Err() | 166 return errors.Annotate(err).Reason("failed to get user h ome directory").Err() |
| 156 } | 167 } |
| 157 a.opts.EnvConfig.BaseDir = filepath.Join(hdir, ".vpython") | 168 a.opts.EnvConfig.BaseDir = filepath.Join(hdir, ".vpython") |
| 158 } | 169 } |
| 159 | 170 |
| 160 // Extract "vpython" arguments and parse them. | 171 // Extract "vpython" arguments and parse them. |
| 161 fs := flag.NewFlagSet("", flag.ExitOnError) | 172 fs := flag.NewFlagSet("", flag.ExitOnError) |
| 162 fs.SetOutput(os.Stdout) // Python uses STDOUT for help and flag informat ion. | 173 fs.SetOutput(os.Stdout) // Python uses STDOUT for help and flag informat ion. |
| 163 | 174 |
| 164 a.addToFlagSet(fs) | 175 a.addToFlagSet(fs) |
| 165 selfArgs, args := extractFlagsForSet(args, fs) | 176 selfArgs, args := extractFlagsForSet(args, fs) |
| 166 if err := fs.Parse(selfArgs); err != nil && err != flag.ErrHelp { | 177 if err := fs.Parse(selfArgs); err != nil && err != flag.ErrHelp { |
| 167 return errors.Annotate(err).Reason("failed to parse flags").Err( ) | 178 return errors.Annotate(err).Reason("failed to parse flags").Err( ) |
| 168 } | 179 } |
| 169 | 180 |
| 181 // Identify the "self" executable. Use this to construct a "lookPath", w hich | |
| 182 // will be used to locate the base Python interpreter. | |
| 183 lp := lookPath{ | |
|
iannucci
2017/06/07 17:40:26
doesn't git implement this too? can some of this m
dnj
2017/06/07 17:43:26
Git does. The common code part is pretty boring -
| |
| 184 probeBase: prober.Probe{ | |
| 185 RelativePathOverride: a.RelativePathOverride, | |
| 186 }, | |
| 187 env: a.opts.Environ, | |
| 188 } | |
| 189 if err := lp.probeBase.ResolveSelf(argv0); err != nil { | |
| 190 logging.WithError(err).Warningf(c, "Failed to resolve 'self'") | |
| 191 } | |
| 192 a.opts.EnvConfig.LookPathFunc = lp.look | |
| 193 | |
| 170 if a.help { | 194 if a.help { |
| 171 » » return a.showPythonHelp(c, fs) | 195 » » return a.showPythonHelp(c, fs, &lp) |
| 172 } | 196 } |
| 173 | 197 |
| 174 c = a.logConfig.Set(c) | 198 c = a.logConfig.Set(c) |
| 175 | 199 |
| 176 // If an spec path was manually specified, load and use it. | 200 // If an spec path was manually specified, load and use it. |
| 177 if a.specPath != "" { | 201 if a.specPath != "" { |
| 178 var err error | 202 var err error |
| 179 if a.opts.EnvConfig.Spec, err = spec.Load(a.specPath); err != ni l { | 203 if a.opts.EnvConfig.Spec, err = spec.Load(a.specPath); err != ni l { |
| 180 return errors.Annotate(err).Reason("failed to load speci fication file (-spec) from: %(path)s"). | 204 return errors.Annotate(err).Reason("failed to load speci fication file (-spec) from: %(path)s"). |
| 181 D("path", a.specPath). | 205 D("path", a.specPath). |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 210 // as our error. | 234 // as our error. |
| 211 if rc, has := exitcode.Get(errors.Unwrap(err)); has { | 235 if rc, has := exitcode.Get(errors.Unwrap(err)); has { |
| 212 err = ReturnCodeError(rc) | 236 err = ReturnCodeError(rc) |
| 213 } | 237 } |
| 214 | 238 |
| 215 return errors.Annotate(err).Err() | 239 return errors.Annotate(err).Err() |
| 216 } | 240 } |
| 217 return nil | 241 return nil |
| 218 } | 242 } |
| 219 | 243 |
| 220 func (a *application) showPythonHelp(c context.Context, fs *flag.FlagSet) error { | 244 func (a *application) showPythonHelp(c context.Context, fs *flag.FlagSet, lp *lo okPath) error { |
| 221 self, err := os.Executable() | 245 self, err := os.Executable() |
| 222 if err != nil { | 246 if err != nil { |
| 223 self = "vpython" | 247 self = "vpython" |
| 224 } | 248 } |
| 225 vers, err := cipdVersion.GetStartupVersion() | 249 vers, err := cipdVersion.GetStartupVersion() |
| 226 if err == nil && vers.PackageName != "" && vers.InstanceID != "" { | 250 if err == nil && vers.PackageName != "" && vers.InstanceID != "" { |
| 227 self = fmt.Sprintf("%s (%s@%s)", self, vers.PackageName, vers.In stanceID) | 251 self = fmt.Sprintf("%s (%s@%s)", self, vers.PackageName, vers.In stanceID) |
| 228 } | 252 } |
| 229 | 253 |
| 230 fmt.Fprintf(os.Stdout, "Usage of %s:\n", self) | 254 fmt.Fprintf(os.Stdout, "Usage of %s:\n", self) |
| 231 fs.SetOutput(os.Stdout) | 255 fs.SetOutput(os.Stdout) |
| 232 fs.PrintDefaults() | 256 fs.PrintDefaults() |
| 233 | 257 |
| 234 » i, err := python.Find(c, python.Version{}) | 258 » i, err := python.Find(c, python.Version{}, lp.look) |
| 235 if err != nil { | 259 if err != nil { |
| 236 return errors.Annotate(err).Reason("could not find Python interp reter for help").Err() | 260 return errors.Annotate(err).Reason("could not find Python interp reter for help").Err() |
| 237 } | 261 } |
| 238 | 262 |
| 239 // Redirect all "--help" to Stdout for consistency. | 263 // Redirect all "--help" to Stdout for consistency. |
| 240 fmt.Fprintf(os.Stdout, "\nPython help for %s:\n", i.Python) | 264 fmt.Fprintf(os.Stdout, "\nPython help for %s:\n", i.Python) |
| 241 cmd := i.IsolatedCommand(c, "--help") | 265 cmd := i.IsolatedCommand(c, "--help") |
| 242 cmd.Stdout = os.Stdout | 266 cmd.Stdout = os.Stdout |
| 243 cmd.Stderr = os.Stdout | 267 cmd.Stderr = os.Stdout |
| 244 if err := cmd.Run(); err != nil { | 268 if err := cmd.Run(); err != nil { |
| 245 return errors.Annotate(err).Reason("failed to dump Python help f rom: %(interpreter)s"). | 269 return errors.Annotate(err).Reason("failed to dump Python help f rom: %(interpreter)s"). |
| 246 D("interpreter", i.Python). | 270 D("interpreter", i.Python). |
| 247 Err() | 271 Err() |
| 248 } | 272 } |
| 249 return nil | 273 return nil |
| 250 } | 274 } |
| 251 | 275 |
| 252 // Main is the main application entry point. | 276 // Main is the main application entry point. |
| 253 func (cfg *Config) Main(c context.Context) int { | 277 func (cfg *Config) Main(c context.Context) int { |
| 278 // Implementation of "checkWrapper": if CheckWrapperENV is set, we immed iately | |
| 279 // exit with a non-zero value. | |
| 280 env := environ.System() | |
| 281 if env.GetEmpty(CheckWrapperENV) != "" { | |
| 282 return 1 | |
| 283 } | |
| 284 | |
| 254 c = gologger.StdConfig.Use(c) | 285 c = gologger.StdConfig.Use(c) |
| 255 c = logging.SetLevel(c, logging.Error) | 286 c = logging.SetLevel(c, logging.Error) |
| 256 | 287 |
| 257 a := application{ | 288 a := application{ |
| 258 Config: cfg, | 289 Config: cfg, |
| 259 opts: vpython.Options{ | 290 opts: vpython.Options{ |
| 260 EnvConfig: venv.Config{ | 291 EnvConfig: venv.Config{ |
| 261 BaseDir: "", // (Determined below). | 292 BaseDir: "", // (Determined below). |
| 262 MaxHashLen: 6, | 293 MaxHashLen: 6, |
| 263 Package: cfg.VENVPackage, | 294 Package: cfg.VENVPackage, |
| 264 PruneThreshold: cfg.PruneThreshold, | 295 PruneThreshold: cfg.PruneThreshold, |
| 265 MaxPrunesPerSweep: cfg.MaxPrunesPerSweep, | 296 MaxPrunesPerSweep: cfg.MaxPrunesPerSweep, |
| 266 MaxScriptPathLen: cfg.MaxScriptPathLen, | 297 MaxScriptPathLen: cfg.MaxScriptPathLen, |
| 267 Loader: cfg.PackageLoader, | 298 Loader: cfg.PackageLoader, |
| 268 }, | 299 }, |
| 269 WaitForEnv: true, | 300 WaitForEnv: true, |
| 270 » » » Environ: environ.System(), | 301 » » » Environ: env, |
| 271 }, | 302 }, |
| 272 logConfig: logging.Config{ | 303 logConfig: logging.Config{ |
| 273 Level: logging.Error, | 304 Level: logging.Error, |
| 274 }, | 305 }, |
| 275 } | 306 } |
| 276 | 307 |
| 277 return run(c, func(c context.Context) error { | 308 return run(c, func(c context.Context) error { |
| 278 » » return a.mainImpl(c, os.Args[1:]) | 309 » » return a.mainImpl(c, os.Args[0], os.Args[1:]) |
| 279 }) | 310 }) |
| 280 } | 311 } |
| OLD | NEW |