diff options
-rw-r--r-- | etc/spack/defaults/config.yaml | 10 | ||||
-rw-r--r-- | lib/spack/llnl/util/filesystem.py | 12 | ||||
-rw-r--r-- | lib/spack/spack/__init__.py | 21 | ||||
-rw-r--r-- | lib/spack/spack/repository.py | 14 | ||||
-rw-r--r-- | lib/spack/spack/stage.py | 117 | ||||
-rw-r--r-- | lib/spack/spack/test/config.py | 52 | ||||
-rw-r--r-- | lib/spack/spack/test/mock_packages_test.py | 28 | ||||
-rw-r--r-- | lib/spack/spack/test/stage.py | 184 | ||||
-rw-r--r-- | lib/spack/spack/util/path.py | 68 |
9 files changed, 330 insertions, 176 deletions
diff --git a/etc/spack/defaults/config.yaml b/etc/spack/defaults/config.yaml index 5ce60049ce..6a749b5f6d 100644 --- a/etc/spack/defaults/config.yaml +++ b/etc/spack/defaults/config.yaml @@ -31,13 +31,13 @@ config: # You can use $tempdir to refer to the system default temp directory # (as returned by tempfile.gettempdir()). # - # A value of $local indicates that Spack should run builds directly - # inside its install directory without staging them in temporary space. + # A value of $spack/var/spack/stage indicates that Spack should run + # builds directly inside its install directory without staging them in + # temporary space. build_stage: - - /usr/workspace/*/%u - $tempdir - - /nfs/tmp2/%u - - $local + - /nfs/tmp2/$user + - $spack/var/spack/stage # Cache directory already downloaded source tarballs and archived diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py index e522fdda6d..31e09f2fe6 100644 --- a/lib/spack/llnl/util/filesystem.py +++ b/lib/spack/llnl/util/filesystem.py @@ -25,7 +25,6 @@ import collections import errno import fileinput -import getpass import glob import numbers import os @@ -46,7 +45,6 @@ __all__ = [ 'can_access', 'change_sed_delimiter', 'copy_mode', - 'expand_user', 'filter_file', 'find_libraries', 'fix_darwin_install_name', @@ -229,16 +227,6 @@ def is_exe(path): return os.path.isfile(path) and os.access(path, os.X_OK) -def expand_user(path): - """Find instances of '%u' in a path and replace with the current user's - username.""" - username = getpass.getuser() - if not username and '%u' in path: - tty.die("Couldn't get username to complete path '%s'" % path) - - return path.replace('%u', username) - - def mkdirp(*paths): """Creates a directory, as well as parent directories if needed.""" for path in paths: diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index ab03c0c848..782a9b8a9f 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -119,27 +119,6 @@ editor = Executable(os.environ.get("EDITOR", "vi")) # certifiates. e.g., curl should use the -k option. insecure = False -# Whether to build in tmp space or directly in the stage_path. -# If this is true, then spack will make stage directories in -# a tmp filesystem, and it will symlink them into stage_path. -use_tmp_stage = True - -# Locations to use for staging and building, in order of preference -# Use a %u to add a username to the stage paths here, in case this -# is a shared filesystem. Spack will use the first of these paths -# that it can create. -tmp_dirs = [] -_default_tmp = tempfile.gettempdir() -_tmp_user = getpass.getuser() - -_tmp_candidates = (_default_tmp, '/nfs/tmp2', '/tmp', '/var/tmp') -for path in _tmp_candidates: - # don't add a second username if it's already unique by user. - if _tmp_user not in path: - tmp_dirs.append(join_path(path, '%u', 'spack-stage')) - else: - tmp_dirs.append(join_path(path, 'spack-stage')) - # Whether spack should allow installation of unsafe versions of # software. "Unsafe" versions are ones it doesn't have a checksum # for. diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py index 4696d1d5bc..47d6df85b3 100644 --- a/lib/spack/spack/repository.py +++ b/lib/spack/spack/repository.py @@ -44,6 +44,7 @@ import spack import spack.error import spack.spec from spack.provider_index import ProviderIndex +from spack.util.path import canonicalize_path from spack.util.naming import * # @@ -93,19 +94,6 @@ class SpackNamespace(ModuleType): return getattr(self, name) -def substitute_spack_prefix(path): - """Replaces instances of $spack with Spack's prefix.""" - return re.sub(r'^\$spack', spack.prefix, path) - - -def canonicalize_path(path): - """Substitute $spack, expand user home, take abspath.""" - path = substitute_spack_prefix(path) - path = os.path.expanduser(path) - path = os.path.abspath(path) - return path - - class RepoPath(object): """A RepoPath is a list of repos that function as one. diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py index 7e6b543799..ff10a38ca8 100644 --- a/lib/spack/spack/stage.py +++ b/lib/spack/spack/stage.py @@ -28,6 +28,7 @@ import errno import hashlib import shutil import tempfile +import getpass from urlparse import urljoin import llnl.util.tty as tty @@ -41,9 +42,72 @@ import spack.config import spack.fetch_strategy as fs import spack.error from spack.version import * +from spack.util.path import canonicalize_path from spack.util.crypto import prefix_bits, bit_length -STAGE_PREFIX = 'spack-stage-' +_stage_prefix = 'spack-stage-' + + +def _first_accessible_path(paths): + """Find a tmp dir that exists that we can access.""" + for path in paths: + try: + # try to create the path if it doesn't exist. + path = canonicalize_path(path) + mkdirp(path) + + # ensure accessible + if not can_access(path): + continue + + # return it if successful. + return path + + except OSError: + tty.debug('OSError while checking temporary path: %s' % path) + continue + + return None + + +# cached temporary root +_tmp_root = None +_use_tmp_stage = True + + +def get_tmp_root(): + global _tmp_root, _use_tmp_stage + + if not _use_tmp_stage: + return None + + if _tmp_root is None: + config = spack.config.get_config('config') + candidates = config['build_stage'] + if isinstance(candidates, basestring): + candidates = [candidates] + + path = _first_accessible_path(candidates) + if not path: + raise StageError("No accessible stage paths in %s", candidates) + + # Return None to indicate we're using a local staging area. + if path == canonicalize_path(spack.stage_path): + _use_tmp_stage = False + return None + + # ensure that any temp path is unique per user, so users don't + # fight over shared temporary space. + user = getpass.getuser() + if user not in path: + path = os.path.join(path, user, 'spack-stage') + else: + path = os.path.join(path, 'spack-stage') + + mkdirp(path) + _tmp_root = path + + return _tmp_root class Stage(object): @@ -141,9 +205,8 @@ class Stage(object): # TODO : won't be the same as the temporary stage area in tmp_root self.name = name if name is None: - self.name = STAGE_PREFIX + next(tempfile._get_candidate_names()) + self.name = _stage_prefix + next(tempfile._get_candidate_names()) self.mirror_path = mirror_path - self.tmp_root = find_tmp_root() # Try to construct here a temporary name for the stage directory # If this is a named stage, then construct a named path. @@ -217,10 +280,11 @@ class Stage(object): # Path looks ok, but need to check the target of the link. if os.path.islink(self.path): - real_path = os.path.realpath(self.path) - real_tmp = os.path.realpath(self.tmp_root) + tmp_root = get_tmp_root() + if tmp_root is not None: + real_path = os.path.realpath(self.path) + real_tmp = os.path.realpath(tmp_root) - if spack.use_tmp_stage: # If we're using a tmp dir, it's a link, and it points at the # right spot, then keep it. if (real_path.startswith(real_tmp) and @@ -416,11 +480,11 @@ class Stage(object): """ path = self.source_path if not path: - tty.die("Attempt to chdir before expanding archive.") + raise StageError("Attempt to chdir before expanding archive.") else: os.chdir(path) if not os.listdir(path): - tty.die("Archive was empty for %s" % self.name) + raise StageError("Archive was empty for %s" % self.name) def restage(self): """Removes the expanded archive path if it exists, then re-expands @@ -429,17 +493,17 @@ class Stage(object): self.fetcher.reset() def create(self): - """ - Creates the stage directory + """Creates the stage directory. - If self.tmp_root evaluates to False, the stage directory is - created directly under spack.stage_path, otherwise this will - attempt to create a stage in a temporary directory and link it - into spack.stage_path. + If get_tmp_root() is None, the stage directory is created + directly under spack.stage_path, otherwise this will attempt to + create a stage in a temporary directory and link it into + spack.stage_path. Spack will use the first writable location in spack.tmp_dirs to create a stage. If there is no valid location in tmp_dirs, fall back to making the stage inside spack.stage_path. + """ # Create the top-level stage directory mkdirp(spack.stage_path) @@ -448,8 +512,10 @@ class Stage(object): # If a tmp_root exists then create a directory there and then link it # in the stage area, otherwise create the stage directory in self.path if self._need_to_create_path(): - if self.tmp_root: - tmp_dir = tempfile.mkdtemp('', STAGE_PREFIX, self.tmp_root) + tmp_root = get_tmp_root() + if tmp_root is not None: + tmp_dir = tempfile.mkdtemp('', _stage_prefix, tmp_root) + tty.debug('link %s -> %s' % (self.path, tmp_dir)) os.symlink(tmp_dir, self.path) else: mkdirp(self.path) @@ -614,25 +680,6 @@ def purge(): remove_linked_tree(stage_path) -def find_tmp_root(): - if spack.use_tmp_stage: - for tmp in spack.tmp_dirs: - try: - # Replace %u with username - expanded = expand_user(tmp) - - # try to create a directory for spack stuff - mkdirp(expanded) - - # return it if successful. - return expanded - - except OSError: - continue - - return None - - class StageError(spack.error.SpackError): """"Superclass for all errors encountered during staging.""" diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py index 02f53a5261..de6cd79594 100644 --- a/lib/spack/spack/test/config.py +++ b/lib/spack/spack/test/config.py @@ -24,10 +24,12 @@ ############################################################################## import os import shutil +import getpass from tempfile import mkdtemp import spack import spack.config +from spack.util.path import canonicalize_path from ordereddict_backport import OrderedDict from spack.test.mock_packages_test import * @@ -217,3 +219,53 @@ class ConfigTest(MockPackagesTest): # Same check again, to ensure consistency. self.check_config(a_comps, *self.a_comp_specs) self.check_config(b_comps, *self.b_comp_specs) + + def check_canonical(self, var, expected): + """ensure things are substituted properly and canonicalized.""" + path = '/foo/bar/baz' + + self.assertEqual(canonicalize_path(var + path), + expected + path) + + self.assertEqual(canonicalize_path(path + var), + path + '/' + expected) + + self.assertEqual(canonicalize_path(path + var + path), + expected + path) + + def test_substitute_config_variables(self): + prefix = spack.prefix.lstrip('/') + + self.assertEqual(os.path.join('/foo/bar/baz', prefix), + canonicalize_path('/foo/bar/baz/$spack')) + + self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'), + canonicalize_path('$spack/foo/bar/baz/')) + + self.assertEqual(os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'), + canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/')) + + self.assertEqual(os.path.join('/foo/bar/baz', prefix), + canonicalize_path('/foo/bar/baz/${spack}')) + + self.assertEqual(os.path.join(spack.prefix, 'foo/bar/baz'), + canonicalize_path('${spack}/foo/bar/baz/')) + + self.assertEqual( + os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'), + canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/')) + + self.assertNotEqual( + os.path.join('/foo/bar/baz', prefix, 'foo/bar/baz'), + canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/')) + + def test_substitute_user(self): + user = getpass.getuser() + self.assertEqual('/foo/bar/' + user + '/baz', + canonicalize_path('/foo/bar/$user/baz')) + + def test_substitute_tempdir(self): + tempdir = tempfile.gettempdir() + self.assertEqual(tempdir, canonicalize_path('$tempdir')) + self.assertEqual(tempdir + '/foo/bar/baz', + canonicalize_path('$tempdir/foo/bar/baz')) diff --git a/lib/spack/spack/test/mock_packages_test.py b/lib/spack/spack/test/mock_packages_test.py index cba9c81e9d..4e1f243c82 100644 --- a/lib/spack/spack/test/mock_packages_test.py +++ b/lib/spack/spack/test/mock_packages_test.py @@ -180,6 +180,27 @@ packages: externalmodule@1.0%gcc@4.5.0: external-module """ +mock_config = """\ +config: + install_tree: $spack/opt/spack + build_stage: + - $tempdir + - /nfs/tmp2/$user + - $spack/var/spack/stage + source_cache: $spack/var/spack/cache + misc_cache: ~/.spack/cache + verify_ssl: true + checksum: true + dirty: false +""" + +# these are written out to mock config files. +mock_configs = { + 'config.yaml': mock_config, + 'compilers.yaml': mock_compiler_config, + 'packages.yaml': mock_packages_config, +} + class MockPackagesTest(unittest.TestCase): @@ -199,11 +220,10 @@ class MockPackagesTest(unittest.TestCase): self.mock_user_config = os.path.join(self.temp_config, 'user') mkdirp(self.mock_site_config) mkdirp(self.mock_user_config) - for confs in [('compilers.yaml', mock_compiler_config), - ('packages.yaml', mock_packages_config)]: - conf_yaml = os.path.join(self.mock_site_config, confs[0]) + for filename, data in mock_configs.items(): + conf_yaml = os.path.join(self.mock_site_config, filename) with open(conf_yaml, 'w') as f: - f.write(confs[1]) + f.write(data) # TODO: Mocking this up is kind of brittle b/c ConfigScope # TODO: constructor modifies config_scopes. Make it cleaner. diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py index ec661bfe50..a21142c2cb 100644 --- a/lib/spack/spack/test/stage.py +++ b/lib/spack/spack/test/stage.py @@ -27,64 +27,71 @@ Test that the Stage class works correctly. """ import os import shutil -import unittest +import tempfile from contextlib import * import spack +import spack.stage from llnl.util.filesystem import * from spack.stage import Stage from spack.util.executable import which +from spack.test.mock_packages_test import * -test_files_dir = os.path.realpath(join_path(spack.stage_path, '.test')) -test_tmp_path = os.path.realpath(join_path(test_files_dir, 'tmp')) - -archive_dir = 'test-files' -archive_name = archive_dir + '.tar.gz' -archive_dir_path = join_path(test_files_dir, archive_dir) -archive_url = 'file://' + join_path(test_files_dir, archive_name) -readme_name = 'README.txt' -test_readme = join_path(archive_dir_path, readme_name) -readme_text = "hello world!\n" - -stage_name = 'spack-test-stage' +_test_tmp_path = None @contextmanager def use_tmp(use_tmp): - """Allow some test code to be executed with spack.use_tmp_stage - set to a certain value. Context manager makes sure it's reset - on failure. + """Allow some test code to be executed such that spack will either use or + not use temporary space for stages. """ - old_tmp = spack.use_tmp_stage - spack.use_tmp_stage = use_tmp + # mock up config + path = _test_tmp_path if use_tmp else spack.stage_path + spack.config.update_config( + 'config', {'build_stage': [path]}, scope='user') yield - spack.use_tmp_stage = old_tmp -class StageTest(unittest.TestCase): +class StageTest(MockPackagesTest): def setUp(self): """This sets up a mock archive to fetch, and a mock temp space for use by the Stage class. It doesn't actually create the Stage -- that is done by individual tests. """ - if os.path.exists(test_files_dir): - shutil.rmtree(test_files_dir) + global _test_tmp_path + + self.test_files_dir = tempfile.mkdtemp() + self.test_tmp_path = os.path.realpath( + os.path.join(self.test_files_dir, 'tmp')) + _test_tmp_path = self.test_tmp_path + + self.archive_dir = 'test-files' + self.archive_name = self.archive_dir + '.tar.gz' + archive_dir_path = os.path.join(self.test_files_dir, + self.archive_dir) + self.archive_url = 'file://' + os.path.join(self.test_files_dir, + self.archive_name) + test_readme = join_path(archive_dir_path, 'README.txt') + self.readme_text = "hello world!\n" + + self.stage_name = 'spack-test-stage' - mkdirp(test_files_dir) mkdirp(archive_dir_path) - mkdirp(test_tmp_path) + mkdirp(self.test_tmp_path) with open(test_readme, 'w') as readme: - readme.write(readme_text) + readme.write(self.readme_text) - with working_dir(test_files_dir): - tar = which('tar') - tar('czf', archive_name, archive_dir) + with working_dir(self.test_files_dir): + tar = which('tar', required=True) + tar('czf', self.archive_name, self.archive_dir) # Make spack use the test environment for tmp stuff. - self.old_tmp_dirs = spack.tmp_dirs - spack.tmp_dirs = [test_tmp_path] + self._old_tmp_root = spack.stage._tmp_root + self._old_use_tmp_stage = spack.stage._use_tmp_stage + spack.stage._tmp_root = None + spack.stage._use_tmp_stage = True # record this since this test changes to directories that will # be removed. @@ -92,13 +99,14 @@ class StageTest(unittest.TestCase): def tearDown(self): """Blows away the test environment directory.""" - shutil.rmtree(test_files_dir) + shutil.rmtree(self.test_files_dir, ignore_errors=True) # chdir back to original working dir os.chdir(self.working_dir) # restore spack's original tmp environment - spack.tmp_dirs = self.old_tmp_dirs + spack.stage._tmp_root = self._old_tmp_root + spack.stage._use_tmp_stage = self._old_use_tmp_stage def get_stage_path(self, stage, stage_name): """Figure out where a stage should be living. This depends on @@ -120,7 +128,7 @@ class StageTest(unittest.TestCase): # Ensure stage was created in the spack stage directory self.assertTrue(os.path.isdir(stage_path)) - if spack.use_tmp_stage: + if spack.stage.get_tmp_root(): # Check that the stage dir is really a symlink. self.assertTrue(os.path.islink(stage_path)) @@ -131,7 +139,7 @@ class StageTest(unittest.TestCase): # Make sure the directory is in the place we asked it to # be (see setUp and tearDown) - self.assertTrue(target.startswith(test_tmp_path)) + self.assertTrue(target.startswith(self.test_tmp_path)) else: # Make sure the stage path is NOT a link for a non-tmp stage @@ -139,24 +147,24 @@ class StageTest(unittest.TestCase): def check_fetch(self, stage, stage_name): stage_path = self.get_stage_path(stage, stage_name) - self.assertTrue(archive_name in os.listdir(stage_path)) - self.assertEqual(join_path(stage_path, archive_name), + self.assertTrue(self.archive_name in os.listdir(stage_path)) + self.assertEqual(join_path(stage_path, self.archive_name), stage.fetcher.archive_file) def check_expand_archive(self, stage, stage_name): stage_path = self.get_stage_path(stage, stage_name) - self.assertTrue(archive_name in os.listdir(stage_path)) - self.assertTrue(archive_dir in os.listdir(stage_path)) + self.assertTrue(self.archive_name in os.listdir(stage_path)) + self.assertTrue(self.archive_dir in os.listdir(stage_path)) self.assertEqual( - join_path(stage_path, archive_dir), + join_path(stage_path, self.archive_dir), stage.source_path) - readme = join_path(stage_path, archive_dir, readme_name) + readme = join_path(stage_path, self.archive_dir, 'README.txt') self.assertTrue(os.path.isfile(readme)) with open(readme) as file: - self.assertEqual(readme_text, file.read()) + self.assertEqual(self.readme_text, file.read()) def check_chdir(self, stage, stage_name): stage_path = self.get_stage_path(stage, stage_name) @@ -165,7 +173,7 @@ class StageTest(unittest.TestCase): def check_chdir_to_source(self, stage, stage_name): stage_path = self.get_stage_path(stage, stage_name) self.assertEqual( - join_path(os.path.realpath(stage_path), archive_dir), + join_path(os.path.realpath(stage_path), self.archive_dir), os.getcwd()) def check_destroy(self, stage, stage_name): @@ -176,76 +184,76 @@ class StageTest(unittest.TestCase): self.assertFalse(os.path.exists(stage_path)) # tmp stage needs to remove tmp dir too. - if spack.use_tmp_stage: + if spack.stage._use_tmp_stage: target = os.path.realpath(stage_path) self.assertFalse(os.path.exists(target)) def test_setup_and_destroy_name_with_tmp(self): with use_tmp(True): - with Stage(archive_url, name=stage_name) as stage: - self.check_setup(stage, stage_name) - self.check_destroy(stage, stage_name) + with Stage(self.archive_url, name=self.stage_name) as stage: + self.check_setup(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_setup_and_destroy_name_without_tmp(self): with use_tmp(False): - with Stage(archive_url, name=stage_name) as stage: - self.check_setup(stage, stage_name) - self.check_destroy(stage, stage_name) + with Stage(self.archive_url, name=self.stage_name) as stage: + self.check_setup(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_setup_and_destroy_no_name_with_tmp(self): with use_tmp(True): - with Stage(archive_url) as stage: + with Stage(self.archive_url) as stage: self.check_setup(stage, None) self.check_destroy(stage, None) def test_setup_and_destroy_no_name_without_tmp(self): with use_tmp(False): - with Stage(archive_url) as stage: + with Stage(self.archive_url) as stage: self.check_setup(stage, None) self.check_destroy(stage, None) def test_chdir(self): - with Stage(archive_url, name=stage_name) as stage: + with Stage(self.archive_url, name=self.stage_name) as stage: stage.chdir() - self.check_setup(stage, stage_name) - self.check_chdir(stage, stage_name) - self.check_destroy(stage, stage_name) + self.check_setup(stage, self.stage_name) + self.check_chdir(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_fetch(self): - with Stage(archive_url, name=stage_name) as stage: + with Stage(self.archive_url, name=self.stage_name) as stage: stage.fetch() - self.check_setup(stage, stage_name) - self.check_chdir(stage, stage_name) - self.check_fetch(stage, stage_name) - self.check_destroy(stage, stage_name) + self.check_setup(stage, self.stage_name) + self.check_chdir(stage, self.stage_name) + self.check_fetch(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_expand_archive(self): - with Stage(archive_url, name=stage_name) as stage: + with Stage(self.archive_url, name=self.stage_name) as stage: stage.fetch() - self.check_setup(stage, stage_name) - self.check_fetch(stage, stage_name) + self.check_setup(stage, self.stage_name) + self.check_fetch(stage, self.stage_name) stage.expand_archive() - self.check_expand_archive(stage, stage_name) - self.check_destroy(stage, stage_name) + self.check_expand_archive(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_expand_archive_with_chdir(self): - with Stage(archive_url, name=stage_name) as stage: + with Stage(self.archive_url, name=self.stage_name) as stage: stage.fetch() - self.check_setup(stage, stage_name) - self.check_fetch(stage, stage_name) + self.check_setup(stage, self.stage_name) + self.check_fetch(stage, self.stage_name) stage.expand_archive() stage.chdir_to_source() - self.check_expand_archive(stage, stage_name) - self.check_chdir_to_source(stage, stage_name) - self.check_destroy(stage, stage_name) + self.check_expand_archive(stage, self.stage_name) + self.check_chdir_to_source(stage, self.stage_name) + self.check_destroy(stage, self.stage_name) def test_restage(self): - with Stage(archive_url, name=stage_name) as stage: + with Stage(self.archive_url, name=self.stage_name) as stage: stage.fetch() stage.expand_archive() stage.chdir_to_source() - self.check_expand_archive(stage, stage_name) - self.check_chdir_to_source(stage, stage_name) + self.check_expand_archive(stage, self.stage_name) + self.check_chdir_to_source(stage, self.stage_name) # Try to make a file in the old archive dir with open('foobar', 'w') as file: @@ -255,40 +263,44 @@ class StageTest(unittest.TestCase): # Make sure the file is not there after restage. stage.restage() - self.check_chdir(stage, stage_name) - self.check_fetch(stage, stage_name) + self.check_chdir(stage, self.stage_name) + self.check_fetch(stage, self.stage_name) stage.chdir_to_source() - self.check_chdir_to_source(stage, stage_name) + self.check_chdir_to_source(stage, self.stage_name) self.assertFalse('foobar' in os.listdir(stage.source_path)) - self.check_destroy(stage, stage_name) + self.check_destroy(stage, self.stage_name) def test_no_keep_without_exceptions(self): - with Stage(archive_url, name=stage_name, keep=False) as stage: + with Stage(self.archive_url, + name=self.stage_name, keep=False) as stage: pass - self.check_destroy(stage, stage_name) + self.check_destroy(stage, self.stage_name) def test_keep_without_exceptions(self): - with Stage(archive_url, name=stage_name, keep=True) as stage: + with Stage(self.archive_url, + name=self.stage_name, keep=True) as stage: pass - path = self.get_stage_path(stage, stage_name) + path = self.get_stage_path(stage, self.stage_name) self.assertTrue(os.path.isdir(path)) def test_no_keep_with_exceptions(self): try: - with Stage(archive_url, name=stage_name, keep=False) as stage: + with Stage(self.archive_url, + name=self.stage_name, keep=False) as stage: raise Exception() - path = self.get_stage_path(stage, stage_name) + path = self.get_stage_path(stage, self.stage_name) self.assertTrue(os.path.isdir(path)) except: pass # ignore here. def test_keep_exceptions(self): try: - with Stage(archive_url, name=stage_name, keep=True) as stage: + with Stage(self.archive_url, + name=self.stage_name, keep=True) as stage: raise Exception() - path = self.get_stage_path(stage, stage_name) + path = self.get_stage_path(stage, self.stage_name) self.assertTrue(os.path.isdir(path)) except: pass # ignore here. diff --git a/lib/spack/spack/util/path.py b/lib/spack/spack/util/path.py new file mode 100644 index 0000000000..5332115ae9 --- /dev/null +++ b/lib/spack/spack/util/path.py @@ -0,0 +1,68 @@ +############################################################################## +# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://github.com/llnl/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (as +# published by the Free Software Foundation) version 2.1, February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## +"""Utilities for managing paths in Spack. +""" +import os +import re +import spack +import getpass +import tempfile + +__all__ = [ + 'substitute_config_variables', + 'canonicalize_path'] + +# Substitutions to perform +replacements = { + 'spack': spack.prefix, + 'user': getpass.getuser(), + 'tempdir': tempfile.gettempdir(), +} + + +def substitute_config_variables(path): + """Substitute placeholders into paths. + + 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() + """ + # Look up replacements for re.sub in the replacements dict. + def repl(match): + m = match.group(0).strip('${}') + return replacements.get(m, match.group(0)) + + # Replace $var or ${var}. + return re.sub(r'(\$\w+\b|\$\{\w+\})', repl, path) + + +def canonicalize_path(path): + """Substitute $spack, expand user home, take abspath.""" + path = substitute_config_variables(path) + path = os.path.expanduser(path) + path = os.path.abspath(path) + return path |