summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/spack/spack/cmd/module.py210
-rw-r--r--lib/spack/spack/config.py5
-rw-r--r--lib/spack/spack/modules.py24
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):