From 1f8ba53ca7767ca452f553f71d49eaf90fa19db8 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 28 Dec 2015 00:33:17 -0800 Subject: 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`. --- lib/spack/spack/cmd/compiler.py | 49 ++++++++++++++----- lib/spack/spack/cmd/compilers.py | 5 ++ lib/spack/spack/compilers/__init__.py | 92 ++++++++++++++++++++++++++--------- lib/spack/spack/config.py | 51 +++++-------------- 4 files changed, 124 insertions(+), 73 deletions(-) diff --git a/lib/spack/spack/cmd/compiler.py b/lib/spack/spack/cmd/compiler.py index af1a22c9dd..a3860abf76 100644 --- a/lib/spack/spack/cmd/compiler.py +++ b/lib/spack/spack/cmd/compiler.py @@ -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 = 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('--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 diff --git a/lib/spack/spack/cmd/compilers.py b/lib/spack/spack/cmd/compilers.py index c485a910eb..7e09016f2d 100644 --- a/lib/spack/spack/cmd/compilers.py +++ b/lib/spack/spack/cmd/compilers.py @@ -26,9 +26,14 @@ import llnl.util.tty as tty 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) diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py index a1b6d978df..67dfaa3ac9 100644 --- a/lib/spack/spack/compilers/__init__.py +++ b/lib/spack/spack/compilers/__init__.py @@ -49,10 +49,10 @@ _required_instance_vars = ['cc', 'cxx', 'f77', 'fc'] _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 {} + + +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) - return merged_config + update = { arch : compiler_config } + spack.config.update_config('compilers', update, scope) -def all_compilers(arch=None): +@_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 compilers_for_spec(compiler_spec): return cls(cspec, *compiler_paths) - matches = find(compiler_spec) + matches = find(compiler_spec, arch, scope) return [get_compiler(cspec) for cspec in matches] diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 9e3b44085f..d266d2e23f 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -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,52 +367,22 @@ 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. """ # read in the config to ensure we've got current data get_config(section) - validate_section(section) # validate section name + validate_section(section) # validate section name 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) -- cgit v1.2.3-70-g09d2