diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/spack/spack/cmd/module.py | 16 | ||||
-rw-r--r-- | lib/spack/spack/environment.py | 58 | ||||
-rw-r--r-- | lib/spack/spack/modules.py | 196 | ||||
-rw-r--r-- | lib/spack/spack/package.py | 353 | ||||
-rw-r--r-- | lib/spack/spack/test/__init__.py | 70 | ||||
-rw-r--r-- | lib/spack/spack/test/modules.py | 42 |
6 files changed, 391 insertions, 344 deletions
diff --git a/lib/spack/spack/cmd/module.py b/lib/spack/spack/cmd/module.py index f996f4eb84..cfe59c8d98 100644 --- a/lib/spack/spack/cmd/module.py +++ b/lib/spack/spack/cmd/module.py @@ -32,18 +32,21 @@ from llnl.util.filesystem import mkdirp from spack.modules import module_types from spack.util.string import * -description ="Manipulate modules and dotkits." +description = "Manipulate modules and dotkits." def setup_parser(subparser): sp = subparser.add_subparsers(metavar='SUBCOMMAND', dest='module_command') - refresh_parser = sp.add_parser('refresh', help='Regenerate all module files.') + sp.add_parser('refresh', help='Regenerate all module files.') 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) + "]") - find_parser.add_argument('spec', nargs='+', help='spec to find a module file for.') + find_parser.add_argument('module_type', + help="Type of module to find file for. [" + + '|'.join(module_types) + "]") + find_parser.add_argument('spec', + nargs='+', + help='spec to find a module file for.') def module_find(mtype, spec_array): @@ -53,7 +56,8 @@ def module_find(mtype, spec_array): should type to use that package's module. """ if mtype not in module_types: - tty.die("Invalid module type: '%s'. Options are %s" % (mtype, comma_or(module_types))) + tty.die("Invalid module type: '%s'. Options are %s" % + (mtype, comma_or(module_types))) specs = spack.cmd.parse_specs(spec_array) if len(specs) > 1: diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index 92ab4e6bea..3fbe2531c1 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -1,7 +1,7 @@ -import os -import os.path import collections import inspect +import os +import os.path class NameModifier(object): @@ -26,7 +26,8 @@ class SetEnv(NameValueModifier): class UnsetEnv(NameModifier): def execute(self): - os.environ.pop(self.name, None) # Avoid throwing if the variable was not set + # Avoid throwing if the variable was not set + os.environ.pop(self.name, None) class SetPath(NameValueModifier): @@ -55,7 +56,9 @@ class RemovePath(NameValueModifier): def execute(self): environment_value = os.environ.get(self.name, '') directories = environment_value.split(':') if environment_value else [] - directories = [os.path.normpath(x) for x in directories if x != os.path.normpath(self.value)] + directories = [os.path.normpath(x) + for x in directories + if x != os.path.normpath(self.value)] os.environ[self.name] = ':'.join(directories) @@ -63,7 +66,8 @@ class EnvironmentModifications(object): """ Keeps track of requests to modify the current environment. - Each call to a method to modify the environment stores the extra information on the caller in the request: + Each call to a method to modify the environment stores the extra + information on the caller in the request: - 'filename' : filename of the module where the caller is defined - 'lineno': line number where the request occurred - 'context' : line of code that issued the request that failed @@ -71,10 +75,10 @@ class EnvironmentModifications(object): def __init__(self, other=None): """ - Initializes a new instance, copying commands from other if it is not None + Initializes a new instance, copying commands from other if not None Args: - other: another instance of EnvironmentModifications from which (optional) + other: another instance of EnvironmentModifications (optional) """ self.env_modifications = [] if other is not None: @@ -93,7 +97,8 @@ class EnvironmentModifications(object): @staticmethod def _check_other(other): if not isinstance(other, EnvironmentModifications): - raise TypeError('other must be an instance of EnvironmentModifications') + raise TypeError( + 'other must be an instance of EnvironmentModifications') def _get_outside_caller_attributes(self): stack = inspect.stack() @@ -101,12 +106,10 @@ class EnvironmentModifications(object): _, filename, lineno, _, context, index = stack[2] context = context[index].strip() except Exception: - filename, lineno, context = 'unknown file', 'unknown line', 'unknown context' - args = { - 'filename': filename, - 'lineno': lineno, - 'context': context - } + filename = 'unknown file' + lineno = 'unknown line' + context = 'unknown context' + args = {'filename': filename, 'lineno': lineno, 'context': context} return args def set(self, name, value, **kwargs): @@ -170,7 +173,8 @@ class EnvironmentModifications(object): def remove_path(self, name, path, **kwargs): """ - Stores in the current object a request to remove a path from a path list + Stores in the current object a request to remove a path from a path + list Args: name: name of the path list in the environment @@ -185,7 +189,8 @@ class EnvironmentModifications(object): Returns a dict of the modifications grouped by variable name Returns: - dict mapping the environment variable name to the modifications to be done on it + dict mapping the environment variable name to the modifications to + be done on it """ modifications = collections.defaultdict(list) for item in self: @@ -203,7 +208,7 @@ class EnvironmentModifications(object): Applies the modifications and clears the list """ modifications = self.group_by_name() - # Apply the modifications to the environment variables one variable at a time + # Apply modifications one variable at a time for name, actions in sorted(modifications.items()): for x in actions: x.execute() @@ -224,13 +229,19 @@ def concatenate_paths(paths): def set_or_unset_not_first(variable, changes, errstream): """ - Check if we are going to set or unset something after other modifications have already been requested + Check if we are going to set or unset something after other modifications + have already been requested """ - indexes = [ii for ii, item in enumerate(changes) if ii != 0 and type(item) in [SetEnv, UnsetEnv]] + indexes = [ii + for ii, item in enumerate(changes) + if ii != 0 and type(item) in [SetEnv, UnsetEnv]] if indexes: good = '\t \t{context} at {filename}:{lineno}' nogood = '\t--->\t{context} at {filename}:{lineno}' - errstream('Suspicious requests to set or unset the variable \'{var}\' found'.format(var=variable)) + message = 'Suspicious requests to set or unset the variable \'{var}\' found' # NOQA: ignore=E501 + errstream( + message.format( + var=variable)) for ii, item in enumerate(changes): print_format = nogood if ii in indexes else good errstream(print_format.format(**item.args)) @@ -238,8 +249,8 @@ def set_or_unset_not_first(variable, changes, errstream): def validate(env, errstream): """ - Validates the environment modifications to check for the presence of suspicious patterns. Prompts a warning for - everything that was found + Validates the environment modifications to check for the presence of + suspicious patterns. Prompts a warning for everything that was found Current checks: - set or unset variables after other changes on the same variable @@ -254,7 +265,8 @@ def validate(env, errstream): def filter_environment_blacklist(env, variables): """ - Generator that filters out any change to environment variables present in the input list + Generator that filters out any change to environment variables present in + the input list Args: env: list of environment modifications diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py index ffed469b20..53f054094a 100644 --- a/lib/spack/spack/modules.py +++ b/lib/spack/spack/modules.py @@ -23,36 +23,35 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################## """ -This module contains code for creating environment modules, which can include dotkits, tcl modules, lmod, and others. +This module contains code for creating environment modules, which can include +dotkits, tcl modules, lmod, and others. -The various types of modules are installed by post-install hooks and removed after an uninstall by post-uninstall hooks. -This class consolidates the logic for creating an abstract description of the information that module systems need. -Currently that includes a number of directories to be appended to paths in the user's environment: +The various types of modules are installed by post-install hooks and removed +after an uninstall by post-uninstall hooks. This class consolidates the logic +for creating an abstract description of the information that module systems +need. - * /bin directories to be appended to PATH - * /lib* directories for LD_LIBRARY_PATH - * /include directories for CPATH - * /man* and /share/man* directories for MANPATH - * the package prefix for CMAKE_PREFIX_PATH +This module also includes logic for coming up with unique names for the module +files so that they can be found by the various shell-support files in +$SPACK/share/spack/setup-env.*. -This module also includes logic for coming up with unique names for the module files so that they can be found by the -various shell-support files in $SPACK/share/spack/setup-env.*. - -Each hook in hooks/ implements the logic for writing its specific type of module file. +Each hook in hooks/ implements the logic for writing its specific type of +module file. """ import copy import datetime import os import os.path import re -import textwrap import string +import textwrap import llnl.util.tty as tty import spack import spack.config from llnl.util.filesystem import join_path, mkdirp -from spack.build_environment import parent_class_modules, set_module_variables_for_package +from spack.build_environment import parent_class_modules +from spack.build_environment import set_module_variables_for_package from spack.environment import * __all__ = ['EnvModule', 'Dotkit', 'TclModule'] @@ -67,30 +66,26 @@ def print_help(): """ For use by commands to tell user how to activate shell support. """ - tty.msg("This command requires spack's shell integration.", - "", + tty.msg("This command requires spack's shell integration.", "", "To initialize spack's shell commands, you must run one of", "the commands below. Choose the right command for your shell.", - "", - "For bash and zsh:", - " . %s/setup-env.sh" % spack.share_path, - "", - "For csh and tcsh:", - " setenv SPACK_ROOT %s" % spack.prefix, - " source %s/setup-env.csh" % spack.share_path, - "") + "", "For bash and zsh:", + " . %s/setup-env.sh" % spack.share_path, "", + "For csh and tcsh:", " setenv SPACK_ROOT %s" % spack.prefix, + " source %s/setup-env.csh" % spack.share_path, "") def inspect_path(prefix): """ - Inspects the prefix of an installation to search for common layouts. Issues a request to modify the environment - accordingly when an item is found. + Inspects the prefix of an installation to search for common layouts. Issues + a request to modify the environment accordingly when an item is found. Args: prefix: prefix of the installation Returns: - instance of EnvironmentModifications containing the requested modifications + instance of EnvironmentModifications containing the requested + modifications """ env = EnvironmentModifications() # Inspect the prefix to check for the existence of common directories @@ -105,18 +100,21 @@ def inspect_path(prefix): def dependencies(spec, request='all'): """ - Returns the list of dependent specs for a given spec, according to the given request + Returns the list of dependent specs for a given spec, according to the + given request Args: spec: target spec request: either 'none', 'direct' or 'all' Returns: - empty list if 'none', direct dependency list if 'direct', all dependencies if 'all' + empty list if 'none', direct dependency list if 'direct', all + dependencies if 'all' """ if request not in ('none', 'direct', 'all'): - raise tty.error("Wrong value for argument 'request' : should be one of ('none', 'direct', 'all') " - " [current value is '%s']" % request) + message = "Wrong value for argument 'request' : " + message += "should be one of ('none', 'direct', 'all')" + raise tty.error(message + " [current value is '%s']" % request) if request == 'none': return [] @@ -124,12 +122,19 @@ def dependencies(spec, request='all'): if request == 'direct': return [xx for _, xx in spec.dependencies.items()] - # FIXME : during module file creation nodes seem to be visited multiple times even if cover='nodes' - # FIXME : is given. This work around permits to get a unique list of spec anyhow. - # FIXME : Possibly we miss a merge step among nodes that refer to the same package. + # FIXME : during module file creation nodes seem to be visited multiple + # FIXME : times even if cover='nodes' is given. This work around permits + # FIXME : to get a unique list of spec anyhow. Do we miss a merge + # FIXME : step among nodes that refer to the same package? seen = set() seen_add = seen.add - l = [xx for xx in sorted(spec.traverse(order='post', depth=True, cover='nodes', root=False), reverse=True)] + l = [xx + for xx in sorted( + spec.traverse(order='post', + depth=True, + cover='nodes', + root=False), + reverse=True)] return [xx for ii, xx in l if not (xx in seen or seen_add(xx))] @@ -146,7 +151,8 @@ def update_dictionary_extending_lists(target, update): def parse_config_options(module_generator): """ - Parse the configuration file and returns a bunch of items that will be needed during module file generation + Parse the configuration file and returns a bunch of items that will be + needed during module file generation Args: module_generator: module generator for a given spec @@ -154,11 +160,14 @@ def parse_config_options(module_generator): Returns: autoloads: list of specs to be autoloaded prerequisites: list of specs to be marked as prerequisite - filters: list of environment variables whose modification is blacklisted in module files - env: list of custom environment modifications to be applied in the module file + filters: list of environment variables whose modification is + blacklisted in module files + env: list of custom environment modifications to be applied in the + module file """ # Get the configuration for this kind of generator - module_configuration = copy.deepcopy(CONFIGURATION.get(module_generator.name, {})) + module_configuration = copy.deepcopy(CONFIGURATION.get( + module_generator.name, {})) ##### # Merge all the rules @@ -179,9 +188,12 @@ def parse_config_options(module_generator): ##### # Automatic loading loads - module_file_actions['autoload'] = dependencies(module_generator.spec, module_file_actions.get('autoload', 'none')) + module_file_actions['autoload'] = dependencies( + module_generator.spec, module_file_actions.get('autoload', 'none')) # Prerequisites - module_file_actions['prerequisites'] = dependencies(module_generator.spec, module_file_actions.get('prerequisites', 'none')) + module_file_actions['prerequisites'] = dependencies( + module_generator.spec, module_file_actions.get('prerequisites', + 'none')) # Environment modifications environment_actions = module_file_actions.pop('environment', {}) env = EnvironmentModifications() @@ -189,7 +201,7 @@ def parse_config_options(module_generator): def process_arglist(arglist): if method == 'unset': for x in arglist: - yield (x,) + yield (x, ) else: for x in arglist.iteritems(): yield x @@ -198,19 +210,13 @@ def parse_config_options(module_generator): for args in process_arglist(arglist): getattr(env, method)(*args) - # for item in arglist: - # if method == 'unset': - # args = [item] - # else: - # args = item.split(',') - # getattr(env, method)(*args) - return module_file_actions, env def filter_blacklisted(specs, module_name): """ - Given a sequence of specs, filters the ones that are blacklisted in the module configuration file. + Given a sequence of specs, filters the ones that are blacklisted in the + module configuration file. Args: specs: sequence of spec instances @@ -233,7 +239,8 @@ class EnvModule(object): class __metaclass__(type): def __init__(cls, name, bases, dict): type.__init__(cls, name, bases, dict) - if cls.name != 'env_module' and cls.name in CONFIGURATION['enable']: + if cls.name != 'env_module' and cls.name in CONFIGURATION[ + 'enable']: module_types[cls.name] = cls def __init__(self, spec=None): @@ -249,7 +256,8 @@ class EnvModule(object): # long description is the docstring with reduced whitespace. self.long_description = None if self.spec.package.__doc__: - self.long_description = re.sub(r'\s+', ' ', self.spec.package.__doc__) + self.long_description = re.sub(r'\s+', ' ', + self.spec.package.__doc__) @property def naming_scheme(self): @@ -271,12 +279,14 @@ class EnvModule(object): @property def use_name(self): """ - Subclasses should implement this to return the name the module command uses to refer to the package. + Subclasses should implement this to return the name the module command + uses to refer to the package. """ naming_tokens = self.tokens naming_scheme = self.naming_scheme name = naming_scheme.format(**naming_tokens) - name += '-' + self.spec.dag_hash() # Always append the hash to make the module file unique + name += '-' + self.spec.dag_hash( + ) # Always append the hash to make the module file unique # Not everybody is working on linux... parts = name.split('/') name = join_path(*parts) @@ -296,8 +306,12 @@ class EnvModule(object): @property def blacklisted(self): configuration = CONFIGURATION.get(self.name, {}) - whitelist_matches = [x for x in configuration.get('whitelist', []) if self.spec.satisfies(x)] - blacklist_matches = [x for x in configuration.get('blacklist', []) if self.spec.satisfies(x)] + whitelist_matches = [x + for x in configuration.get('whitelist', []) + if self.spec.satisfies(x)] + blacklist_matches = [x + for x in configuration.get('blacklist', []) + if self.spec.satisfies(x)] if whitelist_matches: message = '\tWHITELIST : %s [matches : ' % self.spec.cshort_spec for rule in whitelist_matches: @@ -327,7 +341,8 @@ class EnvModule(object): """ if self.blacklisted: return - tty.debug("\tWRITE : %s [%s]" % (self.spec.cshort_spec, self.file_name)) + tty.debug("\tWRITE : %s [%s]" % + (self.spec.cshort_spec, self.file_name)) module_dir = os.path.dirname(self.file_name) if not os.path.exists(module_dir): @@ -337,11 +352,12 @@ class EnvModule(object): # installation prefix env = inspect_path(self.spec.prefix) - # Let the extendee/dependency modify their extensions/dependencies before asking for - # package-specific modifications + # Let the extendee/dependency modify their extensions/dependencies + # before asking for package-specific modifications spack_env = EnvironmentModifications() - # TODO : the code down below is quite similar to build_environment.setup_package and needs to be - # TODO : factored out to a single place + # TODO : the code down below is quite similar to + # TODO : build_environment.setup_package and needs to be factored out + # TODO : to a single place for item in dependencies(self.spec, 'all'): package = self.spec[item.name].package modules = parent_class_modules(package.__class__) @@ -358,14 +374,18 @@ class EnvModule(object): # Parse configuration file module_configuration, conf_env = parse_config_options(self) env.extend(conf_env) - filters = module_configuration.get('filter', {}).get('environment_blacklist',{}) + filters = module_configuration.get('filter', {}).get( + 'environment_blacklist', {}) # Build up the module file content module_file_content = self.header - for x in filter_blacklisted(module_configuration.pop('autoload', []), self.name): + for x in filter_blacklisted( + module_configuration.pop('autoload', []), self.name): module_file_content += self.autoload(x) - for x in filter_blacklisted(module_configuration.pop('prerequisites', []), self.name): + for x in filter_blacklisted( + module_configuration.pop('prerequisites', []), self.name): module_file_content += self.prerequisite(x) - for line in self.process_environment_command(filter_environment_blacklist(env, filters)): + for line in self.process_environment_command( + filter_environment_blacklist(env, filters)): module_file_content += line for line in self.module_specific_content(module_configuration): module_file_content += line @@ -392,10 +412,13 @@ class EnvModule(object): def process_environment_command(self, env): for command in env: try: - yield self.environment_modifications_formats[type(command)].format(**command.args) + yield self.environment_modifications_formats[type( + command)].format(**command.args) except KeyError: - tty.warn('Cannot handle command of type {command} : skipping request'.format(command=type(command))) - tty.warn('{context} at {filename}:{lineno}'.format(**command.args)) + message = 'Cannot handle command of type {command} : skipping request' # NOQA: ignore=E501 + details = '{context} at {filename}:{lineno}' + tty.warn(message.format(command=type(command))) + tty.warn(details.format(**command.args)) @property def file_name(self): @@ -408,9 +431,12 @@ class EnvModule(object): if os.path.exists(mod_file): try: os.remove(mod_file) # Remove the module file - os.removedirs(os.path.dirname(mod_file)) # Remove all the empty directories from the leaf up + os.removedirs( + os.path.dirname(mod_file) + ) # Remove all the empty directories from the leaf up except OSError: - pass # removedirs throws OSError on first non-empty directory found + # removedirs throws OSError on first non-empty directory found + pass class Dotkit(EnvModule): @@ -424,13 +450,12 @@ class Dotkit(EnvModule): autoload_format = 'dk_op {module_file}\n' - prerequisite_format = None # TODO : does something like prerequisite exist for dotkit? - - default_naming_format = '{name}-{version}-{compiler.name}-{compiler.version}' + default_naming_format = '{name}-{version}-{compiler.name}-{compiler.version}' # NOQA: ignore=E501 @property def file_name(self): - return join_path(Dotkit.path, self.spec.architecture, '%s.dk' % self.use_name) + return join_path(Dotkit.path, self.spec.architecture, + '%s.dk' % self.use_name) @property def header(self): @@ -474,7 +499,7 @@ class TclModule(EnvModule): prerequisite_format = 'prereq {module_file}\n' - default_naming_format = '{name}-{version}-{compiler.name}-{compiler.version}' + default_naming_format = '{name}-{version}-{compiler.name}-{compiler.version}' # NOQA: ignore=E501 @property def file_name(self): @@ -482,9 +507,10 @@ class TclModule(EnvModule): @property def header(self): + timestamp = datetime.datetime.now() # TCL Modulefile header header = '#%Module1.0\n' - header += '## Module file created by spack (https://github.com/LLNL/spack) on %s\n' % datetime.datetime.now() + header += '## Module file created by spack (https://github.com/LLNL/spack) on %s\n' % timestamp # NOQA: ignore=E501 header += '##\n' header += '## %s\n' % self.spec.short_spec header += '##\n' @@ -509,16 +535,18 @@ class TclModule(EnvModule): f = string.Formatter() for item in conflict_format: line = 'conflict ' + item + '\n' - if len([x for x in f.parse(line)]) > 1: # We do have placeholder to substitute - for naming_dir, conflict_dir in zip(self.naming_scheme.split('/'), item.split('/')): + if len([x for x in f.parse(line) + ]) > 1: # We do have placeholder to substitute + for naming_dir, conflict_dir in zip( + self.naming_scheme.split('/'), item.split('/')): if naming_dir != conflict_dir: - message = 'conflict scheme does not match naming scheme [{spec}]\n\n' + message = 'conflict scheme does not match naming scheme [{spec}]\n\n' # NOQA: ignore=E501 message += 'naming scheme : "{nformat}"\n' message += 'conflict scheme : "{cformat}"\n\n' - message += '** You may want to check your `modules.yaml` configuration file **\n' - tty.error( - message.format(spec=self.spec, nformat=self.naming_scheme, cformat=item) - ) + message += '** You may want to check your `modules.yaml` configuration file **\n' # NOQA: ignore=E501 + tty.error(message.format(spec=self.spec, + nformat=self.naming_scheme, + cformat=item)) raise SystemExit('Module generation aborted.') line = line.format(**naming_tokens) yield line diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 3626a574c8..8e6cf32954 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -37,7 +37,6 @@ import os import re import textwrap import time -import glob import llnl.util.tty as tty import spack @@ -62,7 +61,6 @@ from spack.util.environment import dump_environment from spack.util.executable import ProcessError from spack.version import * from urlparse import urlparse - """Allowed URL schemes for spack packages.""" _ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"] @@ -305,26 +303,21 @@ class Package(object): # """By default we build in parallel. Subclasses can override this.""" parallel = True - """# jobs to use for parallel make. If set, overrides default of ncpus.""" make_jobs = None - - """Most packages are NOT extendable. Set to True if you want extensions.""" + """Most packages are NOT extendable. Set to True if you want extensions.""" extendable = False - """List of prefix-relative file paths (or a single path). If these do not exist after install, or if they exist but are not files, sanity checks fail. """ sanity_check_is_file = [] - """List of prefix-relative directory paths (or a single path). If these do not exist after install, or if they exist but are not directories, sanity checks will fail. """ sanity_check_is_dir = [] - def __init__(self, spec): # this determines how the package should be built. self.spec = spec @@ -336,23 +329,24 @@ class Package(object): self.name = self.name[self.name.rindex('.') + 1:] # Allow custom staging paths for packages - self.path=None + self.path = None # Sanity check attributes required by Spack directives. spack.directives.ensure_dicts(type(self)) # Check versions in the versions dict. for v in self.versions: - assert(isinstance(v, Version)) + assert (isinstance(v, Version)) # Check version descriptors for v in sorted(self.versions): - assert(isinstance(self.versions[v], dict)) + assert (isinstance(self.versions[v], dict)) # Version-ize the keys in versions dict try: - self.versions = dict((Version(v), h) for v,h in self.versions.items()) - except ValueError, e: + self.versions = dict((Version(v), h) + for v, h in self.versions.items()) + except ValueError as e: raise ValueError("In package %s: %s" % (self.name, e.message)) # stage used to build this package. @@ -366,9 +360,9 @@ class Package(object): # This makes self.url behave sanely. if self.spec.versions.concrete: # TODO: this is a really roundabout way of determining the type - # TODO: of fetch to do. figure out a more sane fetch strategy/package - # TODO: init order (right now it's conflated with stage, package, and - # TODO: the tests make assumptions) + # TODO: of fetch to do. figure out a more sane fetch + # TODO: strategy/package init order (right now it's conflated with + # TODO: stage, package, and the tests make assumptions) f = fs.for_package_version(self, self.version) if isinstance(f, fs.URLFetchStrategy): self.url = self.url_for_version(self.spec.version) @@ -387,14 +381,12 @@ class Package(object): if self.is_extension: spack.repo.get(self.extendee_spec)._check_extendable() - @property def version(self): if not self.spec.versions.concrete: raise ValueError("Can only get of package with concrete version.") return self.spec.versions[0] - @memoized def version_urls(self): """Return a list of URLs for different versions of this @@ -407,7 +399,6 @@ class Package(object): version_urls[v] = args['url'] return version_urls - def nearest_url(self, version): """Finds the URL for the next lowest version with a URL. If there is no lower version with a URL, uses the @@ -424,10 +415,11 @@ class Package(object): url = version_urls[v] return url - # TODO: move this out of here and into some URL extrapolation module? def url_for_version(self, version): - """Returns a URL that you can download a new version of this package from.""" + """ + Returns a URL that you can download a new version of this package from. + """ if not isinstance(version, Version): version = Version(version) @@ -441,14 +433,17 @@ class Package(object): return version_urls[version] # If we have no idea, try to substitute the version. - return spack.url.substitute_version(self.nearest_url(version), - self.url_version(version)) + return spack.url.substitute_version( + self.nearest_url(version), self.url_version(version)) def _make_resource_stage(self, root_stage, fetcher, resource): resource_stage_folder = self._resource_stage(resource) resource_mirror = join_path(self.name, os.path.basename(fetcher.url)) - stage = ResourceStage(resource.fetcher, root=root_stage, resource=resource, - name=resource_stage_folder, mirror_path=resource_mirror, + stage = ResourceStage(resource.fetcher, + root=root_stage, + resource=resource, + name=resource_stage_folder, + mirror_path=resource_mirror, path=self.path) return stage @@ -474,7 +469,8 @@ class Package(object): else: # Construct resource stage resource = resources[ii - 1] # ii == 0 is root! - stage = self._make_resource_stage(composite_stage[0], fetcher, resource) + stage = self._make_resource_stage(composite_stage[0], fetcher, + resource) # Append the item to the composite composite_stage.append(stage) @@ -492,13 +488,11 @@ class Package(object): self._stage = self._make_stage() return self._stage - @stage.setter def stage(self, stage): """Allow a stage object to be set to override the default.""" self._stage = stage - def _make_fetcher(self): # Construct a composite fetcher that always contains at least # one element (the root package). In case there are resources @@ -515,7 +509,8 @@ class Package(object): @property def fetcher(self): if not self.spec.versions.concrete: - raise ValueError("Can only get a fetcher for a package with concrete versions.") + raise ValueError( + "Can only get a fetcher for a package with concrete versions.") if not self._fetcher: self._fetcher = self._make_fetcher() return self._fetcher @@ -524,10 +519,11 @@ class Package(object): def fetcher(self, f): self._fetcher = f - @property def extendee_spec(self): - """Spec of the extendee of this package, or None if it is not an extension.""" + """ + Spec of the extendee of this package, or None if it is not an extension + """ if not self.extendees: return None @@ -549,10 +545,11 @@ class Package(object): spec, kwargs = self.extendees[name] return spec - @property def extendee_args(self): - """Spec of the extendee of this package, or None if it is not an extension.""" + """ + Spec of the extendee of this package, or None if it is not an extension + """ if not self.extendees: return None @@ -560,7 +557,6 @@ class Package(object): name = next(iter(self.extendees)) return self.extendees[name][1] - @property def is_extension(self): # if it is concrete, it's only an extension if it actually @@ -571,22 +567,20 @@ class Package(object): # If not, then it's an extension if it *could* be an extension return bool(self.extendees) - def extends(self, spec): - if not spec.name in self.extendees: + if spec.name not in self.extendees: return False s = self.extendee_spec return s and s.satisfies(spec) - @property def activated(self): if not self.is_extension: - raise ValueError("is_extension called on package that is not an extension.") + raise ValueError( + "is_extension called on package that is not an extension.") exts = spack.install_layout.extension_map(self.extendee_spec) return (self.name in exts) and (exts[self.name] == self.spec) - def preorder_traversal(self, visited=None, **kwargs): """This does a preorder traversal of the package's dependence DAG.""" virtual = kwargs.get("virtual", False) @@ -605,36 +599,35 @@ class Package(object): spec = self.dependencies[name] # currently, we do not descend into virtual dependencies, as this - # makes doing a sensible traversal much harder. We just assume that - # ANY of the virtual deps will work, which might not be true (due to - # conflicts or unsatisfiable specs). For now this is ok but we might - # want to reinvestigate if we start using a lot of complicated virtual - # dependencies + # makes doing a sensible traversal much harder. We just assume + # that ANY of the virtual deps will work, which might not be true + # (due to conflicts or unsatisfiable specs). For now this is ok + # but we might want to reinvestigate if we start using a lot of + # complicated virtual dependencies # TODO: reinvestigate this. if spec.virtual: if virtual: yield spec continue - for pkg in spack.repo.get(name).preorder_traversal(visited, **kwargs): + for pkg in spack.repo.get(name).preorder_traversal(visited, + **kwargs): yield pkg - def provides(self, vpkg_name): - """True if this package provides a virtual package with the specified name.""" + """ + True if this package provides a virtual package with the specified name + """ return any(s.name == vpkg_name for s in self.provided) - def virtual_dependencies(self, visited=None): for spec in sorted(set(self.preorder_traversal(virtual=True))): yield spec - @property def installed(self): return os.path.isdir(self.prefix) - @property def installed_dependents(self): """Return a list of the specs of all installed packages that depend @@ -651,60 +644,62 @@ class Package(object): dependents.append(spec) return dependents - @property def prefix(self): """Get the prefix into which this package should be installed.""" return self.spec.prefix - @property def compiler(self): - """Get the spack.compiler.Compiler object used to build this package.""" + """Get the spack.compiler.Compiler object used to build this package""" if not self.spec.concrete: raise ValueError("Can only get a compiler for a concrete package.") return spack.compilers.compiler_for_spec(self.spec.compiler) - def url_version(self, version): - """Given a version, this returns a string that should be substituted into the - package's URL to download that version. - By default, this just returns the version string. Subclasses may need to - override this, e.g. for boost versions where you need to ensure that there - are _'s in the download URL. """ - return str(version) + Given a version, this returns a string that should be substituted + into the package's URL to download that version. + By default, this just returns the version string. Subclasses may need + to override this, e.g. for boost versions where you need to ensure that + there are _'s in the download URL. + """ + return str(version) def remove_prefix(self): - """Removes the prefix for a package along with any empty parent directories.""" + """ + Removes the prefix for a package along with any empty parent + directories + """ spack.install_layout.remove_install_directory(self.spec) - def do_fetch(self, mirror_only=False): - """Creates a stage directory and downloads the tarball for this package. - Working directory will be set to the stage directory. + """ + Creates a stage directory and downloads the tarball for this package. + Working directory will be set to the stage directory. """ if not self.spec.concrete: raise ValueError("Can only fetch concrete packages.") start_time = time.time() - if spack.do_checksum and not self.version in self.versions: - tty.warn("There is no checksum on file to fetch %s safely." - % self.spec.format('$_$@')) + if spack.do_checksum and self.version not in self.versions: + tty.warn("There is no checksum on file to fetch %s safely." % + self.spec.format('$_$@')) # Ask the user whether to skip the checksum if we're # interactive, but just fail if non-interactive. - checksum_msg = "Add a checksum or use --no-checksum to skip this check." + checksum_msg = "Add a checksum or use --no-checksum to skip this check." # NOQA: ignore=E501 ignore_checksum = False if sys.stdout.isatty(): - ignore_checksum = tty.get_yes_or_no(" Fetch anyway?", default=False) + ignore_checksum = tty.get_yes_or_no(" Fetch anyway?", + default=False) if ignore_checksum: tty.msg("Fetching with no checksum.", checksum_msg) if not ignore_checksum: - raise FetchError( - "Will not fetch %s" % self.spec.format('$_$@'), checksum_msg) + raise FetchError("Will not fetch %s" % + self.spec.format('$_$@'), checksum_msg) self.stage.fetch(mirror_only) @@ -723,7 +718,6 @@ class Package(object): self.stage.expand_archive() self.stage.chdir_to_source() - def do_patch(self): """Calls do_stage(), then applied patches to the expanded tarball if they haven't been applied already.""" @@ -743,10 +737,10 @@ class Package(object): # Construct paths to special files in the archive dir used to # keep track of whether patches were successfully applied. - archive_dir = self.stage.source_path - good_file = join_path(archive_dir, '.spack_patched') + archive_dir = self.stage.source_path + good_file = join_path(archive_dir, '.spack_patched') no_patches_file = join_path(archive_dir, '.spack_no_patches') - bad_file = join_path(archive_dir, '.spack_patch_failed') + bad_file = join_path(archive_dir, '.spack_patch_failed') # If we encounter an archive that failed to patch, restage it # so that we can apply all the patches again. @@ -801,13 +795,11 @@ class Package(object): else: touch(no_patches_file) - @property def namespace(self): namespace, dot, module = self.__module__.rpartition('.') return namespace - def do_fake_install(self): """Make a fake install directory contaiing a 'fake' file in bin.""" mkdirp(self.prefix.bin) @@ -815,15 +807,15 @@ class Package(object): mkdirp(self.prefix.lib) mkdirp(self.prefix.man1) - def _get_needed_resources(self): resources = [] # Select the resources that are needed for this build for when_spec, resource_list in self.resources.items(): if when_spec in self.spec: resources.extend(resource_list) - # Sorts the resources by the length of the string representing their destination. Since any nested resource - # must contain another resource's name in its path, it seems that should work + # Sorts the resources by the length of the string representing their + # destination. Since any nested resource must contain another + # resource's name in its path, it seems that should work resources = sorted(resources, key=lambda res: len(res.destination)) return resources @@ -832,10 +824,14 @@ class Package(object): resource_stage_folder = '-'.join(pieces) return resource_stage_folder - def do_install(self, - keep_prefix=False, keep_stage=False, ignore_deps=False, - skip_patch=False, verbose=False, make_jobs=None, fake=False): + keep_prefix=False, + keep_stage=False, + ignore_deps=False, + skip_patch=False, + verbose=False, + make_jobs=None, + fake=False): """Called by commands to install a package and its dependencies. Package implementations should override install() to describe @@ -846,18 +842,20 @@ class Package(object): keep_stage -- By default, stage is destroyed only if there are no exceptions during build. Set to True to keep the stage even with exceptions. - ignore_deps -- Do not install dependencies before installing this package. + ignore_deps -- Don't install dependencies before installing this + package fake -- Don't really build -- install fake stub files instead. skip_patch -- Skip patch stage of build if True. verbose -- Display verbose build output (by default, suppresses it) - make_jobs -- Number of make jobs to use for install. Default is ncpus. + make_jobs -- Number of make jobs to use for install. Default is ncpus """ if not self.spec.concrete: raise ValueError("Can only install concrete packages.") # No installation needed if package is external if self.spec.external: - tty.msg("%s is externally installed in %s" % (self.name, self.spec.external)) + tty.msg("%s is externally installed in %s" % + (self.name, self.spec.external)) return # Ensure package is not already installed @@ -869,9 +867,13 @@ class Package(object): # First, install dependencies recursively. if not ignore_deps: - self.do_install_dependencies( - keep_prefix=keep_prefix, keep_stage=keep_stage, ignore_deps=ignore_deps, - fake=fake, skip_patch=skip_patch, verbose=verbose, make_jobs=make_jobs) + self.do_install_dependencies(keep_prefix=keep_prefix, + keep_stage=keep_stage, + ignore_deps=ignore_deps, + fake=fake, + skip_patch=skip_patch, + verbose=verbose, + make_jobs=make_jobs) # Set parallelism before starting build. self.make_jobs = make_jobs @@ -899,35 +901,41 @@ class Package(object): self.do_fake_install() else: # Do the real install in the source directory. - self.stage.chdir_to_source() + self.stage.chdir_to_source() - # Save the build environment in a file before building. - env_path = join_path(os.getcwd(), 'spack-build.env') + # Save the build environment in a file before building. + env_path = join_path(os.getcwd(), 'spack-build.env') - try: - # Redirect I/O to a build log (and optionally to the terminal) + try: + # Redirect I/O to a build log (and optionally to + # the terminal) log_path = join_path(os.getcwd(), 'spack-build.out') log_file = open(log_path, 'w') - with log_output(log_file, verbose, sys.stdout.isatty(), True): + with log_output(log_file, verbose, sys.stdout.isatty(), + True): dump_environment(env_path) self.install(self.spec, self.prefix) - except ProcessError as e: - # Annotate ProcessErrors with the location of the build log. - e.build_log = log_path - raise e + except ProcessError as e: + # Annotate ProcessErrors with the location of + # the build log + e.build_log = log_path + raise e - # Ensure that something was actually installed. - self.sanity_check_prefix() + # Ensure that something was actually installed. + self.sanity_check_prefix() - # Copy provenance into the install directory on success - log_install_path = spack.install_layout.build_log_path(self.spec) - env_install_path = spack.install_layout.build_env_path(self.spec) - packages_dir = spack.install_layout.build_packages_path(self.spec) + # Copy provenance into the install directory on success + log_install_path = spack.install_layout.build_log_path( + self.spec) + env_install_path = spack.install_layout.build_env_path( + self.spec) + packages_dir = spack.install_layout.build_packages_path( + self.spec) - install(log_path, log_install_path) - install(env_path, env_install_path) - dump_packages(self.spec, packages_dir) + install(log_path, log_install_path) + install(env_path, env_install_path) + dump_packages(self.spec, packages_dir) # Run post install hooks before build stage is removed. spack.hooks.post_install(self) @@ -937,8 +945,9 @@ class Package(object): build_time = self._total_time - self._fetch_time tty.msg("Successfully installed %s" % self.name, - "Fetch: %s. Build: %s. Total: %s." - % (_hms(self._fetch_time), _hms(build_time), _hms(self._total_time))) + "Fetch: %s. Build: %s. Total: %s." % + (_hms(self._fetch_time), _hms(build_time), + _hms(self._total_time))) print_pkg(self.prefix) try: @@ -953,16 +962,17 @@ class Package(object): tty.warn("Keeping install prefix in place despite error.", "Spack will think this package is installed. " + "Manually remove this directory to fix:", - self.prefix, wrap=True) + self.prefix, + wrap=True) raise # note: PARENT of the build process adds the new package to # the database, so that we don't need to re-read from file. spack.installed_db.add(self.spec, self.prefix) - def sanity_check_prefix(self): """This function checks whether install succeeded.""" + def check_paths(path_list, filetype, predicate): if isinstance(path_list, basestring): path_list = [path_list] @@ -970,8 +980,9 @@ class Package(object): for path in path_list: abs_path = os.path.join(self.prefix, path) if not predicate(abs_path): - raise InstallError("Install failed for %s. No such %s in prefix: %s" - % (self.name, filetype, path)) + raise InstallError( + "Install failed for %s. No such %s in prefix: %s" % + (self.name, filetype, path)) check_paths(self.sanity_check_is_file, 'file', os.path.isfile) check_paths(self.sanity_check_is_dir, 'directory', os.path.isdir) @@ -982,13 +993,11 @@ class Package(object): raise InstallError( "Install failed for %s. Nothing was installed!" % self.name) - def do_install_dependencies(self, **kwargs): # Pass along paths of dependencies here for dep in self.spec.dependencies.values(): dep.package.do_install(**kwargs) - @property def build_log_path(self): if self.installed: @@ -996,7 +1005,6 @@ class Package(object): else: return join_path(self.stage.source_path, 'spack-build.out') - @property def module(self): """Use this to add variables to the class's module's scope. @@ -1037,7 +1045,6 @@ class Package(object): """ pass - def setup_dependent_environment(self, spack_env, run_env, dependent_spec): """Set up the environment of packages that depend on this one. @@ -1077,7 +1084,6 @@ class Package(object): """ self.setup_environment(spack_env, run_env) - def setup_dependent_package(self, module, dependent_spec): """Set up Python module-scope variables for dependent packages. @@ -1123,8 +1129,11 @@ class Package(object): pass def install(self, spec, prefix): - """Package implementations override this with their own build configuration.""" - raise InstallError("Package %s provides no install method!" % self.name) + """ + Package implementations override this with their own configuration + """ + raise InstallError("Package %s provides no install method!" % + self.name) def do_uninstall(self, force=False): if not self.installed: @@ -1146,12 +1155,10 @@ class Package(object): # Once everything else is done, run post install hooks spack.hooks.post_uninstall(self) - def _check_extendable(self): if not self.extendable: raise ValueError("Package %s is not extendable!" % self.name) - def _sanity_check_extension(self): if not self.is_extension: raise ActivationError("This package is not an extension.") @@ -1160,12 +1167,13 @@ class Package(object): extendee_package._check_extendable() if not extendee_package.installed: - raise ActivationError("Can only (de)activate extensions for installed packages.") + raise ActivationError( + "Can only (de)activate extensions for installed packages.") if not self.installed: raise ActivationError("Extensions must first be installed.") - if not self.extendee_spec.name in self.extendees: - raise ActivationError("%s does not extend %s!" % (self.name, self.extendee.name)) - + if self.extendee_spec.name not in self.extendees: + raise ActivationError("%s does not extend %s!" % + (self.name, self.extendee.name)) def do_activate(self, force=False): """Called on an etension to invoke the extendee's activate method. @@ -1175,8 +1183,8 @@ class Package(object): """ self._sanity_check_extension() - spack.install_layout.check_extension_conflict( - self.extendee_spec, self.spec) + spack.install_layout.check_extension_conflict(self.extendee_spec, + self.spec) # Activate any package dependencies that are also extensions. if not force: @@ -1188,9 +1196,8 @@ class Package(object): self.extendee_spec.package.activate(self, **self.extendee_args) spack.install_layout.add_extension(self.extendee_spec, self.spec) - tty.msg("Activated extension %s for %s" - % (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@"))) - + tty.msg("Activated extension %s for %s" % + (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@"))) def activate(self, extension, **kwargs): """Symlinks all files from the extension into extendee's install dir. @@ -1201,6 +1208,7 @@ class Package(object): always executed. """ + def ignore(filename): return (filename in spack.install_layout.hidden_file_paths or kwargs.get('ignore', lambda f: False)(filename)) @@ -1212,7 +1220,6 @@ class Package(object): tree.merge(self.prefix, ignore=ignore) - def do_deactivate(self, **kwargs): """Called on the extension to invoke extendee's deactivate() method.""" self._sanity_check_extension() @@ -1230,7 +1237,7 @@ class Package(object): for dep in aspec.traverse(): if self.spec == dep: raise ActivationError( - "Cannot deactivate %s beacuse %s is activated and depends on it." + "Cannot deactivate %s because %s is activated and depends on it." # NOQA: ignore=E501 % (self.spec.short_spec, aspec.short_spec)) self.extendee_spec.package.deactivate(self, **self.extendee_args) @@ -1238,11 +1245,11 @@ class Package(object): # redundant activation check -- makes SURE the spec is not # still activated even if something was wrong above. if self.activated: - spack.install_layout.remove_extension(self.extendee_spec, self.spec) - - tty.msg("Deactivated extension %s for %s" - % (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@"))) + spack.install_layout.remove_extension(self.extendee_spec, + self.spec) + tty.msg("Deactivated extension %s for %s" % + (self.spec.short_spec, self.extendee_spec.format("$_$@$+$%@"))) def deactivate(self, extension, **kwargs): """Unlinks all files from extension out of this package's install dir. @@ -1253,6 +1260,7 @@ class Package(object): always executed. """ + def ignore(filename): return (filename in spack.install_layout.hidden_file_paths or kwargs.get('ignore', lambda f: False)(filename)) @@ -1260,17 +1268,14 @@ class Package(object): tree = LinkTree(extension.prefix) tree.unmerge(self.prefix, ignore=ignore) - def do_restage(self): """Reverts expanded/checked out source to a pristine state.""" self.stage.restage() - def do_clean(self): """Removes the package's build stage and source tarball.""" self.stage.destroy() - def format_doc(self, **kwargs): """Wrap doc string at 72 characters and format nicely""" indent = kwargs.get('indent', 0) @@ -1285,7 +1290,6 @@ class Package(object): results.write((" " * indent) + line + "\n") return results.getvalue() - @property def all_urls(self): urls = [] @@ -1297,7 +1301,6 @@ class Package(object): urls.append(args['url']) return urls - def fetch_remote_versions(self): """Try to find remote versions of this package using the list_url and any other URLs described in the package file.""" @@ -1306,26 +1309,30 @@ class Package(object): try: return spack.util.web.find_versions_of_archive( - *self.all_urls, list_url=self.list_url, list_depth=self.list_depth) + *self.all_urls, + list_url=self.list_url, + list_depth=self.list_depth) except spack.error.NoNetworkConnectionError as e: - tty.die("Package.fetch_versions couldn't connect to:", - e.url, e.message) - + tty.die("Package.fetch_versions couldn't connect to:", e.url, + e.message) @property def rpath(self): """Get the rpath this package links with, as a list of paths.""" rpaths = [self.prefix.lib, self.prefix.lib64] - rpaths.extend(d.prefix.lib for d in self.spec.traverse(root=False) + rpaths.extend(d.prefix.lib + for d in self.spec.traverse(root=False) if os.path.isdir(d.prefix.lib)) - rpaths.extend(d.prefix.lib64 for d in self.spec.traverse(root=False) + rpaths.extend(d.prefix.lib64 + for d in self.spec.traverse(root=False) if os.path.isdir(d.prefix.lib64)) return rpaths - @property def rpath_args(self): - """Get the rpath args as a string, with -Wl,-rpath, for each element.""" + """ + Get the rpath args as a string, with -Wl,-rpath, for each element + """ return " ".join("-Wl,-rpath,%s" % p for p in self.rpath) @@ -1333,6 +1340,7 @@ def install_dependency_symlinks(pkg, spec, prefix): """Execute a dummy install and flatten dependencies""" flatten_dependencies(spec, prefix) + def flatten_dependencies(spec, flat_dir): """Make each dependency of spec present in dir via symlink.""" for dep in spec.traverse(root=False): @@ -1341,13 +1349,13 @@ def flatten_dependencies(spec, flat_dir): dep_path = spack.install_layout.path_for_spec(dep) dep_files = LinkTree(dep_path) - os.mkdir(flat_dir+'/'+name) + os.mkdir(flat_dir + '/' + name) - conflict = dep_files.find_conflict(flat_dir+'/'+name) + conflict = dep_files.find_conflict(flat_dir + '/' + name) if conflict: raise DependencyConflictError(conflict) - dep_files.merge(flat_dir+'/'+name) + dep_files.merge(flat_dir + '/' + name) def validate_package_url(url_string): @@ -1388,9 +1396,11 @@ def dump_packages(spec, path): # Create a source repo and get the pkg directory out of it. try: source_repo = spack.repository.Repo(source_repo_root) - source_pkg_dir = source_repo.dirname_for_package_name(node.name) - except RepoError as e: - tty.warn("Warning: Couldn't copy in provenance for %s" % node.name) + source_pkg_dir = source_repo.dirname_for_package_name( + node.name) + except RepoError: + tty.warn("Warning: Couldn't copy in provenance for %s" % + node.name) # Create a destination repository dest_repo_root = join_path(path, node.namespace) @@ -1410,7 +1420,7 @@ def print_pkg(message): """Outputs a message with a package icon.""" from llnl.util.tty.color import cwrite cwrite('@*g{[+]} ') - print message + print(message) def _hms(seconds): @@ -1419,20 +1429,25 @@ def _hms(seconds): h, m = divmod(m, 60) parts = [] - if h: parts.append("%dh" % h) - if m: parts.append("%dm" % m) - if s: parts.append("%.2fs" % s) + if h: + parts.append("%dh" % h) + if m: + parts.append("%dm" % m) + if s: + parts.append("%.2fs" % s) return ' '.join(parts) class FetchError(spack.error.SpackError): """Raised when something goes wrong during fetch.""" + def __init__(self, message, long_msg=None): super(FetchError, self).__init__(message, long_msg) class InstallError(spack.error.SpackError): """Raised when something goes wrong during install or uninstall.""" + def __init__(self, message, long_msg=None): super(InstallError, self).__init__(message, long_msg) @@ -1443,21 +1458,24 @@ class ExternalPackageError(InstallError): class PackageStillNeededError(InstallError): """Raised when package is still needed by another on uninstall.""" + def __init__(self, spec, dependents): - super(PackageStillNeededError, self).__init__( - "Cannot uninstall %s" % spec) + super(PackageStillNeededError, self).__init__("Cannot uninstall %s" % + spec) self.spec = spec self.dependents = dependents class PackageError(spack.error.SpackError): """Raised when something is wrong with a package definition.""" + def __init__(self, message, long_msg=None): super(PackageError, self).__init__(message, long_msg) class PackageVersionError(PackageError): """Raised when a version URL cannot automatically be determined.""" + def __init__(self, version): super(PackageVersionError, self).__init__( "Cannot determine a URL automatically for version %s" % version, @@ -1466,6 +1484,7 @@ class PackageVersionError(PackageError): class VersionFetchError(PackageError): """Raised when a version URL cannot automatically be determined.""" + def __init__(self, cls): super(VersionFetchError, self).__init__( "Cannot fetch versions for package %s " % cls.__name__ + @@ -1474,12 +1493,15 @@ class VersionFetchError(PackageError): class NoURLError(PackageError): """Raised when someone tries to build a URL for a package with no URLs.""" + def __init__(self, cls): super(NoURLError, self).__init__( "Package %s has no version with a URL." % cls.__name__) -class ExtensionError(PackageError): pass +class ExtensionError(PackageError): + + pass class ExtensionConflictError(ExtensionError): @@ -1495,7 +1517,8 @@ class ActivationError(ExtensionError): class DependencyConflictError(spack.error.SpackError): """Raised when the dependencies cannot be flattened as asked for.""" + def __init__(self, conflict): super(DependencyConflictError, self).__init__( - "%s conflicts with another file in the flattened directory." %( + "%s conflicts with another file in the flattened directory." % ( conflict)) diff --git a/lib/spack/spack/test/__init__.py b/lib/spack/spack/test/__init__.py index 05f58ab7b1..10eaac1344 100644 --- a/lib/spack/spack/test/__init__.py +++ b/lib/spack/spack/test/__init__.py @@ -23,53 +23,23 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################## import sys -import unittest -import nose -from spack.test.tally_plugin import Tally -from llnl.util.filesystem import join_path import llnl.util.tty as tty -from llnl.util.tty.colify import colify - +import nose import spack - +from llnl.util.filesystem import join_path +from llnl.util.tty.colify import colify +from spack.test.tally_plugin import Tally """Names of tests to be included in Spack's test suite""" -test_names = ['versions', - 'url_parse', - 'url_substitution', - 'packages', - 'stage', - 'spec_syntax', - 'spec_semantics', - 'spec_dag', - 'concretize', - 'multimethod', - 'install', - 'package_sanity', - 'config', - 'directory_layout', - 'pattern', - 'python_version', - 'git_fetch', - 'svn_fetch', - 'hg_fetch', - 'mirror', - 'modules', - 'url_extrapolate', - 'cc', - 'link_tree', - 'spec_yaml', - 'optional_deps', - 'make_executable', - 'configure_guess', - 'lock', - 'database', - 'namespace_trie', - 'yaml', - 'sbang', - 'environment', - 'cmd.uninstall', - 'cmd.test_install'] +test_names = ['versions', 'url_parse', 'url_substitution', 'packages', 'stage', + 'spec_syntax', 'spec_semantics', 'spec_dag', 'concretize', + 'multimethod', 'install', 'package_sanity', 'config', + 'directory_layout', 'pattern', 'python_version', 'git_fetch', + 'svn_fetch', 'hg_fetch', 'mirror', 'modules', 'url_extrapolate', + 'cc', 'link_tree', 'spec_yaml', 'optional_deps', + 'make_executable', 'configure_guess', 'lock', 'database', + 'namespace_trie', 'yaml', 'sbang', 'environment', + 'cmd.uninstall', 'cmd.test_install'] def list_tests(): @@ -80,8 +50,6 @@ def list_tests(): def run(names, outputDir, verbose=False): """Run tests with the supplied names. Names should be a list. If it's empty, run ALL of Spack's tests.""" - verbosity = 1 if not verbose else 2 - if not names: names = test_names else: @@ -95,7 +63,7 @@ def run(names, outputDir, verbose=False): tally = Tally() for test in names: module = 'spack.test.' + test - print module + print(module) tty.msg("Running test: %s" % test) @@ -105,15 +73,13 @@ def run(names, outputDir, verbose=False): xmlOutputFname = "unittests-{0}.xml".format(test) xmlOutputPath = join_path(outputDir, xmlOutputFname) runOpts += ["--with-xunit", - "--xunit-file={0}".format(xmlOutputPath)] + "--xunit-file={0}".format(xmlOutputPath)] argv = [""] + runOpts + [module] - result = nose.run(argv=argv, addplugins=[tally]) + nose.run(argv=argv, addplugins=[tally]) succeeded = not tally.failCount and not tally.errorCount - tty.msg("Tests Complete.", - "%5d tests run" % tally.numberOfTestsRun, - "%5d failures" % tally.failCount, - "%5d errors" % tally.errorCount) + tty.msg("Tests Complete.", "%5d tests run" % tally.numberOfTestsRun, + "%5d failures" % tally.failCount, "%5d errors" % tally.errorCount) if succeeded: tty.info("OK", format='g') diff --git a/lib/spack/spack/test/modules.py b/lib/spack/spack/test/modules.py index b8b0d6fc6a..c65d663250 100644 --- a/lib/spack/spack/test/modules.py +++ b/lib/spack/spack/test/modules.py @@ -2,14 +2,18 @@ import collections from contextlib import contextmanager import StringIO +import spack.modules +from spack.test.mock_packages_test import MockPackagesTest FILE_REGISTRY = collections.defaultdict(StringIO.StringIO) + # Monkey-patch open to write module files to a StringIO instance @contextmanager def mock_open(filename, mode): if not mode == 'w': - raise RuntimeError('test.modules : unexpected opening mode for monkey-patched open') + raise RuntimeError( + 'test.modules : unexpected opening mode for monkey-patched open') FILE_REGISTRY[filename] = StringIO.StringIO() @@ -20,7 +24,6 @@ def mock_open(filename, mode): FILE_REGISTRY[filename] = handle.getvalue() handle.close() -import spack.modules configuration_autoload_direct = { 'enable': ['tcl'], @@ -47,7 +50,8 @@ configuration_alter_environment = { 'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']} }, '=x86-linux': { - 'environment': {'set': {'FOO': 'foo'}, 'unset': ['BAR']} + 'environment': {'set': {'FOO': 'foo'}, + 'unset': ['BAR']} } } } @@ -72,15 +76,14 @@ configuration_conflicts = { } } -from spack.test.mock_packages_test import MockPackagesTest - class TclTests(MockPackagesTest): def setUp(self): super(TclTests, self).setUp() self.configuration_obj = spack.modules.CONFIGURATION spack.modules.open = mock_open - spack.modules.CONFIGURATION = None # Make sure that a non-mocked configuration will trigger an error + # Make sure that a non-mocked configuration will trigger an error + spack.modules.CONFIGURATION = None def tearDown(self): del spack.modules.open @@ -98,7 +101,7 @@ class TclTests(MockPackagesTest): spack.modules.CONFIGURATION = configuration_autoload_direct spec = spack.spec.Spec('mpich@3.0.4=x86-linux') content = self.get_modulefile_content(spec) - self.assertTrue('module-whatis "mpich @3.0.4"' in content ) + self.assertTrue('module-whatis "mpich @3.0.4"' in content) def test_autoload(self): spack.modules.CONFIGURATION = configuration_autoload_direct @@ -117,14 +120,22 @@ class TclTests(MockPackagesTest): spack.modules.CONFIGURATION = configuration_alter_environment spec = spack.spec.Spec('mpileaks=x86-linux') content = self.get_modulefile_content(spec) - self.assertEqual(len([x for x in content if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0) - self.assertEqual(len([x for x in content if 'setenv FOO "foo"' in x]), 1) + self.assertEqual( + len([x + for x in content + if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0) + self.assertEqual( + len([x for x in content if 'setenv FOO "foo"' in x]), 1) self.assertEqual(len([x for x in content if 'unsetenv BAR' in x]), 1) spec = spack.spec.Spec('libdwarf=x64-linux') content = self.get_modulefile_content(spec) - self.assertEqual(len([x for x in content if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0) - self.assertEqual(len([x for x in content if 'setenv FOO "foo"' in x]), 0) + self.assertEqual( + len([x + for x in content + if x.startswith('prepend-path CMAKE_PREFIX_PATH')]), 0) + self.assertEqual( + len([x for x in content if 'setenv FOO "foo"' in x]), 0) self.assertEqual(len([x for x in content if 'unsetenv BAR' in x]), 0) def test_blacklist(self): @@ -138,6 +149,9 @@ class TclTests(MockPackagesTest): spack.modules.CONFIGURATION = configuration_conflicts spec = spack.spec.Spec('mpileaks=x86-linux') content = self.get_modulefile_content(spec) - self.assertEqual(len([x for x in content if x.startswith('conflict')]), 2) - self.assertEqual(len([x for x in content if x == 'conflict mpileaks']), 1) - self.assertEqual(len([x for x in content if x == 'conflict intel/14.0.1']), 1) + self.assertEqual( + len([x for x in content if x.startswith('conflict')]), 2) + self.assertEqual( + len([x for x in content if x == 'conflict mpileaks']), 1) + self.assertEqual( + len([x for x in content if x == 'conflict intel/14.0.1']), 1) |