concretizer: simplify handling of virtual version constraints

Previously, the concretizer handled version constraints by comparing all
pairs of constraints and ensuring they satisfied each other. This led to
INCONSISTENT ressults from clingo, due to ambiguous semantics like:

    version_constraint_satisfies("mpi", ":1", ":3")
    version_constraint_satisfies("mpi", ":3", ":1")

To get around this, we introduce possible (fake) versions for virtuals,
based on their constraints. Essentially, we add any Versions,
VersionRange endpoints, and all such Versions and endpoints from
VersionLists to the constraint. Virtuals will have one of these synthetic
versions "picked" by the solver. This also allows us to remove a special
case from handling of `version_satisfies/3` -- virtuals now work just
like regular packages.
This commit is contained in:
Todd Gamblin 2021-01-02 22:44:33 -08:00
parent a1ed71f7e4
commit 9c941bb706
2 changed files with 29 additions and 18 deletions

View File

@ -37,7 +37,7 @@
import spack.package_prefs
import spack.repo
import spack.variant
from spack.version import ver
import spack.version
class Timer(object):
@ -451,7 +451,7 @@ def spec_versions(self, spec):
if spec.concrete:
return [fn.version(spec.name, spec.version)]
if spec.versions == ver(":"):
if spec.versions == spack.version.ver(":"):
return []
# record all version constraints for later
@ -1174,6 +1174,10 @@ def define_version_constraints(self):
self.gen.newline()
def define_virtual_constraints(self):
"""Define versions for constraints on virtuals.
Must be called before define_version_constraints().
"""
# aggregate constraints into per-virtual sets
constraint_map = collections.defaultdict(lambda: set())
for pkg_name, versions in self.version_constraints:
@ -1181,13 +1185,28 @@ def define_virtual_constraints(self):
continue
constraint_map[pkg_name].add(versions)
# extract all the real versions mentioned in version ranges
def versions_for(v):
if isinstance(v, spack.version.Version):
return [v]
elif isinstance(v, spack.version.VersionRange):
result = [v.start] if v.start else []
result += [v.end] if v.end else []
return result
elif isinstance(v, spack.version.VersionList):
return sum((versions_for(e) for e in v), [])
else:
raise TypeError("expected version type, found: %s" % type(v))
# define a set of synthetic possible versions for virtuals, so
# that `version_satisfies(Package, Constraint, Version)` has the
# same semantics for virtuals as for regular packages.
for pkg_name, versions in sorted(constraint_map.items()):
for v1 in sorted(versions):
for v2 in sorted(versions):
if v1.satisfies(v2):
self.gen.fact(
fn.version_constraint_satisfies(pkg_name, v1, v2)
)
possible_versions = set(
sum([versions_for(v) for v in versions], [])
)
for version in sorted(possible_versions):
self.possible_versions[pkg_name].add(version)
def define_compiler_version_constraints(self):
compiler_list = spack.compilers.all_compiler_specs()
@ -1400,7 +1419,7 @@ def variant_value(self, pkg, name, value):
self._specs[pkg].update_variant_validate(name, value)
def version(self, pkg, version):
self._specs[pkg].versions = ver([version])
self._specs[pkg].versions = spack.version.ver([version])
def node_compiler(self, pkg, compiler):
self._specs[pkg].compiler = spack.spec.CompilerSpec(compiler)

View File

@ -29,8 +29,7 @@ version_weight(Package, Weight)
% version_satisfies implies that exactly one of the satisfying versions
% is the package's version, and vice versa.
1 { version(Package, Version) : version_satisfies(Package, Constraint, Version) } 1
:- version_satisfies(Package, Constraint),
not virtual(Package). % TODO: fix this and handle versionless virtuals separately
:- version_satisfies(Package, Constraint).
version_satisfies(Package, Constraint)
:- version(Package, Version), version_satisfies(Package, Constraint, Version).
@ -164,12 +163,6 @@ dependency_conditions_hold(ID, Provider, Virtual) :-
virtual(Virtual);
provider_condition(ID, Provider, Virtual).
% virtuals do not have well defined possible versions, so just ensure
% that all constraints on versions are consistent
:- virtual_node(Virtual),
version_satisfies(Virtual, V1), version_satisfies(Virtual, V2),
not version_constraint_satisfies(Virtual, V1, V2).
% The provider provides the virtual if some provider condition holds.
provides_virtual(Provider, Virtual) :-
provider_condition(ID, Provider, Virtual),
@ -236,7 +229,6 @@ provider_weight(Package, 100)
#defined required_provider_condition/3.
#defined required_provider_condition/4.
#defined required_provider_condition/5.
#defined version_constraint_satisfies/3.
%-----------------------------------------------------------------------------
% Spec Attributes