diff options
Diffstat (limited to 'lib')
28 files changed, 1009 insertions, 319 deletions
diff --git a/lib/spack/env/cc b/lib/spack/env/cc index b9b79f83a3..9758b74f37 100755 --- a/lib/spack/env/cc +++ b/lib/spack/env/cc @@ -55,7 +55,10 @@ parameters=( # The compiler input variables are checked for sanity later: # SPACK_CC, SPACK_CXX, SPACK_F77, SPACK_FC -# Debug flag is optional; set to "TRUE" for debug logging: +# The default compiler flags are passed from these variables: +# SPACK_CFLAGS, SPACK_CXXFLAGS, SPACK_FCFLAGS, SPACK_FFLAGS, +# SPACK_LDFLAGS, SPACK_LDLIBS +# Debug env var is optional; set to true for debug logging: # SPACK_DEBUG # Test command is used to unit test the compiler script. # SPACK_TEST_COMMAND @@ -99,21 +102,25 @@ case "$command" in command="$SPACK_CC" language="C" comp="CC" + lang_flags=C ;; c++|CC|g++|clang++|icpc|pgc++|xlc++) command="$SPACK_CXX" language="C++" comp="CXX" + lang_flags=CXX ;; f90|fc|f95|gfortran|ifort|pgfortran|xlf90|nagfor) command="$SPACK_FC" language="Fortran 90" comp="FC" + lang_flags=F ;; f77|gfortran|ifort|pgfortran|xlf|nagfor) command="$SPACK_F77" language="Fortran 77" comp="F77" + lang_flags=F ;; ld) mode=ld @@ -131,7 +138,7 @@ if [[ -z $mode ]]; then if [[ $arg == -v || $arg == -V || $arg == --version || $arg == -dumpversion ]]; then mode=vcheck break - fi + fi done fi @@ -188,6 +195,42 @@ fi input_command="$@" args=("$@") +# Prepend cppflags, cflags, cxxflags, fcflags, fflags, and ldflags + +# Add ldflags +case "$mode" in + ld|ccld) + args=(${SPACK_LDFLAGS[@]} "${args[@]}") ;; +esac + +# Add compiler flags. +case "$mode" in + cc|ccld) + # Add c, cxx, fc, and f flags + case $lang_flags in + C) + args=(${SPACK_CFLAGS[@]} "${args[@]}") ;; + CXX) + args=(${SPACK_CXXFLAGS[@]} "${args[@]}") ;; + esac + ;; +esac + +# Add cppflags +case "$mode" in + cpp|as|cc|ccld) + args=(${SPACK_CPPFLAGS[@]} "${args[@]}") ;; +esac + +case "$mode" in cc|ccld) + # Add fortran flags + case $lang_flags in + F) + args=(${SPACK_FFLAGS[@]} "${args[@]}") ;; + esac + ;; +esac + # Read spack dependencies from the path environment variable IFS=':' read -ra deps <<< "$SPACK_DEPENDENCIES" for dep in "${deps[@]}"; do @@ -230,6 +273,12 @@ elif [[ $mode == ld ]]; then $add_rpaths && args=("-rpath" "$SPACK_PREFIX/lib" "${args[@]}") fi +# Add SPACK_LDLIBS to args +case "$mode" in + ld|ccld) + args=("${args[@]}" ${SPACK_LDLIBS[@]}) ;; +esac + # # Unset pesky environment variables that could affect build sanity. # diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index 164340bf0f..c7a155597f 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -105,7 +105,7 @@ concretizer = DefaultConcretizer() # Version information from spack.version import Version -spack_version = Version("0.8.15") +spack_version = Version("0.9") # # Executables used by Spack diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index 5ce4cb1ce1..d87aaa6285 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -51,15 +51,16 @@ There are two parts to the build environment: Skimming this module is a nice way to get acquainted with the types of calls you can make from within the install() function. """ -import multiprocessing import os -import platform -import shutil import sys +import shutil +import multiprocessing +import platform -import spack import llnl.util.tty as tty from llnl.util.filesystem import * + +import spack from spack.environment import EnvironmentModifications, validate from spack.util.environment import * from spack.util.executable import Executable, which @@ -115,22 +116,24 @@ class MakeExecutable(Executable): def set_compiler_environment_variables(pkg, env): assert pkg.spec.concrete + compiler = pkg.compiler + flags = pkg.spec.compiler_flags + # Set compiler variables used by CMake and autotools - assert all(key in pkg.compiler.link_paths for key in ('cc', 'cxx', 'f77', 'fc')) + assert all(key in compiler.link_paths for key in ('cc', 'cxx', 'f77', 'fc')) # Populate an object with the list of environment modifications # and return it # TODO : add additional kwargs for better diagnostics, like requestor, ttyout, ttyerr, etc. link_dir = spack.build_env_path - env.set('CC', join_path(link_dir, pkg.compiler.link_paths['cc'])) - env.set('CXX', join_path(link_dir, pkg.compiler.link_paths['cxx'])) - env.set('F77', join_path(link_dir, pkg.compiler.link_paths['f77'])) - env.set('FC', join_path(link_dir, pkg.compiler.link_paths['fc'])) + env.set('CC', join_path(link_dir, compiler.link_paths['cc'])) + env.set('CXX', join_path(link_dir, compiler.link_paths['cxx'])) + env.set('F77', join_path(link_dir, compiler.link_paths['f77'])) + env.set('FC', join_path(link_dir, compiler.link_paths['fc'])) # Set SPACK compiler variables so that our wrapper knows what to call - compiler = pkg.compiler if compiler.cc: - env.set('SPACK_CC', compiler.cc) + env.set('SPACK_CC', compiler.cc) if compiler.cxx: env.set('SPACK_CXX', compiler.cxx) if compiler.f77: @@ -144,6 +147,12 @@ def set_compiler_environment_variables(pkg, env): env.set('SPACK_F77_RPATH_ARG', compiler.f77_rpath_arg) env.set('SPACK_FC_RPATH_ARG', compiler.fc_rpath_arg) + # Add every valid compiler flag to the environment, prefixed with "SPACK_" + for flag in spack.spec.FlagMap.valid_compiler_flags(): + # Concreteness guarantees key safety here + if flags[flag] != []: + env.set('SPACK_' + flag.upper(), ' '.join(f for f in flags[flag])) + env.set('SPACK_COMPILER_SPEC', str(pkg.spec.compiler)) return env diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py index a99012a275..b9feed839c 100644 --- a/lib/spack/spack/cmd/find.py +++ b/lib/spack/spack/cmd/find.py @@ -51,20 +51,29 @@ def setup_parser(subparser): help='Show full dependency DAG of installed packages') subparser.add_argument( - '-l', '--long', action='store_true', + '-l', '--long', action='store_true', dest='long', help='Show dependency hashes as well as versions.') subparser.add_argument( - '-L', '--very-long', action='store_true', + '-L', '--very-long', action='store_true', dest='very_long', help='Show dependency hashes as well as versions.') + subparser.add_argument( + '-f', '--show-flags', action='store_true', dest='show_flags', + help='Show spec compiler flags.') subparser.add_argument( - '-u', '--unknown', action='store_true', + '-e', '--explicit', action='store_true', + help='Show only specs that were installed explicitly') + subparser.add_argument( + '-E', '--implicit', action='store_true', + help='Show only specs that were installed as dependencies') + subparser.add_argument( + '-u', '--unknown', action='store_true', dest='unknown', help='Show only specs Spack does not have a package for.') subparser.add_argument( - '-m', '--missing', action='store_true', + '-m', '--missing', action='store_true', dest='missing', help='Show missing dependencies as well as installed specs.') subparser.add_argument( - '-M', '--only-missing', action='store_true', + '-M', '--only-missing', action='store_true', dest='only_missing', help='Show only missing dependencies.') subparser.add_argument( '-N', '--namespace', action='store_true', @@ -89,6 +98,12 @@ def display_specs(specs, **kwargs): hashes = True hlen = None + nfmt = '.' if namespace else '_' + format_string = '$%s$@$+' % nfmt + flags = kwargs.get('show_flags', False) + if flags: + format_string = '$%s$@$%%+$+' % nfmt + # Make a dict with specs keyed by architecture and compiler. index = index_by(specs, ('architecture', 'compiler')) @@ -104,8 +119,7 @@ def display_specs(specs, **kwargs): specs = index[(architecture,compiler)] specs.sort() - nfmt = '.' if namespace else '_' - abbreviated = [s.format('$%s$@$+' % nfmt, color=True) for s in specs] + abbreviated = [s.format(format_string, color=True) for s in specs] if mode == 'paths': # Print one spec per line along with prefix path width = max(len(s) for s in abbreviated) @@ -120,20 +134,28 @@ def display_specs(specs, **kwargs): elif mode == 'deps': for spec in specs: print spec.tree( - format='$%s$@$+' % nfmt, + format=format_string, color=True, indent=4, prefix=(lambda s: gray_hash(s, hlen)) if hashes else None) elif mode == 'short': - def fmt(s): - string = "" - if hashes: - string += gray_hash(s, hlen) + ' ' - string += s.format('$-%s$@$+' % nfmt, color=True) - - return string - colify(fmt(s) for s in specs) + # Print columns of output if not printing flags + if not flags: + def fmt(s): + string = "" + if hashes: + string += gray_hash(s, hlen) + ' ' + string += s.format('$-%s$@$+' % nfmt, color=True) + + return string + colify(fmt(s) for s in specs) + # Print one entry per line if including flags + else: + for spec in specs: + # Print the hash if necessary + hsh = gray_hash(spec, hlen) + ' ' if hashes else '' + print hsh + spec.format(format_string, color=True) + '\n' else: raise ValueError( @@ -145,7 +167,7 @@ def find(parser, args): # Filter out specs that don't exist. query_specs = spack.cmd.parse_specs(args.query_specs) query_specs, nonexisting = partition_list( - query_specs, lambda s: spack.repo.exists(s.name)) + query_specs, lambda s: spack.repo.exists(s.name) or not s.name) if nonexisting: msg = "No such package%s: " % ('s' if len(nonexisting) > 1 else '') @@ -163,7 +185,14 @@ def find(parser, args): installed = any if args.unknown: known = False - q_args = { 'installed' : installed, 'known' : known } + + explicit = None + if args.explicit: + explicit = False + if args.implicit: + explicit = True + + q_args = { 'installed' : installed, 'known' : known, "explicit" : explicit } # Get all the specs the user asked for if not query_specs: @@ -180,4 +209,4 @@ def find(parser, args): display_specs(specs, mode=args.mode, long=args.long, very_long=args.very_long, - namespace=args.namespace) + show_flags=args.show_flags) diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py index fef21f15ba..9d3175786b 100644 --- a/lib/spack/spack/cmd/install.py +++ b/lib/spack/spack/cmd/install.py @@ -78,4 +78,5 @@ def install(parser, args): ignore_deps=args.ignore_deps, make_jobs=args.jobs, verbose=args.verbose, - fake=args.fake) + fake=args.fake, + explicit=True) diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py index 3bffc2633b..9fdf3045b2 100644 --- a/lib/spack/spack/cmd/uninstall.py +++ b/lib/spack/spack/cmd/uninstall.py @@ -92,7 +92,7 @@ def concretize_specs(specs, allow_multiple_matches=False, force=False): if not allow_multiple_matches and len(matching) > 1: tty.error("%s matches multiple packages:" % spec) print() - display_specs(matching, long=True) + display_specs(matching, long=True, show_flags=True) print() has_errors = True @@ -186,7 +186,7 @@ def uninstall(parser, args): if not args.yes_to_all: tty.msg("The following packages will be uninstalled : ") print('') - display_specs(uninstall_list, long=True) + display_specs(uninstall_list, long=True, show_flags=True) print('') ask_for_confirmation('Do you want to proceed ? ') diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py index e2da272212..2ae305f201 100644 --- a/lib/spack/spack/compiler.py +++ b/lib/spack/spack/compiler.py @@ -109,7 +109,7 @@ class Compiler(object): return '-Wl,-rpath,' - def __init__(self, cspec, cc, cxx, f77, fc): + def __init__(self, cspec, cc, cxx, f77, fc, **kwargs): def check(exe): if exe is None: return None @@ -121,6 +121,15 @@ class Compiler(object): self.f77 = check(f77) self.fc = check(fc) + # Unfortunately have to make sure these params are accepted + # in the same order they are returned by sorted(flags) + # in compilers/__init__.py + self.flags = {} + for flag in spack.spec.FlagMap.valid_compiler_flags(): + value = kwargs.get(flag, None) + if value is not None: + self.flags[flag] = value.split() + self.spec = cspec @@ -188,7 +197,6 @@ class Compiler(object): def fc_version(cls, fc): return cls.default_version(fc) - @classmethod def _find_matches_in_path(cls, compiler_names, detect_version, *path): """Finds compilers in the paths supplied. diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py index 692e5518aa..7c951ae8bc 100644 --- a/lib/spack/spack/compilers/__init__.py +++ b/lib/spack/spack/compilers/__init__.py @@ -255,7 +255,11 @@ def compilers_for_spec(compiler_spec, arch=None, scope=None): else: compiler_paths.append(None) - return cls(cspec, *compiler_paths) + flags = {} + for f in spack.spec.FlagMap.valid_compiler_flags(): + if f in items: + flags[f] = items[f] + return cls(cspec, *compiler_paths, **flags) matches = find(compiler_spec, arch, scope) return [get_compiler(cspec) for cspec in matches] diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py index f5e1c10b48..4f78bfc347 100644 --- a/lib/spack/spack/concretize.py +++ b/lib/spack/spack/concretize.py @@ -44,6 +44,7 @@ from spec import DependencyMap from itertools import chain from spack.config import * + class DefaultConcretizer(object): """This class doesn't have any state, it just provides some methods for concretization. You can subclass it to override just some of the @@ -269,6 +270,59 @@ class DefaultConcretizer(object): return True # things changed. + def concretize_compiler_flags(self, spec): + """ + The compiler flags are updated to match those of the spec whose + compiler is used, defaulting to no compiler flags in the spec. + Default specs set at the compiler level will still be added later. + """ + ret = False + for flag in spack.spec.FlagMap.valid_compiler_flags(): + try: + nearest = next(p for p in spec.traverse(direction='parents') + if ((p.compiler == spec.compiler and p is not spec) + and flag in p.compiler_flags)) + if ((not flag in spec.compiler_flags) or + sorted(spec.compiler_flags[flag]) != sorted(nearest.compiler_flags[flag])): + if flag in spec.compiler_flags: + spec.compiler_flags[flag] = list(set(spec.compiler_flags[flag]) | + set(nearest.compiler_flags[flag])) + else: + spec.compiler_flags[flag] = nearest.compiler_flags[flag] + ret = True + + except StopIteration: + if (flag in spec.root.compiler_flags and ((not flag in spec.compiler_flags) or + sorted(spec.compiler_flags[flag]) != sorted(spec.root.compiler_flags[flag]))): + if flag in spec.compiler_flags: + spec.compiler_flags[flag] = list(set(spec.compiler_flags[flag]) | + set(spec.root.compiler_flags[flag])) + else: + spec.compiler_flags[flag] = spec.root.compiler_flags[flag] + ret = True + else: + if not flag in spec.compiler_flags: + spec.compiler_flags[flag] = [] + + # Include the compiler flag defaults from the config files + # This ensures that spack will detect conflicts that stem from a change + # in default compiler flags. + compiler = spack.compilers.compiler_for_spec(spec.compiler) + for flag in compiler.flags: + if flag not in spec.compiler_flags: + spec.compiler_flags[flag] = compiler.flags[flag] + if compiler.flags[flag] != []: + ret = True + else: + if ((sorted(spec.compiler_flags[flag]) != sorted(compiler.flags[flag])) and + (not set(spec.compiler_flags[flag]) >= set(compiler.flags[flag]))): + ret = True + spec.compiler_flags[flag] = list(set(spec.compiler_flags[flag]) | + set(compiler.flags[flag])) + + return ret + + def find_spec(spec, condition): """Searches the dag from spec in an intelligent order and looks for a spec that matches a condition""" @@ -330,7 +384,6 @@ def cmp_specs(lhs, rhs): return 0 - class UnavailableCompilerVersionError(spack.error.SpackError): """Raised when there is no available compiler that satisfies a compiler spec.""" diff --git a/lib/spack/spack/database.py b/lib/spack/spack/database.py index ee4473e079..f3967e6b72 100644 --- a/lib/spack/spack/database.py +++ b/lib/spack/spack/database.py @@ -60,7 +60,7 @@ from spack.repository import UnknownPackageError _db_dirname = '.spack-db' # DB version. This is stuck in the DB file to track changes in format. -_db_version = Version('0.9') +_db_version = Version('0.9.1') # Default timeout for spack database locks is 5 min. _db_lock_timeout = 60 @@ -92,22 +92,24 @@ class InstallRecord(object): dependents left. """ - def __init__(self, spec, path, installed, ref_count=0): + def __init__(self, spec, path, installed, ref_count=0, explicit=False): self.spec = spec self.path = str(path) self.installed = bool(installed) self.ref_count = ref_count + self.explicit = explicit def to_dict(self): return { 'spec' : self.spec.to_node_dict(), 'path' : self.path, 'installed' : self.installed, - 'ref_count' : self.ref_count } + 'ref_count' : self.ref_count, + 'explicit' : self.explicit } @classmethod def from_dict(cls, spec, dictionary): d = dictionary - return InstallRecord(spec, d['path'], d['installed'], d['ref_count']) + return InstallRecord(spec, d['path'], d['installed'], d['ref_count'], d.get('explicit', False)) class Database(object): @@ -203,6 +205,11 @@ class Database(object): spec_dict = installs[hash_key]['spec'] + # Install records don't include hash with spec, so we add it in here + # to ensure it is read properly. + for name in spec_dict: + spec_dict[name]['hash'] = hash_key + # Build spec from dict first. spec = Spec.from_node_dict(spec_dict) @@ -248,13 +255,18 @@ class Database(object): check('installs' in db, "No 'installs' in YAML DB.") check('version' in db, "No 'version' in YAML DB.") + + installs = db['installs'] + # TODO: better version checking semantics. version = Version(db['version']) - if version != _db_version: + if version > _db_version: raise InvalidDatabaseVersionError(_db_version, version) + elif version < _db_version: + self.reindex(spack.install_layout) + installs = dict((k, v.to_dict()) for k, v in self._data.items()) # Iterate through database and check each record. - installs = db['installs'] data = {} for hash_key, rec in installs.items(): try: @@ -370,7 +382,7 @@ class Database(object): self.reindex(spack.install_layout) - def _add(self, spec, path, directory_layout=None): + def _add(self, spec, path, directory_layout=None, explicit=False): """Add an install record for spec at path to the database. This assumes that the spec is not already installed. It @@ -392,7 +404,7 @@ class Database(object): rec.path = path else: - self._data[key] = InstallRecord(spec, path, True) + self._data[key] = InstallRecord(spec, path, True, explicit=explicit) for dep in spec.dependencies.values(): self._increment_ref_count(dep, directory_layout) @@ -415,7 +427,7 @@ class Database(object): self._data[key].ref_count += 1 @_autospec - def add(self, spec, path): + def add(self, spec, path, explicit=False): """Add spec at path to database, locking and reading DB to sync. ``add()`` will lock and read from the DB on disk. @@ -424,7 +436,7 @@ class Database(object): # TODO: ensure that spec is concrete? # Entire add is transactional. with self.write_transaction(): - self._add(spec, path) + self._add(spec, path, explicit=explicit) def _get_matching_spec_key(self, spec, **kwargs): @@ -513,7 +525,7 @@ class Database(object): # TODO: conditional way to do this instead of catching exceptions - def query(self, query_spec=any, known=any, installed=True): + def query(self, query_spec=any, known=any, installed=True, explicit=any): """Run a query on the database. ``query_spec`` @@ -553,6 +565,8 @@ class Database(object): for key, rec in self._data.items(): if installed is not any and rec.installed != installed: continue + if explicit is not any and rec.explicit != explicit: + continue if known is not any and spack.repo.exists(rec.spec.name) != known: continue if query_spec is any or rec.spec.satisfies(query_spec): diff --git a/lib/spack/spack/directives.py b/lib/spack/spack/directives.py index 74ee7b0add..51b26773e2 100644 --- a/lib/spack/spack/directives.py +++ b/lib/spack/spack/directives.py @@ -45,11 +45,8 @@ The available directives are: * ``resource`` """ -__all__ = ['depends_on', 'extends', 'provides', 'patch', 'version', - 'variant', 'resource'] import re -import inspect import os.path import functools @@ -67,6 +64,9 @@ from spack.spec import Spec, parse_anonymous_spec from spack.resource import Resource from spack.fetch_strategy import from_kwargs +__all__ = ['depends_on', 'extends', 'provides', 'patch', 'version', 'variant', + 'resource'] + # # This is a list of all directives, built up as they are defined in # this file. @@ -122,15 +122,14 @@ class directive(object): def __init__(self, dicts=None): if isinstance(dicts, basestring): - dicts = (dicts,) + dicts = (dicts, ) elif type(dicts) not in (list, tuple): raise TypeError( - "dicts arg must be list, tuple, or string. Found %s" - % type(dicts)) + "dicts arg must be list, tuple, or string. Found %s" % + type(dicts)) self.dicts = dicts - def ensure_dicts(self, pkg): """Ensure that a package has the dicts required by this directive.""" for d in self.dicts: @@ -142,7 +141,6 @@ class directive(object): raise spack.error.SpackError( "Package %s has non-dict %s attribute!" % (pkg, d)) - def __call__(self, directive_function): directives[directive_function.__name__] = self @@ -259,11 +257,12 @@ def variant(pkg, name, default=False, description=""): """Define a variant for the package. Packager can specify a default value (on or off) as well as a text description.""" - default = bool(default) + default = bool(default) description = str(description).strip() if not re.match(spack.spec.identifier_re, name): - raise DirectiveError("Invalid variant name in %s: '%s'" % (pkg.name, name)) + raise DirectiveError("Invalid variant name in %s: '%s'" % + (pkg.name, name)) pkg.variants[name] = Variant(default, description) @@ -271,31 +270,37 @@ def variant(pkg, name, default=False, description=""): @directive('resources') def resource(pkg, **kwargs): """ - Define an external resource to be fetched and staged when building the package. Based on the keywords present in the - dictionary the appropriate FetchStrategy will be used for the resource. Resources are fetched and staged in their - own folder inside spack stage area, and then linked into the stage area of the package that needs them. + Define an external resource to be fetched and staged when building the + package. Based on the keywords present in the dictionary the appropriate + FetchStrategy will be used for the resource. Resources are fetched and + staged in their own folder inside spack stage area, and then linked into + the stage area of the package that needs them. List of recognized keywords: - * 'when' : (optional) represents the condition upon which the resource is needed - * 'destination' : (optional) path where to link the resource. This path must be relative to the main package stage - area. - * 'placement' : (optional) gives the possibility to fine tune how the resource is linked into the main package stage - area. + * 'when' : (optional) represents the condition upon which the resource is + needed + * 'destination' : (optional) path where to link the resource. This path + must be relative to the main package stage area. + * 'placement' : (optional) gives the possibility to fine tune how the + resource is linked into the main package stage area. """ when = kwargs.get('when', pkg.name) destination = kwargs.get('destination', "") placement = kwargs.get('placement', None) # Check if the path is relative if os.path.isabs(destination): - message = "The destination keyword of a resource directive can't be an absolute path.\n" + message = "The destination keyword of a resource directive can't be" + " an absolute path.\n" message += "\tdestination : '{dest}\n'".format(dest=destination) raise RuntimeError(message) # Check if the path falls within the main package stage area - test_path = 'stage_folder_root/' - normalized_destination = os.path.normpath(join_path(test_path, destination)) # Normalized absolute path + test_path = 'stage_folder_root' + normalized_destination = os.path.normpath(join_path(test_path, destination) + ) # Normalized absolute path if test_path not in normalized_destination: - message = "The destination folder of a resource must fall within the main package stage directory.\n" + message = "The destination folder of a resource must fall within the" + " main package stage directory.\n" message += "\tdestination : '{dest}'\n".format(dest=destination) raise RuntimeError(message) when_spec = parse_anonymous_spec(when, pkg.name) @@ -307,6 +312,7 @@ def resource(pkg, **kwargs): class DirectiveError(spack.error.SpackError): """This is raised when something is wrong with a package directive.""" + def __init__(self, directive, message): super(DirectiveError, self).__init__(message) self.directive = directive @@ -314,6 +320,7 @@ class DirectiveError(spack.error.SpackError): class CircularReferenceError(DirectiveError): """This is raised when something depends on itself.""" + def __init__(self, directive, package): super(CircularReferenceError, self).__init__( directive, diff --git a/lib/spack/spack/environment.py b/lib/spack/spack/environment.py index 11998ad8d2..af642dcc9b 100644 --- a/lib/spack/spack/environment.py +++ b/lib/spack/spack/environment.py @@ -39,7 +39,8 @@ class NameValueModifier(object): def __init__(self, name, value, **kwargs): self.name = name self.value = value - self.args = {'name': name, 'value': value} + self.separator = kwargs.get('separator', ':') + self.args = {'name': name, 'value': value, 'delim': self.separator} self.args.update(kwargs) @@ -56,34 +57,36 @@ class UnsetEnv(NameModifier): class SetPath(NameValueModifier): def execute(self): - string_path = concatenate_paths(self.value) + string_path = concatenate_paths(self.value, separator=self.separator) os.environ[self.name] = string_path class AppendPath(NameValueModifier): def execute(self): environment_value = os.environ.get(self.name, '') - directories = environment_value.split(':') if environment_value else [] + directories = environment_value.split( + self.separator) if environment_value else [] directories.append(os.path.normpath(self.value)) - os.environ[self.name] = ':'.join(directories) + os.environ[self.name] = self.separator.join(directories) class PrependPath(NameValueModifier): def execute(self): environment_value = os.environ.get(self.name, '') - directories = environment_value.split(':') if environment_value else [] + directories = environment_value.split( + self.separator) if environment_value else [] directories = [os.path.normpath(self.value)] + directories - os.environ[self.name] = ':'.join(directories) + os.environ[self.name] = self.separator.join(directories) 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 + directories = environment_value.split( + self.separator) if environment_value else [] + directories = [os.path.normpath(x) for x in directories if x != os.path.normpath(self.value)] - os.environ[self.name] = ':'.join(directories) + os.environ[self.name] = self.separator.join(directories) class EnvironmentModifications(object): @@ -238,17 +241,19 @@ class EnvironmentModifications(object): x.execute() -def concatenate_paths(paths): +def concatenate_paths(paths, separator=':'): """ - Concatenates an iterable of paths into a string of column separated paths + Concatenates an iterable of paths into a string of paths separated by + separator, defaulting to colon Args: paths: iterable of paths + separator: the separator to use, default ':' Returns: string """ - return ':'.join(str(item) for item in paths) + return separator.join(str(item) for item in paths) def set_or_unset_not_first(variable, changes, errstream): @@ -256,16 +261,13 @@ 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 """ - indexes = [ii - for ii, item in enumerate(changes) + 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}' message = 'Suspicious requests to set or unset the variable \'{var}\' found' # NOQA: ignore=E501 - errstream( - message.format( - var=variable)) + 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)) diff --git a/lib/spack/spack/fetch_strategy.py b/lib/spack/spack/fetch_strategy.py index e05cb13c1e..7c8cebe0c9 100644 --- a/lib/spack/spack/fetch_strategy.py +++ b/lib/spack/spack/fetch_strategy.py @@ -57,7 +57,6 @@ from spack.version import Version, ver from spack.util.compression import decompressor_for, extension import spack.util.pattern as pattern - """List of all fetch strategies, created by FetchStrategy metaclass.""" all_strategies = [] @@ -82,13 +81,16 @@ class FetchStrategy(object): class __metaclass__(type): """This metaclass registers all fetch strategies in a list.""" + def __init__(cls, name, bases, dict): type.__init__(cls, name, bases, dict) - if cls.enabled: all_strategies.append(cls) + if cls.enabled: + all_strategies.append(cls) def __init__(self): - # The stage is initialized late, so that fetch strategies can be constructed - # at package construction time. This is where things will be fetched. + # The stage is initialized late, so that fetch strategies can be + # constructed at package construction time. This is where things + # will be fetched. self.stage = None def set_stage(self, stage): @@ -97,15 +99,20 @@ class FetchStrategy(object): self.stage = stage # Subclasses need to implement these methods - def fetch(self): pass # Return True on success, False on fail. + def fetch(self): + pass # Return True on success, False on fail. - def check(self): pass # Do checksum. + def check(self): + pass # Do checksum. - def expand(self): pass # Expand archive. + def expand(self): + pass # Expand archive. - def reset(self): pass # Revert to freshly downloaded state. + def reset(self): + pass # Revert to freshly downloaded state. - def archive(self, destination): pass # Used to create tarball for mirror. + def archive(self, destination): + pass # Used to create tarball for mirror. def __str__(self): # Should be human readable URL. return "FetchStrategy.__str___" @@ -139,10 +146,12 @@ class URLFetchStrategy(FetchStrategy): # If URL or digest are provided in the kwargs, then prefer # those values. self.url = kwargs.get('url', None) - if not self.url: self.url = url + if not self.url: + self.url = url self.digest = kwargs.get('md5', None) - if not self.digest: self.digest = digest + if not self.digest: + self.digest = digest self.expand_archive = kwargs.get('expand', True) @@ -167,16 +176,20 @@ class URLFetchStrategy(FetchStrategy): tty.msg("Trying to fetch from %s" % self.url) if partial_file: - save_args = ['-C', '-', # continue partial downloads - '-o', partial_file] # use a .part file + save_args = ['-C', + '-', # continue partial downloads + '-o', + partial_file] # use a .part file else: save_args = ['-O'] curl_args = save_args + [ - '-f', # fail on >400 errors - '-D', '-', # print out HTML headers - '-L', # resolve 3xx redirects - self.url, ] + '-f', # fail on >400 errors + '-D', + '-', # print out HTML headers + '-L', # resolve 3xx redirects + self.url, + ] if sys.stdout.isatty(): curl_args.append('-#') # status bar when using a tty @@ -184,8 +197,7 @@ class URLFetchStrategy(FetchStrategy): curl_args.append('-sS') # just errors when not. # Run curl but grab the mime type from the http headers - headers = spack.curl( - *curl_args, output=str, fail_on_error=False) + headers = spack.curl(*curl_args, output=str, fail_on_error=False) if spack.curl.returncode != 0: # clean up archive on failure. @@ -198,33 +210,36 @@ class URLFetchStrategy(FetchStrategy): if spack.curl.returncode == 22: # This is a 404. Curl will print the error. raise FailedDownloadError( - self.url, "URL %s was not found!" % self.url) + self.url, "URL %s was not found!" % self.url) elif spack.curl.returncode == 60: # This is a certificate error. Suggest spack -k raise FailedDownloadError( - self.url, - "Curl was unable to fetch due to invalid certificate. " - "This is either an attack, or your cluster's SSL configuration " - "is bad. If you believe your SSL configuration is bad, you " - "can try running spack -k, which will not check SSL certificates." - "Use this at your own risk.") + self.url, + "Curl was unable to fetch due to invalid certificate. " + "This is either an attack, or your cluster's SSL " + "configuration is bad. If you believe your SSL " + "configuration is bad, you can try running spack -k, " + "which will not check SSL certificates." + "Use this at your own risk.") else: # This is some other curl error. Curl will print the # error, but print a spack message too raise FailedDownloadError( - self.url, "Curl failed with error %d" % spack.curl.returncode) + self.url, + "Curl failed with error %d" % spack.curl.returncode) # Check if we somehow got an HTML file rather than the archive we # asked for. We only look at the last content type, to handle # redirects properly. content_types = re.findall(r'Content-Type:[^\r\n]+', headers) if content_types and 'text/html' in content_types[-1]: - tty.warn("The contents of " + self.archive_file + " look like HTML.", - "The checksum will likely be bad. If it is, you can use", - "'spack clean <package>' to remove the bad archive, then fix", - "your internet gateway issue and install again.") + tty.warn( + "The contents of " + self.archive_file + " look like HTML.", + "The checksum will likely be bad. If it is, you can use", + "'spack clean <package>' to remove the bad archive, then fix", + "your internet gateway issue and install again.") if save_file: os.rename(partial_file, save_file) @@ -247,14 +262,16 @@ class URLFetchStrategy(FetchStrategy): self.stage.chdir() if not self.archive_file: - raise NoArchiveFileError("URLFetchStrategy couldn't find archive file", - "Failed on expand() for URL %s" % self.url) + raise NoArchiveFileError( + "URLFetchStrategy couldn't find archive file", + "Failed on expand() for URL %s" % self.url) decompress = decompressor_for(self.archive_file) # Expand all tarballs in their own directory to contain # exploding tarballs. - tarball_container = os.path.join(self.stage.path, "spack-expanded-archive") + tarball_container = os.path.join(self.stage.path, + "spack-expanded-archive") mkdirp(tarball_container) os.chdir(tarball_container) decompress(self.archive_file) @@ -295,20 +312,25 @@ class URLFetchStrategy(FetchStrategy): """Check the downloaded archive against a checksum digest. No-op if this stage checks code out of a repository.""" if not self.digest: - raise NoDigestError("Attempt to check URLFetchStrategy with no digest.") + raise NoDigestError( + "Attempt to check URLFetchStrategy with no digest.") checker = crypto.Checker(self.digest) if not checker.check(self.archive_file): raise ChecksumError( - "%s checksum failed for %s" % (checker.hash_name, self.archive_file), - "Expected %s but got %s" % (self.digest, checker.sum)) + "%s checksum failed for %s" % + (checker.hash_name, self.archive_file), + "Expected %s but got %s" % (self.digest, checker.sum)) @_needs_stage def reset(self): - """Removes the source path if it exists, then re-expands the archive.""" + """ + Removes the source path if it exists, then re-expands the archive. + """ if not self.archive_file: - raise NoArchiveFileError("Tried to reset URLFetchStrategy before fetching", - "Failed on reset() for URL %s" % self.url) + raise NoArchiveFileError( + "Tried to reset URLFetchStrategy before fetching", + "Failed on reset() for URL %s" % self.url) # Remove everythigng but the archive from the stage for filename in os.listdir(self.stage.path): @@ -337,14 +359,16 @@ class VCSFetchStrategy(FetchStrategy): # Set a URL based on the type of fetch strategy. self.url = kwargs.get(name, None) - if not self.url: raise ValueError( + if not self.url: + raise ValueError( "%s requires %s argument." % (self.__class__, name)) # Ensure that there's only one of the rev_types if sum(k in kwargs for k in rev_types) > 1: raise FetchStrategyError( - "Supply only one of %s to fetch with %s" % ( - comma_or(rev_types), name)) + "Supply only one of %s to fetch with %s" % ( + comma_or(rev_types), name + )) # Set attributes for each rev type. for rt in rev_types: @@ -382,32 +406,93 @@ class VCSFetchStrategy(FetchStrategy): return "%s<%s>" % (self.__class__, self.url) +class GoFetchStrategy(VCSFetchStrategy): + """ + Fetch strategy that employs the `go get` infrastructure + Use like this in a package: + + version('name', + go='github.com/monochromegane/the_platinum_searcher/...') + + Go get does not natively support versions, they can be faked with git + """ + enabled = True + required_attributes = ('go', ) + + def __init__(self, **kwargs): + # Discards the keywords in kwargs that may conflict with the next + # call to __init__ + forwarded_args = copy.copy(kwargs) + forwarded_args.pop('name', None) + + super(GoFetchStrategy, self).__init__('go', **forwarded_args) + self._go = None + + @property + def go_version(self): + vstring = self.go('version', output=str).split(' ')[2] + return Version(vstring) + + @property + def go(self): + if not self._go: + self._go = which('go', required=True) + return self._go + + @_needs_stage + def fetch(self): + self.stage.chdir() + + tty.msg("Trying to get go resource:", self.url) + + try: + os.mkdir('go') + except OSError: + pass + env = dict(os.environ) + env['GOPATH'] = os.path.join(os.getcwd(), 'go') + self.go('get', '-v', '-d', self.url, env=env) + + def archive(self, destination): + super(GoFetchStrategy, self).archive(destination, exclude='.git') + + @_needs_stage + def reset(self): + self.stage.chdir_to_source() + self.go('clean') + + def __str__(self): + return "[go] %s" % self.url + + class GitFetchStrategy(VCSFetchStrategy): - """Fetch strategy that gets source code from a git repository. - Use like this in a package: + """ + Fetch strategy that gets source code from a git repository. + Use like this in a package: - version('name', git='https://github.com/project/repo.git') + version('name', git='https://github.com/project/repo.git') - Optionally, you can provide a branch, or commit to check out, e.g.: + Optionally, you can provide a branch, or commit to check out, e.g.: - version('1.1', git='https://github.com/project/repo.git', tag='v1.1') + version('1.1', git='https://github.com/project/repo.git', tag='v1.1') - You can use these three optional attributes in addition to ``git``: + You can use these three optional attributes in addition to ``git``: - * ``branch``: Particular branch to build from (default is master) - * ``tag``: Particular tag to check out - * ``commit``: Particular commit hash in the repo + * ``branch``: Particular branch to build from (default is master) + * ``tag``: Particular tag to check out + * ``commit``: Particular commit hash in the repo """ enabled = True - required_attributes = ('git',) + required_attributes = ('git', ) def __init__(self, **kwargs): - # Discards the keywords in kwargs that may conflict with the next call to __init__ + # Discards the keywords in kwargs that may conflict with the next call + # to __init__ forwarded_args = copy.copy(kwargs) forwarded_args.pop('name', None) super(GitFetchStrategy, self).__init__( - 'git', 'tag', 'branch', 'commit', **forwarded_args) + 'git', 'tag', 'branch', 'commit', **forwarded_args) self._git = None @property @@ -515,12 +600,13 @@ class SvnFetchStrategy(VCSFetchStrategy): required_attributes = ['svn'] def __init__(self, **kwargs): - # Discards the keywords in kwargs that may conflict with the next call to __init__ + # Discards the keywords in kwargs that may conflict with the next call + # to __init__ forwarded_args = copy.copy(kwargs) forwarded_args.pop('name', None) super(SvnFetchStrategy, self).__init__( - 'svn', 'revision', **forwarded_args) + 'svn', 'revision', **forwarded_args) self._svn = None if self.revision is not None: self.revision = str(self.revision) @@ -576,32 +662,35 @@ class SvnFetchStrategy(VCSFetchStrategy): class HgFetchStrategy(VCSFetchStrategy): - """Fetch strategy that gets source code from a Mercurial repository. - Use like this in a package: + """ + Fetch strategy that gets source code from a Mercurial repository. + Use like this in a package: - version('name', hg='https://jay.grs.rwth-aachen.de/hg/lwm2') + version('name', hg='https://jay.grs.rwth-aachen.de/hg/lwm2') - Optionally, you can provide a branch, or revision to check out, e.g.: + Optionally, you can provide a branch, or revision to check out, e.g.: - version('torus', hg='https://jay.grs.rwth-aachen.de/hg/lwm2', branch='torus') + version('torus', + hg='https://jay.grs.rwth-aachen.de/hg/lwm2', branch='torus') - You can use the optional 'revision' attribute to check out a - branch, tag, or particular revision in hg. To prevent - non-reproducible builds, using a moving target like a branch is - discouraged. + You can use the optional 'revision' attribute to check out a + branch, tag, or particular revision in hg. To prevent + non-reproducible builds, using a moving target like a branch is + discouraged. - * ``revision``: Particular revision, branch, or tag. + * ``revision``: Particular revision, branch, or tag. """ enabled = True required_attributes = ['hg'] def __init__(self, **kwargs): - # Discards the keywords in kwargs that may conflict with the next call to __init__ + # Discards the keywords in kwargs that may conflict with the next call + # to __init__ forwarded_args = copy.copy(kwargs) forwarded_args.pop('name', None) super(HgFetchStrategy, self).__init__( - 'hg', 'revision', **forwarded_args) + 'hg', 'revision', **forwarded_args) self._hg = None @property @@ -675,7 +764,8 @@ def from_kwargs(**kwargs): return fetcher(**kwargs) # Raise an error in case we can't instantiate any known strategy message = "Cannot instantiate any FetchStrategy" - long_message = message + " from the given arguments : {arguments}".format(srguments=kwargs) + long_message = message + " from the given arguments : {arguments}".format( + srguments=kwargs) raise FetchError(message, long_message) @@ -687,7 +777,7 @@ def for_package_version(pkg, version): """Determine a fetch strategy based on the arguments supplied to version() in the package description.""" # If it's not a known version, extrapolate one. - if not version in pkg.versions: + if version not in pkg.versions: url = pkg.url_for_version(version) if not url: raise InvalidArgsError(pkg, version) @@ -725,7 +815,7 @@ class FailedDownloadError(FetchError): def __init__(self, url, msg=""): super(FailedDownloadError, self).__init__( - "Failed to fetch file from URL: %s" % url, msg) + "Failed to fetch file from URL: %s" % url, msg) self.url = url @@ -741,7 +831,8 @@ class NoDigestError(FetchError): class InvalidArgsError(FetchError): def __init__(self, pkg, version): - msg = "Could not construct a fetch strategy for package %s at version %s" + msg = ("Could not construct a fetch strategy for package %s at " + "version %s") msg %= (pkg.name, version) super(InvalidArgsError, self).__init__(msg) @@ -758,4 +849,5 @@ class NoStageError(FetchError): def __init__(self, method): super(NoStageError, self).__init__( - "Must call FetchStrategy.set_stage() before calling %s" % method.__name__) + "Must call FetchStrategy.set_stage() before calling %s" % + method.__name__) diff --git a/lib/spack/spack/modules.py b/lib/spack/spack/modules.py index a35e21c3db..d2b819e80a 100644 --- a/lib/spack/spack/modules.py +++ b/lib/spack/spack/modules.py @@ -485,9 +485,9 @@ class TclModule(EnvModule): path = join_path(spack.share_path, "modules") environment_modifications_formats = { - PrependPath: 'prepend-path {name} \"{value}\"\n', - AppendPath: 'append-path {name} \"{value}\"\n', - RemovePath: 'remove-path {name} \"{value}\"\n', + PrependPath: 'prepend-path --delim "{delim}" {name} \"{value}\"\n', + AppendPath: 'append-path --delim "{delim}" {name} \"{value}\"\n', + RemovePath: 'remove-path --delim "{delim}" {name} \"{value}\"\n', SetEnv: 'setenv {name} \"{value}\"\n', UnsetEnv: 'unsetenv {name}\n' } diff --git a/lib/spack/spack/multimethod.py b/lib/spack/spack/multimethod.py index 5fda9328d6..170ef3cea2 100644 --- a/lib/spack/spack/multimethod.py +++ b/lib/spack/spack/multimethod.py @@ -146,12 +146,12 @@ class when(object): def install(self, prefix): # Do default install - @when('=chaos_5_x86_64_ib') + @when('arch=chaos_5_x86_64_ib') def install(self, prefix): # This will be executed instead of the default install if # the package's sys_type() is chaos_5_x86_64_ib. - @when('=bgqos_0") + @when('arch=bgqos_0") def install(self, prefix): # This will be executed if the package's sys_type is bgqos_0 diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index 8167341127..2e7d8a7709 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -857,7 +857,8 @@ class Package(object): skip_patch=False, verbose=False, make_jobs=None, - fake=False): + fake=False, + explicit=False): """Called by commands to install a package and its dependencies. Package implementations should override install() to describe @@ -887,6 +888,11 @@ class Package(object): # Ensure package is not already installed if spack.install_layout.check_installed(self.spec): tty.msg("%s is already installed in %s" % (self.name, self.prefix)) + rec = spack.installed_db.get_record(self.spec) + if (not rec.explicit) and explicit: + with spack.installed_db.write_transaction(): + rec = spack.installed_db.get_record(self.spec) + rec.explicit = True return tty.msg("Installing %s" % self.name) @@ -995,7 +1001,7 @@ class Package(object): # 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) + spack.installed_db.add(self.spec, self.prefix, explicit=explicit) def sanity_check_prefix(self): """This function checks whether install succeeded.""" diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 89a023a750..58bdc0e430 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -72,7 +72,9 @@ Here is the EBNF grammar for a spec:: dep_list = { ^ spec } spec = id [ options ] options = { @version-list | +variant | -variant | ~variant | - %compiler | =architecture } + %compiler | arch=architecture | [ flag ]=value} + flag = { cflags | cxxflags | fcflags | fflags | cppflags | + ldflags | ldlibs } variant = id architecture = id compiler = id [ version-list ] @@ -80,6 +82,9 @@ Here is the EBNF grammar for a spec:: version = id | id: | :id | id:id id = [A-Za-z0-9_][A-Za-z0-9_.-]* +Identifiers using the <name>=<value> command, such as architectures and +compiler flags, require a space before the name. + There is one context-sensitive part: ids in versions may contain '.', while other ids may not. @@ -108,6 +113,8 @@ import spack.parse import spack.error import spack.compilers as compilers +# TODO: move display_specs to some other location. +from spack.cmd.find import display_specs from spack.version import * from spack.util.string import * from spack.util.prefix import Prefix @@ -143,7 +150,6 @@ _separators = '[%s]' % ''.join(color_formats.keys()) every time we call str()""" _any_version = VersionList([':']) - def index_specs(specs): """Take a list of specs and return a dict of lists. Dict is keyed by spec name and lists include all specs with the @@ -296,22 +302,25 @@ class VariantSpec(object): on the particular package being built, and each named variant can be enabled or disabled. """ - def __init__(self, name, enabled): + def __init__(self, name, value): self.name = name - self.enabled = enabled + self.value = value def _cmp_key(self): - return (self.name, self.enabled) + return (self.name, self.value) def copy(self): - return VariantSpec(self.name, self.enabled) + return VariantSpec(self.name, self.value) def __str__(self): - out = '+' if self.enabled else '~' - return out + self.name + if self.value in [True,False]: + out = '+' if self.value else '~' + return out + self.name + else: + return ' ' + self.name + "=" + self.value class VariantMap(HashableMap): @@ -322,10 +331,10 @@ class VariantMap(HashableMap): def satisfies(self, other, strict=False): if strict or self.spec._concrete: - return all(k in self and self[k].enabled == other[k].enabled + return all(k in self and self[k].value == other[k].value for k in other) else: - return all(self[k].enabled == other[k].enabled + return all(self[k].value == other[k].value for k in other if k in self) @@ -343,7 +352,7 @@ class VariantMap(HashableMap): changed = False for k in other: if k in self: - if self[k].enabled != other[k].enabled: + if self[k].value != other[k].value: raise UnsatisfiableVariantSpecError(self[k], other[k]) else: self[k] = other[k].copy() @@ -368,6 +377,70 @@ class VariantMap(HashableMap): return ''.join(str(self[key]) for key in sorted_keys) +_valid_compiler_flags = [ + 'cflags', 'cxxflags', 'fflags', 'ldflags', 'ldlibs', 'cppflags'] + +class FlagMap(HashableMap): + def __init__(self, spec): + super(FlagMap, self).__init__() + self.spec = spec + + + def satisfies(self, other, strict=False): + if strict or (self.spec and self.spec._concrete): + return all(f in self and set(self[f]) <= set(other[f]) + for f in other) + else: + return all(set(self[f]) <= set(other[f]) + for f in other if (other[f] != [] and f in self)) + + + def constrain(self, other): + """Add all flags in other that aren't in self to self. + + Return whether the spec changed. + """ + if other.spec and other.spec._concrete: + for k in self: + if k not in other: + raise UnsatisfiableCompilerFlagSpecError(self[k], '<absent>') + + changed = False + for k in other: + if k in self and not set(self[k]) <= set(other[k]): + raise UnsatisfiableCompilerFlagSpecError( + ' '.join(f for f in self[k]), ' '.join( f for f in other[k])) + elif k not in self: + self[k] = other[k] + changed = True + return changed + + @staticmethod + def valid_compiler_flags(): + return _valid_compiler_flags + + @property + def concrete(self): + return all(flag in self for flag in _valid_compiler_flags) + + + def copy(self): + clone = FlagMap(None) + for name, value in self.items(): + clone[name] = value + return clone + + + def _cmp_key(self): + return ''.join(str(key) + ' '.join(str(v) for v in value) for key, value in sorted(self.items())) + + + def __str__(self): + sorted_keys = filter(lambda flag: self[flag] != [], sorted(self.keys())) + cond_symbol = ' ' if len(sorted_keys)>0 else '' + return cond_symbol + ' '.join(str(key) + '=\"' + ' '.join(str(f) for f in self[key]) + '\"' for key in sorted_keys) + + class DependencyMap(HashableMap): """Each spec has a DependencyMap containing specs for its dependencies. The DependencyMap is keyed by name. """ @@ -409,10 +482,13 @@ class Spec(object): self.versions = other.versions self.architecture = other.architecture self.compiler = other.compiler + self.compiler_flags = other.compiler_flags + self.compiler_flags.spec = self self.dependencies = other.dependencies self.variants = other.variants self.variants.spec = self self.namespace = other.namespace + self._hash = other._hash # Specs are by default not assumed to be normal, but in some # cases we've read them from a file want to assume normal. @@ -440,13 +516,30 @@ class Spec(object): self.versions.add(version) - def _add_variant(self, name, enabled): + def _add_variant(self, name, value): """Called by the parser to add a variant.""" if name in self.variants: raise DuplicateVariantError( "Cannot specify variant '%s' twice" % name) - self.variants[name] = VariantSpec(name, enabled) + if isinstance(value, basestring) and value.upper() == 'TRUE': + value = True + elif isinstance(value, basestring) and value.upper() == 'FALSE': + value = False + self.variants[name] = VariantSpec(name, value) + def _add_flag(self, name, value): + """Called by the parser to add a known flag. + Known flags currently include "arch" + """ + valid_flags = FlagMap.valid_compiler_flags() + if name == 'arch': + self._set_architecture(value) + elif name in valid_flags: + assert(self.compiler_flags is not None) + self.compiler_flags[name] = value.split() + else: + self._add_variant(name,value) + def _set_compiler(self, compiler): """Called by the parser to set the compiler.""" if self.compiler: raise DuplicateCompilerSpecError( @@ -473,7 +566,7 @@ class Spec(object): # @property def fullname(self): - return '%s.%s' % (self.namespace, self.name) if self.namespace else self.name + return '%s.%s' % (self.namespace, self.name) if self.namespace else (self.name if self.name else '') @property @@ -523,7 +616,7 @@ class Spec(object): @staticmethod def is_virtual(name): """Test if a name is virtual without requiring a Spec.""" - return not spack.repo.exists(name) + return (not name is None) and ( not spack.repo.exists(name) ) @property @@ -541,8 +634,8 @@ class Spec(object): and self.variants.concrete and self.architecture and self.compiler and self.compiler.concrete + and self.compiler_flags.concrete and self.dependencies.concrete) - return self._concrete @@ -664,19 +757,25 @@ class Spec(object): """ Return a hash of the entire spec DAG, including connectivity. """ - yaml_text = yaml.dump( - self.to_node_dict(), default_flow_style=True, width=sys.maxint) - sha = hashlib.sha1(yaml_text) - return base64.b32encode(sha.digest()).lower()[:length] - + if self._hash: + return self._hash[:length] + else: + yaml_text = yaml.dump( + self.to_node_dict(), default_flow_style=True, width=sys.maxint) + sha = hashlib.sha1(yaml_text) + b32_hash = base64.b32encode(sha.digest()).lower()[:length] + if self.concrete: + self._hash = b32_hash + return b32_hash def to_node_dict(self): + params = dict( (name, v.value) for name, v in self.variants.items() ) + params.update( dict( (name, value) for name, value in self.compiler_flags.items()) ) d = { - 'variants' : dict( - (name,v.enabled) for name, v in self.variants.items()), + 'parameters' : params, 'arch' : self.architecture, 'dependencies' : dict((d, self.dependencies[d].dag_hash()) - for d in sorted(self.dependencies)) + for d in sorted(self.dependencies)), } # Older concrete specs do not have a namespace. Omit for @@ -689,6 +788,7 @@ class Spec(object): else: d['compiler'] = None d.update(self.versions.to_dict()) + return { self.name : d } @@ -712,13 +812,27 @@ class Spec(object): spec.versions = VersionList.from_dict(node) spec.architecture = node['arch'] + if 'hash' in node: + spec._hash = node['hash'] + if node['compiler'] is None: spec.compiler = None else: spec.compiler = CompilerSpec.from_dict(node) - for name, enabled in node['variants'].items(): - spec.variants[name] = VariantSpec(name, enabled) + if 'parameters' in node: + for name, value in node['parameters'].items(): + if name in _valid_compiler_flags: + spec.compiler_flags[name] = value + else: + spec.variants[name] = VariantSpec(name, value) + elif 'variants' in node: + for name, value in node['variants'].items(): + spec.variants[name] = VariantSpec(name, value) + for name in FlagMap.valid_compiler_flags(): + spec.compiler_flags[name] = [] + else: + raise SpackRecordError("Did not find a valid format for variants in YAML file") return spec @@ -781,11 +895,13 @@ class Spec(object): # Concretize virtual dependencies last. Because they're added # to presets below, their constraints will all be merged, but we'll # still need to select a concrete package later. - changed |= any( - (spack.concretizer.concretize_architecture(self), - spack.concretizer.concretize_compiler(self), - spack.concretizer.concretize_version(self), - spack.concretizer.concretize_variants(self))) + if not self.virtual: + changed |= any( + (spack.concretizer.concretize_architecture(self), + spack.concretizer.concretize_compiler(self), + spack.concretizer.concretize_compiler_flags(self),#has to be concretized after compiler + spack.concretizer.concretize_version(self), + spack.concretizer.concretize_variants(self))) presets[self.name] = self visited.add(self.name) @@ -928,6 +1044,8 @@ class Spec(object): with requirements of its pacakges. See flatten() and normalize() for more details on this. """ + if not self.name: + raise SpecError("Attempting to concretize anonymous spec") if self._concrete: return @@ -1071,6 +1189,11 @@ class Spec(object): # If there is a provider for the vpkg, then use that instead of # the virtual package. if providers: + # Remove duplicate providers that can concretize to the same result. + for provider in providers: + for spec in providers: + if spec is not provider and provider.satisfies(spec): + providers.remove(spec) # Can't have multiple providers for the same thing in one spec. if len(providers) > 1: raise MultipleProviderError(vdep, providers) @@ -1123,13 +1246,11 @@ class Spec(object): if required: raise UnsatisfiableProviderSpecError(required[0], dep) provider_index.update(dep) - # If the spec isn't already in the set of dependencies, clone # it from the package description. if dep.name not in spec_deps: spec_deps[dep.name] = dep.copy() changed = True - # Constrain package information with spec info try: changed |= spec_deps[dep.name].constrain(dep) @@ -1172,7 +1293,6 @@ class Spec(object): for dep_name in pkg.dependencies: # Do we depend on dep_name? If so pkg_dep is not None. pkg_dep = self._evaluate_dependency_conditions(dep_name) - # If pkg_dep is a dependency, merge it. if pkg_dep: changed |= self._merge_dependency( @@ -1197,8 +1317,10 @@ class Spec(object): TODO: normalize should probably implement some form of cycle detection, to ensure that the spec is actually a DAG. - """ + if not self.name: + raise SpecError("Attempting to normalize anonymous spec") + if self._normal and not force: return False @@ -1243,7 +1365,7 @@ class Spec(object): """ for spec in self.traverse(): # Don't get a package for a virtual name. - if not spec.virtual: + if (not spec.virtual) and spec.name: spack.repo.get(spec.fullname) # validate compiler in addition to the package name. @@ -1264,7 +1386,7 @@ class Spec(object): """ other = self._autospec(other) - if not self.name == other.name: + if not (self.name == other.name or (not self.name) or (not other.name) ): raise UnsatisfiableSpecNameError(self.name, other.name) if other.namespace is not None: @@ -1276,7 +1398,7 @@ class Spec(object): for v in other.variants: if (v in self.variants and - self.variants[v].enabled != other.variants[v].enabled): + self.variants[v].value != other.variants[v].value): raise UnsatisfiableVariantSpecError(self.variants[v], other.variants[v]) @@ -1295,6 +1417,8 @@ class Spec(object): changed |= self.versions.intersect(other.versions) changed |= self.variants.constrain(other.variants) + changed |= self.compiler_flags.constrain(other.compiler_flags) + old = self.architecture self.architecture = self.architecture or other.architecture changed |= (self.architecture != old) @@ -1365,7 +1489,10 @@ class Spec(object): return spec_like try: - return spack.spec.Spec(spec_like) + spec = spack.spec.Spec(spec_like) + if not spec.name: + raise SpecError("anonymous package -- this will always be handled") + return spec except SpecError: return parse_anonymous_spec(spec_like, self.name) @@ -1385,7 +1512,7 @@ class Spec(object): """ other = self._autospec(other) - # A concrete provider can satisfy a virtual dependency. + # A concrete provider can satisfy a virtual dependency. if not self.virtual and other.virtual: pkg = spack.repo.get(self.fullname) if pkg.provides(other.name): @@ -1396,7 +1523,7 @@ class Spec(object): return False # Otherwise, first thing we care about is whether the name matches - if self.name != other.name: + if self.name != other.name and self.name and other.name: return False # namespaces either match, or other doesn't require one. @@ -1417,7 +1544,10 @@ class Spec(object): elif strict and (other.compiler and not self.compiler): return False - if not self.variants.satisfies(other.variants, strict=strict): + var_strict = strict + if (not self.name) or (not other.name): + var_strict = True + if not self.variants.satisfies(other.variants, strict=var_strict): return False # Architecture satisfaction is currently just string equality. @@ -1428,9 +1558,15 @@ class Spec(object): elif strict and (other.architecture and not self.architecture): return False + if not self.compiler_flags.satisfies(other.compiler_flags, strict=strict): + return False + # If we need to descend into dependencies, do it, otherwise we're done. if deps: - return self.satisfies_dependencies(other, strict=strict) + deps_strict = strict + if not (self.name and other.name): + deps_strict=True + return self.satisfies_dependencies(other, strict=deps_strict) else: return True @@ -1510,10 +1646,12 @@ class Spec(object): if kwargs.get('cleardeps', True): self.dependents = DependencyMap() self.dependencies = DependencyMap() + self.compiler_flags = other.compiler_flags.copy() self.variants = other.variants.copy() self.variants.spec = self self.external = other.external self.namespace = other.namespace + self._hash = other._hash # If we copy dependencies, preserve DAG structure in the new spec if kwargs.get('deps', True): @@ -1638,7 +1776,9 @@ class Spec(object): self.versions, self.variants, self.architecture, - self.compiler) + self.compiler, + self.compiler_flags, + self.dag_hash()) def eq_node(self, other): @@ -1667,7 +1807,7 @@ class Spec(object): return colorize_spec(self) - def format(self, format_string='$_$@$%@$+$=', **kwargs): + def format(self, format_string='$_$@$%@+$+$=', **kwargs): """Prints out particular pieces of a spec, depending on what is in the format string. The format strings you can provide are:: @@ -1676,8 +1816,10 @@ class Spec(object): $@ Version with '@' prefix $% Compiler with '%' prefix $%@ Compiler with '%' prefix & compiler version with '@' prefix + $%+ Compiler with '%' prefix & compiler flags prefixed by name + $%@+ Compiler, compiler version, and compiler flags with same prefixes as above $+ Options - $= Architecture with '=' prefix + $= Architecture prefixed by 'arch=' $# 7-char prefix of DAG hash with '-' prefix $$ $ @@ -1688,6 +1830,7 @@ class Spec(object): ${COMPILER} Full compiler string ${COMPILERNAME} Compiler name ${COMPILERVER} Compiler version + ${COMPILERFLAGS} Compiler flags ${OPTIONS} Options ${ARCHITECTURE} Architecture ${SHA1} Dependencies 8-char sha1 prefix @@ -1734,7 +1877,8 @@ class Spec(object): fmt += 's' if c == '_': - out.write(fmt % self.name) + name = self.name if self.name else '' + out.write(fmt % name) elif c == '.': out.write(fmt % self.fullname) elif c == '@': @@ -1749,7 +1893,7 @@ class Spec(object): write(fmt % str(self.variants), c) elif c == '=': if self.architecture: - write(fmt % (c + str(self.architecture)), c) + write(fmt % (' arch' + c + str(self.architecture)), c) elif c == '#': out.write('-' + fmt % (self.dag_hash(7))) elif c == '$': @@ -1766,11 +1910,16 @@ class Spec(object): if (self.compiler and self.compiler.versions and self.compiler.versions != _any_version): write(c + str(self.compiler.versions), '%') + elif c == '+': + if self.compiler_flags: + write(fmt % str(self.compiler_flags), '%') + compiler = False elif c == '$': escape = True + compiler = False else: out.write(c) - compiler = False + compiler = False elif named: if not c == '}': @@ -1780,6 +1929,7 @@ class Spec(object): named_str += c continue; if named_str == 'PACKAGE': + name = self.name if self.name else '' write(fmt % self.name, '@') if named_str == 'VERSION': if self.versions and self.versions != _any_version: @@ -1793,6 +1943,9 @@ class Spec(object): elif named_str == 'COMPILERVER': if self.compiler: write(fmt % self.compiler.versions, '%') + elif named_str == 'COMPILERFLAGS': + if self.compiler: + write(fmt % str(self.compiler_flags), '%') elif named_str == 'OPTIONS': if self.variants: write(fmt % str(self.variants), '+') @@ -1837,7 +1990,7 @@ class Spec(object): showid = kwargs.pop('ids', False) cover = kwargs.pop('cover', 'nodes') indent = kwargs.pop('indent', 0) - fmt = kwargs.pop('format', '$_$@$%@$+$=') + fmt = kwargs.pop('format', '$_$@$%@+$+$=') prefix = kwargs.pop('prefix', None) check_kwargs(kwargs, self.tree) @@ -1869,12 +2022,13 @@ class Spec(object): # # These are possible token types in the spec grammar. # -DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, ID = range(9) +HASH, DEP, AT, COLON, COMMA, ON, OFF, PCT, EQ, QT, ID = range(11) class SpecLexer(spack.parse.Lexer): """Parses tokens that make up spack specs.""" def __init__(self): super(SpecLexer, self).__init__([ + (r'/', lambda scanner, val: self.token(HASH, val)), (r'\^', lambda scanner, val: self.token(DEP, val)), (r'\@', lambda scanner, val: self.token(AT, val)), (r'\:', lambda scanner, val: self.token(COLON, val)), @@ -1886,6 +2040,7 @@ class SpecLexer(spack.parse.Lexer): (r'\=', lambda scanner, val: self.token(EQ, val)), # This is more liberal than identifier_re (see above). # Checked by check_identifier() for better error messages. + (r'([\"\'])(?:(?=(\\?))\2.)*?\1',lambda scanner, val: self.token(QT, val)), (r'\w[\w.-]*', lambda scanner, val: self.token(ID, val)), (r'\s+', lambda scanner, val: None)]) @@ -1893,27 +2048,52 @@ class SpecLexer(spack.parse.Lexer): class SpecParser(spack.parse.Parser): def __init__(self): super(SpecParser, self).__init__(SpecLexer()) - + self.previous = None def do_parse(self): specs = [] try: while self.next: + # TODO: clean this parsing up a bit + if self.previous: + specs.append(self.spec(self.previous.value)) if self.accept(ID): - specs.append(self.spec()) + self.previous = self.token + if self.accept(EQ): + if not specs: + specs.append(self.spec(None)) + if self.accept(QT): + self.token.value = self.token.value[1:-1] + else: + self.expect(ID) + specs[-1]._add_flag(self.previous.value, self.token.value) + else: + specs.append(self.spec(self.previous.value)) + self.previous = None + elif self.accept(HASH): + specs.append(self.spec_by_hash()) elif self.accept(DEP): if not specs: - self.last_token_error("Dependency has no package") - self.expect(ID) - specs[-1]._add_dependency(self.spec()) + self.previous = self.token + specs.append(self.spec(None)) + self.previous = None + if self.accept(HASH): + specs[-1]._add_dependency(self.spec_by_hash()) + else: + self.expect(ID) + specs[-1]._add_dependency(self.spec(self.token.value)) else: - self.unexpected_token() + # Attempt to construct an anonymous spec, but check that the first token is valid + # TODO: Is this check even necessary, or will it all be Lex errors now? + specs.append(self.spec(None,True)) + except spack.parse.ParseError, e: raise SpecParseError(e) + return specs @@ -1922,15 +2102,35 @@ class SpecParser(spack.parse.Parser): return self.compiler() - def spec(self): + def spec_by_hash(self): + self.expect(ID) + + specs = spack.installed_db.query() + matches = [spec for spec in specs if + spec.dag_hash()[:len(self.token.value)] == self.token.value] + + if not matches: + tty.die("%s does not match any installed packages." %self.token.value) + + if len(matches) != 1: + raise AmbiguousHashError("Multiple packages specify hash %s." % self.token.value, *matches) + + return matches[0] + + + def spec(self, name, check_valid_token = False): """Parse a spec out of the input. If a spec is supplied, then initialize and return it instead of creating a new one.""" - - spec_namespace, dot, spec_name = self.token.value.rpartition('.') - if not spec_namespace: + if name: + spec_namespace, dot, spec_name = name.rpartition('.') + if not spec_namespace: + spec_namespace = None + self.check_identifier(spec_name) + else: spec_namespace = None + spec_name = None + - self.check_identifier(spec_name) # This will init the spec without calling __init__. spec = Spec.__new__(Spec) @@ -1940,9 +2140,11 @@ class SpecParser(spack.parse.Parser): spec.architecture = None spec.compiler = None spec.external = None + spec.compiler_flags = FlagMap(spec) spec.dependents = DependencyMap() spec.dependencies = DependencyMap() spec.namespace = spec_namespace + spec._hash = None spec._normal = False spec._concrete = False @@ -1951,26 +2153,50 @@ class SpecParser(spack.parse.Parser): # unspecified or not. added_version = False + if self.previous and self.previous.value == DEP: + if self.accept(HASH): + spec.add_dependency(self.spec_by_hash()) + else: + self.expect(ID) + if self.accept(EQ): + raise SpecParseError(spack.parse.ParseError("","","Expected dependency received anonymous spec")) + spec.add_dependency(self.spec(self.token.value)) + while self.next: if self.accept(AT): vlist = self.version_list() for version in vlist: spec._add_version(version) added_version = True + check_valid_token = False elif self.accept(ON): spec._add_variant(self.variant(), True) + check_valid_token = False elif self.accept(OFF): - spec._add_variant(self.variant(), False) + spec._add_variant(self.variant(),False) + check_valid_token = False elif self.accept(PCT): spec._set_compiler(self.compiler()) + check_valid_token = False - elif self.accept(EQ): - spec._set_architecture(self.architecture()) + elif self.accept(ID): + self.previous = self.token + if self.accept(EQ): + if self.accept(QT): + self.token.value = self.token.value[1:-1] + else: + self.expect(ID) + spec._add_flag(self.previous.value, self.token.value) + self.previous = None + else: + return spec else: + if check_valid_token: + self.unexpected_token() break # If there was no version in the spec, consier it an open range @@ -1980,13 +2206,17 @@ class SpecParser(spack.parse.Parser): return spec - def variant(self): - self.expect(ID) - self.check_identifier() - return self.token.value - + def variant(self,name=None): + #TODO: Make generalized variants possible + if name: + return name + else: + self.expect(ID) + self.check_identifier() + return self.token.value def architecture(self): + #TODO: Make this work properly as a subcase of variant (includes adding names to grammar) self.expect(ID) return self.token.value @@ -2068,8 +2298,10 @@ def parse_anonymous_spec(spec_like, pkg_name): if isinstance(spec_like, str): try: anon_spec = Spec(spec_like) + if anon_spec.name != pkg_name: + raise SpecParseError(spack.parse.ParseError("","","Expected anonymous spec for package %s but found spec for package %s" % (pkg_name, anon_spec.name) )) except SpecParseError: - anon_spec = Spec(pkg_name + spec_like) + anon_spec = Spec(pkg_name + ' ' + spec_like) if anon_spec.name != pkg_name: raise ValueError( "Invalid spec for package %s: %s" % (pkg_name, spec_like)) else: @@ -2170,7 +2402,6 @@ class MultipleProviderError(SpecError): self.vpkg = vpkg self.providers = providers - class UnsatisfiableSpecError(SpecError): """Raised when a spec conflicts with package constraints. Provide the requirement that was violated when raising.""" @@ -2209,6 +2440,11 @@ class UnsatisfiableVariantSpecError(UnsatisfiableSpecError): super(UnsatisfiableVariantSpecError, self).__init__( provided, required, "variant") +class UnsatisfiableCompilerFlagSpecError(UnsatisfiableSpecError): + """Raised when a spec variant conflicts with package constraints.""" + def __init__(self, provided, required): + super(UnsatisfiableCompilerFlagSpecError, self).__init__( + provided, required, "compiler_flags") class UnsatisfiableArchitectureSpecError(UnsatisfiableSpecError): """Raised when a spec architecture conflicts with package constraints.""" @@ -2235,3 +2471,13 @@ class UnsatisfiableDependencySpecError(UnsatisfiableSpecError): class SpackYAMLError(spack.error.SpackError): def __init__(self, msg, yaml_error): super(SpackYAMLError, self).__init__(msg, str(yaml_error)) + +class SpackRecordError(spack.error.SpackError): + def __init__(self, msg): + super(SpackRecordError, self).__init__(msg) + +class AmbiguousHashError(SpecError): + def __init__(self, msg, *specs): + super(AmbiguousHashError, self).__init__(msg) + for spec in specs: + print ' ', spec.format('$.$@$%@+$+$=$#') diff --git a/lib/spack/spack/test/cc.py b/lib/spack/spack/test/cc.py index a630866143..ea2b164462 100644 --- a/lib/spack/spack/test/cc.py +++ b/lib/spack/spack/test/cc.py @@ -56,11 +56,16 @@ class CompilerTest(unittest.TestCase): self.cc = Executable(join_path(spack.build_env_path, "cc")) self.ld = Executable(join_path(spack.build_env_path, "ld")) self.cpp = Executable(join_path(spack.build_env_path, "cpp")) + self.cxx = Executable(join_path(spack.build_env_path, "c++")) + self.fc = Executable(join_path(spack.build_env_path, "fc")) self.realcc = "/bin/mycc" self.prefix = "/spack-test-prefix" os.environ['SPACK_CC'] = self.realcc + os.environ['SPACK_CXX'] = self.realcc + os.environ['SPACK_FC'] = self.realcc + os.environ['SPACK_PREFIX'] = self.prefix os.environ['SPACK_ENV_PATH']="test" os.environ['SPACK_DEBUG_LOG_DIR'] = "." @@ -102,6 +107,15 @@ class CompilerTest(unittest.TestCase): self.assertEqual(self.cc(*args, output=str).strip(), expected) + def check_cxx(self, command, args, expected): + os.environ['SPACK_TEST_COMMAND'] = command + self.assertEqual(self.cxx(*args, output=str).strip(), expected) + + def check_fc(self, command, args, expected): + os.environ['SPACK_TEST_COMMAND'] = command + self.assertEqual(self.fc(*args, output=str).strip(), expected) + + def check_ld(self, command, args, expected): os.environ['SPACK_TEST_COMMAND'] = command self.assertEqual(self.ld(*args, output=str).strip(), expected) @@ -142,6 +156,64 @@ class CompilerTest(unittest.TestCase): self.check_ld('dump-mode', ['foo.o', 'bar.o', 'baz.o', '-o', 'foo', '-Wl,-rpath,foo'], "ld") + def test_flags(self): + os.environ['SPACK_LDFLAGS'] = '-L foo' + os.environ['SPACK_LDLIBS'] = '-lfoo' + os.environ['SPACK_CPPFLAGS'] = '-g -O1' + os.environ['SPACK_CFLAGS'] = '-Wall' + os.environ['SPACK_CXXFLAGS'] = '-Werror' + os.environ['SPACK_FFLAGS'] = '-w' + + # Test ldflags added properly in ld mode + self.check_ld('dump-args', test_command, + "ld " + + '-rpath ' + self.prefix + '/lib ' + + '-rpath ' + self.prefix + '/lib64 ' + + '-L foo ' + + ' '.join(test_command) + ' ' + + '-lfoo') + + # Test cppflags added properly in cpp mode + self.check_cpp('dump-args', test_command, + "cpp " + + '-g -O1 ' + + ' '.join(test_command)) + + # Test ldflags, cppflags, and language specific flags are added in proper order + self.check_cc('dump-args', test_command, + self.realcc + ' ' + + '-Wl,-rpath,' + self.prefix + '/lib ' + + '-Wl,-rpath,' + self.prefix + '/lib64 ' + + '-g -O1 ' + + '-Wall ' + + '-L foo ' + + ' '.join(test_command) + ' ' + + '-lfoo') + + self.check_cxx('dump-args', test_command, + self.realcc + ' ' + + '-Wl,-rpath,' + self.prefix + '/lib ' + + '-Wl,-rpath,' + self.prefix + '/lib64 ' + + '-g -O1 ' + + '-Werror ' + + '-L foo ' + + ' '.join(test_command) + ' ' + + '-lfoo') + + self.check_fc('dump-args', test_command, + self.realcc + ' ' + + '-Wl,-rpath,' + self.prefix + '/lib ' + + '-Wl,-rpath,' + self.prefix + '/lib64 ' + + '-w ' + + '-g -O1 ' + + '-L foo ' + + ' '.join(test_command) + ' ' + + '-lfoo') + + os.environ['SPACK_LDFLAGS']='' + os.environ['SPACK_LDLIBS']='' + + def test_dep_rpath(self): """Ensure RPATHs for root package are added.""" self.check_cc('dump-args', test_command, diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index 799fdae3a9..963481054e 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -38,11 +38,20 @@ class ConcretizeTest(MockPackagesTest): for name in abstract.variants: avariant = abstract.variants[name] cvariant = concrete.variants[name] - self.assertEqual(avariant.enabled, cvariant.enabled) + self.assertEqual(avariant.value, cvariant.value) + + if abstract.compiler_flags: + for flag in abstract.compiler_flags: + aflag = abstract.compiler_flags[flag] + cflag = concrete.compiler_flags[flag] + self.assertTrue(set(aflag) <= set(cflag)) for name in abstract.package.variants: self.assertTrue(name in concrete.variants) + for flag in concrete.compiler_flags.valid_compiler_flags(): + self.assertTrue(flag in concrete.compiler_flags) + if abstract.compiler and abstract.compiler.concrete: self.assertEqual(abstract.compiler, concrete.compiler) @@ -75,9 +84,14 @@ class ConcretizeTest(MockPackagesTest): def test_concretize_variant(self): self.check_concretize('mpich+debug') self.check_concretize('mpich~debug') + self.check_concretize('mpich debug=2') self.check_concretize('mpich') + def test_conretize_compiler_flags(self): + self.check_concretize('mpich cppflags="-O3"') + + def test_concretize_preferred_version(self): spec = self.check_concretize('python') self.assertEqual(spec.versions, ver('2.7.11')) @@ -231,7 +245,7 @@ class ConcretizeTest(MockPackagesTest): def test_external_package(self): - spec = Spec('externaltool') + spec = Spec('externaltool%gcc') spec.concretize() self.assertEqual(spec['externaltool'].external, '/path/to/external_tool') diff --git a/lib/spack/spack/test/modules.py b/lib/spack/spack/test/modules.py index 56e294de26..c73badf8f2 100644 --- a/lib/spack/spack/test/modules.py +++ b/lib/spack/spack/test/modules.py @@ -73,7 +73,7 @@ configuration_alter_environment = { 'all': { 'filter': {'environment_blacklist': ['CMAKE_PREFIX_PATH']} }, - '=x86-linux': { + 'arch=x86-linux': { 'environment': {'set': {'FOO': 'foo'}, 'unset': ['BAR']} } @@ -123,26 +123,26 @@ class TclTests(MockPackagesTest): def test_simple_case(self): spack.modules.CONFIGURATION = configuration_autoload_direct - spec = spack.spec.Spec('mpich@3.0.4=x86-linux') + spec = spack.spec.Spec('mpich@3.0.4 arch=x86-linux') content = self.get_modulefile_content(spec) self.assertTrue('module-whatis "mpich @3.0.4"' in content) def test_autoload(self): spack.modules.CONFIGURATION = configuration_autoload_direct - spec = spack.spec.Spec('mpileaks=x86-linux') + spec = spack.spec.Spec('mpileaks arch=x86-linux') content = self.get_modulefile_content(spec) self.assertEqual(len([x for x in content if 'is-loaded' in x]), 2) self.assertEqual(len([x for x in content if 'module load ' in x]), 2) spack.modules.CONFIGURATION = configuration_autoload_all - spec = spack.spec.Spec('mpileaks=x86-linux') + spec = spack.spec.Spec('mpileaks arch=x86-linux') content = self.get_modulefile_content(spec) self.assertEqual(len([x for x in content if 'is-loaded' in x]), 5) self.assertEqual(len([x for x in content if 'module load ' in x]), 5) def test_alter_environment(self): spack.modules.CONFIGURATION = configuration_alter_environment - spec = spack.spec.Spec('mpileaks=x86-linux') + spec = spack.spec.Spec('mpileaks arch=x86-linux') content = self.get_modulefile_content(spec) self.assertEqual( len([x @@ -152,7 +152,7 @@ class TclTests(MockPackagesTest): 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') + spec = spack.spec.Spec('libdwarf arch=x64-linux') content = self.get_modulefile_content(spec) self.assertEqual( len([x @@ -164,14 +164,14 @@ class TclTests(MockPackagesTest): def test_blacklist(self): spack.modules.CONFIGURATION = configuration_blacklist - spec = spack.spec.Spec('mpileaks=x86-linux') + spec = spack.spec.Spec('mpileaks arch=x86-linux') content = self.get_modulefile_content(spec) self.assertEqual(len([x for x in content if 'is-loaded' in x]), 1) self.assertEqual(len([x for x in content if 'module load ' in x]), 1) def test_conflicts(self): spack.modules.CONFIGURATION = configuration_conflicts - spec = spack.spec.Spec('mpileaks=x86-linux') + spec = spack.spec.Spec('mpileaks arch=x86-linux') content = self.get_modulefile_content(spec) self.assertEqual( len([x for x in content if x.startswith('conflict')]), 2) diff --git a/lib/spack/spack/test/multimethod.py b/lib/spack/spack/test/multimethod.py index f653ca3477..a33656adcc 100644 --- a/lib/spack/spack/test/multimethod.py +++ b/lib/spack/spack/test/multimethod.py @@ -25,9 +25,13 @@ """ Test for multi_method dispatch. """ +import unittest import spack from spack.multimethod import * +from spack.version import * +from spack.spec import Spec +from spack.multimethod import when from spack.test.mock_packages_test import * from spack.version import * @@ -89,19 +93,19 @@ class MultiMethodTest(MockPackagesTest): def test_architecture_match(self): - pkg = spack.repo.get('multimethod=x86_64') + pkg = spack.repo.get('multimethod arch=x86_64') self.assertEqual(pkg.different_by_architecture(), 'x86_64') - pkg = spack.repo.get('multimethod=ppc64') + pkg = spack.repo.get('multimethod arch=ppc64') self.assertEqual(pkg.different_by_architecture(), 'ppc64') - pkg = spack.repo.get('multimethod=ppc32') + pkg = spack.repo.get('multimethod arch=ppc32') self.assertEqual(pkg.different_by_architecture(), 'ppc32') - pkg = spack.repo.get('multimethod=arm64') + pkg = spack.repo.get('multimethod arch=arm64') self.assertEqual(pkg.different_by_architecture(), 'arm64') - pkg = spack.repo.get('multimethod=macos') + pkg = spack.repo.get('multimethod arch=macos') self.assertRaises(NoSuchMethodError, pkg.different_by_architecture) diff --git a/lib/spack/spack/test/optional_deps.py b/lib/spack/spack/test/optional_deps.py index 90382dfc4a..b5ba0ecf35 100644 --- a/lib/spack/spack/test/optional_deps.py +++ b/lib/spack/spack/test/optional_deps.py @@ -42,6 +42,13 @@ class ConcretizeTest(MockPackagesTest): self.check_normalize('optional-dep-test+a', Spec('optional-dep-test+a', Spec('a'))) + self.check_normalize('optional-dep-test a=true', + Spec('optional-dep-test a=true', Spec('a'))) + + + self.check_normalize('optional-dep-test a=true', + Spec('optional-dep-test+a', Spec('a'))) + self.check_normalize('optional-dep-test@1.1', Spec('optional-dep-test@1.1', Spec('b'))) diff --git a/lib/spack/spack/test/spec_dag.py b/lib/spack/spack/test/spec_dag.py index 4645f98565..52f4f7395e 100644 --- a/lib/spack/spack/test/spec_dag.py +++ b/lib/spack/spack/test/spec_dag.py @@ -31,6 +31,8 @@ You can find the dummy packages here:: import spack import spack.package +from llnl.util.lang import list_modules + from spack.spec import Spec from spack.test.mock_packages_test import * @@ -239,8 +241,8 @@ class SpecDagTest(MockPackagesTest): def test_unsatisfiable_architecture(self): - self.set_pkg_dep('mpileaks', 'mpich=bgqos_0') - spec = Spec('mpileaks ^mpich=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf') + self.set_pkg_dep('mpileaks', 'mpich arch=bgqos_0') + spec = Spec('mpileaks ^mpich arch=sles_10_ppc64 ^callpath ^dyninst ^libelf ^libdwarf') self.assertRaises(spack.spec.UnsatisfiableArchitectureSpecError, spec.normalize) diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py index 60eb86d652..0cb78b90ed 100644 --- a/lib/spack/spack/test/spec_semantics.py +++ b/lib/spack/spack/test/spec_semantics.py @@ -22,6 +22,7 @@ # 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 unittest from spack.spec import * from spack.test.mock_packages_test import * @@ -138,11 +139,11 @@ class SpecSematicsTest(MockPackagesTest): def test_satisfies_architecture(self): - self.check_satisfies('foo=chaos_5_x86_64_ib', '=chaos_5_x86_64_ib') - self.check_satisfies('foo=bgqos_0', '=bgqos_0') + self.check_satisfies('foo arch=chaos_5_x86_64_ib', ' arch=chaos_5_x86_64_ib') + self.check_satisfies('foo arch=bgqos_0', ' arch=bgqos_0') - self.check_unsatisfiable('foo=bgqos_0', '=chaos_5_x86_64_ib') - self.check_unsatisfiable('foo=chaos_5_x86_64_ib', '=bgqos_0') + self.check_unsatisfiable('foo arch=bgqos_0', ' arch=chaos_5_x86_64_ib') + self.check_unsatisfiable('foo arch=chaos_5_x86_64_ib', ' arch=bgqos_0') def test_satisfies_dependencies(self): @@ -190,12 +191,20 @@ class SpecSematicsTest(MockPackagesTest): def test_satisfies_matching_variant(self): self.check_satisfies('mpich+foo', 'mpich+foo') self.check_satisfies('mpich~foo', 'mpich~foo') + self.check_satisfies('mpich foo=1', 'mpich foo=1') + + #confirm that synonymous syntax works correctly + self.check_satisfies('mpich+foo', 'mpich foo=True') + self.check_satisfies('mpich foo=true', 'mpich+foo') + self.check_satisfies('mpich~foo', 'mpich foo=FALSE') + self.check_satisfies('mpich foo=False', 'mpich~foo') def test_satisfies_unconstrained_variant(self): # only asked for mpich, no constraints. Either will do. self.check_satisfies('mpich+foo', 'mpich') self.check_satisfies('mpich~foo', 'mpich') + self.check_satisfies('mpich foo=1', 'mpich') def test_unsatisfiable_variants(self): @@ -204,16 +213,44 @@ class SpecSematicsTest(MockPackagesTest): # 'mpich' is not concrete: self.check_satisfies('mpich', 'mpich+foo', False) self.check_satisfies('mpich', 'mpich~foo', False) + self.check_satisfies('mpich', 'mpich foo=1', False) # 'mpich' is concrete: self.check_unsatisfiable('mpich', 'mpich+foo', True) self.check_unsatisfiable('mpich', 'mpich~foo', True) + self.check_unsatisfiable('mpich', 'mpich foo=1', True) def test_unsatisfiable_variant_mismatch(self): # No matchi in specs self.check_unsatisfiable('mpich~foo', 'mpich+foo') self.check_unsatisfiable('mpich+foo', 'mpich~foo') + self.check_unsatisfiable('mpich foo=1', 'mpich foo=2') + + + def test_satisfies_matching_compiler_flag(self): + self.check_satisfies('mpich cppflags="-O3"', 'mpich cppflags="-O3"') + self.check_satisfies('mpich cppflags="-O3 -Wall"', 'mpich cppflags="-O3 -Wall"') + + + def test_satisfies_unconstrained_compiler_flag(self): + # only asked for mpich, no constraints. Any will do. + self.check_satisfies('mpich cppflags="-O3"', 'mpich') + + + def test_unsatisfiable_compiler_flag(self): + # This case is different depending on whether the specs are concrete. + + # 'mpich' is not concrete: + self.check_satisfies('mpich', 'mpich cppflags="-O3"', False) + + # 'mpich' is concrete: + self.check_unsatisfiable('mpich', 'mpich cppflags="-O3"', True) + + + def test_unsatisfiable_compiler_flag_mismatch(self): + # No matchi in specs + self.check_unsatisfiable('mpich cppflags="-O3"', 'mpich cppflags="-O2"') def test_satisfies_virtual(self): @@ -301,18 +338,26 @@ class SpecSematicsTest(MockPackagesTest): self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+foo') self.check_constrain('libelf+debug+foo', 'libelf+debug', 'libelf+debug+foo') + self.check_constrain('libelf debug=2 foo=1', 'libelf debug=2', 'libelf foo=1') + self.check_constrain('libelf debug=2 foo=1', 'libelf debug=2', 'libelf debug=2 foo=1') + self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf~foo') self.check_constrain('libelf+debug~foo', 'libelf+debug', 'libelf+debug~foo') + def test_constrain_compiler_flags(self): + self.check_constrain('libelf cflags="-O3" cppflags="-Wall"', 'libelf cflags="-O3"', 'libelf cppflags="-Wall"') + self.check_constrain('libelf cflags="-O3" cppflags="-Wall"', 'libelf cflags="-O3"', 'libelf cflags="-O3" cppflags="-Wall"') + + def test_constrain_arch(self): - self.check_constrain('libelf=bgqos_0', 'libelf=bgqos_0', 'libelf=bgqos_0') - self.check_constrain('libelf=bgqos_0', 'libelf', 'libelf=bgqos_0') + self.check_constrain('libelf arch=bgqos_0', 'libelf arch=bgqos_0', 'libelf arch=bgqos_0') + self.check_constrain('libelf arch=bgqos_0', 'libelf', 'libelf arch=bgqos_0') def test_constrain_compiler(self): - self.check_constrain('libelf=bgqos_0', 'libelf=bgqos_0', 'libelf=bgqos_0') - self.check_constrain('libelf=bgqos_0', 'libelf', 'libelf=bgqos_0') + self.check_constrain('libelf %gcc@4.4.7', 'libelf %gcc@4.4.7', 'libelf %gcc@4.4.7') + self.check_constrain('libelf %gcc@4.4.7', 'libelf', 'libelf %gcc@4.4.7') def test_invalid_constraint(self): @@ -321,8 +366,11 @@ class SpecSematicsTest(MockPackagesTest): self.check_invalid_constraint('libelf+debug', 'libelf~debug') self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo') + self.check_invalid_constraint('libelf debug=2', 'libelf debug=1') - self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54') + self.check_invalid_constraint('libelf cppflags="-O3"', 'libelf cppflags="-O2"') + + self.check_invalid_constraint('libelf arch=bgqos_0', 'libelf arch=x86_54') def test_constrain_changed(self): @@ -332,7 +380,9 @@ class SpecSematicsTest(MockPackagesTest): self.check_constrain_changed('libelf%gcc', '%gcc@4.5') self.check_constrain_changed('libelf', '+debug') self.check_constrain_changed('libelf', '~debug') - self.check_constrain_changed('libelf', '=bgqos_0') + self.check_constrain_changed('libelf', 'debug=2') + self.check_constrain_changed('libelf', 'cppflags="-O3"') + self.check_constrain_changed('libelf', ' arch=bgqos_0') def test_constrain_not_changed(self): @@ -343,7 +393,9 @@ class SpecSematicsTest(MockPackagesTest): self.check_constrain_not_changed('libelf%gcc@4.5', '%gcc@4.5') self.check_constrain_not_changed('libelf+debug', '+debug') self.check_constrain_not_changed('libelf~debug', '~debug') - self.check_constrain_not_changed('libelf=bgqos_0', '=bgqos_0') + self.check_constrain_not_changed('libelf debug=2', 'debug=2') + self.check_constrain_not_changed('libelf cppflags="-O3"', 'cppflags="-O3"') + self.check_constrain_not_changed('libelf arch=bgqos_0', ' arch=bgqos_0') self.check_constrain_not_changed('libelf^foo', 'libelf^foo') self.check_constrain_not_changed('libelf^foo^bar', 'libelf^foo^bar') @@ -355,7 +407,8 @@ class SpecSematicsTest(MockPackagesTest): self.check_constrain_changed('libelf^foo%gcc', 'libelf^foo%gcc@4.5') self.check_constrain_changed('libelf^foo', 'libelf^foo+debug') self.check_constrain_changed('libelf^foo', 'libelf^foo~debug') - self.check_constrain_changed('libelf^foo', 'libelf^foo=bgqos_0') + self.check_constrain_changed('libelf^foo', 'libelf^foo cppflags="-O3"') + self.check_constrain_changed('libelf^foo', 'libelf^foo arch=bgqos_0') def test_constrain_dependency_not_changed(self): @@ -365,4 +418,6 @@ class SpecSematicsTest(MockPackagesTest): self.check_constrain_not_changed('libelf^foo%gcc@4.5', 'libelf^foo%gcc@4.5') self.check_constrain_not_changed('libelf^foo+debug', 'libelf^foo+debug') self.check_constrain_not_changed('libelf^foo~debug', 'libelf^foo~debug') - self.check_constrain_not_changed('libelf^foo=bgqos_0', 'libelf^foo=bgqos_0') + self.check_constrain_not_changed('libelf^foo cppflags="-O3"', 'libelf^foo cppflags="-O3"') + self.check_constrain_not_changed('libelf^foo arch=bgqos_0', 'libelf^foo arch=bgqos_0') + diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py index 928d111ea9..c4e4c9cdfe 100644 --- a/lib/spack/spack/test/spec_syntax.py +++ b/lib/spack/spack/test/spec_syntax.py @@ -104,6 +104,8 @@ class SpecSyntaxTest(unittest.TestCase): def test_full_specs(self): self.check_parse("mvapich_foo^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4^stackwalker@8.1_1e") + self.check_parse("mvapich_foo^_openmpi@1.2:1.4,1.6%intel@12.1 debug=2~qt_4^stackwalker@8.1_1e") + self.check_parse('mvapich_foo^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags="-O3"+debug~qt_4^stackwalker@8.1_1e') def test_canonicalize(self): self.check_parse( @@ -128,7 +130,10 @@ class SpecSyntaxTest(unittest.TestCase): def test_duplicate_variant(self): self.assertRaises(DuplicateVariantError, self.check_parse, "x@1.2+debug+debug") - self.assertRaises(DuplicateVariantError, self.check_parse, "x ^y@1.2+debug+debug") + self.assertRaises(DuplicateVariantError, self.check_parse, "x ^y@1.2+debug debug=true") + self.assertRaises(DuplicateVariantError, self.check_parse, "x ^y@1.2 debug=false debug=true") + self.assertRaises(DuplicateVariantError, self.check_parse, "x ^y@1.2 debug=false~debug") + def test_duplicate_depdendence(self): self.assertRaises(DuplicateDependencyError, self.check_parse, "x ^y ^y") diff --git a/lib/spack/spack/util/executable.py b/lib/spack/spack/util/executable.py index d2ccfde69b..38b778fa00 100644 --- a/lib/spack/spack/util/executable.py +++ b/lib/spack/spack/util/executable.py @@ -22,10 +22,8 @@ # License along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ############################################################################## -__all__ = ['Executable', 'which', 'ProcessError'] import os -import sys import re import subprocess import inspect @@ -34,9 +32,12 @@ import llnl.util.tty as tty import spack import spack.error +__all__ = ['Executable', 'which', 'ProcessError'] + class Executable(object): """Class representing a program that can be run on the command line.""" + def __init__(self, name): self.exe = name.split(' ') self.returncode = None @@ -44,16 +45,13 @@ class Executable(object): if not self.exe: raise ProcessError("Cannot construct executable for '%s'" % name) - def add_default_arg(self, arg): self.exe.append(arg) - @property def command(self): return ' '.join(self.exe) - def __call__(self, *args, **kwargs): """Run this executable in a subprocess. @@ -105,6 +103,8 @@ class Executable(object): fail_on_error = kwargs.pop("fail_on_error", True) ignore_errors = kwargs.pop("ignore_errors", ()) + env = kwargs.get('env', None) + # TODO: This is deprecated. Remove in a future version. return_output = kwargs.pop("return_output", False) @@ -114,8 +114,8 @@ class Executable(object): else: output = kwargs.pop("output", None) - error = kwargs.pop("error", None) - input = kwargs.pop("input", None) + error = kwargs.pop("error", None) + input = kwargs.pop("input", None) if input is str: raise ValueError("Cannot use `str` as input stream.") @@ -126,85 +126,90 @@ class Executable(object): return subprocess.PIPE, False else: return arg, False + ostream, close_ostream = streamify(output, 'w') - estream, close_estream = streamify(error, 'w') - istream, close_istream = streamify(input, 'r') + estream, close_estream = streamify(error, 'w') + istream, close_istream = streamify(input, 'r') # if they just want to ignore one error code, make it a tuple. if isinstance(ignore_errors, int): - ignore_errors = (ignore_errors,) + ignore_errors = (ignore_errors, ) quoted_args = [arg for arg in args if re.search(r'^"|^\'|"$|\'$', arg)] if quoted_args: - tty.warn("Quotes in command arguments can confuse scripts like configure.", - "The following arguments may cause problems when executed:", - str("\n".join([" "+arg for arg in quoted_args])), - "Quotes aren't needed because spack doesn't use a shell.", - "Consider removing them") + tty.warn( + "Quotes in command arguments can confuse scripts like" + " configure.", + "The following arguments may cause problems when executed:", + str("\n".join([" " + arg for arg in quoted_args])), + "Quotes aren't needed because spack doesn't use a shell.", + "Consider removing them") cmd = self.exe + list(args) - cmd_line = "'%s'" % "' '".join(map(lambda arg: arg.replace("'", "'\"'\"'"), cmd)) + cmd_line = "'%s'" % "' '".join( + map(lambda arg: arg.replace("'", "'\"'\"'"), cmd)) tty.debug(cmd_line) try: proc = subprocess.Popen( - cmd, stdin=istream, stderr=estream, stdout=ostream) + cmd, + stdin=istream, + stderr=estream, + stdout=ostream, + env=env) out, err = proc.communicate() rc = self.returncode = proc.returncode if fail_on_error and rc != 0 and (rc not in ignore_errors): - raise ProcessError("Command exited with status %d:" - % proc.returncode, cmd_line) + raise ProcessError("Command exited with status %d:" % + proc.returncode, cmd_line) if output is str or error is str: result = '' - if output is str: result += out - if error is str: result += err + if output is str: + result += out + if error is str: + result += err return result except OSError, e: raise ProcessError( - "%s: %s" % (self.exe[0], e.strerror), - "Command: " + cmd_line) + "%s: %s" % (self.exe[0], e.strerror), "Command: " + cmd_line) except subprocess.CalledProcessError, e: if fail_on_error: raise ProcessError( - str(e), - "\nExit status %d when invoking command: %s" - % (proc.returncode, cmd_line)) + str(e), "\nExit status %d when invoking command: %s" % + (proc.returncode, cmd_line)) finally: - if close_ostream: output.close() - if close_estream: error.close() - if close_istream: input.close() - + if close_ostream: + output.close() + if close_estream: + error.close() + if close_istream: + input.close() def __eq__(self, other): return self.exe == other.exe - def __neq__(self, other): return not (self == other) - def __hash__(self): - return hash((type(self),) + tuple(self.exe)) - + return hash((type(self), ) + tuple(self.exe)) def __repr__(self): return "<exe: %s>" % self.exe - def __str__(self): return ' '.join(self.exe) - def which(name, **kwargs): """Finds an executable in the path like command-line which.""" - path = kwargs.get('path', os.environ.get('PATH', '').split(os.pathsep)) + path = kwargs.get('path', os.environ.get('PATH', '').split(os.pathsep)) required = kwargs.get('required', False) if not path: @@ -233,14 +238,16 @@ class ProcessError(spack.error.SpackError): @property def long_message(self): msg = self._long_message - if msg: msg += "\n\n" + if msg: + msg += "\n\n" if self.build_log: msg += "See build log for details:\n" msg += " %s" % self.build_log if self.package_context: - if msg: msg += "\n\n" + if msg: + msg += "\n\n" msg += '\n'.join(self.package_context) return msg @@ -267,7 +274,7 @@ def _get_package_context(): frame = f[0] # Find a frame with 'self' in the local variables. - if not 'self' in frame.f_locals: + if 'self' not in frame.f_locals: continue # Look only at a frame in a subclass of spack.Package @@ -280,9 +287,8 @@ def _get_package_context(): # Build a message showing where in install we failed. lines.append("%s:%d, in %s:" % ( - inspect.getfile(frame.f_code), - frame.f_lineno, - frame.f_code.co_name)) + inspect.getfile(frame.f_code), frame.f_lineno, frame.f_code.co_name + )) sourcelines, start = inspect.getsourcelines(frame) for i, line in enumerate(sourcelines): diff --git a/lib/spack/spack/variant.py b/lib/spack/spack/variant.py index 20686d44b2..ad875f5ef5 100644 --- a/lib/spack/spack/variant.py +++ b/lib/spack/spack/variant.py @@ -32,5 +32,5 @@ currently variants are just flags. class Variant(object): """Represents a variant on a build. Can be either on or off.""" def __init__(self, default, description): - self.default = bool(default) + self.default = default self.description = str(description) diff --git a/lib/spack/spack/virtual.py b/lib/spack/spack/virtual.py index 91ad77c8fd..bb8333f023 100644 --- a/lib/spack/spack/virtual.py +++ b/lib/spack/spack/virtual.py @@ -67,10 +67,15 @@ class ProviderIndex(object): if type(spec) != spack.spec.Spec: spec = spack.spec.Spec(spec) + if not spec.name: + # Empty specs do not have a package + return + assert(not spec.virtual) pkg = spec.package for provided_spec, provider_spec in pkg.provided.iteritems(): + provider_spec.compiler_flags = spec.compiler_flags.copy()#We want satisfaction other than flags if provider_spec.satisfies(spec, deps=False): provided_name = provided_spec.name |