From f78475711343a98b1f0e756d6c39df87802c25c8 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 19 May 2014 16:07:42 -0700 Subject: Initial ability to swap compilers. Fixes SPACK-16 and forces compiler script to build using compiler wrappers. - works with gcc and clang on laptop. --- lib/spack/env/cc | 47 ++++++++++++++++--------- lib/spack/spack/build_environment.py | 29 +++++++++++---- lib/spack/spack/cmd/compilers.py | 2 +- lib/spack/spack/cmd/find.py | 2 +- lib/spack/spack/cmd/install.py | 6 +++- lib/spack/spack/cmd/uninstall.py | 19 ++++++---- lib/spack/spack/compiler.py | 10 +++--- lib/spack/spack/compilers/__init__.py | 64 ++++++++++++++++++++++------------ lib/spack/spack/compilers/clang.py | 6 ++-- lib/spack/spack/compilers/gcc.py | 6 ++-- lib/spack/spack/compilers/intel.py | 6 ++-- lib/spack/spack/concretize.py | 24 ++++++++++--- lib/spack/spack/package.py | 14 ++++---- lib/spack/spack/spec.py | 41 ++++++++++++---------- lib/spack/spack/test/spec_semantics.py | 36 +++++++++---------- lib/spack/spack/test/versions.py | 30 +++++++++++++++- lib/spack/spack/util/executable.py | 2 +- 17 files changed, 227 insertions(+), 117 deletions(-) (limited to 'lib') diff --git a/lib/spack/env/cc b/lib/spack/env/cc index e5dbf21beb..09abf3a31d 100755 --- a/lib/spack/env/cc +++ b/lib/spack/env/cc @@ -10,7 +10,7 @@ import argparse from contextlib import closing # Import spack parameters through the build environment. -spack_lib = os.environ.get("SPACK_LIB") +spack_lib = os.environ.get("SPACK_LIB") if not spack_lib: print "Spack compiler must be run from spack!" sys.exit(1) @@ -20,24 +20,23 @@ sys.path.append(spack_lib) from spack.compilation import * import llnl.util.tty as tty -spack_prefix = get_env_var("SPACK_PREFIX") -spack_build_root = get_env_var("SPACK_BUILD_ROOT") -spack_debug = get_env_flag("SPACK_DEBUG") -spack_deps = get_path("SPACK_DEPENDENCIES") -spack_env_path = get_path("SPACK_ENV_PATH") +spack_prefix = get_env_var("SPACK_PREFIX") +spack_debug = get_env_flag("SPACK_DEBUG") +spack_deps = get_path("SPACK_DEPENDENCIES") +spack_env_path = get_path("SPACK_ENV_PATH") +spack_debug_log_dir = get_env_var("SPACK_DEBUG_LOG_DIR") +spack_spec = get_env_var("SPACK_SPEC") + +spack_cc = get_env_var("SPACK_CC") +spack_cxx = get_env_var("SPACK_CXX") +spack_f77 = get_env_var("SPACK_F77") +spack_fc = get_env_var("SPACK_FC") # Figure out what type of operation we're doing command = os.path.basename(sys.argv[0]) cpp, cc, ccld, ld, version_check = range(5) -######################################################################## -# TODO: this can to be removed once JIRA issue SPACK-16 is resolved -# -if command == 'CC': - command = 'c++' -######################################################################## - if command == 'cpp': mode = cpp elif command == 'ld': @@ -49,7 +48,23 @@ elif '-c' in sys.argv: else: mode = ccld -if '-V' in sys.argv or '-v' in sys.argv or '--version' in sys.argv: + +if command in ('cc', 'gcc', 'c89', 'c99', 'clang'): + command = spack_cc +elif command in ('c++', 'CC', 'g++', 'clang++'): + command = spack_cxx +elif command in ('f77'): + command = spack_f77 +elif command in ('fc'): + command = spack_fc +elif command in ('ld', 'cpp'): + pass # leave it the same. TODO: what's the right thing? +else: + raise Exception("Unknown compiler: %s" % command) + + +version_args = ['-V', '-v', '--version', '-dumpversion'] +if any(arg in sys.argv for arg in version_args): mode = version_check # Parse out the includes, libs, etc. so we can adjust them if need be. @@ -104,8 +119,8 @@ os.environ["PATH"] = ":".join(path) full_command = [command] + arguments if spack_debug: - input_log = os.path.join(spack_build_root, 'spack_cc_in.log') - output_log = os.path.join(spack_build_root, 'spack_cc_out.log') + input_log = os.path.join(spack_debug_log_dir, 'spack-cc-%s.in.log' % spack_spec) + output_log = os.path.join(spack_debug_log_dir, 'spack-cc-%s.out.log' % spack_spec) with closing(open(input_log, 'a')) as log: args = [os.path.basename(sys.argv[0])] + sys.argv[1:] log.write("%s\n" % " ".join(arg.replace(' ', r'\ ') for arg in args)) diff --git a/lib/spack/spack/build_environment.py b/lib/spack/spack/build_environment.py index 8d3d0909db..36dae74e84 100644 --- a/lib/spack/spack/build_environment.py +++ b/lib/spack/spack/build_environment.py @@ -34,7 +34,7 @@ import platform from llnl.util.filesystem import * import spack -from spack.compilers import compiler_for_spec +import spack.compilers as compilers from spack.util.executable import Executable, which from spack.util.environment import * @@ -52,7 +52,9 @@ SPACK_LIB = 'SPACK_LIB' SPACK_ENV_PATH = 'SPACK_ENV_PATH' SPACK_DEPENDENCIES = 'SPACK_DEPENDENCIES' SPACK_PREFIX = 'SPACK_PREFIX' -SPACK_BUILD_ROOT = 'SPACK_BUILD_ROOT' +SPACK_DEBUG = 'SPACK_DEBUG' +SPACK_SPEC = 'SPACK_SPEC' +SPACK_DEBUG_LOG_DIR = 'SPACK_DEBUG_LOG_DIR' class MakeExecutable(Executable): @@ -82,7 +84,19 @@ class MakeExecutable(Executable): def set_compiler_environment_variables(pkg): assert(pkg.spec.concrete) - compiler = compiler_for_spec(pkg.spec.compiler) + compiler = compilers.compiler_for_spec(pkg.spec.compiler) + + # Set compiler variables used by CMake and autotools + os.environ['CC'] = 'cc' + os.environ['CXX'] = 'c++' + os.environ['F77'] = 'f77' + os.environ['FC'] = 'fc' + + # Set SPACK compiler variables so that our wrapper knows what to call + os.environ['SPACK_CC'] = compiler.cc.command + os.environ['SPACK_CXX'] = compiler.cxx.command + os.environ['SPACK_F77'] = compiler.f77.command + os.environ['SPACK_FC'] = compiler.fc.command def set_build_environment_variables(pkg): @@ -108,9 +122,6 @@ def set_build_environment_variables(pkg): # Install prefix os.environ[SPACK_PREFIX] = pkg.prefix - # Build root for logging. - os.environ[SPACK_BUILD_ROOT] = pkg.stage.expanded_archive_path - # Remove these vars from the environment during build becaus they # can affect how some packages find libraries. We want to make # sure that builds never pull in unintended external dependencies. @@ -120,6 +131,12 @@ def set_build_environment_variables(pkg): bin_dirs = ['%s/bin' % prefix for prefix in dep_prefixes] path_put_first('PATH', [bin for bin in bin_dirs if os.path.isdir(bin)]) + # Working directory for the spack command itself, for debug logs. + if spack.debug: + os.environ[SPACK_DEBUG] = "TRUE" + os.environ[SPACK_SPEC] = str(pkg.spec) + os.environ[SPACK_DEBUG_LOG_DIR] = spack.spack_working_dir + def set_module_variables_for_package(pkg): """Populate the module scope of install() with some useful functions. diff --git a/lib/spack/spack/cmd/compilers.py b/lib/spack/spack/cmd/compilers.py index 8267ecbd6b..c34118f033 100644 --- a/lib/spack/spack/cmd/compilers.py +++ b/lib/spack/spack/cmd/compilers.py @@ -34,7 +34,7 @@ description = "List available compilers" def compilers(parser, args): tty.msg("Available compilers") - index = index_by(spack.compilers.available_compilers(), 'name') + index = index_by(spack.compilers.all_compilers(), 'name') for name, compilers in index.items(): tty.hline(name, char='-', color=spack.spec.compiler_color) colify(compilers, indent=4) diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py index eaa3632043..08cfb5af96 100644 --- a/lib/spack/spack/cmd/find.py +++ b/lib/spack/spack/cmd/find.py @@ -62,7 +62,7 @@ def find(parser, args): # Make a dict with specs keyed by architecture and compiler. specs = [s for s in spack.db.installed_package_specs() - if not query_specs or any(spec.satisfies(q) for q in query_specs)] + if not query_specs or any(s.satisfies(q) for q in query_specs)] index = index_by(specs, 'architecture', 'compiler') # Traverse the index and print out each package diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py index ea11cb89a9..4570d6c40f 100644 --- a/lib/spack/spack/cmd/install.py +++ b/lib/spack/spack/cmd/install.py @@ -36,7 +36,10 @@ def setup_parser(subparser): help="Do not try to install dependencies of requested packages.") subparser.add_argument( '--keep-prefix', action='store_true', dest='keep_prefix', - help="Don't clean up staging area when install completes.") + help="Don't remove the install prefix if installation fails.") + subparser.add_argument( + '--keep-stage', action='store_true', dest='keep_stage', + help="Don't remove the build stage if installation succeeds.") subparser.add_argument( '-n', '--no-checksum', action='store_true', dest='no_checksum', help="Do not check packages against checksum") @@ -55,4 +58,5 @@ def install(parser, args): for spec in specs: package = spack.db.get(spec) package.do_install(keep_prefix=args.keep_prefix, + keep_stage=args.keep_stage, ignore_deps=args.ignore_deps) diff --git a/lib/spack/spack/cmd/uninstall.py b/lib/spack/spack/cmd/uninstall.py index df208b3a6a..044a9b2960 100644 --- a/lib/spack/spack/cmd/uninstall.py +++ b/lib/spack/spack/cmd/uninstall.py @@ -35,6 +35,11 @@ def setup_parser(subparser): subparser.add_argument( '-f', '--force', action='store_true', dest='force', help="Remove regardless of whether other packages depend on this one.") + subparser.add_argument( + '-a', '--all', action='store_true', dest='all', + help="USE CAREFULLY. Remove ALL installed packages that match each supplied spec. " + + "i.e., if you say uninstall libelf, ALL versions of libelf are uninstalled. " + + "This is both useful and dangerous, like rm -r.") subparser.add_argument( 'packages', nargs=argparse.REMAINDER, help="specs of packages to uninstall") @@ -50,15 +55,17 @@ def uninstall(parser, args): pkgs = [] for spec in specs: matching_specs = spack.db.get_installed(spec) - if len(matching_specs) > 1: - tty.die("%s matches multiple packages. Which one did you mean?" - % spec, *matching_specs) + if not args.all and len(matching_specs) > 1: + tty.die("%s matches multiple packages." % spec, + "You can either:", + " a) Use spack uninstall -a to uninstall ALL matching specs, or", + " b) use a more specific spec.", + "Matching packages:", *(" " + str(s) for s in matching_specs)) - elif len(matching_specs) == 0: + if len(matching_specs) == 0: tty.die("%s does not match any installed packages." % spec) - installed_spec = matching_specs[0] - pkgs.append(spack.db.get(installed_spec)) + pkgs.extend(spack.db.get(s) for s in matching_specs) # Sort packages to be uninstalled by the number of installed dependents # This ensures we do things in the right order diff --git a/lib/spack/spack/compiler.py b/lib/spack/spack/compiler.py index 27effa20c4..3d097b6180 100644 --- a/lib/spack/spack/compiler.py +++ b/lib/spack/spack/compiler.py @@ -16,7 +16,7 @@ def _verify_executables(*paths): class Compiler(object): """This class encapsulates a Spack "compiler", which includes C, - C++, Fortran, and F90 compilers. Subclasses should implement + C++, and Fortran compilers. Subclasses should implement support for specific compilers, their possible names, arguments, and how to identify the particular type of compiler.""" @@ -30,20 +30,20 @@ class Compiler(object): f77_names = [] # Subclasses use possible names of Fortran 90 compiler - f90_names = [] + fc_names = [] # Names of generic arguments used by this compiler arg_version = '-dumpversion' arg_rpath = '-Wl,-rpath,%s' - def __init__(self, cc, cxx, f77, f90): - _verify_executables(cc, cxx, f77, f90) + def __init__(self, cc, cxx, f77, fc): + _verify_executables(cc, cxx, f77, fc) self.cc = Executable(cc) self.cxx = Executable(cxx) self.f77 = Executable(f77) - self.f90 = Executable(f90) + self.fc = Executable(fc) @property @memoized diff --git a/lib/spack/spack/compilers/__init__.py b/lib/spack/spack/compilers/__init__.py index 095e26edb5..a36ea618bc 100644 --- a/lib/spack/spack/compilers/__init__.py +++ b/lib/spack/spack/compilers/__init__.py @@ -41,49 +41,69 @@ _imported_compilers_module = 'spack.compiler.versions' _imported_versions_module = 'spack.compilers' +def _auto_compiler_spec(function): + def converter(cspec_like): + if not isinstance(cspec_like, spack.spec.CompilerSpec): + cspec_like = spack.spec.CompilerSpec(cspec_like) + return function(cspec_like) + return converter + + @memoized def supported_compilers(): - """Return a list of names of compilers supported by Spack. + """Return a set of names of compilers supported by Spack. See available_compilers() to get a list of all the available versions of supported compilers. """ - return sorted(c for c in list_modules(spack.compilers_path)) + return sorted(name for name in list_modules(spack.compilers_path)) +@_auto_compiler_spec def supported(compiler_spec): """Test if a particular compiler is supported.""" - if not isinstance(compiler_spec, spack.spec.CompilerSpec): - compiler_spec = spack.spec.CompilerSpec(compiler_spec) return compiler_spec.name in supported_compilers() -def available_compilers(): - """Return a list of specs for all the compiler versions currently - available to build with. These are instances of - CompilerSpec. +@memoized +def all_compilers(): + """Return a set of specs for all the compiler versions currently + available to build with. These are instances of CompilerSpec. """ - return [spack.spec.CompilerSpec(c) - for c in list_modules(spack.compiler_version_path)] + return set(spack.spec.CompilerSpec(c) + for c in list_modules(spack.compiler_version_path)) -def compiler_for_spec(compiler_spec): - """This gets an instance of an actual spack.compiler.Compiler object - from a compiler spec. The spec needs to be concrete for this to - work; it will raise an error if passed an abstract compiler. +@_auto_compiler_spec +def find(compiler_spec): + """Return specs of available compilers that match the supplied + compiler spec. Return an list if nothing found.""" + return [c for c in all_compilers() if c.satisfies(compiler_spec)] + + +@_auto_compiler_spec +def compilers_for_spec(compiler_spec): + """This gets all compilers that satisfy the supplied CompilerSpec. + Returns an empty list if none are found. """ - matches = [c for c in available_compilers() if c.satisfies(compiler_spec)] + matches = find(compiler_spec) - # TODO: do something when there are zero matches. - assert(len(matches) >= 1) + compilers = [] + for cspec in matches: + path = join_path(spack.compiler_version_path, "%s.py" % cspec) + mod = imp.load_source(_imported_versions_module, path) + cls = class_for_compiler_name(cspec.name) + compilers.append(cls(mod.cc, mod.cxx, mod.f77, mod.fc)) - compiler = matches[0] - file_path = join_path(spack.compiler_version_path, "%s.py" % compiler) + return compilers - mod = imp.load_source(_imported_versions_module, file_path) - compiler_class = class_for_compiler_name(compiler.name) - return compiler_class(mod.cc, mod.cxx, mod.f77, mod.f90) +@_auto_compiler_spec +def compiler_for_spec(compiler_spec): + assert(compiler_spec.concrete) + compilers = compilers_for_spec(compiler_spec) + assert(len(compilers) == 1) + return compilers[0] def class_for_compiler_name(compiler_name): diff --git a/lib/spack/spack/compilers/clang.py b/lib/spack/spack/compilers/clang.py index 9ed7b57846..1616fcaf08 100644 --- a/lib/spack/spack/compilers/clang.py +++ b/lib/spack/spack/compilers/clang.py @@ -35,7 +35,7 @@ class Clang(Compiler): f77_names = [] # Subclasses use possible names of Fortran 90 compiler - f90_names = [] + fc_names = [] - def __init__(self, cc, cxx, f77, f90): - super(Gcc, self).__init__(cc, cxx, f77, f90) + def __init__(self, cc, cxx, f77, fc): + super(Clang, self).__init__(cc, cxx, f77, fc) diff --git a/lib/spack/spack/compilers/gcc.py b/lib/spack/spack/compilers/gcc.py index 638051008f..f73cb08c63 100644 --- a/lib/spack/spack/compilers/gcc.py +++ b/lib/spack/spack/compilers/gcc.py @@ -35,7 +35,7 @@ class Gcc(Compiler): f77_names = ['gfortran'] # Subclasses use possible names of Fortran 90 compiler - f90_names = ['gfortran'] + fc_names = ['gfortran'] - def __init__(self, cc, cxx, f77, f90): - super(Gcc, self).__init__(cc, cxx, f77, f90) + def __init__(self, cc, cxx, f77, fc): + super(Gcc, self).__init__(cc, cxx, f77, fc) diff --git a/lib/spack/spack/compilers/intel.py b/lib/spack/spack/compilers/intel.py index ebbab57ed1..fe2aabd864 100644 --- a/lib/spack/spack/compilers/intel.py +++ b/lib/spack/spack/compilers/intel.py @@ -35,7 +35,7 @@ class Intel(Compiler): f77_names = ['ifort'] # Subclasses use possible names of Fortran 90 compiler - f90_names = ['ifort'] + fc_names = ['ifort'] - def __init__(self, cc, cxx, f77, f90): - super(Gcc, self).__init__(cc, cxx, f77, f90) + def __init__(self, cc, cxx, f77, fc): + super(Intel, self).__init__(cc, cxx, f77, fc) diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py index 28efcaea64..312b9ce1b1 100644 --- a/lib/spack/spack/concretize.py +++ b/lib/spack/spack/concretize.py @@ -33,9 +33,10 @@ or user preferences. TODO: make this customizable and allow users to configure concretization policies. """ -import spack.architecture -import spack.compilers import spack.spec +import spack.compilers +import spack.architecture +import spack.error from spack.version import * @@ -117,9 +118,13 @@ class DefaultConcretizer(object): if p.compiler is not None).compiler if not nearest.concrete: - matches = [c for c in spack.compilers.available_compilers() - if c.name == nearest.name] - nearest.versions = sorted(matches)[-1].versions.copy() + # Take the newest compiler that saisfies the spec + matches = sorted(spack.compilers.find(nearest)) + if not matches: + raise UnavailableCompilerVersionError(nearest) + + # copy concrete version into nearest spec + nearest.versions = matches[-1].versions.copy() assert(nearest.concrete) spec.compiler = nearest.copy() @@ -140,3 +145,12 @@ class DefaultConcretizer(object): first_key = sorted(index.keys())[0] latest_version = sorted(index[first_key])[-1] return latest_version + + +class UnavailableCompilerVersionError(spack.error.SpackError): + """Raised when there is no available compiler that satisfies a + compiler spec.""" + def __init__(self, compiler_spec): + super(UnavailableCompilerVersionError, self).__init__( + "No available compiler version matches '%s'" % compiler_spec, + "Run 'spack compilers' to see available compiler Options.") diff --git a/lib/spack/spack/package.py b/lib/spack/spack/package.py index b40448df37..069c66f4a7 100644 --- a/lib/spack/spack/package.py +++ b/lib/spack/spack/package.py @@ -626,6 +626,7 @@ class Package(object): """ # whether to keep the prefix on failure. Default is to destroy it. keep_prefix = kwargs.get('keep_prefix', False) + keep_stage = kwargs.get('keep_stage', False) ignore_deps = kwargs.get('ignore_deps', False) if not self.spec.concrete: @@ -671,9 +672,10 @@ class Package(object): "Install failed for %s. Nothing was installed!" % self.name) - # On successful install, remove the stage. - # Leave if there is an error - self.stage.destroy() + if not keep_stage: + # On successful install, remove the stage. + # Leave it if there is an error + self.stage.destroy() tty.msg("Successfully installed %s" % self.name) print_pkg(self.prefix) @@ -725,16 +727,16 @@ class Package(object): force = kwargs.get('force', False) if not self.installed: - raise InstallError(self.name + " is not installed.") + raise InstallError(self.spec + " is not installed.") if not force: deps = self.installed_dependents if deps: raise InstallError( "Cannot uninstall %s. The following installed packages depend on it: %s" - % (self.name, deps)) + % (self.spec, deps)) self.remove_prefix() - tty.msg("Successfully uninstalled %s." % self.name) + tty.msg("Successfully uninstalled %s." % self.spec) def do_clean(self): diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 35a17621b6..5848ac2000 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -102,7 +102,7 @@ from llnl.util.tty.color import * import spack import spack.parse import spack.error -from spack.compilers import supported as supported_compiler +import spack.compilers as compilers from spack.version import * from spack.util.string import * @@ -231,8 +231,9 @@ class CompilerSpec(object): @property def concrete(self): - """A CompilerSpec is concrete if its versions are concrete.""" - return self.versions.concrete + """A CompilerSpec is concrete if its versions are concrete and there + is an available compiler with the right version.""" + return self.versions.concrete and self in compilers.all_compilers() @property @@ -260,6 +261,9 @@ class CompilerSpec(object): out += "@%s" % vlist return out + def __repr__(self): + return str(self) + @key_ordering class Variant(object): @@ -821,12 +825,13 @@ class Spec(object): # validate compiler in addition to the package name. if spec.compiler: - if not supported_compiler(spec.compiler): + if not compilers.supported(spec.compiler): raise UnsupportedCompilerError(spec.compiler.name) def constrain(self, other, **kwargs): other = self._autospec(other) + constrain_deps = kwargs.get('deps', True) if not self.name == other.name: raise UnsatisfiableSpecNameError(self.name, other.name) @@ -854,7 +859,7 @@ class Spec(object): self.variants.update(other.variants) self.architecture = self.architecture or other.architecture - if kwargs.get('deps', True): + if constrain_deps: self._constrain_dependencies(other) @@ -911,28 +916,28 @@ class Spec(object): def satisfies(self, other, **kwargs): other = self._autospec(other) + satisfy_deps = kwargs.get('deps', True) # First thing we care about is whether the name matches if self.name != other.name: return False - # This function simplifies null checking below - def check(attribute, op): - s = getattr(self, attribute) - o = getattr(other, attribute) - return not s or not o or op(s,o) - - # All these attrs have satisfies criteria of their own - for attr in ('versions', 'variants', 'compiler'): - if not check(attr, lambda s, o: s.satisfies(o)): + # All these attrs have satisfies criteria of their own, + # but can be None to indicate no constraints. + for s, o in ((self.versions, other.versions), + (self.variants, other.variants), + (self.compiler, other.compiler)): + if s and o and not s.satisfies(o): return False - # Architecture is just a string - # TODO: inviestigate making an Architecture class for symmetry - if not check('architecture', lambda s,o: s == o): + # Architecture satisfaction is currently just string equality. + # Can be None for unconstrained, though. + if (self.architecture and other.architecture and + self.architecture != other.architecture): return False - if kwargs.get('deps', True): + # If we need to descend into dependencies, do it, otherwise we're done. + if satisfy_deps: return self.satisfies_dependencies(other) else: return True diff --git a/lib/spack/spack/test/spec_semantics.py b/lib/spack/spack/test/spec_semantics.py index dbd04f9449..5fb09e68af 100644 --- a/lib/spack/spack/test/spec_semantics.py +++ b/lib/spack/spack/test/spec_semantics.py @@ -37,16 +37,13 @@ class SpecSematicsTest(MockPackagesTest): left = Spec(spec) right = parse_anonymous_spec(anon_spec, left.name) + # Satisfies is one-directional. self.assertTrue(left.satisfies(right)) self.assertTrue(left.satisfies(anon_spec)) - self.assertTrue(right.satisfies(left)) - try: - left.copy().constrain(right) - left.copy().constrain(anon_spec) - right.copy().constrain(left) - except SpecError, e: - self.fail("Got a SpecError in constrain! " + e.message) + # if left satisfies right, then we should be able to consrain + # right by left. Reverse is not always true. + right.copy().constrain(left) def check_unsatisfiable(self, spec, anon_spec): @@ -56,25 +53,21 @@ class SpecSematicsTest(MockPackagesTest): self.assertFalse(left.satisfies(right)) self.assertFalse(left.satisfies(anon_spec)) - self.assertFalse(right.satisfies(left)) + self.assertRaises(UnsatisfiableSpecError, right.copy().constrain, left) - self.assertRaises(UnsatisfiableSpecError, left.constrain, right) - self.assertRaises(UnsatisfiableSpecError, left.constrain, anon_spec) - self.assertRaises(UnsatisfiableSpecError, right.constrain, left) - - def check_constrain(self, expected, constrained, constraint): + def check_constrain(self, expected, spec, constraint): exp = Spec(expected) - constrained = Spec(constrained) + spec = Spec(spec) constraint = Spec(constraint) - constrained.constrain(constraint) - self.assertEqual(exp, constrained) + spec.constrain(constraint) + self.assertEqual(exp, spec) - def check_invalid_constraint(self, constrained, constraint): - constrained = Spec(constrained) + def check_invalid_constraint(self, spec, constraint): + spec = Spec(spec) constraint = Spec(constraint) - self.assertRaises(UnsatisfiableSpecError, constrained.constrain, constraint) + self.assertRaises(UnsatisfiableSpecError, spec.constrain, constraint) # ================================================================================ @@ -177,3 +170,8 @@ class SpecSematicsTest(MockPackagesTest): self.check_invalid_constraint('libelf+debug~foo', 'libelf+debug+foo') self.check_invalid_constraint('libelf=bgqos_0', 'libelf=x86_54') + + + def test_compiler_satisfies(self): + self.check_satisfies('foo %gcc@4.7.3', '%gcc@4.7') + self.check_unsatisfiable('foo %gcc@4.7', '%gcc@4.7.3') diff --git a/lib/spack/spack/test/versions.py b/lib/spack/spack/test/versions.py index e272274a4f..454ab36b8a 100644 --- a/lib/spack/spack/test/versions.py +++ b/lib/spack/spack/test/versions.py @@ -311,7 +311,7 @@ class VersionsTest(unittest.TestCase): self.check_intersection(['0:1'], [':'], ['0:1']) - def test_satisfaction(self): + def test_basic_version_satisfaction(self): self.assert_satisfies('4.7.3', '4.7.3') self.assert_satisfies('4.7.3', '4.7') @@ -326,6 +326,22 @@ class VersionsTest(unittest.TestCase): self.assert_does_not_satisfy('4.8', '4.9') self.assert_does_not_satisfy('4', '4.9') + def test_basic_version_satisfaction_in_lists(self): + self.assert_satisfies(['4.7.3'], ['4.7.3']) + + self.assert_satisfies(['4.7.3'], ['4.7']) + self.assert_satisfies(['4.7.3b2'], ['4.7']) + self.assert_satisfies(['4.7b6'], ['4.7']) + + self.assert_satisfies(['4.7.3'], ['4']) + self.assert_satisfies(['4.7.3b2'], ['4']) + self.assert_satisfies(['4.7b6'], ['4']) + + self.assert_does_not_satisfy(['4.8.0'], ['4.9']) + self.assert_does_not_satisfy(['4.8'], ['4.9']) + self.assert_does_not_satisfy(['4'], ['4.9']) + + def test_version_range_satisfaction(self): self.assert_satisfies('4.7b6', '4.3:4.7') self.assert_satisfies('4.3.0', '4.3:4.7') self.assert_satisfies('4.3.2', '4.3:4.7') @@ -336,6 +352,18 @@ class VersionsTest(unittest.TestCase): self.assert_satisfies('4.7b6', '4.3:4.7') self.assert_does_not_satisfy('4.8.0', '4.3:4.7') + def test_version_range_satisfaction_in_lists(self): + self.assert_satisfies(['4.7b6'], ['4.3:4.7']) + self.assert_satisfies(['4.3.0'], ['4.3:4.7']) + self.assert_satisfies(['4.3.2'], ['4.3:4.7']) + + self.assert_does_not_satisfy(['4.8.0'], ['4.3:4.7']) + self.assert_does_not_satisfy(['4.3'], ['4.4:4.7']) + + self.assert_satisfies(['4.7b6'], ['4.3:4.7']) + self.assert_does_not_satisfy(['4.8.0'], ['4.3:4.7']) + + def test_satisfaction_with_lists(self): self.assert_satisfies('4.7', '4.3, 4.6, 4.7') self.assert_satisfies('4.7.3', '4.3, 4.6, 4.7') self.assert_satisfies('4.6.5', '4.3, 4.6, 4.7') diff --git a/lib/spack/spack/util/executable.py b/lib/spack/spack/util/executable.py index 845d1470e7..6dea46a5c8 100644 --- a/lib/spack/spack/util/executable.py +++ b/lib/spack/spack/util/executable.py @@ -43,7 +43,7 @@ class Executable(object): @property def command(self): - return self.exe[0] + return ' '.join(self.exe) def __call__(self, *args, **kwargs): -- cgit v1.2.3-70-g09d2