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 venv | 5 package venv |
6 | 6 |
7 import ( | 7 import ( |
8 "fmt" | 8 "fmt" |
9 "os" | 9 "os" |
10 "path/filepath" | 10 "path/filepath" |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
133 return nil, errors.New("no package loader provided") | 133 return nil, errors.New("no package loader provided") |
134 } | 134 } |
135 | 135 |
136 // Resolve our base directory, if one is not supplied. | 136 // Resolve our base directory, if one is not supplied. |
137 if cfg.BaseDir == "" { | 137 if cfg.BaseDir == "" { |
138 // Use one in a temporary directory. | 138 // Use one in a temporary directory. |
139 cfg.BaseDir = filepath.Join(os.TempDir(), "vpython") | 139 cfg.BaseDir = filepath.Join(os.TempDir(), "vpython") |
140 logging.Debugf(c, "Using tempdir-relative environment root: %s",
cfg.BaseDir) | 140 logging.Debugf(c, "Using tempdir-relative environment root: %s",
cfg.BaseDir) |
141 } | 141 } |
142 if err := filesystem.AbsPath(&cfg.BaseDir); err != nil { | 142 if err := filesystem.AbsPath(&cfg.BaseDir); err != nil { |
143 » » return nil, errors.Annotate(err).Reason("failed to resolve absol
ute path of base directory").Err() | 143 » » return nil, errors.Annotate(err, "failed to resolve absolute pat
h of base directory").Err() |
144 } | 144 } |
145 | 145 |
146 // Enforce maximum path length. | 146 // Enforce maximum path length. |
147 if cfg.MaxScriptPathLen > 0 { | 147 if cfg.MaxScriptPathLen > 0 { |
148 if longestPath := longestGeneratedScriptPath(cfg.BaseDir); longe
stPath != "" { | 148 if longestPath := longestGeneratedScriptPath(cfg.BaseDir); longe
stPath != "" { |
149 longestPathLen := utf8.RuneCountInString(longestPath) | 149 longestPathLen := utf8.RuneCountInString(longestPath) |
150 if longestPathLen > cfg.MaxScriptPathLen { | 150 if longestPathLen > cfg.MaxScriptPathLen { |
151 » » » » return nil, errors.Reason("expected deepest path
length (%(len)d) exceeds threshold (%(threshold)d)"). | 151 » » » » return nil, errors.Reason( |
152 » » » » » D("len", longestPathLen). | 152 » » » » » "expected deepest path length (%d) excee
ds threshold (%d)", |
153 » » » » » D("threshold", cfg.MaxScriptPathLen). | 153 » » » » » longestPathLen, cfg.MaxScriptPathLen, |
154 » » » » » D("longestPath", longestPath). | 154 » » » » ).InternalReason("longestPath(%q)", longestPath)
.Err() |
155 » » » » » Err() | |
156 } | 155 } |
157 } | 156 } |
158 } | 157 } |
159 | 158 |
160 // Construct a new, independent Environment for this Env. | 159 // Construct a new, independent Environment for this Env. |
161 e = e.Clone() | 160 e = e.Clone() |
162 if cfg.Spec != nil { | 161 if cfg.Spec != nil { |
163 e.Spec = cfg.Spec.Clone() | 162 e.Spec = cfg.Spec.Clone() |
164 } | 163 } |
165 if err := spec.NormalizeEnvironment(e); err != nil { | 164 if err := spec.NormalizeEnvironment(e); err != nil { |
166 » » return nil, errors.Annotate(err).Reason("invalid environment").E
rr() | 165 » » return nil, errors.Annotate(err, "invalid environment").Err() |
167 } | 166 } |
168 | 167 |
169 // If the environment doesn't specify a VirtualEnv package (expected), u
se | 168 // If the environment doesn't specify a VirtualEnv package (expected), u
se |
170 // our default. | 169 // our default. |
171 if e.Spec.Virtualenv == nil { | 170 if e.Spec.Virtualenv == nil { |
172 e.Spec.Virtualenv = &cfg.Package | 171 e.Spec.Virtualenv = &cfg.Package |
173 } | 172 } |
174 | 173 |
175 if err := cfg.Loader.Resolve(c, e); err != nil { | 174 if err := cfg.Loader.Resolve(c, e); err != nil { |
176 » » return nil, errors.Annotate(err).Reason("failed to resolve packa
ges").Err() | 175 » » return nil, errors.Annotate(err, "failed to resolve packages").E
rr() |
177 } | 176 } |
178 | 177 |
179 if err := cfg.resolvePythonInterpreter(c, e.Spec); err != nil { | 178 if err := cfg.resolvePythonInterpreter(c, e.Spec); err != nil { |
180 » » return nil, errors.Annotate(err).Reason("failed to resolve syste
m Python interpreter").Err() | 179 » » return nil, errors.Annotate(err, "failed to resolve system Pytho
n interpreter").Err() |
181 } | 180 } |
182 e.Runtime.Path = cfg.si.Python | 181 e.Runtime.Path = cfg.si.Python |
183 e.Runtime.Version = e.Spec.PythonVersion | 182 e.Runtime.Version = e.Spec.PythonVersion |
184 | 183 |
185 var err error | 184 var err error |
186 if e.Runtime.Hash, err = cfg.si.Hash(); err != nil { | 185 if e.Runtime.Hash, err = cfg.si.Hash(); err != nil { |
187 return nil, err | 186 return nil, err |
188 } | 187 } |
189 logging.Debugf(c, "Resolved system Python runtime (%s @ %s): %s", | 188 logging.Debugf(c, "Resolved system Python runtime (%s @ %s): %s", |
190 e.Runtime.Version, e.Runtime.Hash, e.Runtime.Path) | 189 e.Runtime.Version, e.Runtime.Hash, e.Runtime.Path) |
191 | 190 |
192 // Ensure that our base directory exists. | 191 // Ensure that our base directory exists. |
193 if err := filesystem.MakeDirs(cfg.BaseDir); err != nil { | 192 if err := filesystem.MakeDirs(cfg.BaseDir); err != nil { |
194 » » return nil, errors.Annotate(err).Reason("could not create enviro
nment root: %(root)s"). | 193 » » return nil, errors.Annotate(err, "could not create environment r
oot: %s", cfg.BaseDir).Err() |
195 » » » D("root", cfg.BaseDir). | |
196 » » » Err() | |
197 } | 194 } |
198 | 195 |
199 // Generate our environment name based on the deterministic hash of its | 196 // Generate our environment name based on the deterministic hash of its |
200 // fully-resolved specification. | 197 // fully-resolved specification. |
201 envName := cfg.OverrideName | 198 envName := cfg.OverrideName |
202 if envName == "" { | 199 if envName == "" { |
203 envName = cfg.envNameForSpec(e.Spec, e.Runtime) | 200 envName = cfg.envNameForSpec(e.Spec, e.Runtime) |
204 } | 201 } |
205 env := cfg.envForName(envName, e) | 202 env := cfg.envForName(envName, e) |
206 return env, nil | 203 return env, nil |
207 } | 204 } |
208 | 205 |
209 // EnvName returns the VirtualEnv environment name for the environment that cfg | 206 // EnvName returns the VirtualEnv environment name for the environment that cfg |
210 // describes. | 207 // describes. |
211 func (cfg *Config) envNameForSpec(s *vpython.Spec, rt *vpython.Runtime) string { | 208 func (cfg *Config) envNameForSpec(s *vpython.Spec, rt *vpython.Runtime) string { |
212 name := spec.Hash(s, rt, EnvironmentVersion) | 209 name := spec.Hash(s, rt, EnvironmentVersion) |
213 if cfg.MaxHashLen > 0 && len(name) > cfg.MaxHashLen { | 210 if cfg.MaxHashLen > 0 && len(name) > cfg.MaxHashLen { |
214 name = name[:cfg.MaxHashLen] | 211 name = name[:cfg.MaxHashLen] |
215 } | 212 } |
216 return name | 213 return name |
217 } | 214 } |
218 | 215 |
219 // Prune performs a pruning round on the environment set described by this | 216 // Prune performs a pruning round on the environment set described by this |
220 // Config. | 217 // Config. |
221 func (cfg *Config) Prune(c context.Context) error { | 218 func (cfg *Config) Prune(c context.Context) error { |
222 if err := prune(c, cfg, nil); err != nil { | 219 if err := prune(c, cfg, nil); err != nil { |
223 » » return errors.Annotate(err).Err() | 220 » » return errors.Annotate(err, "").Err() |
224 } | 221 } |
225 return nil | 222 return nil |
226 } | 223 } |
227 | 224 |
228 // envForName creates an Env for a named directory. | 225 // envForName creates an Env for a named directory. |
229 // | 226 // |
230 // The Environment, e, can be nil; however, code paths that require it may not | 227 // The Environment, e, can be nil; however, code paths that require it may not |
231 // be called. | 228 // be called. |
232 func (cfg *Config) envForName(name string, e *vpython.Environment) *Env { | 229 func (cfg *Config) envForName(name string, e *vpython.Environment) *Env { |
233 // Env-specific root directory: <BaseDir>/<name> | 230 // Env-specific root directory: <BaseDir>/<name> |
234 venvRoot := filepath.Join(cfg.BaseDir, name) | 231 venvRoot := filepath.Join(cfg.BaseDir, name) |
235 binDir := venvBinDir(venvRoot) | 232 binDir := venvBinDir(venvRoot) |
236 return &Env{ | 233 return &Env{ |
237 Config: cfg, | 234 Config: cfg, |
238 Root: venvRoot, | 235 Root: venvRoot, |
239 Name: name, | 236 Name: name, |
240 Python: filepath.Join(binDir, "python"), | 237 Python: filepath.Join(binDir, "python"), |
241 Environment: e, | 238 Environment: e, |
242 BinDir: binDir, | 239 BinDir: binDir, |
243 EnvironmentStampPath: filepath.Join(venvRoot, fmt.Sprintf("envir
onment.%s.pb.txt", vpython.Version)), | 240 EnvironmentStampPath: filepath.Join(venvRoot, fmt.Sprintf("envir
onment.%s.pb.txt", vpython.Version)), |
244 | 241 |
245 lockPath: filepath.Join(cfg.BaseDir, fmt.Sprintf(".%s.lo
ck", name)), | 242 lockPath: filepath.Join(cfg.BaseDir, fmt.Sprintf(".%s.lo
ck", name)), |
246 completeFlagPath: filepath.Join(venvRoot, "complete.flag"), | 243 completeFlagPath: filepath.Join(venvRoot, "complete.flag"), |
247 } | 244 } |
248 } | 245 } |
249 | 246 |
250 func (cfg *Config) resolvePythonInterpreter(c context.Context, s *vpython.Spec)
error { | 247 func (cfg *Config) resolvePythonInterpreter(c context.Context, s *vpython.Spec)
error { |
251 specVers, err := python.ParseVersion(s.PythonVersion) | 248 specVers, err := python.ParseVersion(s.PythonVersion) |
252 if err != nil { | 249 if err != nil { |
253 » » return errors.Annotate(err).Reason("failed to parse Python versi
on from: %(value)q"). | 250 » » return errors.Annotate(err, "failed to parse Python version from
: %q", s.PythonVersion).Err() |
254 » » » D("value", s.PythonVersion). | |
255 » » » Err() | |
256 } | 251 } |
257 | 252 |
258 if cfg.Python == "" { | 253 if cfg.Python == "" { |
259 // No explicitly-specified Python path. Determine one based on t
he | 254 // No explicitly-specified Python path. Determine one based on t
he |
260 // specification. | 255 // specification. |
261 if cfg.si, err = python.Find(c, specVers, cfg.LookPathFunc); err
!= nil { | 256 if cfg.si, err = python.Find(c, specVers, cfg.LookPathFunc); err
!= nil { |
262 » » » return errors.Annotate(err).Reason("could not find Pytho
n for: %(vers)s"). | 257 » » » return errors.Annotate(err, "could not find Python for:
%s", specVers).Err() |
263 » » » » D("vers", specVers). | |
264 » » » » Err() | |
265 } | 258 } |
266 cfg.Python = cfg.si.Python | 259 cfg.Python = cfg.si.Python |
267 } else { | 260 } else { |
268 cfg.si = &python.Interpreter{ | 261 cfg.si = &python.Interpreter{ |
269 Python: cfg.Python, | 262 Python: cfg.Python, |
270 } | 263 } |
271 } | 264 } |
272 if err := cfg.si.Normalize(); err != nil { | 265 if err := cfg.si.Normalize(); err != nil { |
273 return err | 266 return err |
274 } | 267 } |
275 | 268 |
276 // Confirm that the version of the interpreter matches that which is | 269 // Confirm that the version of the interpreter matches that which is |
277 // expected. | 270 // expected. |
278 interpreterVers, err := cfg.si.GetVersion(c) | 271 interpreterVers, err := cfg.si.GetVersion(c) |
279 if err != nil { | 272 if err != nil { |
280 » » return errors.Annotate(err).Reason("failed to determine Python v
ersion for: %(python)s"). | 273 » » return errors.Annotate(err, "failed to determine Python version
for: %s", cfg.Python).Err() |
281 » » » D("python", cfg.Python). | |
282 » » » Err() | |
283 } | 274 } |
284 if !specVers.IsSatisfiedBy(interpreterVers) { | 275 if !specVers.IsSatisfiedBy(interpreterVers) { |
285 » » return errors.Reason("supplied Python version (%(supplied)s) doe
sn't match specification (%(spec)s)"). | 276 » » return errors.Reason("supplied Python version (%s) doesn't match
specification (%s)", interpreterVers, specVers).Err() |
286 » » » D("supplied", interpreterVers). | |
287 » » » D("spec", specVers). | |
288 » » » Err() | |
289 } | 277 } |
290 s.PythonVersion = interpreterVers.String() | 278 s.PythonVersion = interpreterVers.String() |
291 | 279 |
292 // Resolve to absolute path. | 280 // Resolve to absolute path. |
293 if err := filesystem.AbsPath(&cfg.Python); err != nil { | 281 if err := filesystem.AbsPath(&cfg.Python); err != nil { |
294 » » return errors.Annotate(err).Reason("could not get absolute path
for: %(python)s"). | 282 » » return errors.Annotate(err, "could not get absolute path for: %s
", cfg.Python).Err() |
295 » » » D("python", cfg.Python). | |
296 » » » Err() | |
297 } | 283 } |
298 return nil | 284 return nil |
299 } | 285 } |
300 | 286 |
301 func (cfg *Config) systemInterpreter() *python.Interpreter { return cfg.si } | 287 func (cfg *Config) systemInterpreter() *python.Interpreter { return cfg.si } |
OLD | NEW |