From b9ec69dce15c69ba11f6d637614399b3a3a470e5 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Sat, 31 Dec 2016 08:12:38 -0800 Subject: Disallow vdeps in `packages.yaml` (#2699) * Consolidate packages.yaml code to preferred_packages * Add validation check and a test for packages.py parsing. * flake8 --- lib/spack/spack/__init__.py | 7 +- lib/spack/spack/concretize.py | 50 +--- lib/spack/spack/config.py | 49 +--- lib/spack/spack/package_prefs.py | 314 +++++++++++++++++++++++++ lib/spack/spack/preferred_packages.py | 196 --------------- lib/spack/spack/repository.py | 12 + lib/spack/spack/spec.py | 10 +- lib/spack/spack/test/concretize_preferences.py | 33 ++- 8 files changed, 372 insertions(+), 299 deletions(-) create mode 100644 lib/spack/spack/package_prefs.py delete mode 100644 lib/spack/spack/preferred_packages.py (limited to 'lib') diff --git a/lib/spack/spack/__init__.py b/lib/spack/spack/__init__.py index 39a0b78f82..771fc7b32a 100644 --- a/lib/spack/spack/__init__.py +++ b/lib/spack/spack/__init__.py @@ -77,7 +77,7 @@ import spack.error import spack.config import spack.fetch_strategy from spack.file_cache import FileCache -from spack.preferred_packages import PreferredPackages +from spack.package_prefs import PreferredPackages from spack.abi import ABI from spack.concretize import DefaultConcretizer from spack.version import Version @@ -99,11 +99,6 @@ except spack.error.SpackError, e: tty.die('while initializing Spack RepoPath:', e.message) -# PreferredPackages controls preference sort order during concretization. -# More preferred packages are sorted first. -pkgsort = PreferredPackages() - - # Tests ABI compatibility between packages abi = ABI() diff --git a/lib/spack/spack/concretize.py b/lib/spack/spack/concretize.py index 4690691174..36e8b30196 100644 --- a/lib/spack/spack/concretize.py +++ b/lib/spack/spack/concretize.py @@ -42,8 +42,7 @@ import spack.error from spack.version import * from functools import partial from itertools import chain -from spack.config import * -import spack.preferred_packages +from spack.package_prefs import * class DefaultConcretizer(object): @@ -64,11 +63,11 @@ class DefaultConcretizer(object): raise UnsatisfiableProviderSpecError(providers[0], spec) spec_w_preferred_providers = find_spec( spec, - lambda x: spack.pkgsort.spec_has_preferred_provider( + lambda x: pkgsort().spec_has_preferred_provider( x.name, spec.name)) if not spec_w_preferred_providers: spec_w_preferred_providers = spec - provider_cmp = partial(spack.pkgsort.provider_compare, + provider_cmp = partial(pkgsort().provider_compare, spec_w_preferred_providers.name, spec.name) candidates = sorted(providers, cmp=provider_cmp) @@ -169,8 +168,8 @@ class DefaultConcretizer(object): # ---------- Produce prioritized list of versions # Get list of preferences from packages.yaml - preferred = spack.pkgsort - # NOTE: spack.pkgsort == spack.preferred_packages.PreferredPackages() + preferred = pkgsort() + # NOTE: pkgsort() == spack.package_prefs.PreferredPackages() yaml_specs = [ x[0] for x in @@ -277,7 +276,7 @@ class DefaultConcretizer(object): the package specification. """ changed = False - preferred_variants = spack.pkgsort.spec_preferred_variants( + preferred_variants = pkgsort().spec_preferred_variants( spec.package_class.name) for name, variant in spec.package_class.variants.items(): if name not in spec.variants: @@ -342,7 +341,7 @@ class DefaultConcretizer(object): compiler_list = all_compilers if not other_compiler else \ spack.compilers.find(other_compiler) cmp_compilers = partial( - spack.pkgsort.compiler_compare, other_spec.name) + pkgsort().compiler_compare, other_spec.name) matches = sorted(compiler_list, cmp=cmp_compilers) if not matches: arch = spec.architecture @@ -467,41 +466,6 @@ def find_spec(spec, condition): return None # Nothing matched the condition. -def cmp_specs(lhs, rhs): - # Package name sort order is not configurable, always goes alphabetical - if lhs.name != rhs.name: - return cmp(lhs.name, rhs.name) - - # Package version is second in compare order - pkgname = lhs.name - if lhs.versions != rhs.versions: - return spack.pkgsort.version_compare( - pkgname, lhs.versions, rhs.versions) - - # Compiler is third - if lhs.compiler != rhs.compiler: - return spack.pkgsort.compiler_compare( - pkgname, lhs.compiler, rhs.compiler) - - # Variants - if lhs.variants != rhs.variants: - return spack.pkgsort.variant_compare( - pkgname, lhs.variants, rhs.variants) - - # Architecture - if lhs.architecture != rhs.architecture: - return spack.pkgsort.architecture_compare( - pkgname, lhs.architecture, rhs.architecture) - - # Dependency is not configurable - lhash, rhash = hash(lhs), hash(rhs) - if lhash != rhash: - return -1 if lhash < rhash else 1 - - # Equal specs - return 0 - - class UnavailableCompilerVersionError(spack.error.SpackError): """Raised when there is no available compiler that satisfies a diff --git a/lib/spack/spack/config.py b/lib/spack/spack/config.py index 989b3da169..56c6421457 100644 --- a/lib/spack/spack/config.py +++ b/lib/spack/spack/config.py @@ -199,6 +199,7 @@ class ConfigScope(object): def __repr__(self): return '' % (self.name, self.path) + # # Below are configuration scopes. # @@ -458,54 +459,6 @@ def print_section(section): raise ConfigError("Error reading configuration: %s" % section) -def spec_externals(spec): - """Return a list of external specs (with external directory path filled in), - one for each known external installation.""" - # break circular import. - from spack.build_environment import get_path_from_module - - allpkgs = get_config('packages') - name = spec.name - - external_specs = [] - pkg_paths = allpkgs.get(name, {}).get('paths', None) - pkg_modules = allpkgs.get(name, {}).get('modules', None) - if (not pkg_paths) and (not pkg_modules): - return [] - - for external_spec, path in pkg_paths.iteritems(): - if not path: - # skip entries without paths (avoid creating extra Specs) - continue - - external_spec = spack.spec.Spec(external_spec, external=path) - if external_spec.satisfies(spec): - external_specs.append(external_spec) - - for external_spec, module in pkg_modules.iteritems(): - if not module: - continue - - path = get_path_from_module(module) - - external_spec = spack.spec.Spec( - external_spec, external=path, external_module=module) - if external_spec.satisfies(spec): - external_specs.append(external_spec) - - return external_specs - - -def is_spec_buildable(spec): - """Return true if the spec pkgspec is configured as buildable""" - allpkgs = get_config('packages') - if spec.name not in allpkgs: - return True - if 'buildable' not in allpkgs[spec.name]: - return True - return allpkgs[spec.name]['buildable'] - - class ConfigError(SpackError): pass diff --git a/lib/spack/spack/package_prefs.py b/lib/spack/spack/package_prefs.py new file mode 100644 index 0000000000..190647bb81 --- /dev/null +++ b/lib/spack/spack/package_prefs.py @@ -0,0 +1,314 @@ +############################################################################## +# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. +# Produced at the Lawrence Livermore National Laboratory. +# +# This file is part of Spack. +# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. +# LLNL-CODE-647188 +# +# For details, see https://github.com/llnl/spack +# Please also see the LICENSE file for our notice and the LGPL. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License (as +# published by the Free Software Foundation) version 2.1, February 1999. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and +# conditions of the GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## + +import spack +import spack.error +from spack.version import * + + +def get_packages_config(): + """Wrapper around get_packages_config() to validate semantics.""" + config = spack.config.get_config('packages') + + # Get a list of virtuals from packages.yaml. Note that because we + # check spack.repo, this collects virtuals that are actually provided + # by sometihng, not just packages/names that don't exist. + # So, this won't include, e.g., 'all'. + virtuals = [(pkg_name, pkg_name._start_mark) for pkg_name in config + if spack.repo.is_virtual(pkg_name)] + + # die if there are virtuals in `packages.py` + if virtuals: + errors = ["%s: %s" % (line_info, name) for name, line_info in virtuals] + raise VirtualInPackagesYAMLError( + "packages.yaml entries cannot be virtual packages:", *errors) + + return config + + +class PreferredPackages(object): + def __init__(self): + self.preferred = get_packages_config() + self._spec_for_pkgname_cache = {} + + # Given a package name, sort component (e.g, version, compiler, ...), and + # a second_key (used by providers), return the list + def _order_for_package(self, pkgname, component, second_key, + test_all=True): + pkglist = [pkgname] + if test_all: + pkglist.append('all') + for pkg in pkglist: + order = self.preferred.get(pkg, {}).get(component, {}) + if isinstance(order, dict) and second_key: + order = order.get(second_key, {}) + if not order: + continue + return [str(s).strip() for s in order] + return [] + + # A generic sorting function. Given a package name and sort + # component, return less-than-0, 0, or greater-than-0 if + # a is respectively less-than, equal to, or greater than b. + def _component_compare(self, pkgname, component, a, b, + reverse_natural_compare, second_key): + if a is None: + return -1 + if b is None: + return 1 + orderlist = self._order_for_package(pkgname, component, second_key) + a_in_list = str(a) in orderlist + b_in_list = str(b) in orderlist + if a_in_list and not b_in_list: + return -1 + elif b_in_list and not a_in_list: + return 1 + + cmp_a = None + cmp_b = None + reverse = None + if not a_in_list and not b_in_list: + cmp_a = a + cmp_b = b + reverse = -1 if reverse_natural_compare else 1 + else: + cmp_a = orderlist.index(str(a)) + cmp_b = orderlist.index(str(b)) + reverse = 1 + + if cmp_a < cmp_b: + return -1 * reverse + elif cmp_a > cmp_b: + return 1 * reverse + else: + return 0 + + # A sorting function for specs. Similar to component_compare, but + # a and b are considered to match entries in the sorting list if they + # satisfy the list component. + def _spec_compare(self, pkgname, component, a, b, + reverse_natural_compare, second_key): + if not a or (not a.concrete and not second_key): + return -1 + if not b or (not b.concrete and not second_key): + return 1 + specs = self._spec_for_pkgname(pkgname, component, second_key) + a_index = None + b_index = None + reverse = -1 if reverse_natural_compare else 1 + for i, cspec in enumerate(specs): + if a_index is None and (cspec.satisfies(a) or a.satisfies(cspec)): + a_index = i + if b_index: + break + if b_index is None and (cspec.satisfies(b) or b.satisfies(cspec)): + b_index = i + if a_index: + break + + if a_index is not None and b_index is None: + return -1 + elif a_index is None and b_index is not None: + return 1 + elif a_index is not None and b_index == a_index: + return -1 * cmp(a, b) + elif (a_index is not None and b_index is not None and + a_index != b_index): + return cmp(a_index, b_index) + else: + return cmp(a, b) * reverse + + # Given a sort order specified by the pkgname/component/second_key, return + # a list of CompilerSpecs, VersionLists, or Specs for that sorting list. + def _spec_for_pkgname(self, pkgname, component, second_key): + key = (pkgname, component, second_key) + if key not in self._spec_for_pkgname_cache: + pkglist = self._order_for_package(pkgname, component, second_key) + if component == 'compiler': + self._spec_for_pkgname_cache[key] = \ + [spack.spec.CompilerSpec(s) for s in pkglist] + elif component == 'version': + self._spec_for_pkgname_cache[key] = \ + [VersionList(s) for s in pkglist] + else: + self._spec_for_pkgname_cache[key] = \ + [spack.spec.Spec(s) for s in pkglist] + return self._spec_for_pkgname_cache[key] + + def provider_compare(self, pkgname, provider_str, a, b): + """Return less-than-0, 0, or greater than 0 if a is respecively + less-than, equal-to, or greater-than b. A and b are possible + implementations of provider_str. One provider is less-than another + if it is preferred over the other. For example, + provider_compare('scorep', 'mpi', 'mvapich', 'openmpi') would + return -1 if mvapich should be preferred over openmpi for scorep.""" + return self._spec_compare(pkgname, 'providers', a, b, False, + provider_str) + + def spec_has_preferred_provider(self, pkgname, provider_str): + """Return True iff the named package has a list of preferred + providers""" + return bool(self._order_for_package(pkgname, 'providers', + provider_str, False)) + + def spec_preferred_variants(self, pkgname): + """Return a VariantMap of preferred variants and their values""" + for pkg in (pkgname, 'all'): + variants = self.preferred.get(pkg, {}).get('variants', '') + if variants: + break + if not isinstance(variants, basestring): + variants = " ".join(variants) + pkg = spack.repo.get(pkgname) + spec = spack.spec.Spec("%s %s" % (pkgname, variants)) + # Only return variants that are actually supported by the package + return dict((name, variant) for name, variant in spec.variants.items() + if name in pkg.variants) + + def version_compare(self, pkgname, a, b): + """Return less-than-0, 0, or greater than 0 if version a of pkgname is + respectively less-than, equal-to, or greater-than version b of + pkgname. One version is less-than another if it is preferred over + the other.""" + return self._spec_compare(pkgname, 'version', a, b, True, None) + + def variant_compare(self, pkgname, a, b): + """Return less-than-0, 0, or greater than 0 if variant a of pkgname is + respectively less-than, equal-to, or greater-than variant b of + pkgname. One variant is less-than another if it is preferred over + the other.""" + return self._component_compare(pkgname, 'variant', a, b, False, None) + + def architecture_compare(self, pkgname, a, b): + """Return less-than-0, 0, or greater than 0 if architecture a of pkgname + is respectively less-than, equal-to, or greater-than architecture b + of pkgname. One architecture is less-than another if it is preferred + over the other.""" + return self._component_compare(pkgname, 'architecture', a, b, + False, None) + + def compiler_compare(self, pkgname, a, b): + """Return less-than-0, 0, or greater than 0 if compiler a of pkgname is + respecively less-than, equal-to, or greater-than compiler b of + pkgname. One compiler is less-than another if it is preferred over + the other.""" + return self._spec_compare(pkgname, 'compiler', a, b, False, None) + + +def spec_externals(spec): + """Return a list of external specs (with external directory path filled in), + one for each known external installation.""" + # break circular import. + from spack.build_environment import get_path_from_module + + allpkgs = get_packages_config() + name = spec.name + + external_specs = [] + pkg_paths = allpkgs.get(name, {}).get('paths', None) + pkg_modules = allpkgs.get(name, {}).get('modules', None) + if (not pkg_paths) and (not pkg_modules): + return [] + + for external_spec, path in pkg_paths.iteritems(): + if not path: + # skip entries without paths (avoid creating extra Specs) + continue + + external_spec = spack.spec.Spec(external_spec, external=path) + if external_spec.satisfies(spec): + external_specs.append(external_spec) + + for external_spec, module in pkg_modules.iteritems(): + if not module: + continue + + path = get_path_from_module(module) + + external_spec = spack.spec.Spec( + external_spec, external=path, external_module=module) + if external_spec.satisfies(spec): + external_specs.append(external_spec) + + return external_specs + + +def is_spec_buildable(spec): + """Return true if the spec pkgspec is configured as buildable""" + allpkgs = get_packages_config() + if spec.name not in allpkgs: + return True + if 'buildable' not in allpkgs[spec.name]: + return True + return allpkgs[spec.name]['buildable'] + + +def cmp_specs(lhs, rhs): + # Package name sort order is not configurable, always goes alphabetical + if lhs.name != rhs.name: + return cmp(lhs.name, rhs.name) + + # Package version is second in compare order + pkgname = lhs.name + if lhs.versions != rhs.versions: + return pkgsort().version_compare( + pkgname, lhs.versions, rhs.versions) + + # Compiler is third + if lhs.compiler != rhs.compiler: + return pkgsort().compiler_compare( + pkgname, lhs.compiler, rhs.compiler) + + # Variants + if lhs.variants != rhs.variants: + return pkgsort().variant_compare( + pkgname, lhs.variants, rhs.variants) + + # Architecture + if lhs.architecture != rhs.architecture: + return pkgsort().architecture_compare( + pkgname, lhs.architecture, rhs.architecture) + + # Dependency is not configurable + lhash, rhash = hash(lhs), hash(rhs) + if lhash != rhash: + return -1 if lhash < rhash else 1 + + # Equal specs + return 0 + + +_pkgsort = None + + +def pkgsort(): + global _pkgsort + if _pkgsort is None: + _pkgsort = PreferredPackages() + return _pkgsort + + +class VirtualInPackagesYAMLError(spack.error.SpackError): + """Raised when a disallowed virtual is found in packages.yaml""" diff --git a/lib/spack/spack/preferred_packages.py b/lib/spack/spack/preferred_packages.py deleted file mode 100644 index dc6cb3e921..0000000000 --- a/lib/spack/spack/preferred_packages.py +++ /dev/null @@ -1,196 +0,0 @@ -############################################################################## -# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. -# Produced at the Lawrence Livermore National Laboratory. -# -# This file is part of Spack. -# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. -# LLNL-CODE-647188 -# -# For details, see https://github.com/llnl/spack -# Please also see the LICENSE file for our notice and the LGPL. -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License (as -# published by the Free Software Foundation) version 2.1, February 1999. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and -# conditions of the GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -############################################################################## - -import spack -from spack.version import * - - -class PreferredPackages(object): - def __init__(self): - self.preferred = spack.config.get_config('packages') - self._spec_for_pkgname_cache = {} - - # Given a package name, sort component (e.g, version, compiler, ...), and - # a second_key (used by providers), return the list - def _order_for_package(self, pkgname, component, second_key, - test_all=True): - pkglist = [pkgname] - if test_all: - pkglist.append('all') - for pkg in pkglist: - order = self.preferred.get(pkg, {}).get(component, {}) - if isinstance(order, dict) and second_key: - order = order.get(second_key, {}) - if not order: - continue - return [str(s).strip() for s in order] - return [] - - # A generic sorting function. Given a package name and sort - # component, return less-than-0, 0, or greater-than-0 if - # a is respectively less-than, equal to, or greater than b. - def _component_compare(self, pkgname, component, a, b, - reverse_natural_compare, second_key): - if a is None: - return -1 - if b is None: - return 1 - orderlist = self._order_for_package(pkgname, component, second_key) - a_in_list = str(a) in orderlist - b_in_list = str(b) in orderlist - if a_in_list and not b_in_list: - return -1 - elif b_in_list and not a_in_list: - return 1 - - cmp_a = None - cmp_b = None - reverse = None - if not a_in_list and not b_in_list: - cmp_a = a - cmp_b = b - reverse = -1 if reverse_natural_compare else 1 - else: - cmp_a = orderlist.index(str(a)) - cmp_b = orderlist.index(str(b)) - reverse = 1 - - if cmp_a < cmp_b: - return -1 * reverse - elif cmp_a > cmp_b: - return 1 * reverse - else: - return 0 - - # A sorting function for specs. Similar to component_compare, but - # a and b are considered to match entries in the sorting list if they - # satisfy the list component. - def _spec_compare(self, pkgname, component, a, b, - reverse_natural_compare, second_key): - if not a or (not a.concrete and not second_key): - return -1 - if not b or (not b.concrete and not second_key): - return 1 - specs = self._spec_for_pkgname(pkgname, component, second_key) - a_index = None - b_index = None - reverse = -1 if reverse_natural_compare else 1 - for i, cspec in enumerate(specs): - if a_index is None and (cspec.satisfies(a) or a.satisfies(cspec)): - a_index = i - if b_index: - break - if b_index is None and (cspec.satisfies(b) or b.satisfies(cspec)): - b_index = i - if a_index: - break - - if a_index is not None and b_index is None: - return -1 - elif a_index is None and b_index is not None: - return 1 - elif a_index is not None and b_index == a_index: - return -1 * cmp(a, b) - elif (a_index is not None and b_index is not None and - a_index != b_index): - return cmp(a_index, b_index) - else: - return cmp(a, b) * reverse - - # Given a sort order specified by the pkgname/component/second_key, return - # a list of CompilerSpecs, VersionLists, or Specs for that sorting list. - def _spec_for_pkgname(self, pkgname, component, second_key): - key = (pkgname, component, second_key) - if key not in self._spec_for_pkgname_cache: - pkglist = self._order_for_package(pkgname, component, second_key) - if component == 'compiler': - self._spec_for_pkgname_cache[key] = \ - [spack.spec.CompilerSpec(s) for s in pkglist] - elif component == 'version': - self._spec_for_pkgname_cache[key] = \ - [VersionList(s) for s in pkglist] - else: - self._spec_for_pkgname_cache[key] = \ - [spack.spec.Spec(s) for s in pkglist] - return self._spec_for_pkgname_cache[key] - - def provider_compare(self, pkgname, provider_str, a, b): - """Return less-than-0, 0, or greater than 0 if a is respecively - less-than, equal-to, or greater-than b. A and b are possible - implementations of provider_str. One provider is less-than another - if it is preferred over the other. For example, - provider_compare('scorep', 'mpi', 'mvapich', 'openmpi') would - return -1 if mvapich should be preferred over openmpi for scorep.""" - return self._spec_compare(pkgname, 'providers', a, b, False, - provider_str) - - def spec_has_preferred_provider(self, pkgname, provider_str): - """Return True iff the named package has a list of preferred - providers""" - return bool(self._order_for_package(pkgname, 'providers', - provider_str, False)) - - def spec_preferred_variants(self, pkgname): - """Return a VariantMap of preferred variants and their values""" - for pkg in (pkgname, 'all'): - variants = self.preferred.get(pkg, {}).get('variants', '') - if variants: - break - if not isinstance(variants, basestring): - variants = " ".join(variants) - pkg = spack.repo.get(pkgname) - spec = spack.spec.Spec("%s %s" % (pkgname, variants)) - # Only return variants that are actually supported by the package - return dict((name, variant) for name, variant in spec.variants.items() - if name in pkg.variants) - - def version_compare(self, pkgname, a, b): - """Return less-than-0, 0, or greater than 0 if version a of pkgname is - respectively less-than, equal-to, or greater-than version b of - pkgname. One version is less-than another if it is preferred over - the other.""" - return self._spec_compare(pkgname, 'version', a, b, True, None) - - def variant_compare(self, pkgname, a, b): - """Return less-than-0, 0, or greater than 0 if variant a of pkgname is - respectively less-than, equal-to, or greater-than variant b of - pkgname. One variant is less-than another if it is preferred over - the other.""" - return self._component_compare(pkgname, 'variant', a, b, False, None) - - def architecture_compare(self, pkgname, a, b): - """Return less-than-0, 0, or greater than 0 if architecture a of pkgname - is respectively less-than, equal-to, or greater-than architecture b - of pkgname. One architecture is less-than another if it is preferred - over the other.""" - return self._component_compare(pkgname, 'architecture', a, b, - False, None) - - def compiler_compare(self, pkgname, a, b): - """Return less-than-0, 0, or greater than 0 if compiler a of pkgname is - respecively less-than, equal-to, or greater-than compiler b of - pkgname. One compiler is less-than another if it is preferred over - the other.""" - return self._spec_compare(pkgname, 'compiler', a, b, False, None) diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py index d77700c01f..1536ecb0e6 100644 --- a/lib/spack/spack/repository.py +++ b/lib/spack/spack/repository.py @@ -337,8 +337,16 @@ class RepoPath(object): return self.repo_for_pkg(pkg_name).filename_for_package_name(pkg_name) def exists(self, pkg_name): + """Whether package with the give name exists in the path's repos. + + Note that virtual packages do not "exist". + """ return any(repo.exists(pkg_name) for repo in self.repos) + def is_virtual(self, pkg_name): + """True if the package with this name is virtual, False otherwise.""" + return pkg_name in self.provider_index + def __contains__(self, pkg_name): return self.exists(pkg_name) @@ -772,6 +780,10 @@ class Repo(object): filename = self.filename_for_package_name(pkg_name) return os.path.exists(filename) + def is_virtual(self, pkg_name): + """True if the package with this name is virtual, False otherwise.""" + return self.provider_index.contains(pkg_name) + def _get_pkg_module(self, pkg_name): """Create a module for a particular package. diff --git a/lib/spack/spack/spec.py b/lib/spack/spack/spec.py index 5291a7c9d0..c96ef1dc30 100644 --- a/lib/spack/spack/spec.py +++ b/lib/spack/spack/spec.py @@ -2546,6 +2546,8 @@ class Spec(object): return ''.join("^" + dep.format() for dep in self.sorted_deps()) def __cmp__(self, other): + from package_prefs import pkgsort + # Package name sort order is not configurable, always goes alphabetical if self.name != other.name: return cmp(self.name, other.name) @@ -2553,22 +2555,22 @@ class Spec(object): # Package version is second in compare order pkgname = self.name if self.versions != other.versions: - return spack.pkgsort.version_compare( + return pkgsort().version_compare( pkgname, self.versions, other.versions) # Compiler is third if self.compiler != other.compiler: - return spack.pkgsort.compiler_compare( + return pkgsort().compiler_compare( pkgname, self.compiler, other.compiler) # Variants if self.variants != other.variants: - return spack.pkgsort.variant_compare( + return pkgsort().variant_compare( pkgname, self.variants, other.variants) # Target if self.architecture != other.architecture: - return spack.pkgsort.architecture_compare( + return pkgsort().architecture_compare( pkgname, self.architecture, other.architecture) # Dependency is not configurable diff --git a/lib/spack/spack/test/concretize_preferences.py b/lib/spack/spack/test/concretize_preferences.py index 21d457d2e0..b95fc83010 100644 --- a/lib/spack/spack/test/concretize_preferences.py +++ b/lib/spack/spack/test/concretize_preferences.py @@ -25,7 +25,9 @@ import pytest import spack +import spack.util.spack_yaml as syaml from spack.spec import Spec +from spack.package_prefs import PreferredPackages @pytest.fixture() @@ -39,7 +41,7 @@ def concretize_scope(config, tmpdir): # This is kind of weird, but that's how config scopes are # set in ConfigScope.__init__ spack.config.config_scopes.pop('concretize') - spack.pkgsort = spack.PreferredPackages() + spack.package_prefs._pkgsort = PreferredPackages() def concretize(abstract_spec): @@ -50,7 +52,7 @@ def update_packages(pkgname, section, value): """Update config and reread package list""" conf = {pkgname: {section: value}} spack.config.update_config('packages', conf, 'concretize') - spack.pkgsort = spack.PreferredPackages() + spack.package_prefs._pkgsort = PreferredPackages() def assert_variant_values(spec, **variants): @@ -114,3 +116,30 @@ class TestConcretizePreferences(object): spec = Spec('builtin.mock.develop-test') spec.concretize() assert spec.version == spack.spec.Version('0.2.15') + + def test_no_virtuals_in_packages_yaml(self): + """Verify that virtuals are not allowed in packages.yaml.""" + + # set up a packages.yaml file with a vdep as a key. We use + # syaml.load here to make sure source lines in the config are + # attached to parsed strings, as the error message uses them. + conf = syaml.load("""mpi: + paths: + mpi-with-lapack@2.1: /path/to/lapack +""") + spack.config.update_config('packages', conf, 'concretize') + + # now when we get the packages.yaml config, there should be an error + with pytest.raises(spack.package_prefs.VirtualInPackagesYAMLError): + spack.package_prefs.get_packages_config() + + def test_all_is_not_a_virtual(self): + """Verify that `all` is allowed in packages.yaml.""" + conf = syaml.load("""all: + variants: [+mpi] +""") + spack.config.update_config('packages', conf, 'concretize') + + # should be no error for 'all': + spack.package_prefs._pkgsort = PreferredPackages() + spack.package_prefs.get_packages_config() -- cgit v1.2.3-70-g09d2