Compare commits

...

1 Commits

Author SHA1 Message Date
Todd Gamblin
71b877b1d2 stage: add $instance path component
Separate spack instances installing to separate install trees can fight
over the same stage directory because we do not currently unique stage
paths by instance.

- [x] add a new `$instance` substitution that gives an 8-digit hash
      unique to the spack instance
- [x] make the default stage directory use `$instance`

- [x] rework `spack.util.path.substitute_config_variables()` so that
      expensive operations like hashing are done lazily, not at module
      load time.
2021-03-26 22:20:18 -07:00
3 changed files with 83 additions and 46 deletions

View File

@@ -40,35 +40,32 @@ config:
lmod: $spack/share/spack/lmod
# Temporary locations Spack can try to use for builds.
# `build_stage` determines where Spack builds packages.
#
# Recommended options are given below.
# The default build location is `$tempdir/$user/spack-stage/$instance`.
# `$tempdir` indicates that we should build in a temporary directory
# (i.e., ``$TMP` or ``$TMPDIR``). On most systems (especially HPC
# machines), building in a temporary directory is significantly faster
# than other locations. `$user` ensures that the directory is unique by
# user, so different users do not fight over Spack's build location.
# Finally, `$instance` is an 8-digit hash that is unique per instance
# of Spack. This ensures that different Spack instances do not fight
# over build locations.
#
# Builds can be faster in temporary directories on some (e.g., HPC) systems.
# Specifying `$tempdir` will ensure use of the default temporary directory
# (i.e., ``$TMP` or ``$TMPDIR``).
# The second choice, if Spack cannot create the first one for some
# reason, is `~/.spack/stage/$instance`. This is unique to each user's
# home directory, and it is also unique to each Spack instance.
#
# Another option that prevents conflicts and potential permission issues is
# to specify `~/.spack/stage`, which ensures each user builds in their home
# directory.
# These choices both have the username in the path. If the username is
# NOT in your chosen `build_stage` location, Spack will append it
# anyway, to avoid conflicts among users in shared temporary spaces.
#
# A more traditional path uses the value of `$spack/var/spack/stage`, which
# builds directly inside Spack's instance without staging them in a
# temporary space. Problems with specifying a path inside a Spack instance
# are that it precludes its use as a system package and its ability to be
# pip installable.
#
# In any case, if the username is not already in the path, Spack will append
# the value of `$user` in an attempt to avoid potential conflicts between
# users in shared temporary spaces.
#
# The build stage can be purged with `spack clean --stage` and
# `spack clean -a`, so it is important that the specified directory uniquely
# identifies Spack staging to avoid accidentally wiping out non-Spack work.
# The build stage can be purged with `spack clean`, so it is important
# to choose a directory that is ONLY used by Spack so that you do not
# accidentally wipe out files that have nothing to do with Spack.
build_stage:
- $tempdir/$user/spack-stage
- ~/.spack/stage
# - $spack/var/spack/stage
- $tempdir/$user/spack-stage/$instance
- ~/.spack/stage/$instance
# Directory in which to run tests and store test results.
# Tests will be stored in directories named by date/time and package

View File

@@ -6,6 +6,7 @@
import os
import collections
import getpass
import re
import tempfile
from six import StringIO
@@ -362,6 +363,23 @@ def test_substitute_config_variables(mock_low_high_config, monkeypatch):
os.path.join(mock_low_high_config.scopes['low'].path,
'foo/bar/baz'))
# The 'instance' substitution allows us to make a unique hash for the
# spack instance. Test that they're all the right length, right
# chars, and all different.
path = "/foo/$instance/bar/baz"
prefixes = [spack.paths.prefix, "/foo/bar/baz", "/foo/bar", "/blah/blah"]
paths = []
for prefix in prefixes:
monkeypatch.setattr(spack.paths, "prefix", prefix)
canonical = spack_path.canonicalize_path(path)
# all hashed paths are 12-character hashes
assert re.match("/foo/[0-9a-z]{8}/bar/baz", canonical)
paths.append(canonical)
# ensure all hashed paths are different
assert len(set(paths)) == len(prefixes)
packages_merge_low = {
'packages': {

View File

@@ -7,9 +7,11 @@
TODO: this is really part of spack.config. Consolidate it.
"""
import base64
import getpass
import hashlib
import os
import re
import getpass
import subprocess
import tempfile
@@ -24,12 +26,6 @@
'substitute_path_variables',
'canonicalize_path']
# Substitutions to perform
replacements = {
'spack': spack.paths.prefix,
'user': getpass.getuser(),
'tempdir': tempfile.gettempdir(),
}
# This is intended to be longer than the part of the install path
# spack generates from the root path we give it. Included in the
@@ -69,28 +65,54 @@ def substitute_config_variables(path):
Spack allows paths in configs to have some placeholders, as follows:
- $spack The Spack instance's prefix
- $user The current user's username
- $tempdir Default temporary directory returned by tempfile.gettempdir()
- $env The active Spack environment.
``$spack``
The Spack instance's prefix.
``$user``
The current user's username.
``$tempdir``
Default temporary directory returned by ``tempfile.gettempdir()``.
``$env``
The active Spack environment.
``$instance``
Hash of the spack prefix, for creating paths unique to a spack
instance outside of that instance (e.g., in $tempdir).
These are substituted case-insensitively into the path, and users can
use either ``$var`` or ``${var}`` syntax for the variables. $env is only
replaced if there is an active environment, and should only be used in
environment yaml files.
"""
import spack.environment as ev # break circular
env = ev.get_env({}, '')
if env:
replacements.update({'env': env.path})
else:
# If a previous invocation added env, remove it
replacements.pop('env', None)
# Look up replacements
"""
# Possible replacements
def repl(match):
m = match.group(0).strip('${}')
return replacements.get(m.lower(), match.group(0))
raw_match = match.group(0)
name = raw_match.strip('${}').lower()
if name == "spack":
return spack.paths.prefix
elif name == "user":
return getpass.getuser()
elif name == "tempdir":
return tempfile.gettempdir()
elif name == "env":
import spack.environment as ev # break circular
env = ev.get_env({}, '')
if env:
return env.path
elif name == "instance":
sha = hashlib.sha1(spack.paths.prefix.encode("utf-8"))
b32_hash = base64.b32encode(sha.digest()).lower()
return b32_hash[:8].decode("utf-8")
return raw_match
# Replace $var or ${var}.
return re.sub(r'(\$\w+\b|\$\{\w+\})', repl, path)