diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py index 2c47e9fcb6..374e71a4d8 100644 --- a/lib/spack/spack/cmd/module.py +++ b/lib/spack/spack/cmd/module.py @@ -32,12 +32,16 @@ import llnl.util.tty as tty import spack.cmd import spack.cmd.common.arguments as arguments -from llnl.util.filesystem import mkdirp +import llnl.util.filesystem as filesystem from spack.modules import module_types -#from spack.util.string import * description = "Manipulate module files" +# Dictionary that will be populated with the list of sub-commands +# Each sub-command must be callable and accept 3 arguments : +# - mtype : the type of the module file +# - specs : the list of specs to be processed +# - args : namespace containing the parsed command line arguments callbacks = {} @@ -51,6 +55,7 @@ def decorator(callback): def setup_parser(subparser): sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='subparser_name') + # spack module refresh refresh_parser = sp.add_parser('refresh', help='Regenerate module files') refresh_parser.add_argument('--delete-tree', help='Delete the module file tree before refresh', action='store_true') @@ -71,11 +76,12 @@ def setup_parser(subparser): ) loads_parser.add_argument( '--input-only', action='store_false', dest='shell', - help='Generate input for module command (instead of a shell script)') - + help='Generate input for module command (instead of a shell script)' + ) loads_parser.add_argument( '-p', '--prefix', dest='prefix', default='', - help='Prepend to module names when issuing module load commands') + help='Prepend to module names when issuing module load commands' + ) arguments.add_common_arguments(loads_parser, ['constraint', 'module_type', 'recurse_dependencies']) @@ -86,8 +92,10 @@ class MultipleMatches(Exception): class NoMatch(Exception): pass + @subcommand('loads') def loads(mtype, specs, args): + """Prompt the list of modules associated with a list of specs""" # Get a comprehensive list of specs if args.recurse_dependencies: specs_from_user_constraint = specs[:] @@ -123,6 +131,7 @@ def loads(mtype, specs, args): d['name'] = mod print(prompt_template.format(**d)) + @subcommand('find') def find(mtype, specs, args): """ @@ -146,13 +155,14 @@ def find(mtype, specs, args): @subcommand('rm') def rm(mtype, specs, args): + """Deletes module files associated with items in specs""" module_cls = module_types[mtype] specs_with_modules = [spec for spec in specs if os.path.exists(module_cls(spec).file_name)] modules = [module_cls(spec) for spec in specs_with_modules] if not modules: tty.msg('No module file matches your query') - return + raise SystemExit(1) # Ask for confirmation if not args.yes_to_all: @@ -168,9 +178,7 @@ def rm(mtype, specs, args): @subcommand('refresh') def refresh(mtype, specs, args): - """Regenerate module files for installed packages - - """ + """Regenerate module files for item in specs""" # Prompt a message to the user about what is going to change if not specs: tty.msg('No package matches your query') @@ -205,7 +213,7 @@ def refresh(mtype, specs, args): tty.msg('Regenerating {name} module files'.format(name=mtype)) if os.path.isdir(cls.path) and args.delete_tree: shutil.rmtree(cls.path, ignore_errors=False) - mkdirp(cls.path) + filesystem.mkdirp(cls.path) for x in writers: x.write(overwrite=True) diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py index fb91f24721..dfbd73b91f 100644 --- a/lib/spack/spack/test/__init__.py +++ b/lib/spack/spack/test/__init__.py @@ -40,7 +40,7 @@ 'cc', 'link_tree', 'spec_yaml', 'optional_deps', 'make_executable', 'configure_guess', 'lock', 'database', 'namespace_trie', 'yaml', 'sbang', 'environment', 'cmd.find', - 'cmd.uninstall', 'cmd.test_install', 'cmd.test_compiler_cmd'] + 'cmd.uninstall', 'cmd.test_install', 'cmd.test_compiler_cmd', 'cmd.module'] def list_tests(): diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py new file mode 100644 index 0000000000..52628f5b3b --- /dev/null +++ b/lib/spack/spack/test/cmd/module.py @@ -0,0 +1,82 @@ +############################################################################## +# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://github.com/llnl/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (as +# published by the Free Software Foundation) version 2.1, February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## +import argparse +import os.path + +import spack.cmd.module as module +import spack.modules as modules +import spack.test.mock_database + + +class TestModule(spack.test.mock_database.MockDatabase): + def _get_module_files(self, args): + return [ + modules.module_types[args.module_type](spec).file_name for spec in args.specs + ] + + def test_module_common_operations(self): + parser = argparse.ArgumentParser() + module.setup_parser(parser) + # Try to remove a non existing module [tcl] + args = parser.parse_args(['rm', 'doesnotexist']) + self.assertRaises(SystemExit, module.module, parser, args) + # Remove existing modules [tcl] + args = parser.parse_args(['rm', '-y', 'mpileaks']) + module_files = self._get_module_files(args) + for item in module_files: + self.assertTrue(os.path.exists(item)) + module.module(parser, args) + for item in module_files: + self.assertFalse(os.path.exists(item)) + # Add them back [tcl] + args = parser.parse_args(['refresh', '-y', 'mpileaks']) + module.module(parser, args) + for item in module_files: + self.assertTrue(os.path.exists(item)) + # TODO : test the --delete-tree option + # TODO : this requires having a separate directory for test modules + # Try to find a module with multiple matches + args = parser.parse_args(['find', 'mpileaks']) + self.assertRaises(SystemExit, module.module, parser, args) + # Try to find a module with no matches + args = parser.parse_args(['find', 'doesnotexist']) + self.assertRaises(SystemExit, module.module, parser, args) + # Try to find a module + args = parser.parse_args(['find', 'libelf']) + module.module(parser, args) + # Remove existing modules [dotkit] + args = parser.parse_args(['rm', '-y', '-m', 'dotkit', 'mpileaks']) + module_files = self._get_module_files(args) + for item in module_files: + self.assertTrue(os.path.exists(item)) + module.module(parser, args) + for item in module_files: + self.assertFalse(os.path.exists(item)) + # Add them back [dotkit] + args = parser.parse_args(['refresh', '-y', '-m', 'dotkit', 'mpileaks']) + module.module(parser, args) + for item in module_files: + self.assertTrue(os.path.exists(item)) + # TODO : add tests for loads and find to check the prompt format