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 "bytes" | 8 "bytes" |
9 "encoding/json" | 9 "encoding/json" |
10 "io/ioutil" | 10 "io/ioutil" |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
48 return func() error { | 48 return func() error { |
49 logging.Debugf(c, "Lock is currently held. Sleeping %v and retry
ing...", lockHeldDelay) | 49 logging.Debugf(c, "Lock is currently held. Sleeping %v and retry
ing...", lockHeldDelay) |
50 tr := clock.Sleep(c, lockHeldDelay) | 50 tr := clock.Sleep(c, lockHeldDelay) |
51 return tr.Err | 51 return tr.Err |
52 } | 52 } |
53 } | 53 } |
54 | 54 |
55 func withTempDir(l logging.Logger, prefix string, fn func(string) error) error { | 55 func withTempDir(l logging.Logger, prefix string, fn func(string) error) error { |
56 tdir, err := ioutil.TempDir("", prefix) | 56 tdir, err := ioutil.TempDir("", prefix) |
57 if err != nil { | 57 if err != nil { |
58 » » return errors.Annotate(err).Reason("failed to create temporary d
irectory").Err() | 58 » » return errors.Annotate(err, "failed to create temporary director
y").Err() |
59 } | 59 } |
60 defer func() { | 60 defer func() { |
61 if err := filesystem.RemoveAll(tdir); err != nil { | 61 if err := filesystem.RemoveAll(tdir); err != nil { |
62 l.Infof("Failed to remove temporary directory: %s", err) | 62 l.Infof("Failed to remove temporary directory: %s", err) |
63 } | 63 } |
64 }() | 64 }() |
65 | 65 |
66 return fn(tdir) | 66 return fn(tdir) |
67 } | 67 } |
68 | 68 |
69 // EnvRootFromStampPath calculates the environment root from an exported | 69 // EnvRootFromStampPath calculates the environment root from an exported |
70 // environment specification file path. | 70 // environment specification file path. |
71 // | 71 // |
72 // The specification path is: <EnvRoot>/<SpecHash>/EnvironmentStampPath, so our | 72 // The specification path is: <EnvRoot>/<SpecHash>/EnvironmentStampPath, so our |
73 // EnvRoot is two directories up. | 73 // EnvRoot is two directories up. |
74 // | 74 // |
75 // We export EnvSpecPath as an asbolute path. However, since someone else | 75 // We export EnvSpecPath as an asbolute path. However, since someone else |
76 // could have overridden it or exported their own, let's make sure. | 76 // could have overridden it or exported their own, let's make sure. |
77 func EnvRootFromStampPath(path string) (string, error) { | 77 func EnvRootFromStampPath(path string) (string, error) { |
78 if err := filesystem.AbsPath(&path); err != nil { | 78 if err := filesystem.AbsPath(&path); err != nil { |
79 » » return "", errors.Annotate(err). | 79 » » return "", errors.Annotate(err, |
80 » » » Reason("failed to get absolute path for specification fi
le path: %(path)s"). | 80 » » » "failed to get absolute path for specification file path
: %(path)s", path).Err() |
81 » » » Err() | |
82 } | 81 } |
83 return filepath.Dir(filepath.Dir(path)), nil | 82 return filepath.Dir(filepath.Dir(path)), nil |
84 } | 83 } |
85 | 84 |
86 // With creates a new Env and executes "fn" with assumed ownership of that Env. | 85 // With creates a new Env and executes "fn" with assumed ownership of that Env. |
87 // | 86 // |
88 // The Context passed to "fn" will be cancelled if we lose perceived ownership | 87 // The Context passed to "fn" will be cancelled if we lose perceived ownership |
89 // of the configured environment. This is not an expected scenario, and should | 88 // of the configured environment. This is not an expected scenario, and should |
90 // be considered an error condition. The Env passed to "fn" is valid only for | 89 // be considered an error condition. The Env passed to "fn" is valid only for |
91 // the duration of the callback. | 90 // the duration of the callback. |
(...skipping 14 matching lines...) Expand all Loading... |
106 // If our configured VirtualEnv is, itself, an empty then we can | 105 // If our configured VirtualEnv is, itself, an empty then we can |
107 // skip this. | 106 // skip this. |
108 var e *vpython.Environment | 107 var e *vpython.Environment |
109 if cfg.HasWheels() { | 108 if cfg.HasWheels() { |
110 // Use an empty VirtualEnv to probe the runtime environment. | 109 // Use an empty VirtualEnv to probe the runtime environment. |
111 // | 110 // |
112 // Disable pruning for this step since we'll be doing that later
with the | 111 // Disable pruning for this step since we'll be doing that later
with the |
113 // full environment initialization. | 112 // full environment initialization. |
114 emptyEnv, err := cfg.WithoutWheels().makeEnv(c, nil) | 113 emptyEnv, err := cfg.WithoutWheels().makeEnv(c, nil) |
115 if err != nil { | 114 if err != nil { |
116 » » » return errors.Annotate(err).Reason("failed to initialize
empty probe environment").Err() | 115 » » » return errors.Annotate(err, "failed to initialize empty
probe environment").Err() |
117 } | 116 } |
118 if err := emptyEnv.ensure(c, blocking); err != nil { | 117 if err := emptyEnv.ensure(c, blocking); err != nil { |
119 » » » return errors.Annotate(err).Reason("failed to create emp
ty probe environment").Err() | 118 » » » return errors.Annotate(err, "failed to create empty prob
e environment").Err() |
120 } | 119 } |
121 | 120 |
122 usedEnvs.Add(emptyEnv.Name) | 121 usedEnvs.Add(emptyEnv.Name) |
123 e = emptyEnv.Environment | 122 e = emptyEnv.Environment |
124 } | 123 } |
125 | 124 |
126 // Run the real config, now with runtime data. | 125 // Run the real config, now with runtime data. |
127 env, err := cfg.makeEnv(c, e) | 126 env, err := cfg.makeEnv(c, e) |
128 if err != nil { | 127 if err != nil { |
129 return err | 128 return err |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
221 // This will generally happen if another
process created the environment | 220 // This will generally happen if another
process created the environment |
222 // in between when we checked for the co
mpletion stamp initially and | 221 // in between when we checked for the co
mpletion stamp initially and |
223 // when we actually obtained the lock. | 222 // when we actually obtained the lock. |
224 logging.Debugf(c, "Completion flag found
! Environment is set-up: %s", e.completeFlagPath) | 223 logging.Debugf(c, "Completion flag found
! Environment is set-up: %s", e.completeFlagPath) |
225 return nil | 224 return nil |
226 } | 225 } |
227 logging.WithError(err).Debugf(c, "VirtualEnv is
not complete.") | 226 logging.WithError(err).Debugf(c, "VirtualEnv is
not complete.") |
228 | 227 |
229 // No complete flag. Create a new VirtualEnv her
e. | 228 // No complete flag. Create a new VirtualEnv her
e. |
230 if err := e.createLocked(c); err != nil { | 229 if err := e.createLocked(c); err != nil { |
231 » » » » » return errors.Annotate(err).Reason("fail
ed to create new VirtualEnv").Err() | 230 » » » » » return errors.Annotate(err, "failed to c
reate new VirtualEnv").Err() |
232 } | 231 } |
233 | 232 |
234 // Mark that this environment is complete. This
MUST succeed so other | 233 // Mark that this environment is complete. This
MUST succeed so other |
235 // instances know that this environment is compl
ete. | 234 // instances know that this environment is compl
ete. |
236 if err := e.touchCompleteFlagLocked(); err != ni
l { | 235 if err := e.touchCompleteFlagLocked(); err != ni
l { |
237 » » » » » return errors.Annotate(err).Reason("fail
ed to create complete flag").Err() | 236 » » » » » return errors.Annotate(err, "failed to c
reate complete flag").Err() |
238 } | 237 } |
239 | 238 |
240 logging.Debugf(c, "Successfully created new virt
ual environment [%s]!", e.Name) | 239 logging.Debugf(c, "Successfully created new virt
ual environment [%s]!", e.Name) |
241 return nil | 240 return nil |
242 }) | 241 }) |
243 | 242 |
244 case fslock.ErrLockHeld: | 243 case fslock.ErrLockHeld: |
245 // We couldn't get an exclusive lock. Try again to load
the environment | 244 // We couldn't get an exclusive lock. Try again to load
the environment |
246 // stamp, asserting the existence of the completion flag
in the process. | 245 // stamp, asserting the existence of the completion flag
in the process. |
247 // If another process has created the environment, we ma
y be able to use | 246 // If another process has created the environment, we ma
y be able to use |
248 // it without ever having to obtain its lock! | 247 // it without ever having to obtain its lock! |
249 if err := e.AssertCompleteAndLoad(); err == nil { | 248 if err := e.AssertCompleteAndLoad(); err == nil { |
250 logging.Infof(c, "Environment was completed whil
e waiting for lock: %s", e.EnvironmentStampPath) | 249 logging.Infof(c, "Environment was completed whil
e waiting for lock: %s", e.EnvironmentStampPath) |
251 return nil | 250 return nil |
252 } | 251 } |
253 | 252 |
254 logging.Fields{ | 253 logging.Fields{ |
255 logging.ErrorKey: err, | 254 logging.ErrorKey: err, |
256 "path": e.EnvironmentStampPath, | 255 "path": e.EnvironmentStampPath, |
257 }.Debugf(c, "Lock is held, and environment is not comple
te.") | 256 }.Debugf(c, "Lock is held, and environment is not comple
te.") |
258 if !blocking { | 257 if !blocking { |
259 » » » » return errors.Annotate(err).Reason("VirtualEnv l
ock is currently held (non-blocking)").Err() | 258 » » » » return errors.Annotate(err, "VirtualEnv lock is
currently held (non-blocking)").Err() |
260 } | 259 } |
261 | 260 |
262 // Some other process holds the lock. Sleep a little and
retry. | 261 // Some other process holds the lock. Sleep a little and
retry. |
263 if err := blocker(c)(); err != nil { | 262 if err := blocker(c)(); err != nil { |
264 return err | 263 return err |
265 } | 264 } |
266 | 265 |
267 default: | 266 default: |
268 » » » return errors.Annotate(err).Reason("failed to create Vir
tualEnv").Err() | 267 » » » return errors.Annotate(err, "failed to create VirtualEnv
").Err() |
269 } | 268 } |
270 } | 269 } |
271 } | 270 } |
272 | 271 |
273 func (e *Env) withImpl(c context.Context, blocking bool, used stringset.Set, | 272 func (e *Env) withImpl(c context.Context, blocking bool, used stringset.Set, |
274 fn func(context.Context, *Env) error) (err error) { | 273 fn func(context.Context, *Env) error) (err error) { |
275 | 274 |
276 // Setup the VirtualEnv environment. | 275 // Setup the VirtualEnv environment. |
277 // | 276 // |
278 // Setup will obtain an exclusive lock on the environment for set-up and | 277 // Setup will obtain an exclusive lock on the environment for set-up and |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
323 if !environmentWasIncomplete { | 322 if !environmentWasIncomplete { |
324 return nil | 323 return nil |
325 } | 324 } |
326 | 325 |
327 case fslock.ErrLockHeld: | 326 case fslock.ErrLockHeld: |
328 logging.Fields{ | 327 logging.Fields{ |
329 logging.ErrorKey: err, | 328 logging.ErrorKey: err, |
330 "path": e.EnvironmentStampPath, | 329 "path": e.EnvironmentStampPath, |
331 }.Debugf(c, "Could not obtain shared usage lock.") | 330 }.Debugf(c, "Could not obtain shared usage lock.") |
332 if !blocking { | 331 if !blocking { |
333 » » » » return errors.Annotate(err).Reason("VirtualEnv l
ock is currently held (non-blocking)").Err() | 332 » » » » return errors.Annotate(err, "VirtualEnv lock is
currently held (non-blocking)").Err() |
334 } | 333 } |
335 | 334 |
336 // Some other process holds the lock. Sleep a little and
retry. | 335 // Some other process holds the lock. Sleep a little and
retry. |
337 if err := blocker(c)(); err != nil { | 336 if err := blocker(c)(); err != nil { |
338 return err | 337 return err |
339 } | 338 } |
340 | 339 |
341 default: | 340 default: |
342 » » » return errors.Annotate(err).Reason("failed to use Virtua
lEnv").Err() | 341 » » » return errors.Annotate(err, "failed to use VirtualEnv").
Err() |
343 } | 342 } |
344 } | 343 } |
345 } | 344 } |
346 | 345 |
347 // Interpreter returns the VirtualEnv's isolated Python Interpreter instance. | 346 // Interpreter returns the VirtualEnv's isolated Python Interpreter instance. |
348 func (e *Env) Interpreter() *python.Interpreter { | 347 func (e *Env) Interpreter() *python.Interpreter { |
349 if e.interpreter == nil { | 348 if e.interpreter == nil { |
350 e.interpreter = &python.Interpreter{ | 349 e.interpreter = &python.Interpreter{ |
351 Python: e.Python, | 350 Python: e.Python, |
352 } | 351 } |
(...skipping 24 matching lines...) Expand all Loading... |
377 // | 376 // |
378 // An error is returned if the completion flag does not exist, or if the | 377 // An error is returned if the completion flag does not exist, or if the |
379 // VirtualEnv environment stamp could not be loaded. | 378 // VirtualEnv environment stamp could not be loaded. |
380 func (e *Env) AssertCompleteAndLoad() error { | 379 func (e *Env) AssertCompleteAndLoad() error { |
381 if err := e.assertComplete(); err != nil { | 380 if err := e.assertComplete(); err != nil { |
382 return err | 381 return err |
383 } | 382 } |
384 | 383 |
385 content, err := ioutil.ReadFile(e.EnvironmentStampPath) | 384 content, err := ioutil.ReadFile(e.EnvironmentStampPath) |
386 if err != nil { | 385 if err != nil { |
387 » » return errors.Annotate(err).Reason("failed to load file from: %(
path)s"). | 386 » » return errors.Annotate(err, "failed to load file from: %s", e.En
vironmentStampPath).Err() |
388 » » » D("path", e.EnvironmentStampPath). | |
389 » » » Err() | |
390 } | 387 } |
391 | 388 |
392 var environment vpython.Environment | 389 var environment vpython.Environment |
393 if err := proto.UnmarshalText(string(content), &environment); err != nil
{ | 390 if err := proto.UnmarshalText(string(content), &environment); err != nil
{ |
394 » » return errors.Annotate(err).Reason("failed to unmarshal vpython.
Env stamp from: %(path)s"). | 391 » » return errors.Annotate(err, "failed to unmarshal vpython.Env sta
mp from: %s", |
395 » » » D("path", e.EnvironmentStampPath). | 392 » » » e.EnvironmentStampPath).Err() |
396 » » » Err() | |
397 } | 393 } |
398 if err := spec.NormalizeEnvironment(&environment); err != nil { | 394 if err := spec.NormalizeEnvironment(&environment); err != nil { |
399 » » return errors.Annotate(err).Reason("failed to normalize stamp en
vironment").Err() | 395 » » return errors.Annotate(err, "failed to normalize stamp environme
nt").Err() |
400 } | 396 } |
401 | 397 |
402 // If we are configured with an environment, validate that it matches th
e | 398 // If we are configured with an environment, validate that it matches th
e |
403 // the environment that we just loaded. | 399 // the environment that we just loaded. |
404 // | 400 // |
405 // We only consider our environment-defining fields (Spec and Runtime). | 401 // We only consider our environment-defining fields (Spec and Runtime). |
406 // | 402 // |
407 // Note that both environments will have been normalized at this point,
so | 403 // Note that both environments will have been normalized at this point,
so |
408 // comparison should be reliable. | 404 // comparison should be reliable. |
409 if e.Environment != nil { | 405 if e.Environment != nil { |
410 if !proto.Equal(e.Environment.Spec, environment.Spec) { | 406 if !proto.Equal(e.Environment.Spec, environment.Spec) { |
411 return errors.New("environment stamp specification does
not match") | 407 return errors.New("environment stamp specification does
not match") |
412 } | 408 } |
413 if !proto.Equal(e.Environment.Runtime, environment.Runtime) { | 409 if !proto.Equal(e.Environment.Runtime, environment.Runtime) { |
414 return errors.New("environment stamp runtime does not ma
tch") | 410 return errors.New("environment stamp runtime does not ma
tch") |
415 } | 411 } |
416 } | 412 } |
417 e.Environment = &environment | 413 e.Environment = &environment |
418 return nil | 414 return nil |
419 } | 415 } |
420 | 416 |
421 func (e *Env) assertComplete() error { | 417 func (e *Env) assertComplete() error { |
422 // Ensure that the environment has its completion flag. | 418 // Ensure that the environment has its completion flag. |
423 switch _, err := os.Stat(e.completeFlagPath); { | 419 switch _, err := os.Stat(e.completeFlagPath); { |
424 case filesystem.IsNotExist(err): | 420 case filesystem.IsNotExist(err): |
425 return ErrNotComplete | 421 return ErrNotComplete |
426 case err != nil: | 422 case err != nil: |
427 » » return errors.Annotate(err).Reason("failed to check for completi
on flag").Err() | 423 » » return errors.Annotate(err, "failed to check for completion flag
").Err() |
428 default: | 424 default: |
429 return nil | 425 return nil |
430 } | 426 } |
431 } | 427 } |
432 | 428 |
433 func (e *Env) createLocked(c context.Context) error { | 429 func (e *Env) createLocked(c context.Context) error { |
434 // If our root directory already exists, delete it. | 430 // If our root directory already exists, delete it. |
435 if _, err := os.Stat(e.Root); err == nil { | 431 if _, err := os.Stat(e.Root); err == nil { |
436 logging.Infof(c, "Deleting existing VirtualEnv: %s", e.Root) | 432 logging.Infof(c, "Deleting existing VirtualEnv: %s", e.Root) |
437 if err := filesystem.RemoveAll(e.Root); err != nil { | 433 if err := filesystem.RemoveAll(e.Root); err != nil { |
438 return errors.Reason("failed to remove existing root").E
rr() | 434 return errors.Reason("failed to remove existing root").E
rr() |
439 } | 435 } |
440 } | 436 } |
441 | 437 |
442 // Make sure our environment's base directory exists. | 438 // Make sure our environment's base directory exists. |
443 if err := filesystem.MakeDirs(e.Root); err != nil { | 439 if err := filesystem.MakeDirs(e.Root); err != nil { |
444 » » return errors.Annotate(err).Reason("failed to create environment
root").Err() | 440 » » return errors.Annotate(err, "failed to create environment root")
.Err() |
445 } | 441 } |
446 logging.Infof(c, "Using virtual environment root: %s", e.Root) | 442 logging.Infof(c, "Using virtual environment root: %s", e.Root) |
447 | 443 |
448 // Build our package list. Always install our base VirtualEnv package. | 444 // Build our package list. Always install our base VirtualEnv package. |
449 packages := make([]*vpython.Spec_Package, 1, 1+len(e.Environment.Spec.Wh
eel)) | 445 packages := make([]*vpython.Spec_Package, 1, 1+len(e.Environment.Spec.Wh
eel)) |
450 packages[0] = e.Environment.Spec.Virtualenv | 446 packages[0] = e.Environment.Spec.Virtualenv |
451 packages = append(packages, e.Environment.Spec.Wheel...) | 447 packages = append(packages, e.Environment.Spec.Wheel...) |
452 | 448 |
453 // Create a directory to bootstrap VirtualEnv from. | 449 // Create a directory to bootstrap VirtualEnv from. |
454 // | 450 // |
455 // This directory will be a very short-named temporary directory. This i
s | 451 // This directory will be a very short-named temporary directory. This i
s |
456 // because it really quickly runs up into traditional Windows path limit
ations | 452 // because it really quickly runs up into traditional Windows path limit
ations |
457 // when ZIP-importing sub-sub-sub-sub-packages (e.g., pip, requests, etc
.). | 453 // when ZIP-importing sub-sub-sub-sub-packages (e.g., pip, requests, etc
.). |
458 // | 454 // |
459 // We will clean this directory up on termination. | 455 // We will clean this directory up on termination. |
460 err := withTempDir(logging.Get(c), "vpython_bootstrap", func(bootstrapDi
r string) error { | 456 err := withTempDir(logging.Get(c), "vpython_bootstrap", func(bootstrapDi
r string) error { |
461 pkgDir := filepath.Join(bootstrapDir, "packages") | 457 pkgDir := filepath.Join(bootstrapDir, "packages") |
462 if err := filesystem.MakeDirs(pkgDir); err != nil { | 458 if err := filesystem.MakeDirs(pkgDir); err != nil { |
463 » » » return errors.Annotate(err).Reason("could not create boo
tstrap packages directory").Err() | 459 » » » return errors.Annotate(err, "could not create bootstrap
packages directory").Err() |
464 } | 460 } |
465 | 461 |
466 if err := e.downloadPackages(c, pkgDir, packages); err != nil { | 462 if err := e.downloadPackages(c, pkgDir, packages); err != nil { |
467 » » » return errors.Annotate(err).Reason("failed to download p
ackages").Err() | 463 » » » return errors.Annotate(err, "failed to download packages
").Err() |
468 } | 464 } |
469 | 465 |
470 // Installing base VirtualEnv. | 466 // Installing base VirtualEnv. |
471 if err := e.installVirtualEnv(c, pkgDir); err != nil { | 467 if err := e.installVirtualEnv(c, pkgDir); err != nil { |
472 » » » return errors.Annotate(err).Reason("failed to install Vi
rtualEnv").Err() | 468 » » » return errors.Annotate(err, "failed to install VirtualEn
v").Err() |
473 } | 469 } |
474 | 470 |
475 // Load PEP425 tags, if we don't already have them. | 471 // Load PEP425 tags, if we don't already have them. |
476 if e.Environment.Pep425Tag == nil { | 472 if e.Environment.Pep425Tag == nil { |
477 pep425Tags, err := e.getPEP425Tags(c) | 473 pep425Tags, err := e.getPEP425Tags(c) |
478 if err != nil { | 474 if err != nil { |
479 » » » » return errors.Annotate(err).Reason("failed to ge
t PEP425 tags").Err() | 475 » » » » return errors.Annotate(err, "failed to get PEP42
5 tags").Err() |
480 } | 476 } |
481 e.Environment.Pep425Tag = pep425Tags | 477 e.Environment.Pep425Tag = pep425Tags |
482 } | 478 } |
483 | 479 |
484 // Install our wheel files. | 480 // Install our wheel files. |
485 if len(e.Environment.Spec.Wheel) > 0 { | 481 if len(e.Environment.Spec.Wheel) > 0 { |
486 // Install wheels into our VirtualEnv. | 482 // Install wheels into our VirtualEnv. |
487 if err := e.installWheels(c, bootstrapDir, pkgDir); err
!= nil { | 483 if err := e.installWheels(c, bootstrapDir, pkgDir); err
!= nil { |
488 » » » » return errors.Annotate(err).Reason("failed to in
stall wheels").Err() | 484 » » » » return errors.Annotate(err, "failed to install w
heels").Err() |
489 } | 485 } |
490 } | 486 } |
491 return nil | 487 return nil |
492 }) | 488 }) |
493 if err != nil { | 489 if err != nil { |
494 return err | 490 return err |
495 } | 491 } |
496 | 492 |
497 // Write our specification file. | 493 // Write our specification file. |
498 if err := e.WriteEnvironmentStamp(); err != nil { | 494 if err := e.WriteEnvironmentStamp(); err != nil { |
499 » » return errors.Annotate(err).Reason("failed to write environment
stamp file to: %(path)s"). | 495 » » return errors.Annotate(err, "failed to write environment stamp f
ile to: %s", |
500 » » » D("path", e.EnvironmentStampPath). | 496 » » » e.EnvironmentStampPath).Err() |
501 » » » Err() | |
502 } | 497 } |
503 logging.Debugf(c, "Wrote environment stamp file to: %s", e.EnvironmentSt
ampPath) | 498 logging.Debugf(c, "Wrote environment stamp file to: %s", e.EnvironmentSt
ampPath) |
504 | 499 |
505 // Finalize our VirtualEnv for bootstrap execution. | 500 // Finalize our VirtualEnv for bootstrap execution. |
506 if err := e.finalize(c); err != nil { | 501 if err := e.finalize(c); err != nil { |
507 » » return errors.Annotate(err).Reason("failed to prepare VirtualEnv
").Err() | 502 » » return errors.Annotate(err, "failed to prepare VirtualEnv").Err(
) |
508 } | 503 } |
509 | 504 |
510 return nil | 505 return nil |
511 } | 506 } |
512 | 507 |
513 func (e *Env) downloadPackages(c context.Context, dst string, packages []*vpytho
n.Spec_Package) error { | 508 func (e *Env) downloadPackages(c context.Context, dst string, packages []*vpytho
n.Spec_Package) error { |
514 // Create a wheel sub-directory underneath of root. | 509 // Create a wheel sub-directory underneath of root. |
515 logging.Debugf(c, "Loading %d package(s) into: %s", len(packages), dst) | 510 logging.Debugf(c, "Loading %d package(s) into: %s", len(packages), dst) |
516 if err := e.Config.Loader.Ensure(c, dst, packages); err != nil { | 511 if err := e.Config.Loader.Ensure(c, dst, packages); err != nil { |
517 » » return errors.Annotate(err).Reason("failed to download packages"
).Err() | 512 » » return errors.Annotate(err, "failed to download packages").Err() |
518 } | 513 } |
519 return nil | 514 return nil |
520 } | 515 } |
521 | 516 |
522 func (e *Env) installVirtualEnv(c context.Context, pkgDir string) error { | 517 func (e *Env) installVirtualEnv(c context.Context, pkgDir string) error { |
523 // Create our VirtualEnv package staging sub-directory underneath of roo
t. | 518 // Create our VirtualEnv package staging sub-directory underneath of roo
t. |
524 bsDir := filepath.Join(e.Root, ".virtualenv") | 519 bsDir := filepath.Join(e.Root, ".virtualenv") |
525 if err := filesystem.MakeDirs(bsDir); err != nil { | 520 if err := filesystem.MakeDirs(bsDir); err != nil { |
526 » » return errors.Annotate(err).Reason("failed to create VirtualEnv
bootstrap directory"). | 521 » » return errors.Annotate(err, "failed to create VirtualEnv bootstr
ap directory"). |
527 » » » D("path", bsDir). | 522 » » » InternalReason("path(%s)", bsDir).Err() |
528 » » » Err() | |
529 } | 523 } |
530 | 524 |
531 // Identify the virtualenv directory: will have "virtualenv-" prefix. | 525 // Identify the virtualenv directory: will have "virtualenv-" prefix. |
532 matches, err := filepath.Glob(filepath.Join(pkgDir, "virtualenv-*")) | 526 matches, err := filepath.Glob(filepath.Join(pkgDir, "virtualenv-*")) |
533 if err != nil { | 527 if err != nil { |
534 » » return errors.Annotate(err).Reason("failed to glob for 'virtuale
nv-' directory").Err() | 528 » » return errors.Annotate(err, "failed to glob for 'virtualenv-' di
rectory").Err() |
535 } | 529 } |
536 if len(matches) == 0 { | 530 if len(matches) == 0 { |
537 return errors.Reason("no 'virtualenv-' directory provided by pac
kage").Err() | 531 return errors.Reason("no 'virtualenv-' directory provided by pac
kage").Err() |
538 } | 532 } |
539 venvDir := matches[0] | 533 venvDir := matches[0] |
540 | 534 |
541 logging.Debugf(c, "Creating VirtualEnv at: %s", e.Root) | 535 logging.Debugf(c, "Creating VirtualEnv at: %s", e.Root) |
542 cmd := e.Config.systemInterpreter().IsolatedCommand(c, | 536 cmd := e.Config.systemInterpreter().IsolatedCommand(c, |
543 "virtualenv.py", | 537 "virtualenv.py", |
544 "--no-download", | 538 "--no-download", |
545 e.Root) | 539 e.Root) |
546 cmd.Dir = venvDir | 540 cmd.Dir = venvDir |
547 attachOutputForLogging(c, logging.Debug, cmd) | 541 attachOutputForLogging(c, logging.Debug, cmd) |
548 if err := cmd.Run(); err != nil { | 542 if err := cmd.Run(); err != nil { |
549 » » return errors.Annotate(err).Reason("failed to create VirtualEnv"
).Err() | 543 » » return errors.Annotate(err, "failed to create VirtualEnv").Err() |
550 } | 544 } |
551 | 545 |
552 logging.Debugf(c, "Making VirtualEnv relocatable at: %s", e.Root) | 546 logging.Debugf(c, "Making VirtualEnv relocatable at: %s", e.Root) |
553 cmd = e.Interpreter().IsolatedCommand(c, | 547 cmd = e.Interpreter().IsolatedCommand(c, |
554 "virtualenv.py", | 548 "virtualenv.py", |
555 "--relocatable", | 549 "--relocatable", |
556 e.Root) | 550 e.Root) |
557 cmd.Dir = venvDir | 551 cmd.Dir = venvDir |
558 attachOutputForLogging(c, logging.Debug, cmd) | 552 attachOutputForLogging(c, logging.Debug, cmd) |
559 if err := cmd.Run(); err != nil { | 553 if err := cmd.Run(); err != nil { |
560 » » return errors.Annotate(err).Reason("failed to create VirtualEnv"
).Err() | 554 » » return errors.Annotate(err, "failed to create VirtualEnv").Err() |
561 } | 555 } |
562 | 556 |
563 return nil | 557 return nil |
564 } | 558 } |
565 | 559 |
566 // getPEP425Tags calls Python's pip.pep425tags package to retrieve the tags. | 560 // getPEP425Tags calls Python's pip.pep425tags package to retrieve the tags. |
567 // | 561 // |
568 // This must be run while "pip" is installed in the VirtualEnv. | 562 // This must be run while "pip" is installed in the VirtualEnv. |
569 func (e *Env) getPEP425Tags(c context.Context) ([]*vpython.PEP425Tag, error) { | 563 func (e *Env) getPEP425Tags(c context.Context) ([]*vpython.PEP425Tag, error) { |
570 // This script will return a list of 3-entry lists: | 564 // This script will return a list of 3-entry lists: |
571 // [0]: version (e.g., "cp27") | 565 // [0]: version (e.g., "cp27") |
572 // [1]: abi (e.g., "cp27mu", "none") | 566 // [1]: abi (e.g., "cp27mu", "none") |
573 // [2]: arch (e.g., "x86_64", "armv7l", "any") | 567 // [2]: arch (e.g., "x86_64", "armv7l", "any") |
574 const script = `import json;` + | 568 const script = `import json;` + |
575 `import pip.pep425tags;` + | 569 `import pip.pep425tags;` + |
576 `import sys;` + | 570 `import sys;` + |
577 `sys.stdout.write(json.dumps(pip.pep425tags.get_supported()))` | 571 `sys.stdout.write(json.dumps(pip.pep425tags.get_supported()))` |
578 type pep425TagEntry []string | 572 type pep425TagEntry []string |
579 | 573 |
580 cmd := e.Interpreter().IsolatedCommand(c, "-c", script) | 574 cmd := e.Interpreter().IsolatedCommand(c, "-c", script) |
581 | 575 |
582 var stdout bytes.Buffer | 576 var stdout bytes.Buffer |
583 cmd.Stdout = &stdout | 577 cmd.Stdout = &stdout |
584 | 578 |
585 attachOutputForLogging(c, logging.Debug, cmd) | 579 attachOutputForLogging(c, logging.Debug, cmd) |
586 if err := cmd.Run(); err != nil { | 580 if err := cmd.Run(); err != nil { |
587 » » return nil, errors.Annotate(err).Reason("failed to get PEP425 ta
gs").Err() | 581 » » return nil, errors.Annotate(err, "failed to get PEP425 tags").Er
r() |
588 } | 582 } |
589 | 583 |
590 var tagEntries []pep425TagEntry | 584 var tagEntries []pep425TagEntry |
591 if err := json.Unmarshal(stdout.Bytes(), &tagEntries); err != nil { | 585 if err := json.Unmarshal(stdout.Bytes(), &tagEntries); err != nil { |
592 » » return nil, errors.Annotate(err).Reason("failed to unmarshal PEP
425 tag output: %(output)s"). | 586 » » return nil, errors.Annotate(err, "failed to unmarshal PEP425 tag
output: %s", stdout).Err() |
593 » » » D("output", stdout.String()). | |
594 » » » Err() | |
595 } | 587 } |
596 | 588 |
597 tags := make([]*vpython.PEP425Tag, len(tagEntries)) | 589 tags := make([]*vpython.PEP425Tag, len(tagEntries)) |
598 for i, te := range tagEntries { | 590 for i, te := range tagEntries { |
599 if len(te) != 3 { | 591 if len(te) != 3 { |
600 » » » return nil, errors.Reason("invalid PEP425 tag entry: %(e
ntry)v"). | 592 » » » return nil, errors.Reason("invalid PEP425 tag entry: %v"
, te). |
601 » » » » D("entry", te). | 593 » » » » InternalReason("index(%d)", i).Err() |
602 » » » » D("index", i). | |
603 » » » » Err() | |
604 } | 594 } |
605 | 595 |
606 tags[i] = &vpython.PEP425Tag{ | 596 tags[i] = &vpython.PEP425Tag{ |
607 Python: te[0], | 597 Python: te[0], |
608 Abi: te[1], | 598 Abi: te[1], |
609 Platform: te[2], | 599 Platform: te[2], |
610 } | 600 } |
611 } | 601 } |
612 | 602 |
613 // If we're Debug-logging, calculate and display the tags that were prob
ed. | 603 // If we're Debug-logging, calculate and display the tags that were prob
ed. |
614 if logging.IsLogging(c, logging.Debug) { | 604 if logging.IsLogging(c, logging.Debug) { |
615 tagStr := make([]string, len(tags)) | 605 tagStr := make([]string, len(tags)) |
616 for i, t := range tags { | 606 for i, t := range tags { |
617 tagStr[i] = t.TagString() | 607 tagStr[i] = t.TagString() |
618 } | 608 } |
619 logging.Debugf(c, "Loaded PEP425 tags: [%s]", strings.Join(tagSt
r, ", ")) | 609 logging.Debugf(c, "Loaded PEP425 tags: [%s]", strings.Join(tagSt
r, ", ")) |
620 } | 610 } |
621 | 611 |
622 return tags, nil | 612 return tags, nil |
623 } | 613 } |
624 | 614 |
625 func (e *Env) installWheels(c context.Context, bootstrapDir, pkgDir string) erro
r { | 615 func (e *Env) installWheels(c context.Context, bootstrapDir, pkgDir string) erro
r { |
626 // Identify all downloaded wheels and parse them. | 616 // Identify all downloaded wheels and parse them. |
627 wheels, err := wheel.ScanDir(pkgDir) | 617 wheels, err := wheel.ScanDir(pkgDir) |
628 if err != nil { | 618 if err != nil { |
629 » » return errors.Annotate(err).Reason("failed to load wheels").Err(
) | 619 » » return errors.Annotate(err, "failed to load wheels").Err() |
630 } | 620 } |
631 | 621 |
632 // Build a "wheel" requirements file. | 622 // Build a "wheel" requirements file. |
633 reqPath := filepath.Join(bootstrapDir, "requirements.txt") | 623 reqPath := filepath.Join(bootstrapDir, "requirements.txt") |
634 logging.Debugf(c, "Rendering requirements file to: %s", reqPath) | 624 logging.Debugf(c, "Rendering requirements file to: %s", reqPath) |
635 if err := wheel.WriteRequirementsFile(reqPath, wheels); err != nil { | 625 if err := wheel.WriteRequirementsFile(reqPath, wheels); err != nil { |
636 » » return errors.Annotate(err).Reason("failed to render requirement
s file").Err() | 626 » » return errors.Annotate(err, "failed to render requirements file"
).Err() |
637 } | 627 } |
638 | 628 |
639 cmd := e.Interpreter().IsolatedCommand(c, | 629 cmd := e.Interpreter().IsolatedCommand(c, |
640 "-m", "pip", | 630 "-m", "pip", |
641 "install", | 631 "install", |
642 "--use-wheel", | 632 "--use-wheel", |
643 "--compile", | 633 "--compile", |
644 "--no-index", | 634 "--no-index", |
645 "--find-links", pkgDir, | 635 "--find-links", pkgDir, |
646 "--requirement", reqPath) | 636 "--requirement", reqPath) |
647 attachOutputForLogging(c, logging.Debug, cmd) | 637 attachOutputForLogging(c, logging.Debug, cmd) |
648 if err := cmd.Run(); err != nil { | 638 if err := cmd.Run(); err != nil { |
649 » » return errors.Annotate(err).Reason("failed to install wheels").E
rr() | 639 » » return errors.Annotate(err, "failed to install wheels").Err() |
650 } | 640 } |
651 return nil | 641 return nil |
652 } | 642 } |
653 | 643 |
654 func (e *Env) finalize(c context.Context) error { | 644 func (e *Env) finalize(c context.Context) error { |
655 // Change all files to read-only, except: | 645 // Change all files to read-only, except: |
656 // - Our root directory, which must be writable in order to update our | 646 // - Our root directory, which must be writable in order to update our |
657 // completion flag. | 647 // completion flag. |
658 // - Our environment stamp, which must be trivially re-writable. | 648 // - Our environment stamp, which must be trivially re-writable. |
659 if !e.Config.testLeaveReadWrite { | 649 if !e.Config.testLeaveReadWrite { |
660 err := filesystem.MakeReadOnly(e.Root, func(path string) bool { | 650 err := filesystem.MakeReadOnly(e.Root, func(path string) bool { |
661 switch path { | 651 switch path { |
662 case e.Root, e.completeFlagPath: | 652 case e.Root, e.completeFlagPath: |
663 return false | 653 return false |
664 default: | 654 default: |
665 return true | 655 return true |
666 } | 656 } |
667 }) | 657 }) |
668 if err != nil { | 658 if err != nil { |
669 » » » return errors.Annotate(err).Reason("failed to mark envir
onment read-only").Err() | 659 » » » return errors.Annotate(err, "failed to mark environment
read-only").Err() |
670 } | 660 } |
671 } | 661 } |
672 return nil | 662 return nil |
673 } | 663 } |
674 | 664 |
675 func (e *Env) touchCompleteFlagLocked() error { | 665 func (e *Env) touchCompleteFlagLocked() error { |
676 if err := filesystem.Touch(e.completeFlagPath, time.Time{}, 0644); err !
= nil { | 666 if err := filesystem.Touch(e.completeFlagPath, time.Time{}, 0644); err !
= nil { |
677 » » return errors.Annotate(err).Err() | 667 » » return errors.Annotate(err, "").Err() |
678 } | 668 } |
679 return nil | 669 return nil |
680 } | 670 } |
681 | 671 |
682 // Delete removes all resources consumed by an environment. | 672 // Delete removes all resources consumed by an environment. |
683 // | 673 // |
684 // Delete will acquire an exclusive lock on the environment and assert that it | 674 // Delete will acquire an exclusive lock on the environment and assert that it |
685 // is not in use prior to deletion. This is non-blocking, and an error will be | 675 // is not in use prior to deletion. This is non-blocking, and an error will be |
686 // returned if the lock could not be acquired. | 676 // returned if the lock could not be acquired. |
687 // | 677 // |
688 // If the environment was not deleted, a non-nil wrapped error will be returned. | 678 // If the environment was not deleted, a non-nil wrapped error will be returned. |
689 // If the deletion failed because the lock was held, a wrapped | 679 // If the deletion failed because the lock was held, a wrapped |
690 // fslock.ErrLockHeld will be returned. | 680 // fslock.ErrLockHeld will be returned. |
691 func (e *Env) Delete(c context.Context) error { | 681 func (e *Env) Delete(c context.Context) error { |
692 removedLock := false | 682 removedLock := false |
693 err := e.withExclusiveLockNonBlocking(func() error { | 683 err := e.withExclusiveLockNonBlocking(func() error { |
694 logging.Debugf(c, "(Delete) Got exclusive lock for: %s", e.Name) | 684 logging.Debugf(c, "(Delete) Got exclusive lock for: %s", e.Name) |
695 | 685 |
696 // Delete our environment directory. | 686 // Delete our environment directory. |
697 if err := filesystem.RemoveAll(e.Root); err != nil { | 687 if err := filesystem.RemoveAll(e.Root); err != nil { |
698 » » » return errors.Annotate(err).Reason("failed to delete env
ironment root").Err() | 688 » » » return errors.Annotate(err, "failed to delete environmen
t root").Err() |
699 } | 689 } |
700 | 690 |
701 // Attempt to delete our lock. On POSIX systems, this will succe
ssfully | 691 // Attempt to delete our lock. On POSIX systems, this will succe
ssfully |
702 // delete the lock. On Windows systems, there will be contention
, since the | 692 // delete the lock. On Windows systems, there will be contention
, since the |
703 // lock is held by us. | 693 // lock is held by us. |
704 // | 694 // |
705 // In this case, we'll try again to delete it after we release t
he lock. | 695 // In this case, we'll try again to delete it after we release t
he lock. |
706 // If someone else takes out the lock in between, the delete wil
l similarly | 696 // If someone else takes out the lock in between, the delete wil
l similarly |
707 // fail. | 697 // fail. |
708 if err := os.Remove(e.lockPath); err == nil { | 698 if err := os.Remove(e.lockPath); err == nil { |
709 removedLock = true | 699 removedLock = true |
710 } else { | 700 } else { |
711 logging.WithError(err).Debugf(c, "failed to delete lock
file while holding lock: %s", e.lockPath) | 701 logging.WithError(err).Debugf(c, "failed to delete lock
file while holding lock: %s", e.lockPath) |
712 } | 702 } |
713 return nil | 703 return nil |
714 }) | 704 }) |
715 logging.Debugf(c, "(Delete) Released exclusive lock for: %s", e.Name) | 705 logging.Debugf(c, "(Delete) Released exclusive lock for: %s", e.Name) |
716 | 706 |
717 if err != nil { | 707 if err != nil { |
718 » » return errors.Annotate(err).Reason("failed to delete environment
").Err() | 708 » » return errors.Annotate(err, "failed to delete environment").Err(
) |
719 } | 709 } |
720 | 710 |
721 // Try and remove the lock now that we don't hold it. | 711 // Try and remove the lock now that we don't hold it. |
722 if !removedLock { | 712 if !removedLock { |
723 if err := os.Remove(e.lockPath); err != nil { | 713 if err := os.Remove(e.lockPath); err != nil { |
724 » » » return errors.Annotate(err).Reason("failed to remove loc
k").Err() | 714 » » » return errors.Annotate(err, "failed to remove lock").Err
() |
725 } | 715 } |
726 } | 716 } |
727 return nil | 717 return nil |
728 } | 718 } |
729 | 719 |
730 // completionFlagTimestamp returns the timestamp on the environment's completion | 720 // completionFlagTimestamp returns the timestamp on the environment's completion |
731 // flag. | 721 // flag. |
732 // | 722 // |
733 // If the completion flag does not exist (incomplete environment), a zero time | 723 // If the completion flag does not exist (incomplete environment), a zero time |
734 // will be returned with no error. | 724 // will be returned with no error. |
735 // | 725 // |
736 // If an error is encountered while checking for the timestamp, it will be | 726 // If an error is encountered while checking for the timestamp, it will be |
737 // returned. | 727 // returned. |
738 func (e *Env) completionFlagTimestamp() (time.Time, error) { | 728 func (e *Env) completionFlagTimestamp() (time.Time, error) { |
739 // Read the complete flag file's timestamp. | 729 // Read the complete flag file's timestamp. |
740 switch st, err := os.Stat(e.completeFlagPath); { | 730 switch st, err := os.Stat(e.completeFlagPath); { |
741 case err == nil: | 731 case err == nil: |
742 return st.ModTime(), nil | 732 return st.ModTime(), nil |
743 | 733 |
744 case os.IsNotExist(err): | 734 case os.IsNotExist(err): |
745 return time.Time{}, nil | 735 return time.Time{}, nil |
746 | 736 |
747 default: | 737 default: |
748 » » return time.Time{}, errors.Annotate(err).Reason("failed to stat
completion flag: %(path)s"). | 738 » » return time.Time{}, errors.Annotate(err, "failed to stat complet
ion flag: %s", |
749 » » » D("path", e.completeFlagPath). | 739 » » » e.completeFlagPath).Err() |
750 » » » Err() | |
751 } | 740 } |
752 } | 741 } |
753 | 742 |
754 func attachOutputForLogging(c context.Context, l logging.Level, cmd *exec.Cmd) { | 743 func attachOutputForLogging(c context.Context, l logging.Level, cmd *exec.Cmd) { |
755 if logging.IsLogging(c, logging.Info) { | 744 if logging.IsLogging(c, logging.Info) { |
756 logging.Infof(c, "Running Python command (cwd=%s): %s", | 745 logging.Infof(c, "Running Python command (cwd=%s): %s", |
757 cmd.Dir, strings.Join(cmd.Args, " ")) | 746 cmd.Dir, strings.Join(cmd.Args, " ")) |
758 } | 747 } |
759 | 748 |
760 if logging.IsLogging(c, l) { | 749 if logging.IsLogging(c, l) { |
761 if cmd.Stdout == nil { | 750 if cmd.Stdout == nil { |
762 cmd.Stdout = os.Stdout | 751 cmd.Stdout = os.Stdout |
763 } | 752 } |
764 if cmd.Stderr == nil { | 753 if cmd.Stderr == nil { |
765 cmd.Stderr = os.Stderr | 754 cmd.Stderr = os.Stderr |
766 } | 755 } |
767 } | 756 } |
768 } | 757 } |
769 | 758 |
770 // mustReleaseLock calls the wrapped function, releasing the lock at the end | 759 // mustReleaseLock calls the wrapped function, releasing the lock at the end |
771 // of its execution. If the lock could not be released, this function will | 760 // of its execution. If the lock could not be released, this function will |
772 // panic, since the locking state can no longer be determined. | 761 // panic, since the locking state can no longer be determined. |
773 func mustReleaseLock(c context.Context, lock fslock.Handle, fn func() error) err
or { | 762 func mustReleaseLock(c context.Context, lock fslock.Handle, fn func() error) err
or { |
774 defer func() { | 763 defer func() { |
775 if err := lock.Unlock(); err != nil { | 764 if err := lock.Unlock(); err != nil { |
776 » » » errors.Log(c, errors.Annotate(err).Reason("failed to rel
ease lock").Err()) | 765 » » » errors.Log(c, errors.Annotate(err, "failed to release lo
ck").Err()) |
777 panic(err) | 766 panic(err) |
778 } | 767 } |
779 }() | 768 }() |
780 return fn() | 769 return fn() |
781 } | 770 } |
OLD | NEW |