Rework compiler configuration and simplify config.py logic.

- `spack compiler` subcommands now take an optional --scope argument.

- no more `remove_from_config` in `config.py` -- `update` just
  overwrites b/c it's easier to just call `get_config`, modify YAML
  structures directly, and then call `update`.

- Implemented `spack compiler remove`.
This commit is contained in:
Todd Gamblin 2015-12-28 00:33:17 -08:00
parent ff0d871612
commit 1f8ba53ca7
4 changed files with 125 additions and 74 deletions

View File

@ -22,6 +22,7 @@
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
import sys
import argparse
import llnl.util.tty as tty
@ -41,17 +42,26 @@ def setup_parser(subparser):
sp = subparser.add_subparsers(
metavar='SUBCOMMAND', dest='compiler_command')
update_parser = sp.add_parser(
'add', help='Add compilers to the Spack configuration.')
update_parser.add_argument('add_paths', nargs=argparse.REMAINDER)
add_parser = sp.add_parser('add', help='Add compilers to the Spack configuration.')
add_parser.add_argument('add_paths', nargs=argparse.REMAINDER)
add_parser.add_argument('--scope', choices=spack.config.config_scopes,
help="Configuration scope to modify.")
remove_parser = sp.add_parser('remove', help='remove compiler')
remove_parser.add_argument('path')
remove_parser = sp.add_parser('remove', help='Remove compiler by spec.')
remove_parser.add_argument(
'-a', '--all', action='store_true', help='Remove ALL compilers that match spec.')
remove_parser.add_argument('compiler_spec')
remove_parser.add_argument('--scope', choices=spack.config.config_scopes,
help="Configuration scope to modify.")
list_parser = sp.add_parser('list', help='list available compilers')
list_parser.add_argument('--scope', choices=spack.config.config_scopes,
help="Configuration scope to read from.")
info_parser = sp.add_parser('info', help='Show compiler paths.')
info_parser.add_argument('compiler_spec')
info_parser.add_argument('--scope', choices=spack.config.config_scopes,
help="Configuration scope to read from.")
def compiler_add(args):
@ -62,13 +72,13 @@ def compiler_add(args):
paths = get_path('PATH')
compilers = [c for c in spack.compilers.find_compilers(*args.add_paths)
if c.spec not in spack.compilers.all_compilers()]
if c.spec not in spack.compilers.all_compilers(scope=args.scope)]
if compilers:
spack.compilers.add_compilers_to_config('user', compilers)
spack.compilers.add_compilers_to_config(compilers, scope=args.scope)
n = len(compilers)
s = 's' if n > 1 else ''
filename = spack.config.get_config_filename('user', 'compilers')
filename = spack.config.get_config_filename(args.scope, 'compilers')
tty.msg("Added %d new compiler%s to %s" % (n, s, filename))
colify(reversed(sorted(c.spec for c in compilers)), indent=4)
else:
@ -76,13 +86,26 @@ def compiler_add(args):
def compiler_remove(args):
pass
cspec = CompilerSpec(args.compiler_spec)
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
if not compilers:
tty.die("No compilers match spec %s." % cspec)
elif not args.all and len(compilers) > 1:
tty.error("Multiple compilers match spec %s. Choose one:" % cspec)
colify(reversed(sorted([c.spec for c in compilers])), indent=4)
tty.msg("Or, you can use `spack compiler remove -a` to remove all of them.")
sys.exit(1)
for compiler in compilers:
spack.compilers.remove_compiler_from_config(compiler.spec, scope=args.scope)
tty.msg("Removed compiler %s." % compiler.spec)
def compiler_info(args):
"""Print info about all compilers matching a spec."""
cspec = CompilerSpec(args.compiler_spec)
compilers = spack.compilers.compilers_for_spec(cspec)
compilers = spack.compilers.compilers_for_spec(cspec, scope=args.scope)
if not compilers:
tty.error("No compilers match spec %s." % cspec)
@ -97,7 +120,7 @@ def compiler_info(args):
def compiler_list(args):
tty.msg("Available compilers")
index = index_by(spack.compilers.all_compilers(), 'name')
index = index_by(spack.compilers.all_compilers(scope=args.scope), 'name')
for i, (name, compilers) in enumerate(index.items()):
if i >= 1: print

View File

@ -26,9 +26,14 @@
from llnl.util.tty.colify import colify
from llnl.util.lang import index_by
import spack
from spack.cmd.compiler import compiler_list
description = "List available compilers. Same as 'spack compiler list'."
def setup_parser(subparser):
subparser.add_argument('--scope', choices=spack.config.config_scopes,
help="Configuration scope to read/modify.")
def compilers(parser, args):
compiler_list(args)

View File

