summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Wittenburg <scott.wittenburg@kitware.com>2020-05-12 16:16:52 -0600
committerTodd Gamblin <tgamblin@llnl.gov>2020-05-14 21:11:07 -0700
commit47b3dda1aa92ab98450950ba6383282cbf158857 (patch)
tree34f9f0bfb50e438c3a1f481c5f4bb53b38ea11f0
parentea818caca3589dbc0bba3e36e2d75ee7ef811b56 (diff)
downloadspack-47b3dda1aa92ab98450950ba6383282cbf158857.tar.gz
spack-47b3dda1aa92ab98450950ba6383282cbf158857.tar.bz2
spack-47b3dda1aa92ab98450950ba6383282cbf158857.tar.xz
spack-47b3dda1aa92ab98450950ba6383282cbf158857.zip
Support os-specific $padding in config:install_tree
Providing only $padding or ${padding} results in an attempt to substitute a padding of maximum system path length, while leaving room for the parts of the install path spack generates. Providing $padding-<len> or ${padding-<len>} simply substitutes padding of the specified length.
-rw-r--r--lib/spack/spack/test/config.py46
-rw-r--r--lib/spack/spack/util/path.py73
2 files changed, 108 insertions, 11 deletions
diff --git a/lib/spack/spack/test/config.py b/lib/spack/spack/test/config.py
index b8598616d5..8212db6c21 100644
--- a/lib/spack/spack/test/config.py
+++ b/lib/spack/spack/test/config.py
@@ -23,7 +23,7 @@ import spack.schema.packages
import spack.schema.mirrors
import spack.schema.repos
import spack.util.spack_yaml as syaml
-from spack.util.path import canonicalize_path
+import spack.util.path as spack_path
# sample config data
@@ -272,31 +272,31 @@ def test_substitute_config_variables(mock_low_high_config):
assert os.path.join(
'/foo/bar/baz', prefix
- ) == canonicalize_path('/foo/bar/baz/$spack')
+ ) == spack_path.canonicalize_path('/foo/bar/baz/$spack')
assert os.path.join(
spack.paths.prefix, 'foo/bar/baz'
- ) == canonicalize_path('$spack/foo/bar/baz/')
+ ) == spack_path.canonicalize_path('$spack/foo/bar/baz/')
assert os.path.join(
'/foo/bar/baz', prefix, 'foo/bar/baz'
- ) == canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/')
+ ) == spack_path.canonicalize_path('/foo/bar/baz/$spack/foo/bar/baz/')
assert os.path.join(
'/foo/bar/baz', prefix
- ) == canonicalize_path('/foo/bar/baz/${spack}')
+ ) == spack_path.canonicalize_path('/foo/bar/baz/${spack}')
assert os.path.join(
spack.paths.prefix, 'foo/bar/baz'
- ) == canonicalize_path('${spack}/foo/bar/baz/')
+ ) == spack_path.canonicalize_path('${spack}/foo/bar/baz/')
assert os.path.join(
'/foo/bar/baz', prefix, 'foo/bar/baz'
- ) == canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/')
+ ) == spack_path.canonicalize_path('/foo/bar/baz/${spack}/foo/bar/baz/')
assert os.path.join(
'/foo/bar/baz', prefix, 'foo/bar/baz'
- ) != canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/')
+ ) != spack_path.canonicalize_path('/foo/bar/baz/${spack/foo/bar/baz/')
packages_merge_low = {
@@ -345,19 +345,43 @@ def test_merge_with_defaults(mock_low_high_config, write_config_file):
def test_substitute_user(mock_low_high_config):
user = getpass.getuser()
- assert '/foo/bar/' + user + '/baz' == canonicalize_path(
+ assert '/foo/bar/' + user + '/baz' == spack_path.canonicalize_path(
'/foo/bar/$user/baz'
)
def test_substitute_tempdir(mock_low_high_config):
tempdir = tempfile.gettempdir()
- assert tempdir == canonicalize_path('$tempdir')
- assert tempdir + '/foo/bar/baz' == canonicalize_path(
+ assert tempdir == spack_path.canonicalize_path('$tempdir')
+ assert tempdir + '/foo/bar/baz' == spack_path.canonicalize_path(
'$tempdir/foo/bar/baz'
)
+def test_substitute_padding(mock_low_high_config):
+ max_system_path = spack_path.get_system_path_max()
+ expected_length = (max_system_path -
+ spack_path.SPACK_MAX_INSTALL_PATH_LENGTH)
+
+ install_path = spack_path.canonicalize_path('/foo/bar/${padding}/baz')
+
+ assert spack_path.SPACK_PATH_PADDING_CHARS in install_path
+ assert len(install_path) == expected_length
+
+ install_path = spack_path.canonicalize_path('/foo/bar/baz/gah/$padding')
+
+ assert spack_path.SPACK_PATH_PADDING_CHARS in install_path
+ assert len(install_path) == expected_length
+
+ i_path = spack_path.canonicalize_path('/foo/$padding:10')
+ i_expect = os.path.join('/foo', spack_path.SPACK_PATH_PADDING_CHARS[:10])
+ assert i_path == i_expect
+
+ i_path = spack_path.canonicalize_path('/foo/${padding:20}')
+ i_expect = os.path.join('/foo', spack_path.SPACK_PATH_PADDING_CHARS[:20])
+ assert i_path == i_expect
+
+
def test_read_config(mock_low_high_config, write_config_file):
write_config_file('config', config_low, 'low')
assert spack.config.get('config') == config_low['config']
diff --git a/lib/spack/spack/util/path.py b/lib/spack/spack/util/path.py
index 9d5413c609..8bcf598882 100644
--- a/lib/spack/spack/util/path.py
+++ b/lib/spack/spack/util/path.py
@@ -10,8 +10,12 @@ TODO: this is really part of spack.config. Consolidate it.
import os
import re
import getpass
+import subprocess
import tempfile
+import llnl.util.tty as tty
+from llnl.util.lang import memoized
+
import spack.paths
@@ -27,6 +31,38 @@ replacements = {
'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
+# estimate:
+#
+# os-arch -> 30
+# compiler -> 30
+# package name -> 50 (longest is currently 47 characters)
+# version -> 20
+# hash -> 32
+# buffer -> 138
+# ---------------------
+# total -> 300
+SPACK_MAX_INSTALL_PATH_LENGTH = 300
+SPACK_PATH_PADDING_CHARS = 'spack_path_placeholder'
+
+
+@memoized
+def get_system_path_max():
+ # Choose a conservative default
+ sys_max_path_length = 256
+ try:
+ path_max_proc = subprocess.Popen(['getconf', 'PATH_MAX', '/'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
+ proc_output = str(path_max_proc.communicate()[0].decode())
+ sys_max_path_length = int(proc_output)
+ except (ValueError, subprocess.CalledProcessError, OSError):
+ tty.msg('Unable to find system max path length, using: {0}'.format(
+ sys_max_path_length))
+
+ return sys_max_path_length
+
def substitute_config_variables(path):
"""Substitute placeholders into paths.
@@ -58,8 +94,45 @@ def substitute_path_variables(path):
return path
+def _get_padding_string(length):
+ spack_path_padding_size = len(SPACK_PATH_PADDING_CHARS)
+ num_reps = int(length / (spack_path_padding_size + 1))
+ extra_chars = length % (spack_path_padding_size + 1)
+ reps_list = [SPACK_PATH_PADDING_CHARS for i in range(num_reps)]
+ reps_list.append(SPACK_PATH_PADDING_CHARS[:extra_chars])
+ return os.path.sep.join(reps_list)
+
+
+def _add_computed_padding(path):
+ """Subtitute in padding of os-specific length. The intent is to leave
+ SPACK_MAX_INSTALL_PATH_LENGTH characters available for parts of the
+ path generated by spack. This is to allow for not-completely-known
+ lengths of things like os/arch, compiler, package name, hash length,
+ etc.
+ """
+ padding_regex = re.compile(r'(\$[\w\d\:]+\b|\$\{[\w\d\:]+\})')
+ m = padding_regex.search(path)
+ if m and m.group(0).strip('${}').startswith('padding'):
+ padding_part = m.group(0)
+ len_pad_part = len(m.group(0))
+ p_match = re.search(r'\:(\d+)', padding_part)
+ if p_match:
+ computed_padding = _get_padding_string(int(p_match.group(1)))
+ else:
+ # Take whatever has been computed/substituted so far and add some
+ # room
+ path_len = len(path) - len_pad_part + SPACK_MAX_INSTALL_PATH_LENGTH
+ system_max_path = get_system_path_max()
+ needed_pad_len = system_max_path - path_len
+ computed_padding = _get_padding_string(needed_pad_len)
+ return padding_regex.sub(computed_padding, path)
+ return path
+
+
def canonicalize_path(path):
"""Same as substitute_path_variables, but also take absolute path."""
path = substitute_path_variables(path)
path = os.path.abspath(path)
+ path = _add_computed_padding(path)
+
return path