summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorPaul Ferrell <51765748+Paul-Ferrell@users.noreply.github.com>2021-02-24 11:57:50 -0700
committerGitHub <noreply@github.com>2021-02-24 10:57:50 -0800
commite85a8cde37fbc1568a12d89b3de372bdfb9c8d93 (patch)
treebe15a138629cf8bc669080abe405bc69fe5ebb13 /lib
parentd65002a6761cd2d745b76eae0592a50056255b7f (diff)
downloadspack-e85a8cde37fbc1568a12d89b3de372bdfb9c8d93.tar.gz
spack-e85a8cde37fbc1568a12d89b3de372bdfb9c8d93.tar.bz2
spack-e85a8cde37fbc1568a12d89b3de372bdfb9c8d93.tar.xz
spack-e85a8cde37fbc1568a12d89b3de372bdfb9c8d93.zip
Config prefer upstream (#21487)
This allows for quickly configuring a spack install/env to use upstream packages by default. This is particularly important when upstreaming from a set of officially supported spack installs on a production cluster. By configuring such that package preferences match the upstream, you ensure maximal reuse of existing package installations.
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/config.py88
-rw-r--r--lib/spack/spack/config.py2
-rw-r--r--lib/spack/spack/test/cmd/config.py50
3 files changed, 138 insertions, 2 deletions
diff --git a/lib/spack/spack/cmd/config.py b/lib/spack/spack/cmd/config.py
index 81136d7859..cc7923d450 100644
--- a/lib/spack/spack/cmd/config.py
+++ b/lib/spack/spack/cmd/config.py
@@ -17,6 +17,8 @@ import spack.environment as ev
import spack.schema.packages
import spack.util.spack_yaml as syaml
from spack.util.editor import editor
+import spack.store
+import spack.repo
description = "get and set configuration options"
section = "config"
@@ -73,6 +75,16 @@ def setup_parser(subparser):
help="file from which to set all config values"
)
+ prefer_upstream_parser = sp.add_parser(
+ 'prefer-upstream',
+ help='set package preferences from upstream')
+
+ prefer_upstream_parser.add_argument(
+ '--local', action='store_true', default=False,
+ help="Set packages preferences based on local installs, rather "
+ "than upstream."
+ )
+
remove_parser = sp.add_parser('remove', aliases=['rm'],
help='remove configuration parameters')
remove_parser.add_argument(
@@ -431,6 +443,79 @@ def config_revert(args):
tty.msg(msg.format(cfg_file))
+def config_prefer_upstream(args):
+ """Generate a packages config based on the configuration of all upstream
+ installs."""
+
+ scope = args.scope
+ if scope is None:
+ scope = spack.config.default_modify_scope('packages')
+
+ all_specs = set(spack.store.db.query(installed=True))
+ local_specs = set(spack.store.db.query_local(installed=True))
+ pref_specs = local_specs if args.local else all_specs - local_specs
+
+ conflicting_variants = set()
+
+ pkgs = {}
+ for spec in pref_specs:
+ # Collect all the upstream compilers and versions for this package.
+ pkg = pkgs.get(spec.name, {
+ 'version': [],
+ 'compiler': [],
+ })
+ pkgs[spec.name] = pkg
+
+ # We have no existing variant if this is our first added version.
+ existing_variants = pkg.get('variants',
+ None if not pkg['version'] else '')
+
+ version = spec.version.string
+ if version not in pkg['version']:
+ pkg['version'].append(version)
+
+ compiler = str(spec.compiler)
+ if compiler not in pkg['compiler']:
+ pkg['compiler'].append(compiler)
+
+ # Get and list all the variants that differ from the default.
+ variants = []
+ for var_name, variant in spec.variants.items():
+ if (var_name in ['patches']
+ or var_name not in spec.package.variants):
+ continue
+
+ if variant.value != spec.package.variants[var_name].default:
+ variants.append(str(variant))
+ variants.sort()
+ variants = ' '.join(variants)
+
+ if spec.name not in conflicting_variants:
+ # Only specify the variants if there's a single variant
+ # set across all versions/compilers.
+ if existing_variants is not None and existing_variants != variants:
+ conflicting_variants.add(spec.name)
+ pkg.pop('variants', None)
+ elif variants:
+ pkg['variants'] = variants
+
+ if conflicting_variants:
+ tty.warn(
+ "The following packages have multiple conflicting upstream "
+ "specs. You may have to specify, by "
+ "concretized hash, which spec you want when building "
+ "packages that depend on them:\n - {0}"
+ .format("\n - ".join(sorted(conflicting_variants))))
+
+ # Simply write the config to the specified file.
+ existing = spack.config.get('packages', scope=scope)
+ new = spack.config.merge_yaml(existing, pkgs)
+ spack.config.set('packages', new, scope)
+ config_file = spack.config.config.get_config_filename(scope, section)
+
+ tty.msg("Updated config at {0}".format(config_file))
+
+
def config(parser, args):
action = {
'get': config_get,
@@ -441,6 +526,7 @@ def config(parser, args):
'rm': config_remove,
'remove': config_remove,
'update': config_update,
- 'revert': config_revert
+ 'revert': config_revert,
+ 'prefer-upstream': config_prefer_upstream,
}
action[args.config_command](args)
diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py
index 73d5d06e1c..5f707e883f 100644
--- a/lib/spack/spack/config.py
+++ b/lib/spack/spack/config.py
@@ -553,7 +553,7 @@ class Configuration(object):
If ``scope`` is ``None`` or not provided, return the merged contents
of all of Spack's configuration scopes. If ``scope`` is provided,
- return only the confiugration as specified in that scope.
+ return only the configuration as specified in that scope.
This off the top-level name from the YAML section. That is, for a
YAML config file that looks like this::
diff --git a/lib/spack/spack/test/cmd/config.py b/lib/spack/spack/test/cmd/config.py
index d80932a0f7..2739df1e1e 100644
--- a/lib/spack/spack/test/cmd/config.py
+++ b/lib/spack/spack/test/cmd/config.py
@@ -11,6 +11,9 @@ import spack.config
import spack.environment as ev
import spack.main
import spack.util.spack_yaml as syaml
+import spack.spec
+import spack.database
+import spack.store
config = spack.main.SpackCommand('config')
env = spack.main.SpackCommand('env')
@@ -645,3 +648,50 @@ def check_config_updated(data):
assert isinstance(data['install_tree'], dict)
assert data['install_tree']['root'] == '/fake/path'
assert data['install_tree']['projections'] == {'all': '{name}-{version}'}
+
+
+def test_config_prefer_upstream(tmpdir_factory, install_mockery, mock_fetch,
+ mutable_config, gen_mock_layout, monkeypatch):
+ """Check that when a dependency package is recorded as installed in
+ an upstream database that it is not reinstalled.
+ """
+
+ mock_db_root = str(tmpdir_factory.mktemp('mock_db_root'))
+ prepared_db = spack.database.Database(mock_db_root)
+
+ upstream_layout = gen_mock_layout('/a/')
+
+ for spec in [
+ 'hdf5 +mpi',
+ 'hdf5 ~mpi',
+ 'boost+debug~icu+graph',
+ 'dependency-install',
+ 'patch']:
+ dep = spack.spec.Spec(spec)
+ dep.concretize()
+ prepared_db.add(dep, upstream_layout)
+
+ downstream_db_root = str(
+ tmpdir_factory.mktemp('mock_downstream_db_root'))
+ db_for_test = spack.database.Database(
+ downstream_db_root, upstream_dbs=[prepared_db])
+ monkeypatch.setattr(spack.store, 'db', db_for_test)
+
+ output = config('prefer-upstream')
+ scope = spack.config.default_modify_scope('packages')
+ cfg_file = spack.config.config.get_config_filename(scope, 'packages')
+ packages = syaml.load(open(cfg_file))['packages']
+
+ # Make sure only the non-default variants are set.
+ assert packages['boost'] == {
+ 'compiler': ['gcc@4.5.0'],
+ 'variants': '+debug +graph',
+ 'version': ['1.63.0']}
+ assert packages['dependency-install'] == {
+ 'compiler': ['gcc@4.5.0'], 'version': ['2.0']}
+ # Ensure that neither variant gets listed for hdf5, since they conflict
+ assert packages['hdf5'] == {
+ 'compiler': ['gcc@4.5.0'], 'version': ['2.3']}
+
+ # Make sure a message about the conflicting hdf5's was given.
+ assert '- hdf5' in output