concretizer: handle compiler preferences with optimization
- [x] Add support for packages.yaml and command-line compiler preferences. - [x] Rework compiler version propagation to use optimization rather than hard logic constraints
This commit is contained in:
parent
1859ff31c9
commit
f365373a3d
4 changed files with 122 additions and 69 deletions
|
@ -16,7 +16,6 @@
|
|||
import spack.cmd.common.arguments as arguments
|
||||
import spack.package
|
||||
import spack.solver.asp as asp
|
||||
from spack.util.string import plural
|
||||
|
||||
description = "concretize a specs using an ASP solver"
|
||||
section = 'developer'
|
||||
|
|
|
@ -50,10 +50,11 @@ class PackagePrefs(object):
|
|||
provider_spec_list.sort(key=kf)
|
||||
|
||||
"""
|
||||
def __init__(self, pkgname, component, vpkg=None):
|
||||
def __init__(self, pkgname, component, vpkg=None, all=True):
|
||||
self.pkgname = pkgname
|
||||
self.component = component
|
||||
self.vpkg = vpkg
|
||||
self.all = all
|
||||
|
||||
self._spec_order = None
|
||||
|
||||
|
@ -66,7 +67,7 @@ def __call__(self, spec):
|
|||
"""
|
||||
if self._spec_order is None:
|
||||
self._spec_order = self._specs_for_pkg(
|
||||
self.pkgname, self.component, self.vpkg)
|
||||
self.pkgname, self.component, self.vpkg, self.all)
|
||||
spec_order = self._spec_order
|
||||
|
||||
# integer is the index of the first spec in order that satisfies
|
||||
|
@ -114,12 +115,13 @@ def order_for_package(cls, pkgname, component, vpkg=None, all=True):
|
|||
return []
|
||||
|
||||
@classmethod
|
||||
def _specs_for_pkg(cls, pkgname, component, vpkg=None):
|
||||
def _specs_for_pkg(cls, pkgname, component, vpkg=None, all=True):
|
||||
"""Given a sort order specified by the pkgname/component/second_key,
|
||||
return a list of CompilerSpecs, VersionLists, or Specs for
|
||||
that sorting list.
|
||||
"""
|
||||
pkglist = cls.order_for_package(pkgname, component, vpkg)
|
||||
pkglist = cls.order_for_package(
|
||||
pkgname, component, vpkg, all)
|
||||
spec_type = _spec_type(component)
|
||||
return [spec_type(s) for s in pkglist]
|
||||
|
||||
|
|
|
@ -141,6 +141,7 @@ def __init__(self, out):
|
|||
self.func = AspFunctionBuilder()
|
||||
self.possible_versions = {}
|
||||
self.possible_virtuals = None
|
||||
self.possible_compilers = []
|
||||
|
||||
def title(self, name, char):
|
||||
self.out.write('\n')
|
||||
|
@ -260,7 +261,7 @@ def spec_versions(self, spec):
|
|||
return [self.one_of(*predicates)]
|
||||
return []
|
||||
|
||||
def compiler_defaults(self):
|
||||
def available_compilers(self):
|
||||
"""Facts about available compilers."""
|
||||
compilers = spack.compilers.all_compiler_specs()
|
||||
|
||||
|
@ -276,28 +277,44 @@ def compiler_defaults(self):
|
|||
for v in sorted(compiler_versions[compiler])),
|
||||
fn.compiler(compiler))
|
||||
|
||||
def package_compiler_defaults(self, pkg):
|
||||
"""Add facts about the default compiler.
|
||||
def compilers_for_default_arch(self):
|
||||
default_arch = spack.spec.ArchSpec(spack.architecture.sys_type())
|
||||
return [
|
||||
compiler.spec
|
||||
for compiler in spack.compilers.compilers_for_arch(default_arch)
|
||||
]
|
||||
|
||||
TODO: integrate full set of preferences into the solve (this only
|
||||
TODO: considers the top preference)
|
||||
"""
|
||||
# get list of all compilers
|
||||
compiler_list = spack.compilers.all_compiler_specs()
|
||||
if not compiler_list:
|
||||
raise spack.compilers.NoCompilersError()
|
||||
def compiler_defaults(self):
|
||||
"""Set compiler defaults, given a list of possible compilers."""
|
||||
self.h2("Default compiler preferences")
|
||||
|
||||
# prefer package preferences, then latest version
|
||||
ppk = spack.package_prefs.PackagePrefs(pkg.name, 'compiler')
|
||||
compiler_list = self.possible_compilers.copy()
|
||||
compiler_list = sorted(
|
||||
compiler_list, key=lambda x: (x.name, x.version), reverse=True)
|
||||
compiler_list = sorted(compiler_list, key=ppk)
|
||||
ppk = spack.package_prefs.PackagePrefs("all", 'compiler', all=False)
|
||||
matches = sorted(compiler_list, key=ppk)
|
||||
|
||||
# write out default rules for this package's compilers
|
||||
default_compiler = compiler_list[0]
|
||||
self.fact(fn.node_compiler_default(pkg.name, default_compiler.name))
|
||||
self.fact(fn.node_compiler_default_version(
|
||||
pkg.name, default_compiler.name, default_compiler.version))
|
||||
for i, cspec in enumerate(matches):
|
||||
f = fn.default_compiler_preference(cspec.name, cspec.version, i)
|
||||
self.fact(f)
|
||||
|
||||
def package_compiler_defaults(self, pkg):
|
||||
"""Facts about packages' compiler prefs."""
|
||||
|
||||
packages = spack.config.get("packages")
|
||||
pkg_prefs = packages.get(pkg)
|
||||
if not pkg_prefs or "compiler" not in pkg_prefs:
|
||||
return
|
||||
|
||||
compiler_list = self.possible_compilers.copy()
|
||||
compiler_list = sorted(
|
||||
compiler_list, key=lambda x: (x.name, x.version), reverse=True)
|
||||
ppk = spack.package_prefs.PackagePrefs(pkg.name, 'compiler', all=False)
|
||||
matches = sorted(compiler_list, key=ppk)
|
||||
|
||||
for i, cspec in enumerate(matches):
|
||||
self.fact(fn.node_compiler_preference(
|
||||
pkg.name, cspec.name, cspec.version, i))
|
||||
|
||||
def pkg_rules(self, pkg):
|
||||
pkg = packagize(pkg)
|
||||
|
@ -422,8 +439,8 @@ class Head(object):
|
|||
arch_os = fn.arch_os_set
|
||||
arch_target = fn.arch_target_set
|
||||
variant = fn.variant_set
|
||||
node_compiler = fn.node_compiler_set
|
||||
node_compiler_version = fn.node_compiler_version_set
|
||||
node_compiler = fn.node_compiler
|
||||
node_compiler_version = fn.node_compiler_version
|
||||
|
||||
class Body(object):
|
||||
node = fn.node
|
||||
|
@ -464,10 +481,28 @@ class Body(object):
|
|||
# compiler and compiler version
|
||||
if spec.compiler:
|
||||
clauses.append(f.node_compiler(spec.name, spec.compiler.name))
|
||||
clauses.append(
|
||||
fn.node_compiler_hard(spec.name, spec.compiler.name))
|
||||
|
||||
if spec.compiler.concrete:
|
||||
clauses.append(f.node_compiler_version(
|
||||
spec.name, spec.compiler.name, spec.compiler.version))
|
||||
|
||||
elif spec.compiler.versions:
|
||||
compiler_list = spack.compilers.all_compiler_specs()
|
||||
possible_compiler_versions = [
|
||||
f.node_compiler_version(
|
||||
spec.name, spec.compiler.name, compiler.version
|
||||
)
|
||||
for compiler in compiler_list
|
||||
if compiler.satisfies(spec.compiler)
|
||||
]
|
||||
clauses.append(self.one_of(*possible_compiler_versions))
|
||||
for version in possible_compiler_versions:
|
||||
clauses.append(
|
||||
fn.node_compiler_version_hard(
|
||||
spec.name, spec.compiler.name, version))
|
||||
|
||||
# TODO
|
||||
# external_path
|
||||
# external_module
|
||||
|
@ -525,11 +560,18 @@ def generate_asp_program(self, specs):
|
|||
for name in pkg_names:
|
||||
pkg = spack.repo.path.get_pkg_class(name)
|
||||
possible.update(
|
||||
pkg.possible_dependencies(virtuals=self.possible_virtuals)
|
||||
pkg.possible_dependencies(
|
||||
virtuals=self.possible_virtuals,
|
||||
deptype=("build", "link", "run")
|
||||
)
|
||||
)
|
||||
|
||||
pkgs = set(possible) | set(pkg_names)
|
||||
|
||||
# get possible compilers
|
||||
self.possible_compilers = self.compilers_for_default_arch()
|
||||
|
||||
# read the main ASP program from concrtize.lp
|
||||
concretize_lp = pkgutil.get_data('spack.solver', 'concretize.lp')
|
||||
self.out.write(concretize_lp.decode("utf-8"))
|
||||
|
||||
|
@ -537,6 +579,7 @@ def generate_asp_program(self, specs):
|
|||
self.build_version_dict(possible, specs)
|
||||
|
||||
self.h1('General Constraints')
|
||||
self.available_compilers()
|
||||
self.compiler_defaults()
|
||||
self.arch_defaults()
|
||||
self.virtual_providers()
|
||||
|
@ -628,10 +671,13 @@ def call_actions_for_functions(self, function_strings):
|
|||
for s in args]
|
||||
functions.append((name, args))
|
||||
|
||||
# Functions don't seem to be in particular order in output.
|
||||
# Sort them here so that nodes are first, and so created
|
||||
# before directives that need them (depends_on(), etc.)
|
||||
functions.sort(key=lambda f: f[0] != "node")
|
||||
# Functions don't seem to be in particular order in output. Sort
|
||||
# them here so that directives that build objects (like node and
|
||||
# node_compiler) are called in the right order.
|
||||
functions.sort(key=lambda f: {
|
||||
"node": -2,
|
||||
"node_compiler": -1,
|
||||
}.get(f[0], 0))
|
||||
|
||||
self._specs = {}
|
||||
for name, args in functions:
|
||||
|
@ -639,6 +685,7 @@ def call_actions_for_functions(self, function_strings):
|
|||
if not action:
|
||||
print("%s(%s)" % (name, ", ".join(str(a) for a in args)))
|
||||
continue
|
||||
|
||||
assert action and callable(action)
|
||||
action(*args)
|
||||
|
||||
|
|
|
@ -143,45 +143,46 @@ arch_target_set(D, A) :- node(D), depends_on(P, D), arch_target_set(P, A).
|
|||
%-----------------------------------------------------------------------------
|
||||
|
||||
% one compiler per node
|
||||
{ node_compiler(P, C) : node_compiler(P, C) } = 1 :- node(P).
|
||||
{ node_compiler_version(P, C, V) :
|
||||
node_compiler_version(P, C, V) } = 1 :- node(P).
|
||||
1 { node_compiler(P, C) : compiler(C) } 1 :- node(P).
|
||||
1 { node_compiler_version(P, C, V) : compiler_version(C, V) } 1 :- node(P).
|
||||
1 { compiler_weight(P, N) : compiler_weight(P, N) } 1 :- node(P).
|
||||
|
||||
% compiler fields are set if set to anything
|
||||
node_compiler_set(P) :- node_compiler_set(P, _).
|
||||
node_compiler_version_set(P, C) :- node_compiler_version_set(P, C, _).
|
||||
% dependencies imply we should try to match hard compiler constraints
|
||||
% todo: look at what to do about intersecting constraints here. we'd
|
||||
% ideally go with the "lowest" pref in the DAG
|
||||
node_compiler_match_pref(P, C) :- node_compiler_hard(P, C).
|
||||
node_compiler_match_pref(D, C)
|
||||
:- depends_on(P, D), node_compiler_match_pref(P, C),
|
||||
not node_compiler_hard(D, _).
|
||||
compiler_match(P, 1) :- node_compiler(P, C), node_compiler_match_pref(P, C).
|
||||
|
||||
% avoid warnings: these are set by generated code and it's ok if they're not
|
||||
#defined node_compiler_set/2.
|
||||
#defined node_compiler_version_set/3.
|
||||
node_compiler_version_match_pref(P, C, V)
|
||||
:- node_compiler_version_hard(P, C, V).
|
||||
node_compiler_version_match_pref(D, C, V)
|
||||
:- depends_on(P, D), node_compiler_version_match_pref(P, C, V),
|
||||
not node_compiler_version_hard(D, C, _).
|
||||
compiler_version_match(P, 1)
|
||||
:- node_compiler_version(P, C, V),
|
||||
node_compiler_version_match_pref(P, C, V).
|
||||
|
||||
% if compiler value of node is set to anything, it's the value.
|
||||
node_compiler(P, C)
|
||||
:- node(P), compiler(C), node_compiler_set(P, C).
|
||||
node_compiler_version(P, C, V)
|
||||
:- node(P), compiler(C), compiler_version(C, V), node_compiler(P, C),
|
||||
node_compiler_version_set(P, C, V).
|
||||
#defined node_compiler_hard/2.
|
||||
#defined node_compiler_version_hard/3.
|
||||
|
||||
% node compiler versions can only be from the available compiler versions
|
||||
:- node(P), compiler(C), node_compiler(P, C), node_compiler_version(P, C, V),
|
||||
not compiler_version(C, V).
|
||||
|
||||
% if no compiler is set, fall back to default.
|
||||
node_compiler(P, C)
|
||||
:- node(P), compiler(C), not node_compiler_set(P),
|
||||
node_compiler_default(P, C).
|
||||
node_compiler_version(P, C, V)
|
||||
:- node(P), compiler(C), compiler_version(C, V),
|
||||
not node_compiler_version_set(P, C, V),
|
||||
node_compiler_default_version(P, C, V).
|
||||
|
||||
% propagate compiler, compiler version to dependencies
|
||||
node_compiler_set(D, C)
|
||||
:- node(D), compiler(C), depends_on(P, D), node_compiler_set(P, C).
|
||||
node_compiler_version_set(D, C, V)
|
||||
:- node(D), compiler(C), depends_on(P, D), node_compiler(D, C),
|
||||
node_compiler_version_set(P, C, V).
|
||||
% compilers weighted by preference acccording to packages.yaml
|
||||
compiler_weight(P, N)
|
||||
:- node_compiler(P, C), node_compiler_version(P, C, V),
|
||||
node_compiler_preference(P, C, V, N).
|
||||
compiler_weight(P, N)
|
||||
:- node_compiler(P, C), node_compiler_version(P, C, V),
|
||||
not node_compiler_preference(P, C, _, _),
|
||||
default_compiler_preference(C, V, N).
|
||||
compiler_weight(P, 100)
|
||||
:- node_compiler(P, C), node_compiler_version(P, C, V),
|
||||
not node_compiler_preference(P, C, _, _),
|
||||
not default_compiler_preference(C, _, _).
|
||||
|
||||
#defined node_compiler_preference/4.
|
||||
#defined default_compiler_preference/3.
|
||||
|
||||
%-----------------------------------------------------------------------------
|
||||
% How to optimize the spec (high to low priority)
|
||||
|
@ -206,11 +207,15 @@ node_compiler_version_set(D, C, V)
|
|||
root(D, 2) :- root(D), node(D).
|
||||
root(D, 1) :- not root(D), node(D).
|
||||
|
||||
% prefer default variants
|
||||
#minimize { N*R@5,P,V,X : variant_not_default(P, V, X, N), root(P, R) }.
|
||||
|
||||
% prefer more recent versions.
|
||||
#minimize{ N@4,P,V : version_weight(P, V, N) }.
|
||||
|
||||
% pick most preferred virtual providers
|
||||
#minimize{ N*R@3,D : provider_weight(D, N), root(P, R) }.
|
||||
|
||||
% prefer default variants
|
||||
#minimize { N*R@2,P,V,X : variant_not_default(P, V, X, N), root(P, R) }.
|
||||
|
||||
% prefer more recent versions.
|
||||
#minimize{ N@1,P,V : version_weight(P, V, N) }.
|
||||
% compiler preferences
|
||||
#maximize{ N@2,P : compiler_match(P, N) }.
|
||||
#minimize{ N@1,P : compiler_weight(P, N) }.
|
||||
|
|
Loading…
Reference in a new issue