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

Side by Side Diff: vpython/venv/config.go

Issue 2699063004: vpython: Add VirtualEnv creation package. (Closed)
Patch Set: remake binaries Created 3 years, 10 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 | « no previous file | vpython/venv/prune.go » ('j') | vpython/venv/prune.go » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 venv
6
7 import (
8 "fmt"
9 "os"
10 "path/filepath"
11 "time"
12
13 "github.com/luci/luci-go/vpython/api/env"
14 "github.com/luci/luci-go/vpython/filesystem"
15 "github.com/luci/luci-go/vpython/python"
16 "github.com/luci/luci-go/vpython/spec"
17
18 "github.com/luci/luci-go/common/errors"
19 "github.com/luci/luci-go/common/logging"
20
21 "golang.org/x/net/context"
22 )
23
24 // PackageLoader loads package information from a specification file's Package
25 // message onto the local system.
26 type PackageLoader interface {
27 // Resolve processes the supplied packages, updating their fields to the ir
28 // resolved values. Resolved packages must fully specify the package ins tance
29 // that is being deployed.
30 //
31 // If needed, resolution may use the supplied root path as a persistent
32 // working directory. This path may not exist; Resolve is responsible fo r
33 // creating it if needed.
34 //
35 // root, if used, must be safe for concurrent use.
36 Resolve(c context.Context, root string, packages []*env.Spec_Package) er ror
37
38 // Ensure installs the supplied packages into root.
39 //
40 // The packages will have been previously resolved via Resolve.
41 Ensure(c context.Context, root string, packages []*env.Spec_Package) err or
42 }
43
44 // Config is the configuration for a Env.
45 //
46 // A VirtualEnv is specified based on its resolved env.Spec.
47 type Config struct {
48 // MaxHashLen is the maximum number of hash characters to use in Virtual Env
49 // directory names.
50 MaxHashLen int
51
52 // BaseDir is the parent directory of all VirtualEnv.
53 BaseDir string
54
55 // Package is the VirtualEnv package to install. It must be non-nil and
56 // valid. It will be used if the environment specification doesn't suppl y an
57 // overriding one.
58 Package env.Spec_Package
59
60 // Python is the Python interpreter to use. If empty, one will e resolve d
iannucci 2017/02/23 00:54:20 be
dnj 2017/02/23 20:38:49 Done.
61 // based on the Spec and the system path.
iannucci 2017/02/23 00:54:20 PATH?
dnj 2017/02/23 20:38:49 Done.
62 Python string
63
64 // Spec is the specification file to use to construct the VirtualEnv. If
65 // nil, or if fields are missing, they will be filled in by probing the system
66 // PATH.
67 Spec *env.Spec
68
69 // PruneThreshold, if >0, is the maximum age of a VirtualEnv before it s hould
70 // be pruned. If <= 0, there is no maximum age, so no pruning will be
71 // performed.
72 PruneThreshold time.Duration
73 // PruneLimit applies a limit to the number of items to prune per execut ion.
74 // If <= 0, no limit will be applied.
75 PruneLimit int
iannucci 2017/02/23 00:54:20 unlimited seems like dangerous defaults here. wdyt
dnj 2017/02/23 20:38:49 Renamed to MaxPrunesPerSweep. TBH I think unlimit
76
77 // Loader is the PackageLoader instance to use for package resolution an d
78 // deployment.
79 Loader PackageLoader
80
81 // LoaderResolveRoot is the common persistent root directory to use for the
82 // package loader's package resolution. This must be safe for concurrent
83 // use.
84 //
85 // Each VirtualEnv will have its own loader destination directory (root) .
86 // However, that root is homed in a directory that is named after the re solved
87 // packages. LoaderResolveRoot is used during setup to resolve those pac kage
88 // values, and therefore can't re-use the VirtualEnv root.
89 LoaderResolveRoot string
90 }
91
92 // Env processes the config, validating and, where appropriate, populating
93 // any components. Upon success, it returns a configured Env instance.
94 //
95 // The returned Env instance may or may octually exist. Setup must be called
iannucci 2017/02/23 00:54:20 may not actually
dnj 2017/02/23 20:38:49 Done.
96 // prior to using it.
97 func (cfg *Config) Env(c context.Context) (*Env, error) {
98 // We MUST have a package loader.
99 if cfg.Loader == nil {
100 return nil, errors.New("no package loader provided")
101 }
102
103 // Resolve our base directory, if one is not supplied.
104 if cfg.BaseDir == "" {
105 // Use one underneath our working directory.
iannucci 2017/02/23 00:54:20 comment is wrong
dnj 2017/02/23 20:38:49 Done.
106 cfg.BaseDir = filepath.Join(os.TempDir(), "vpython")
iannucci 2017/02/23 00:54:20 wdyt about warning if BaseDir is longer than some
dnj 2017/02/23 20:38:49 I'll add an option, "MaxGeneratedPathLen". If >0,
107 logging.Debugf(c, "Using tempdir-relative environment root: %s", cfg.BaseDir)
108 }
109 if err := filesystem.AbsPath(&cfg.BaseDir); err != nil {
110 return nil, errors.Annotate(err).Reason("failed to resolve absol ute path of base directory").Err()
111 }
112 if err := filesystem.MakeDirs(cfg.BaseDir); err != nil {
113 return nil, errors.Annotate(err).Reason("could not create enviro nment root: %(root)s").
114 D("root", cfg.BaseDir).
115 Err()
116 }
117
118 // Determine our common loader root.
119 if cfg.LoaderResolveRoot == "" {
120 cfg.LoaderResolveRoot = filepath.Join(cfg.BaseDir, ".package_loa der")
121 }
122
123 // Ensure and normalize our specification file.
124 if cfg.Spec == nil {
125 cfg.Spec = &env.Spec{}
126 }
127 if err := spec.Normalize(cfg.Spec); err != nil {
128 return nil, errors.Annotate(err).Reason("invalid specification") .Err()
129 }
130
131 // Choose our VirtualEnv package.
132 if cfg.Spec.Virtualenv == nil {
133 cfg.Spec.Virtualenv = &cfg.Package
134 }
135
136 if err := cfg.resolvePackages(c); err != nil {
137 return nil, errors.Annotate(err).Reason("failed to resolve packa ges").Err()
138 }
139
140 if err := cfg.resolvePythonInterpreter(c); err != nil {
141 return nil, errors.Annotate(err).Reason("failed to resolve syste m Python interpreter").Err()
142 }
143
144 // Generate our enviornment name based on the deterministic hash of its
145 // fully-resolved specification.
146 envName := spec.Hash(cfg.Spec)
147 if cfg.MaxHashLen > 0 && len(envName) > cfg.MaxHashLen {
148 envName = envName[:cfg.MaxHashLen]
149 }
150 return cfg.envForName(envName), nil
151 }
152
153 // Prune performs a pruning round on the environment set described by this
154 // Config.
155 func (cfg *Config) Prune(c context.Context) error {
156 if err := prune(c, cfg, ""); err != nil {
157 return errors.Annotate(err).Err()
158 }
159 return nil
160 }
161
162 // envForExisting creates an Env for a named directory.
163 func (cfg *Config) envForName(name string) *Env {
iannucci 2017/02/23 00:54:20 is it worth checking collisions?
dnj 2017/02/23 20:38:49 I don't think so. All operations will lock, so thi
164 // Env-specific root directory: <BaseDir>/<name>
165 venvRoot := filepath.Join(cfg.BaseDir, name)
166 return &Env{
167 Config: cfg,
168 Root: venvRoot,
169 Python: venvBinPath(venvRoot, "python"),
170 SpecPath: filepath.Join(venvRoot, "enviornment.pb.txt"),
171
172 name: name,
173 lockPath: filepath.Join(cfg.BaseDir, fmt.Sprintf(".%s.lo ck", name)),
174 completeFlagPath: filepath.Join(venvRoot, "complete.flag"),
175 }
176 }
177
178 func (cfg *Config) resolvePackages(c context.Context) error {
179 // Create a single package list. Our VirtualEnv will be index 0 (need
180 // this so we can back-port it into its VirtualEnv property).
181 packages := make([]*env.Spec_Package, 1, 1+len(cfg.Spec.Wheel))
182 packages[0] = cfg.Spec.Virtualenv
183 packages = append(packages, cfg.Spec.Wheel...)
184
185 // Resolve our packages. Because we're using pointers, the in-place
186 // updating will update the actual spec file!
187 if err := cfg.Loader.Resolve(c, cfg.LoaderResolveRoot, packages); err != nil {
iannucci 2017/02/23 00:54:20 why do we need separate resolve and ensure phases?
dnj 2017/02/23 20:38:49 Yep.
188 return errors.Annotate(err).Reason("failed to resolve packages") .Err()
189 }
190 return nil
191 }
192
193 func (cfg *Config) resolvePythonInterpreter(c context.Context) error {
194 specVers, err := python.ParseVersion(cfg.Spec.PythonVersion)
195 if err != nil {
196 return errors.Annotate(err).Reason("failed to parse Python versi on from: %(value)q").
197 D("value", cfg.Spec.PythonVersion).
198 Err()
199 }
200
201 var i *python.Interpreter
202 if cfg.Python == "" {
203 // No explicitly-specified Python path. Determine one based on t he
204 // specification.
205 if i, err = python.Find(c, specVers); err != nil {
206 return errors.Annotate(err).Reason("could not find Pytho n for: %(vers)s").
207 D("vers", specVers).
208 Err()
209 }
210 cfg.Python = i.Python
211 } else {
212 i = &python.Interpreter{
213 Python: cfg.Python,
214 }
215 }
216
217 // Confirm that the version of the interpreter matches that which is
218 // expected.
219 interpreterVers, err := i.GetVersion(c)
220 if err != nil {
221 return errors.Annotate(err).Reason("failed to determine Python v ersion for: %(python)s").
222 D("python", cfg.Python).
223 Err()
224 }
225 if !specVers.IsSatisfiedBy(interpreterVers) {
226 return errors.Reason("supplied Python version (%(supplied)s) doe sn't match specification (%(spec)s)").
227 D("supplied", interpreterVers).
228 D("spec", specVers).
229 Err()
230 }
231 cfg.Spec.PythonVersion = interpreterVers.String()
232
233 // Resolve to absolute path.
234 if err := filesystem.AbsPath(&cfg.Python); err != nil {
235 return errors.Annotate(err).Reason("could not get absolute path for: %(python)s").
236 D("python", cfg.Python).
237 Err()
238 }
239 logging.Debugf(c, "Resolved system Python interpreter (%s): %s", cfg.Spe c.PythonVersion, cfg.Python)
240 return nil
241 }
242
243 func (cfg *Config) systemInterpreter() *python.Command {
244 if cfg.Python == "" {
245 return nil
246 }
247 i := python.Interpreter{
248 Python: cfg.Python,
249 }
250 cmd := i.Command()
251 cmd.Isolated = true
252 return cmd
253 }
OLDNEW
« no previous file with comments | « no previous file | vpython/venv/prune.go » ('j') | vpython/venv/prune.go » ('J')

Powered by Google App Engine
This is Rietveld 408576698