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.
This commit is contained in:
parent
d65002a676
commit
e85a8cde37
4 changed files with 143 additions and 3 deletions
|
@ -17,6 +17,8 @@
|
||||||
import spack.schema.packages
|
import spack.schema.packages
|
||||||
import spack.util.spack_yaml as syaml
|
import spack.util.spack_yaml as syaml
|
||||||
from spack.util.editor import editor
|
from spack.util.editor import editor
|
||||||
|
import spack.store
|
||||||
|
import spack.repo
|
||||||
|
|
||||||
description = "get and set configuration options"
|
description = "get and set configuration options"
|
||||||
section = "config"
|
section = "config"
|
||||||
|
@ -73,6 +75,16 @@ def setup_parser(subparser):
|
||||||
help="file from which to set all config values"
|
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'],
|
remove_parser = sp.add_parser('remove', aliases=['rm'],
|
||||||
help='remove configuration parameters')
|
help='remove configuration parameters')
|
||||||
remove_parser.add_argument(
|
remove_parser.add_argument(
|
||||||
|
@ -431,6 +443,79 @@ def config_revert(args):
|
||||||
tty.msg(msg.format(cfg_file))
|
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):
|
def config(parser, args):
|
||||||
action = {
|
action = {
|
||||||
'get': config_get,
|
'get': config_get,
|
||||||
|
@ -441,6 +526,7 @@ def config(parser, args):
|
||||||
'rm': config_remove,
|
'rm': config_remove,
|
||||||
'remove': config_remove,
|
'remove': config_remove,
|
||||||
'update': config_update,
|
'update': config_update,
|
||||||
'revert': config_revert
|
'revert': config_revert,
|
||||||
|
'prefer-upstream': config_prefer_upstream,
|
||||||
}
|
}
|
||||||
action[args.config_command](args)
|
action[args.config_command](args)
|
||||||
|
|
|
@ -553,7 +553,7 @@ def get_config(self, section, scope=None):
|
||||||
|
|
||||||
If ``scope`` is ``None`` or not provided, return the merged contents
|
If ``scope`` is ``None`` or not provided, return the merged contents
|
||||||
of all of Spack's configuration scopes. If ``scope`` is provided,
|
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
|
This off the top-level name from the YAML section. That is, for a
|
||||||
YAML config file that looks like this::
|
YAML config file that looks like this::
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
import spack.environment as ev
|
import spack.environment as ev
|
||||||
import spack.main
|
import spack.main
|
||||||
import spack.util.spack_yaml as syaml
|
import spack.util.spack_yaml as syaml
|
||||||
|
import spack.spec
|
||||||
|
import spack.database
|
||||||
|
import spack.store
|
||||||
|
|
||||||
config = spack.main.SpackCommand('config')
|
config = spack.main.SpackCommand('config')
|
||||||
env = spack.main.SpackCommand('env')
|
env = spack.main.SpackCommand('env')
|
||||||
|
@ -645,3 +648,50 @@ def check_config_updated(data):
|
||||||
assert isinstance(data['install_tree'], dict)
|
assert isinstance(data['install_tree'], dict)
|
||||||
assert data['install_tree']['root'] == '/fake/path'
|
assert data['install_tree']['root'] == '/fake/path'
|
||||||
assert data['install_tree']['projections'] == {'all': '{name}-{version}'}
|
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
|
||||||
|
|
|
@ -587,7 +587,7 @@ _spack_config() {
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help --scope"
|
SPACK_COMPREPLY="-h --help --scope"
|
||||||
else
|
else
|
||||||
SPACK_COMPREPLY="get blame edit list add remove rm update revert"
|
SPACK_COMPREPLY="get blame edit list add prefer-upstream remove rm update revert"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,6 +631,10 @@ _spack_config_add() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_spack_config_prefer_upstream() {
|
||||||
|
SPACK_COMPREPLY="-h --help --local"
|
||||||
|
}
|
||||||
|
|
||||||
_spack_config_remove() {
|
_spack_config_remove() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
|
|
Loading…
Reference in a new issue