diff options
author | Todd Gamblin <tgamblin@llnl.gov> | 2017-03-10 22:28:01 -0800 |
---|---|---|
committer | Todd Gamblin <tgamblin@llnl.gov> | 2017-03-31 13:40:41 -0700 |
commit | fe6f39b66287a4b3ecade2d776348d44920ec651 (patch) | |
tree | 4a64815172c967cb6bd1c4ce82062dfaa205b7db /lib/spack/spack/package_prefs.py | |
parent | 0cd6555388ad6fafc110bc2aa60f256acf920bcd (diff) | |
download | spack-fe6f39b66287a4b3ecade2d776348d44920ec651.tar.gz spack-fe6f39b66287a4b3ecade2d776348d44920ec651.tar.bz2 spack-fe6f39b66287a4b3ecade2d776348d44920ec651.tar.xz spack-fe6f39b66287a4b3ecade2d776348d44920ec651.zip |
Use key sorting instead of cmp()
- Get rid of pkgsort() usage for preferred variants.
- Concretization is now entirely based on key-based sorting.
- Remove PreferredPackages class and various spec cmp() methods.
- Replace with PackagePrefs class that implements a key function for
sorting according to packages.yaml.
- Clear package pref caches on config test.
- Explicit compare methods instead of total_ordering in Version.
- Our total_ordering backport wasn't making Python 3 happy for some
reason.
- Python 3's functools.total_ordering and spelling the operators out
fixes the problem.
- Fix unicode issues with spec hashes, json, & YAML
- Try to use str everywhere and avoid unicode objects in python 2.
Diffstat (limited to 'lib/spack/spack/package_prefs.py')
-rw-r--r-- | lib/spack/spack/package_prefs.py | 329 |
1 files changed, 130 insertions, 199 deletions
diff --git a/lib/spack/spack/package_prefs.py b/lib/spack/spack/package_prefs.py index 3dc90a8eb9..f9dac2bef0 100644 --- a/lib/spack/spack/package_prefs.py +++ b/lib/spack/spack/package_prefs.py @@ -25,11 +25,22 @@ from six import string_types from six import iteritems +from llnl.util.lang import classproperty + import spack import spack.error from spack.version import * +_lesser_spec_types = {'compiler': spack.spec.CompilerSpec, + 'version': VersionList} + + +def _spec_type(component): + """Map from component name to spec type for package prefs.""" + return _lesser_spec_types.get(component, spack.spec.Spec) + + def get_packages_config(): """Wrapper around get_packages_config() to validate semantics.""" config = spack.config.get_config('packages') @@ -51,177 +62,141 @@ def get_packages_config(): return config -class PreferredPackages(object): - def __init__(self): - self.preferred = get_packages_config() - self._spec_for_pkgname_cache = {} +class PackagePrefs(object): + """Defines the sort order for a set of specs. + + Spack's package preference implementation uses PackagePrefss to + define sort order. The PackagePrefs class looks at Spack's + packages.yaml configuration and, when called on a spec, returns a key + that can be used to sort that spec in order of the user's + preferences. + + You can use it like this: + + # key function sorts CompilerSpecs for `mpich` in order of preference + kf = PackagePrefs('mpich', 'compiler') + compiler_list.sort(key=kf) + + Or like this: + + # key function to sort VersionLists for OpenMPI in order of preference. + kf = PackagePrefs('openmpi', 'version') + version_list.sort(key=kf) - # 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): + Optionally, you can sort in order of preferred virtual dependency + providers. To do that, provide 'providers' and a third argument + denoting the virtual package (e.g., ``mpi``): + + kf = PackagePrefs('trilinos', 'providers', 'mpi') + provider_spec_list.sort(key=kf) + + """ + _packages_config_cache = None + _spec_cache = {} + + def __init__(self, pkgname, component, vpkg=None): + self.pkgname = pkgname + self.component = component + self.vpkg = vpkg + + def __call__(self, spec): + """Return a key object (an index) that can be used to sort spec. + + Sort is done in package order. We don't cache the result of + this function as Python's sort functions already ensure that the + key function is called at most once per sorted element. + """ + spec_order = self._specs_for_pkg( + self.pkgname, self.component, self.vpkg) + + # integer is the index of the first spec in order that satisfies + # spec, or it's a number larger than any position in the order. + return next( + (i for i, s in enumerate(spec_order) if spec.satisfies(s)), + len(spec_order)) + + @classproperty + @classmethod + def _packages_config(cls): + if cls._packages_config_cache is None: + cls._packages_config_cache = get_packages_config() + return cls._packages_config_cache + + @classmethod + def _order_for_package(cls, pkgname, component, vpkg=None, all=True): + """Given a package name, sort component (e.g, version, compiler, ...), + and an optional vpkg, return the list from the packages config. + """ pkglist = [pkgname] - if test_all: + if 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, {}) + pkg_entry = cls._packages_config.get(pkg) + if not pkg_entry: + continue + + order = pkg_entry.get(component) if not order: continue - return [str(s).strip() for s in order] + + # vpkg is one more level + if vpkg is not None: + order = order.get(vpkg) + + if order: + 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', '') + @classmethod + def _specs_for_pkg(cls, pkgname, component, vpkg=None): + """Given a sort order specified by the pkgname/component/second_key, + return a list of CompilerSpecs, VersionLists, or Specs for + that sorting list. + """ + key = (pkgname, component, vpkg) + + specs = cls._spec_cache.get(key) + if specs is None: + pkglist = cls._order_for_package(pkgname, component, vpkg) + spec_type = _spec_type(component) + specs = [spec_type(s) for s in pkglist] + cls._spec_cache[key] = specs + + return specs + + @classmethod + def clear_caches(cls): + cls._packages_config_cache = None + cls._spec_cache = {} + + @classmethod + def has_preferred_providers(cls, pkgname, vpkg): + """Whether specific package has a preferred vpkg providers.""" + return bool(cls._order_for_package(pkgname, 'providers', vpkg, False)) + + @classmethod + def preferred_variants(cls, pkg_name): + """Return a VariantMap of preferred variants/values for a spec.""" + for pkg in (pkg_name, 'all'): + variants = cls._packages_config.get(pkg, {}).get('variants', '') if variants: break + + # allow variants to be list or string if not isinstance(variants, string_types): 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 + pkg = spack.repo.get(pkg_name) + spec = spack.spec.Spec("%s %s" % (pkg_name, variants)) 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), + """Return a list of external specs (w/external directory path filled in), one for each known external installation.""" # break circular import. from spack.build_environment import get_path_from_module @@ -255,7 +230,8 @@ def spec_externals(spec): if external_spec.satisfies(spec): external_specs.append(external_spec) - return external_specs + # defensively copy returned specs + return [s.copy() for s in external_specs] def is_spec_buildable(spec): @@ -268,50 +244,5 @@ def is_spec_buildable(spec): 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""" |