diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/cmd/module.py | 210 | ||||
-rw-r--r-- | lib/spack/spack/config.py | 5 | ||||
-rw-r--r-- | lib/spack/spack/modules.py | 24 |
3 files changed, 161 insertions, 78 deletions
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py index 3cefb512c2..916bb65646 100644 --- a/lib/spack/spack/cmd/module.py +++ b/lib/spack/spack/cmd/module.py @@ -26,6 +26,7 @@ from __future__ import print_function import os import shutil import sys +import collections import llnl.util.tty as tty import spack.cmd @@ -33,21 +34,27 @@ from llnl.util.filesystem import mkdirp from spack.modules import module_types from spack.util.string import * -description = "Manipulate modules and dotkits." +from spack.cmd.uninstall import ask_for_confirmation +description = "Manipulate module files" -def setup_parser(subparser): - sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='module_command') - sp.add_parser('refresh', help='Regenerate all module files.') +def _add_common_arguments(subparser): + type_help = 'Type of module files' + subparser.add_argument('--module-type', help=type_help, required=True, choices=module_types) + constraint_help = 'Optional constraint to select a subset of installed packages' + subparser.add_argument('constraint', nargs='*', help=constraint_help) - find_parser = sp.add_parser('find', help='Find module files for packages.') - find_parser.add_argument( - 'module_type', - help="Type of module to find file for. [" + - '|'.join(module_types) + "]") +def setup_parser(subparser): + sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='module_command') + # spack module refresh + refresh_parser = sp.add_parser('refresh', help='Regenerate all module files.') + refresh_parser.add_argument('--delete-tree', help='Delete the module file tree before refresh', action='store_true') + _add_common_arguments(refresh_parser) + # spack module find + find_parser = sp.add_parser('find', help='Find module files for packages.') find_parser.add_argument( '-r', '--dependencies', action='store_true', dest='recurse_dependencies', @@ -60,59 +67,57 @@ def setup_parser(subparser): find_parser.add_argument( '-p', '--prefix', dest='prefix', help='Prepend to module names when issuing module load commands') + _add_common_arguments(find_parser) - find_parser.add_argument( - 'spec', nargs='+', - help='spec to find a module file for.') - - -def module_find(mtype, flags, spec_array): - """Look at all installed packages and see if the spec provided - matches any. If it does, check whether there is a module file - of type <mtype> there, and print out the name that the user - should type to use that package's module. - prefix: - Prepend this to module names when issuing "module load" commands. - Some systems seem to need it. - """ - if mtype not in module_types: - tty.die("Invalid module type: '%s'. Options are %s" % - (mtype, comma_or(module_types))) - # -------------------------------------- - def _find_modules(spec, modules_list): - """Finds all modules and sub-modules for a spec""" - if str(spec.version) == 'system': - # No Spack module for system-installed packages - return +class MultipleMatches(Exception): + pass + - if flags.recurse_dependencies: - for dep in spec.dependencies.values(): - _find_modules(dep, modules_list) +class NoMatch(Exception): + pass + +def module_find(mtype, specs, args): + """ + Look at all installed packages and see if the spec provided + matches any. If it does, check whether there is a module file + of type <mtype> there, and print out the name that the user + should type to use that package's module. + """ + if len(specs) == 0: + raise NoMatch() + + if len(specs) > 1: + raise MultipleMatches() + + spec = specs.pop() + if not args.recurse_dependencies: mod = module_types[mtype](spec) if not os.path.isfile(mod.file_name): tty.die("No %s module is installed for %s" % (mtype, spec)) - modules_list.append((spec, mod)) + print(mod.use_name) + else: - # -------------------------------------- - raw_specs = spack.cmd.parse_specs(spec_array) - modules = set() # Modules we will load - seen = set() - for raw_spec in raw_specs: + def _find_modules(spec, modules_list): + """Finds all modules and sub-modules for a spec""" + if str(spec.version) == 'system': + # No Spack module for system-installed packages + return - # ----------- Make sure the spec only resolves to ONE thing - specs = spack.installed_db.query(raw_spec) - if len(specs) == 0: - tty.die("No installed packages match spec %s" % raw_spec) + if args.recurse_dependencies: + for dep in spec.dependencies.values(): + _find_modules(dep, modules_list) - if len(specs) > 1: - tty.error("Multiple matches for spec %s. Choose one:" % spec) - for s in specs: - sys.stderr.write(s.tree(color=True)) - sys.exit(1) - spec = specs[0] + mod = module_types[mtype](spec) + if not os.path.isfile(mod.file_name): + tty.die("No %s module is installed for %s" % (mtype, spec)) + modules_list.append((spec, mod)) + # -------------------------------------- + + modules = set() # Modules we will load + seen = set() # ----------- Chase down modules for it and all its dependencies modules_dups = list() @@ -120,38 +125,99 @@ def module_find(mtype, flags, spec_array): # Remove duplicates while keeping order modules_unique = list() - for spec,mod in modules_dups: + for spec, mod in modules_dups: if mod.use_name not in seen: modules_unique.append((spec,mod)) seen.add(mod.use_name) # Output... - if flags.shell: + if args.shell: module_cmd = {'tcl': 'module load', 'dotkit': 'dotkit use'}[mtype] - for spec,mod in modules_unique: - if flags.shell: + for spec, mod in modules_unique: + if args.shell: print('# %s' % spec.format()) - print('%s %s%s' % (module_cmd, flags.prefix, mod.use_name)) + print('%s %s%s' % (module_cmd, args.prefix, mod.use_name)) else: print(mod.use_name) -def module_refresh(): - """Regenerate all module files for installed packages known to - spack (some packages may no longer exist).""" - specs = [s for s in spack.installed_db.query(installed=True, known=True)] - for name, cls in module_types.items(): - tty.msg("Regenerating %s module files." % name) - if os.path.isdir(cls.path): - shutil.rmtree(cls.path, ignore_errors=False) - mkdirp(cls.path) - for spec in specs: - cls(spec).write() +def module_refresh(name, specs, args): + """ + Regenerate all module files for installed packages known to + spack (some packages may no longer exist). + """ + # Prompt a message to the user about what is going to change + if not specs: + tty.msg('No package matches your query') + return + + tty.msg('You are about to regenerate the {name} module files for the following specs:\n'.format(name=name)) + for s in specs: + print(s.format(color=True)) + print('') + ask_for_confirmation('Do you want to proceed ? ') + + cls = module_types[name] + + # Detect name clashes + writers = [cls(spec) for spec in specs] + file2writer = collections.defaultdict(list) + for item in writers: + file2writer[item.file_name].append(item) + + if len(file2writer) != len(writers): + message = 'Name clashes detected in module files:\n' + for filename, writer_list in file2writer.items(): + if len(writer_list) > 1: + message += '\nfile : {0}\n'.format(filename) + for x in writer_list: + message += 'spec : {0}\n'.format(x.spec.format(color=True)) + tty.error(message) + tty.error('Operation aborted') + raise SystemExit(1) + + # Proceed regenerating module files + tty.msg('Regenerating {name} module files'.format(name=name)) + if os.path.isdir(cls.path) and args.delete_tree: + shutil.rmtree(cls.path, ignore_errors=False) + mkdirp(cls.path) + for x in writers: + x.write(overwrite=True) + +# Qualifiers to be used when querying the db for specs +constraint_qualifiers = { + 'refresh': { + 'installed': True, + 'known': True + }, + 'find': { + } +} + +# Dictionary of callbacks based on the value of module_command +callbacks = { + 'refresh': module_refresh, + 'find': module_find +} def module(parser, args): - if args.module_command == 'refresh': - module_refresh() - - elif args.module_command == 'find': - module_find(args.module_type, args, args.spec) + module_type = args.module_type + # Query specs from command line + qualifiers = constraint_qualifiers[args.module_command] + specs = [s for s in spack.installed_db.query(**qualifiers)] + constraint = ' '.join(args.constraint) + if constraint: + specs = [x for x in specs if x.satisfies(constraint, strict=True)] + # Call the appropriate function + try: + callbacks[args.module_command](module_type, specs, args) + except MultipleMatches: + message = 'the constraint \'{query}\' matches multiple packages, and this is not allowed in this context' + tty.error(message.format(query=constraint)) + for s in specs: + sys.stderr.write(s.format(color=True) + '\n') + raise SystemExit(1) + except NoMatch: + message = 'the constraint \'{query}\' match no package, and this is not allowed in this context' + tty.die(message.format(query=constraint)) diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 84179e1469..3a66e9f2a6 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -328,6 +328,11 @@ section_schemas = { 'anyOf': [ { 'properties': { + 'hash_length': { + 'type': 'integer', + 'minimum': 0, + 'default': 7 + }, 'whitelist': {'$ref': '#/definitions/array_of_strings'}, 'blacklist': {'$ref': '#/definitions/array_of_strings'}, 'naming_scheme': { diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py index ce46047fa3..82016feb84 100644 --- a/lib/spack/spack/modules.py +++ b/lib/spack/spack/modules.py @@ -188,6 +188,7 @@ def parse_config_options(module_generator): ##### # Automatic loading loads + module_file_actions['hash_length'] = module_configuration.get('hash_length', 7) module_file_actions['autoload'] = dependencies( module_generator.spec, module_file_actions.get('autoload', 'none')) # Prerequisites @@ -295,7 +296,9 @@ class EnvModule(object): if constraint in self.spec: suffixes.append(suffix) # Always append the hash to make the module file unique - suffixes.append(self.spec.dag_hash()) + hash_length = configuration.pop('hash_length', 7) + if hash_length != 0: + suffixes.append(self.spec.dag_hash(length=hash_length)) name = '-'.join(suffixes) return name @@ -338,7 +341,7 @@ class EnvModule(object): return False - def write(self): + def write(self, overwrite=False): """ Writes out a module file for this object. @@ -399,6 +402,15 @@ class EnvModule(object): for line in self.module_specific_content(module_configuration): module_file_content += line + # Print a warning in case I am accidentally overwriting + # a module file that is already there (name clash) + if not overwrite and os.path.exists(self.file_name): + message = 'Module file already exists : skipping creation\n' + message += 'file : {0.file_name}\n' + message += 'spec : {0.spec}' + tty.warn(message.format(self)) + return + # Dump to file with open(self.file_name, 'w') as f: f.write(module_file_content) @@ -454,7 +466,7 @@ class EnvModule(object): class Dotkit(EnvModule): name = 'dotkit' - + path = join_path(spack.share_path, 'dotkit') environment_modifications_formats = { PrependPath: 'dk_alter {name} {value}\n', SetEnv: 'dk_setenv {name} {value}\n' @@ -466,7 +478,7 @@ class Dotkit(EnvModule): @property def file_name(self): - return join_path(spack.share_path, "dotkit", self.spec.architecture, + return join_path(self.path, self.spec.architecture, '%s.dk' % self.use_name) @property @@ -494,7 +506,7 @@ class Dotkit(EnvModule): class TclModule(EnvModule): name = 'tcl' - + path = join_path(spack.share_path, "modules") environment_modifications_formats = { PrependPath: 'prepend-path --delim "{delim}" {name} \"{value}\"\n', AppendPath: 'append-path --delim "{delim}" {name} \"{value}\"\n', @@ -514,7 +526,7 @@ class TclModule(EnvModule): @property def file_name(self): - return join_path(spack.share_path, "modules", self.spec.architecture, self.use_name) + return join_path(self.path, self.spec.architecture, self.use_name) @property def header(self): |