@ -49,10 +49,10 @@
_default_order = ['gcc', 'intel', 'pgi', 'clang', 'xlc']
def _auto_compiler_spec(function):
def converter(cspec_like):
def converter(cspec_like, *args, **kwargs):
if not isinstance(cspec_like, spack.spec.CompilerSpec):
cspec_like = spack.spec.CompilerSpec(cspec_like)
return function(cspec_like)
return function(cspec_like, *args, **kwargs)
return converter
@ -65,39 +65,87 @@ def _to_dict(compiler):
}
def get_compiler_config(arch=None):
def get_compiler_config(arch=None, scope=None):
"""Return the compiler configuration for the specified architecture.
If the compiler configuration designates some compilers for
'all' architectures, those are merged into the result, as well.
"""
# If any configuration file has compilers, just stick with the
# ones already configured.
config = spack.config.get_config('compilers')
config = spack.config.get_config('compilers', scope=scope)
my_arch = spack.architecture.sys_type()
if arch is None:
arch = spack.architecture.sys_type()
arch = my_arch
if arch not in config:
if arch in config:
return config[arch]
# Only for the current arch in *highest* scope: automatically try to
# find compilers if none are configured yet.
if arch == my_arch and scope == 'user':
config[arch] = {}
compilers = find_compilers(*get_path('PATH'))
for compiler in compilers:
config[arch].update(_to_dict(compiler))
spack.config.update_config('compilers', config)
spack.config.update_config('compilers', config, scope=scope)
return config[arch]
# Merge 'all' compilers with arch-specific ones.
merged_config = config.get('all', {})
merged_config = spack.config._merge_yaml(merged_config, config[arch])
return merged_config
return {}
def all_compilers(arch=None):
def add_compilers_to_config(compilers, arch=None, scope=None):
"""Add compilers to the config for the specified architecture.
Arguments:
- compilers: a list of Compiler objects.
- arch: arch to add compilers for.
- scope: configuration scope to modify.
"""
if arch is None:
arch = spack.architecture.sys_type()
compiler_config = get_compiler_config(arch, scope)
for compiler in compilers:
compiler_config[str(compiler.spec)] = dict(
(c, getattr(compiler, c, "None"))
for c in _required_instance_vars)
update = { arch : compiler_config }
spack.config.update_config('compilers', update, scope)
@_auto_compiler_spec
def remove_compiler_from_config(compiler_spec, arch=None, scope=None):
"""Remove compilers from the config, by spec.
Arguments:
- compiler_specs: a list of CompilerSpec objects.
- arch: arch to add compilers for.
- scope: configuration scope to modify.
"""
if arch is None:
arch = spack.architecture.sys_type()
compiler_config = get_compiler_config(arch, scope)
del compiler_config[str(compiler_spec)]
update = { arch : compiler_config }
spack.config.update_config('compilers', update, scope)
def all_compilers(arch=None, scope=None):
"""Return a set of specs for all the compiler versions currently
available to build with. These are instances of CompilerSpec.
"""
return [spack.spec.CompilerSpec(s) for s in get_compiler_config(arch)]
# Get compilers for this architecture.
arch_config = get_compiler_config(arch, scope)
# Merge 'all' compilers with arch-specific ones.
# Arch-specific compilers have higher precedence.
merged_config = get_compiler_config('all', scope=scope)
merged_config = spack.config._merge_yaml(merged_config, arch_config)
# Return compiler specs for the result.
return [spack.spec.CompilerSpec(s) for s in merged_config]
_cached_default_compiler = None
@ -165,18 +213,18 @@ def supported(compiler_spec):
@_auto_compiler_spec
def find(compiler_spec):
def find(compiler_spec, arch=None, scope=None):
"""Return specs of available compilers that match the supplied
compiler spec. Return an list if nothing found."""
return [c for c in all_compilers() if c.satisfies(compiler_spec)]
return [c for c in all_compilers(arch, scope) if c.satisfies(compiler_spec)]
@_auto_compiler_spec
def compilers_for_spec(compiler_spec):
def compilers_for_spec(compiler_spec, arch=None, scope=None):
"""This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found.
"""
config = get_compiler_config()
config = get_compiler_config(arch, scope)
def get_compiler(cspec):
items = config[str(cspec)]
@ -195,7 +243,7 @@ def get_compiler(cspec):
return cls(cspec, *compiler_paths)
matches = find(compiler_spec)
matches = find(compiler_spec, arch, scope)
return [get_compiler(cspec) for cspec in matches]

View File

@ -311,7 +311,7 @@ def substitute_spack_prefix(path):
return path.replace('$spack', spack.prefix)
def get_config(section):
def get_config(section, scope=None):
"""Get configuration settings for a section.
Strips off the top-level section name from the YAML dict.
@ -319,7 +319,12 @@ def get_config(section):
validate_section(section)
merged_section = {}
for scope in config_scopes.values():
if scope is None:
scopes = config_scopes.values()
else:
scopes = [validate_scope(scope)]
for scope in scopes:
# read potentially cached data from the scope.
data = scope.get_section(section)
if not data or not section in data:
@ -362,11 +367,11 @@ def get_config_filename(scope, section):
def update_config(section, update_data, scope=None):
"""Update the configuration file for a particular scope.
Merges contents of update_data into the scope's data for the
specified section, then writes out the config file.
Overwrites contents of a section in a scope with update_data,
then writes out the config file.
update_data shoudl contain only the section's data, with the
top-level name stripped off. This can be a list, dict, or any
update_data should have the top-level section name stripped off
(it will be re-added). Data itself can be a list, dict, or any
other yaml-ish structure.
"""
@ -377,37 +382,7 @@ def update_config(section, update_data, scope=None):
scope = validate_scope(scope) # get ConfigScope object from string.
# read only the requested section's data.
data = scope.get_section(section)
data = _merge_yaml(data, { section : update_data })
scope.write_section(section)
def remove_from_config(section, key_to_rm, scope=None):
"""Remove a configuration key and write updated configuration to disk.
Return True if something was removed, False otherwise.
"""
# ensure configs are current by reading in.
get_config(section)
# check args and get the objects we need.
scope = validate_scope(scope)
data = scope.get_section(section)
filename = scope.get_section_filename(section)
# do some checks
if not data:
return False
if not section in data:
raise ConfigFileError("Invalid configuration file: '%s'" % filename)
if key_to_rm not in section[section]:
return False
# remove the key from the section's configuration
del data[section][key_to_rm]
scope.sections[section] = { section : update_data }
scope.write_section(section)