| 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 |