Allow specs to be sorted based on preferred packages, versions, compilers, variants and dependencies.
This commit is contained in:
parent
59f89dd3be
commit
b5c597b318
@ -69,6 +69,14 @@
|
||||
from spack.directory_layout import YamlDirectoryLayout
|
||||
install_layout = YamlDirectoryLayout(install_path)
|
||||
|
||||
#
|
||||
# This controls how packages are sorted when trying to choose
|
||||
# the most preferred package. More preferred packages are sorted
|
||||
# first.
|
||||
#
|
||||
from spack.preferred_packages import PreferredPackages
|
||||
pkgsort = PreferredPackages()
|
||||
|
||||
#
|
||||
# This controls how things are concretized in spack.
|
||||
# Replace it with a subclass if you want different
|
||||
|
@ -89,8 +89,8 @@
|
||||
import os
|
||||
import exceptions
|
||||
import sys
|
||||
import copy
|
||||
|
||||
from external.ordereddict import OrderedDict
|
||||
from llnl.util.lang import memoized
|
||||
import spack.error
|
||||
|
||||
@ -114,8 +114,7 @@ def __init__(self, n, f, m):
|
||||
|
||||
_ConfigCategory('compilers', 'compilers.yaml', True)
|
||||
_ConfigCategory('mirrors', 'mirrors.yaml', True)
|
||||
_ConfigCategory('view', 'views.yaml', True)
|
||||
_ConfigCategory('order', 'orders.yaml', True)
|
||||
_ConfigCategory('preferred', 'preferred.yaml', True)
|
||||
|
||||
"""Names of scopes and their corresponding configuration files."""
|
||||
config_scopes = [('site', os.path.join(spack.etc_path, 'spack')),
|
||||
@ -156,7 +155,7 @@ def _merge_dicts(d1, d2):
|
||||
"""Recursively merges two configuration trees, with entries
|
||||
in d2 taking precedence over d1"""
|
||||
if not d1:
|
||||
return d2.copy()
|
||||
return copy.copy(d2)
|
||||
if not d2:
|
||||
return d1
|
||||
|
||||
@ -230,6 +229,11 @@ def get_mirror_config():
|
||||
return get_config('mirrors')
|
||||
|
||||
|
||||
def get_preferred_config():
|
||||
"""Get the preferred configuration from config files"""
|
||||
return get_config('preferred')
|
||||
|
||||
|
||||
def get_config_scope_dirname(scope):
|
||||
"""For a scope return the config directory"""
|
||||
global config_scopes
|
||||
|
172
lib/spack/spack/preferred_packages.py
Normal file
172
lib/spack/spack/preferred_packages.py
Normal file
@ -0,0 +1,172 @@
|
||||
##############################################################################
|
||||
# Copyright (c) 2013, Lawrence Livermore National Security, LLC.
|
||||
# Produced at the Lawrence Livermore National Laboratory.
|
||||
#
|
||||
# This file is part of Spack.
|
||||
# Written by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
|
||||
# LLNL-CODE-647188
|
||||
#
|
||||
# For details, see https://scalability-llnl.github.io/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 General Public License (as published by
|
||||
# the Free Software Foundation) version 2.1 dated 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 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):
|
||||
_default_order = {'compiler' : [ 'gcc', 'intel', 'clang', 'pgi', 'xlc' ] }, #Arbitrary, but consistent
|
||||
|
||||
def __init__(self):
|
||||
self.preferred = spack.config.get_preferred_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):
|
||||
pkglist = [pkgname]
|
||||
pkglist.append('all')
|
||||
for pkg in pkglist:
|
||||
if not pkg in self.preferred:
|
||||
continue
|
||||
orders = self.preferred[pkg]
|
||||
if not type(orders) is dict:
|
||||
continue
|
||||
if not component in orders:
|
||||
continue
|
||||
order = orders[component]
|
||||
if type(order) is dict:
|
||||
if not second_key in order:
|
||||
continue;
|
||||
order = order[second_key]
|
||||
if not type(order) is str:
|
||||
tty.die('Expected version list in preferred config, but got %s' % str(order))
|
||||
order_list = order.split(',')
|
||||
return [s.strip() for s in order_list]
|
||||
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):
|
||||
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):
|
||||
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 == None and cspec.satisfies(a):
|
||||
a_index = i
|
||||
if b_index:
|
||||
break
|
||||
if b_index == None and cspec.satisfies(b):
|
||||
b_index = i
|
||||
if a_index:
|
||||
break
|
||||
|
||||
if a_index != None and b_index == None: return -1
|
||||
elif a_index == None and b_index != None: return 1
|
||||
elif a_index != None and b_index == a_index: return -1 * cmp(a, b)
|
||||
elif a_index != None and b_index != None and a_index != b_index: return cmp(a_index, b_index)
|
||||
elif a < b: return 1 * reverse
|
||||
elif b < a: return -1 * reverse
|
||||
else: return 0
|
||||
|
||||
|
||||
# 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 not key in self._spec_for_pkgname_cache:
|
||||
pkglist = self._order_for_package(pkgname, component, second_key)
|
||||
if not pkglist:
|
||||
if component in self._default_order:
|
||||
pkglist = self._default_order[component]
|
||||
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 version_compare(self, pkgname, a, b):
|
||||
"""Return less-than-0, 0, or greater than 0 if version a of pkgname is
|
||||
respecively 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, False, None)
|
||||
|
||||
|
||||
def variant_compare(self, pkgname, a, b):
|
||||
"""Return less-than-0, 0, or greater than 0 if variant a of pkgname is
|
||||
respecively 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
|
||||
respecively 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)
|
@ -1653,6 +1653,40 @@ def dep_string(self):
|
||||
return ''.join("^" + dep.format() for dep in self.sorted_deps())
|
||||
|
||||
|
||||
def __cmp__(self, other):
|
||||
#Package name sort order is not configurable, always goes alphabetical
|
||||
if self.name != other.name:
|
||||
return cmp(self.name, other.name)
|
||||
|
||||
#Package version is second in compare order
|
||||
pkgname = self.name
|
||||
if self.versions != other.versions:
|
||||
return spack.pkgsort.version_compare(pkgname,
|
||||
self.versions, other.versions)
|
||||
|
||||
#Compiler is third
|
||||
if self.compiler != other.compiler:
|
||||
return spack.pkgsort.compiler_compare(pkgname,
|
||||
self.compiler, other.compiler)
|
||||
|
||||
#Variants
|
||||
if self.variants != other.variants:
|
||||
return spack.pkgsort.variant_compare(pkgname,
|
||||
self.variants, other.variants)
|
||||
|
||||
#Architecture
|
||||
if self.architecture != other.architecture:
|
||||
return spack.pkgsort.architecture_compare(pkgname,
|
||||
self.architecture, other.architecture)
|
||||
|
||||
#Dependency is not configurable
|
||||
if self.dep_hash() != other.dep_hash():
|
||||
return -1 if self.dep_hash() < other.dep_hash() else 1
|
||||
|
||||
#Equal specs
|
||||
return 0
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.format() + self.dep_string()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user