| OLD | NEW |
| (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 "flag" |
| 9 "fmt" |
| 10 "io/ioutil" |
| 11 "os" |
| 12 "path/filepath" |
| 13 "time" |
| 14 |
| 15 "github.com/luci/luci-go/vpython" |
| 16 vpythonAPI "github.com/luci/luci-go/vpython/api/vpython" |
| 17 "github.com/luci/luci-go/vpython/spec" |
| 18 "github.com/luci/luci-go/vpython/venv" |
| 19 |
| 20 "github.com/luci/luci-go/common/cli" |
| 21 "github.com/luci/luci-go/common/errors" |
| 22 "github.com/luci/luci-go/common/logging" |
| 23 "github.com/luci/luci-go/common/logging/gologger" |
| 24 "github.com/luci/luci-go/common/system/environ" |
| 25 "github.com/luci/luci-go/common/system/exitcode" |
| 26 "github.com/luci/luci-go/common/system/filesystem" |
| 27 |
| 28 "github.com/maruel/subcommands" |
| 29 "github.com/mitchellh/go-homedir" |
| 30 "golang.org/x/net/context" |
| 31 ) |
| 32 |
| 33 // ReturnCodeError is an error wrapping a return code value. |
| 34 type ReturnCodeError int |
| 35 |
| 36 func (err ReturnCodeError) Error() string { |
| 37 return fmt.Sprintf("python interpreter returned non-zero error: %d", err
) |
| 38 } |
| 39 |
| 40 // Config is an application's default configuration. |
| 41 type Config struct { |
| 42 // PackageLoader is the package loader to use. |
| 43 PackageLoader venv.PackageLoader |
| 44 |
| 45 // VENVPackage is the VirtualEnv package to use for bootstrap generation
. |
| 46 VENVPackage vpythonAPI.Spec_Package |
| 47 |
| 48 // PruneThreshold, if > 0, is the maximum age of a VirtualEnv before it |
| 49 // becomes candidate for pruning. If <= 0, no pruning will be performed. |
| 50 // |
| 51 // See venv.Config's PruneThreshold. |
| 52 PruneThreshold time.Duration |
| 53 // MaxPrunesPerSweep, if > 0, is the maximum number of VirtualEnv that s
hould |
| 54 // be pruned passively. If <= 0, no limit will be applied. |
| 55 // |
| 56 // See venv.Config's MaxPrunesPerSweep. |
| 57 MaxPrunesPerSweep int |
| 58 |
| 59 // MaxScriptPathLen, if > 0, is the maximum generated script path lengt.
If |
| 60 // a generated script is expected to exist longer than this, we will err
or. |
| 61 // |
| 62 // See venv.Config's MaxScriptPathLen. |
| 63 MaxScriptPathLen int |
| 64 |
| 65 // Opts is the set of configured options. |
| 66 Opts vpython.Options |
| 67 } |
| 68 |
| 69 func (cfg *Config) mainDev(c context.Context) error { |
| 70 app := cli.Application{ |
| 71 Name: "vpython", |
| 72 Title: "VirtualEnv Python Bootstrap (Development Mode)", |
| 73 Context: func(context.Context) context.Context { |
| 74 // Discard the entry Context and use the one passed to u
s. |
| 75 c := c |
| 76 |
| 77 // Install our Config instance into the Context. |
| 78 c = withConfig(c, cfg) |
| 79 |
| 80 // Drop down to Info level debugging. |
| 81 if logging.GetLevel(c) > logging.Info { |
| 82 c = logging.SetLevel(c, logging.Info) |
| 83 } |
| 84 return c |
| 85 }, |
| 86 Commands: []*subcommands.Command{ |
| 87 subcommands.CmdHelp, |
| 88 subcommandInstall, |
| 89 }, |
| 90 } |
| 91 |
| 92 return ReturnCodeError(subcommands.Run(&app, cfg.Opts.Args)) |
| 93 } |
| 94 |
| 95 func (cfg *Config) mainImpl(c context.Context, args []string) error { |
| 96 logConfig := logging.Config{ |
| 97 Level: logging.Warning, |
| 98 } |
| 99 |
| 100 hdir, err := homedir.Dir() |
| 101 if err != nil { |
| 102 return errors.Annotate(err).Reason("failed to get user home dire
ctory").Err() |
| 103 } |
| 104 |
| 105 cfg.Opts = vpython.Options{ |
| 106 EnvConfig: venv.Config{ |
| 107 BaseDir: filepath.Join(hdir, ".vpython"), |
| 108 MaxHashLen: 6, |
| 109 Package: cfg.VENVPackage, |
| 110 PruneThreshold: cfg.PruneThreshold, |
| 111 MaxPrunesPerSweep: cfg.MaxPrunesPerSweep, |
| 112 MaxScriptPathLen: cfg.MaxScriptPathLen, |
| 113 Loader: cfg.PackageLoader, |
| 114 }, |
| 115 WaitForEnv: true, |
| 116 Environ: environ.System(), |
| 117 } |
| 118 var specPath string |
| 119 var devMode bool |
| 120 |
| 121 fs := flag.NewFlagSet("", flag.ExitOnError) |
| 122 fs.BoolVar(&devMode, "dev", devMode, |
| 123 "Enter development / subcommand mode (use 'help' for more option
s).") |
| 124 fs.StringVar(&cfg.Opts.EnvConfig.Python, "python", cfg.Opts.EnvConfig.Py
thon, |
| 125 "Path to system Python interpreter to use. Default is found on P
ATH.") |
| 126 fs.StringVar(&cfg.Opts.WorkDir, "workdir", cfg.Opts.WorkDir, |
| 127 "Working directory to run the Python interpreter in. Default is
current working directory.") |
| 128 fs.StringVar(&cfg.Opts.EnvConfig.BaseDir, "root", cfg.Opts.EnvConfig.Bas
eDir, |
| 129 "Path to virtual enviornment root directory. Default is the work
ing directory. "+ |
| 130 "If explicitly set to empty string, a temporary director
y will be used and cleaned up "+ |
| 131 "on completion.") |
| 132 fs.StringVar(&specPath, "spec", specPath, |
| 133 "Path to enviornment specification file to load. Default probes
for one.") |
| 134 logConfig.AddFlags(fs) |
| 135 |
| 136 if err := fs.Parse(args); err != nil { |
| 137 if err == flag.ErrHelp { |
| 138 return nil |
| 139 } |
| 140 return errors.Annotate(err).Reason("failed to parse flags").Err(
) |
| 141 } |
| 142 cfg.Opts.Args = fs.Args() |
| 143 |
| 144 c = logConfig.Set(c) |
| 145 |
| 146 // If an spec path was manually specified, load and use it. |
| 147 if specPath != "" { |
| 148 var err error |
| 149 if cfg.Opts.EnvConfig.Spec, err = spec.Load(specPath); err != ni
l { |
| 150 return errors.Annotate(err).Reason("failed to load speci
fication file (-spec) from: %(path)s"). |
| 151 D("path", specPath). |
| 152 Err() |
| 153 } |
| 154 } |
| 155 |
| 156 // If an empty BaseDir was specified, use a temporary directory and clea
n it |
| 157 // up on completion. |
| 158 if cfg.Opts.EnvConfig.BaseDir == "" { |
| 159 tdir, err := ioutil.TempDir("", "vpython") |
| 160 if err != nil { |
| 161 return errors.Annotate(err).Reason("failed to create tem
porary directory").Err() |
| 162 } |
| 163 defer func() { |
| 164 logging.Debugf(c, "Removing temporary directory: %s", td
ir) |
| 165 if terr := filesystem.RemoveAll(tdir); terr != nil { |
| 166 logging.WithError(terr).Warningf(c, "Failed to c
lean up temporary directory; leaking: %s", tdir) |
| 167 } |
| 168 }() |
| 169 cfg.Opts.EnvConfig.BaseDir = tdir |
| 170 } |
| 171 |
| 172 // Development mode (subcommands). |
| 173 if devMode { |
| 174 return cfg.mainDev(c) |
| 175 } |
| 176 |
| 177 if err := vpython.Run(c, cfg.Opts); err != nil { |
| 178 // If the process failed because of a non-zero return value, ret
urn that |
| 179 // as our error. |
| 180 if rc, has := exitcode.Get(errors.Unwrap(err)); has { |
| 181 err = ReturnCodeError(rc) |
| 182 } |
| 183 |
| 184 return errors.Annotate(err).Err() |
| 185 } |
| 186 return nil |
| 187 } |
| 188 |
| 189 // Main is the main application entry point. |
| 190 func (cfg *Config) Main(c context.Context) int { |
| 191 c = gologger.StdConfig.Use(c) |
| 192 c = logging.SetLevel(c, logging.Warning) |
| 193 |
| 194 return run(c, func(c context.Context) error { |
| 195 return cfg.mainImpl(c, os.Args[1:]) |
| 196 }) |
| 197 } |
| OLD | NEW |