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, # along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
############################################################################## ##############################################################################
import sys
import argparse import argparse
import llnl.util.tty as tty import llnl.util.tty as tty
@ -41,17 +42,26 @@ def setup_parser(subparser):
sp = subparser.add_subparsers( sp = subparser.add_subparsers(
metavar='SUBCOMMAND', dest='compiler_command') metavar='SUBCOMMAND', dest='compiler_command')
update_parser = sp.add_parser( add_parser = sp.add_parser('add', help='Add compilers to the Spack configuration.')
'add', help='Add compilers to the Spack configuration.') add_parser.add_argument('add_paths', nargs=argparse.REMAINDER)
update_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 = sp.add_parser('remove', help='Remove compiler by spec.')
remove_parser.add_argument('path') 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 = 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 = sp.add_parser('info', help='Show compiler paths.')
info_parser.add_argument('compiler_spec') 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): def compiler_add(args):
@ -62,13 +72,13 @@ def compiler_add(args):
paths = get_path('PATH') paths = get_path('PATH')
compilers = [c for c in spack.compilers.find_compilers(*args.add_paths) 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: if compilers:
spack.compilers.add_compilers_to_config('user', compilers) spack.compilers.add_compilers_to_config(compilers, scope=args.scope)
n = len(compilers) n = len(compilers)
s = 's' if n > 1 else '' 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)) tty.msg("Added %d new compiler%s to %s" % (n, s, filename))
colify(reversed(sorted(c.spec for c in compilers)), indent=4) colify(reversed(sorted(c.spec for c in compilers)), indent=4)
else: else:
@ -76,13 +86,26 @@ def compiler_add(args):
def compiler_remove(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): def compiler_info(args):
"""Print info about all compilers matching a spec.""" """Print info about all compilers matching a spec."""
cspec = CompilerSpec(args.compiler_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: if not compilers:
tty.error("No compilers match spec %s." % cspec) tty.error("No compilers match spec %s." % cspec)
@ -97,7 +120,7 @@ def compiler_info(args):
def compiler_list(args): def compiler_list(args):
tty.msg("Available compilers") 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()): for i, (name, compilers) in enumerate(index.items()):
if i >= 1: print if i >= 1: print

View File

@ -26,9 +26,14 @@
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
from llnl.util.lang import index_by from llnl.util.lang import index_by
import spack
from spack.cmd.compiler import compiler_list from spack.cmd.compiler import compiler_list
description = "List available compilers. Same as 'spack 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): def compilers(parser, args):
compiler_list(args) compiler_list(args)

View File

@ -49,10 +49,10 @@
_default_order = ['gcc', 'intel', 'pgi', 'clang', 'xlc'] _default_order = ['gcc', 'intel', 'pgi', 'clang', 'xlc']
def _auto_compiler_spec(function): def _auto_compiler_spec(function):
def converter(cspec_like): def converter(cspec_like, *args, **kwargs):
if not isinstance(cspec_like, spack.spec.CompilerSpec): if not isinstance(cspec_like, spack.spec.CompilerSpec):
cspec_like = spack.spec.CompilerSpec(cspec_like) cspec_like = spack.spec.CompilerSpec(cspec_like)
return function(cspec_like) return function(cspec_like, *args, **kwargs)
return converter 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. """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 # If any configuration file has compilers, just stick with the
# ones already configured. # 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: 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] = {} config[arch] = {}
compilers = find_compilers(*get_path('PATH')) compilers = find_compilers(*get_path('PATH'))
for compiler in compilers: for compiler in compilers:
config[arch].update(_to_dict(compiler)) 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. return {}
merged_config = config.get('all', {})
merged_config = spack.config._merge_yaml(merged_config, config[arch])
return merged_config
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 """Return a set of specs for all the compiler versions currently
available to build with. These are instances of CompilerSpec. 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 _cached_default_compiler = None
@ -165,18 +213,18 @@ def supported(compiler_spec):
@_auto_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 """Return specs of available compilers that match the supplied
compiler spec. Return an list if nothing found.""" 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 @_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. """This gets all compilers that satisfy the supplied CompilerSpec.
Returns an empty list if none are found. Returns an empty list if none are found.
""" """
config = get_compiler_config() config = get_compiler_config(arch, scope)
def get_compiler(cspec): def get_compiler(cspec):
items = config[str(cspec)] items = config[str(cspec)]
@ -195,7 +243,7 @@ def get_compiler(cspec):
return cls(cspec, *compiler_paths) return cls(cspec, *compiler_paths)
matches = find(compiler_spec) matches = find(compiler_spec, arch, scope)
return [get_compiler(cspec) for cspec in matches] 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) return path.replace('$spack', spack.prefix)
def get_config(section): def get_config(section, scope=None):
"""Get configuration settings for a section. """Get configuration settings for a section.
Strips off the top-level section name from the YAML dict. Strips off the top-level section name from the YAML dict.
@ -319,7 +319,12 @@ def get_config(section):
validate_section(section) validate_section(section)
merged_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. # read potentially cached data from the scope.
data = scope.get_section(section) data = scope.get_section(section)
if not data or not section in data: 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): def update_config(section, update_data, scope=None):
"""Update the configuration file for a particular scope. """Update the configuration file for a particular scope.
Merges contents of update_data into the scope's data for the Overwrites contents of a section in a scope with update_data,
specified section, then writes out the config file. then writes out the config file.
update_data shoudl contain only the section's data, with the update_data should have the top-level section name stripped off
top-level name stripped off. This can be a list, dict, or any (it will be re-added). Data itself can be a list, dict, or any
other yaml-ish structure. 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. scope = validate_scope(scope) # get ConfigScope object from string.
# read only the requested section's data. # read only the requested section's data.
data = scope.get_section(section) scope.sections[section] = { section : update_data }
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.write_section(section) scope.write_section(section)