Transform many package related facts to use a nested function

Each fact that is deduced from package rules, and start with
a bare package atom, is transformed into a "facts" atom containing
a nested function.

For instance we transformed

  version_declared(Package, ...) -> facts(Package, version_declared(...))

This allows us to clearly mark facts that represent a rule on the package,
and will be of help later when we'll have to distinguish the cases where
the atom "Package" is being used referred to package rules and not to a
node in the DAG.
This commit is contained in:
Massimiliano Culpo 2023-06-13 11:07:12 +02:00 committed by Todd Gamblin
parent 87d4bdaa02
commit 6ad0dc3722
3 changed files with 141 additions and 112 deletions

View file

@ -302,6 +302,8 @@ def argify(arg):
return clingo.String(str(arg)) return clingo.String(str(arg))
elif isinstance(arg, int): elif isinstance(arg, int):
return clingo.Number(arg) return clingo.Number(arg)
elif isinstance(arg, AspFunction):
return clingo.Function(arg.name, [argify(x) for x in arg.args], positive=positive)
else: else:
return clingo.String(str(arg)) return clingo.String(str(arg))
@ -918,16 +920,20 @@ def key_fn(version):
) )
for weight, declared_version in enumerate(most_to_least_preferred): for weight, declared_version in enumerate(most_to_least_preferred):
# TODO: self.package_fact(pkg.name).version_declared(declared_version, weight=weight)
self.gen.fact( self.gen.fact(
fn.version_declared( fn.facts(
pkg.name, declared_version.version, weight, str(declared_version.origin) pkg.name,
fn.version_declared(
declared_version.version, weight, str(declared_version.origin)
),
) )
) )
# Declare deprecated versions for this package, if any # Declare deprecated versions for this package, if any
deprecated = self.deprecated_versions[pkg.name] deprecated = self.deprecated_versions[pkg.name]
for v in sorted(deprecated): for v in sorted(deprecated):
self.gen.fact(fn.deprecated_version(pkg.name, v)) self.gen.fact(fn.facts(pkg.name, fn.deprecated_version(v)))
def spec_versions(self, spec): def spec_versions(self, spec):
"""Return list of clauses expressing spec's version constraints.""" """Return list of clauses expressing spec's version constraints."""
@ -970,7 +976,9 @@ def conflict_rules(self, pkg):
conflict_msg = default_msg.format(pkg.name, trigger, constraint) conflict_msg = default_msg.format(pkg.name, trigger, constraint)
constraint_msg = "conflict constraint %s" % str(constraint) constraint_msg = "conflict constraint %s" % str(constraint)
constraint_id = self.condition(constraint, name=pkg.name, msg=constraint_msg) constraint_id = self.condition(constraint, name=pkg.name, msg=constraint_msg)
self.gen.fact(fn.conflict(pkg.name, trigger_id, constraint_id, conflict_msg)) self.gen.fact(
fn.facts(pkg.name, fn.conflict(trigger_id, constraint_id, conflict_msg))
)
self.gen.newline() self.gen.newline()
def compiler_facts(self): def compiler_facts(self):
@ -1023,8 +1031,11 @@ def package_compiler_defaults(self, pkg):
for i, compiler in enumerate(reversed(matches)): for i, compiler in enumerate(reversed(matches)):
self.gen.fact( self.gen.fact(
fn.node_compiler_preference( fn.facts(
pkg.name, compiler.spec.name, compiler.spec.version, -i * 100 pkg.name,
fn.node_compiler_preference(
compiler.spec.name, compiler.spec.version, -i * 100
),
) )
) )
@ -1119,7 +1130,7 @@ def pkg_rules(self, pkg, tests):
if spack.spec.Spec() in when: if spack.spec.Spec() in when:
# unconditional variant # unconditional variant
self.gen.fact(fn.variant(pkg.name, name)) self.gen.fact(fn.facts(pkg.name, fn.variant(name)))
else: else:
# conditional variant # conditional variant
for w in when: for w in when:
@ -1128,19 +1139,23 @@ def pkg_rules(self, pkg, tests):
msg += " when %s" % w msg += " when %s" % w
cond_id = self.condition(w, name=pkg.name, msg=msg) cond_id = self.condition(w, name=pkg.name, msg=msg)
self.gen.fact(fn.variant_condition(cond_id, pkg.name, name)) self.gen.fact(fn.facts(pkg.name, fn.conditional_variant(cond_id, name)))
single_value = not variant.multi single_value = not variant.multi
if single_value: if single_value:
self.gen.fact(fn.variant_single_value(pkg.name, name)) self.gen.fact(fn.facts(pkg.name, fn.variant_single_value(name)))
self.gen.fact( self.gen.fact(
fn.variant_default_value_from_package_py(pkg.name, name, variant.default) fn.facts(
pkg.name, fn.variant_default_value_from_package_py(name, variant.default)
)
) )
else: else:
spec_variant = variant.make_default() spec_variant = variant.make_default()
defaults = spec_variant.value defaults = spec_variant.value
for val in sorted(defaults): for val in sorted(defaults):
self.gen.fact(fn.variant_default_value_from_package_py(pkg.name, name, val)) self.gen.fact(
fn.facts(pkg.name, fn.variant_default_value_from_package_py(name, val))
)
values = variant.values values = variant.values
if values is None: if values is None:
@ -1151,7 +1166,9 @@ def pkg_rules(self, pkg, tests):
for sid, s in enumerate(values.sets): for sid, s in enumerate(values.sets):
for value in s: for value in s:
self.gen.fact( self.gen.fact(
fn.variant_value_from_disjoint_sets(pkg.name, name, value, sid) fn.facts(
pkg.name, fn.variant_value_from_disjoint_sets(name, value, sid)
)
) )
union.update(s) union.update(s)
values = union values = union
@ -1178,7 +1195,9 @@ def pkg_rules(self, pkg, tests):
msg="empty (total) conflict constraint", msg="empty (total) conflict constraint",
) )
msg = "variant {0}={1} is conditionally disabled".format(name, value) msg = "variant {0}={1} is conditionally disabled".format(name, value)
self.gen.fact(fn.conflict(pkg.name, trigger_id, constraint_id, msg)) self.gen.fact(
fn.facts(pkg.name, fn.conflict(trigger_id, constraint_id, msg))
)
else: else:
imposed = spack.spec.Spec(value.when) imposed = spack.spec.Spec(value.when)
imposed.name = pkg.name imposed.name = pkg.name
@ -1189,10 +1208,10 @@ def pkg_rules(self, pkg, tests):
name=pkg.name, name=pkg.name,
msg="%s variant %s value %s when %s" % (pkg.name, name, value, when), msg="%s variant %s value %s when %s" % (pkg.name, name, value, when),
) )
self.gen.fact(fn.variant_possible_value(pkg.name, name, value)) self.gen.fact(fn.facts(pkg.name, fn.variant_possible_value(name, value)))
if variant.sticky: if variant.sticky:
self.gen.fact(fn.variant_sticky(pkg.name, name)) self.gen.fact(fn.facts(pkg.name, fn.variant_sticky(name)))
self.gen.newline() self.gen.newline()
@ -1210,7 +1229,8 @@ def pkg_rules(self, pkg, tests):
# virtual preferences # virtual preferences
self.virtual_preferences( self.virtual_preferences(
pkg.name, lambda v, p, i: self.gen.fact(fn.pkg_provider_preference(pkg.name, v, p, i)) pkg.name,
lambda v, p, i: self.gen.fact(fn.facts(pkg.name, fn.provider_preference(v, p, i))),
) )
self.package_requirement_rules(pkg) self.package_requirement_rules(pkg)
@ -1232,7 +1252,7 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
""" """
named_cond = required_spec.copy() named_cond = required_spec.copy()
named_cond.name = named_cond.name or name named_cond.name = named_cond.name or name
assert named_cond.name, "must provide name for anonymous condtions!" assert named_cond.name, "must provide name for anonymous conditions!"
# Check if we can emit the requirements before updating the condition ID counter. # Check if we can emit the requirements before updating the condition ID counter.
# In this way, if a condition can't be emitted but the exception is handled in the caller, # In this way, if a condition can't be emitted but the exception is handled in the caller,
@ -1240,7 +1260,8 @@ def condition(self, required_spec, imposed_spec=None, name=None, msg=None, node=
requirements = self.spec_clauses(named_cond, body=True, required_from=name) requirements = self.spec_clauses(named_cond, body=True, required_from=name)
condition_id = next(self._condition_id_counter) condition_id = next(self._condition_id_counter)
self.gen.fact(fn.condition(condition_id, msg)) self.gen.fact(fn.facts(named_cond.name, fn.condition(condition_id)))
self.gen.fact(fn.condition_reason(condition_id, msg))
for pred in requirements: for pred in requirements:
self.gen.fact(fn.condition_requirement(condition_id, *pred.args)) self.gen.fact(fn.condition_requirement(condition_id, *pred.args))
@ -1259,13 +1280,15 @@ def impose(self, condition_id, imposed_spec, node=True, name=None, body=False):
def package_provider_rules(self, pkg): def package_provider_rules(self, pkg):
for provider_name in sorted(set(s.name for s in pkg.provided.keys())): for provider_name in sorted(set(s.name for s in pkg.provided.keys())):
self.gen.fact(fn.possible_provider(pkg.name, provider_name)) self.gen.fact(fn.facts(pkg.name, fn.possible_provider(provider_name)))
for provided, whens in pkg.provided.items(): for provided, whens in pkg.provided.items():
for when in whens: for when in whens:
msg = "%s provides %s when %s" % (pkg.name, provided, when) msg = "%s provides %s when %s" % (pkg.name, provided, when)
condition_id = self.condition(when, provided, pkg.name, msg) condition_id = self.condition(when, provided, pkg.name, msg)
self.gen.fact(fn.provider_condition(condition_id, when.name, provided.name)) self.gen.fact(
fn.facts(when.name, fn.provider_condition(condition_id, provided.name))
)
self.gen.newline() self.gen.newline()
def package_dependencies_rules(self, pkg): def package_dependencies_rules(self, pkg):
@ -1291,7 +1314,9 @@ def package_dependencies_rules(self, pkg):
msg += " when %s" % cond msg += " when %s" % cond
condition_id = self.condition(cond, dep.spec, pkg.name, msg) condition_id = self.condition(cond, dep.spec, pkg.name, msg)
self.gen.fact(fn.dependency_condition(condition_id, pkg.name, dep.spec.name)) self.gen.fact(
fn.facts(pkg.name, fn.dependency_condition(condition_id, dep.spec.name))
)
for t in sorted(deptypes): for t in sorted(deptypes):
# there is a declared dependency of type t # there is a declared dependency of type t
@ -1449,7 +1474,7 @@ def external_packages(self):
for local_idx, spec in enumerate(external_specs): for local_idx, spec in enumerate(external_specs):
msg = "%s available as external when satisfying %s" % (spec.name, spec) msg = "%s available as external when satisfying %s" % (spec.name, spec)
condition_id = self.condition(spec, msg=msg) condition_id = self.condition(spec, msg=msg)
self.gen.fact(fn.possible_external(condition_id, pkg_name, local_idx)) self.gen.fact(fn.facts(pkg_name, fn.possible_external(condition_id, local_idx)))
self.possible_versions[spec.name].add(spec.version) self.possible_versions[spec.name].add(spec.version)
self.gen.newline() self.gen.newline()
@ -1495,7 +1520,9 @@ def target_preferences(self, pkg_name):
if str(preferred.architecture.target) == best_default and i != 0: if str(preferred.architecture.target) == best_default and i != 0:
offset = 100 offset = 100
self.gen.fact( self.gen.fact(
fn.target_weight(pkg_name, str(preferred.architecture.target), i + offset) fn.facts(
pkg_name, fn.target_weight(str(preferred.architecture.target), i + offset)
)
) )
def spec_clauses(self, *args, **kwargs): def spec_clauses(self, *args, **kwargs):
@ -2041,11 +2068,11 @@ def define_version_constraints(self):
# generate facts for each package constraint and the version # generate facts for each package constraint and the version
# that satisfies it # that satisfies it
for v in sorted(v for v in self.possible_versions[pkg_name] if v.satisfies(versions)): for v in sorted(v for v in self.possible_versions[pkg_name] if v.satisfies(versions)):
self.gen.fact(fn.version_satisfies(pkg_name, versions, v)) self.gen.fact(fn.facts(pkg_name, fn.version_satisfies(versions, v)))
self.gen.newline() self.gen.newline()
def define_virtual_constraints(self): def collect_virtual_constraints(self):
"""Define versions for constraints on virtuals. """Define versions for constraints on virtuals.
Must be called before define_version_constraints(). Must be called before define_version_constraints().
@ -2131,7 +2158,7 @@ def define_variant_values(self):
# spec_clauses(). We might want to order these facts by pkg and name # spec_clauses(). We might want to order these facts by pkg and name
# if we are debugging. # if we are debugging.
for pkg, variant, value in self.variant_values_from_specs: for pkg, variant, value in self.variant_values_from_specs:
self.gen.fact(fn.variant_possible_value(pkg, variant, value)) self.gen.fact(fn.facts(pkg, fn.variant_possible_value(variant, value)))
def _facts_from_concrete_spec(self, spec, possible): def _facts_from_concrete_spec(self, spec, possible):
# tell the solver about any installed packages that could # tell the solver about any installed packages that could
@ -2280,10 +2307,8 @@ def setup(self, driver, specs, reuse=None):
self.gen.h1("Variant Values defined in specs") self.gen.h1("Variant Values defined in specs")
self.define_variant_values() self.define_variant_values()
self.gen.h1("Virtual Constraints")
self.define_virtual_constraints()
self.gen.h1("Version Constraints") self.gen.h1("Version Constraints")
self.collect_virtual_constraints()
self.define_version_constraints() self.define_version_constraints()
self.gen.h1("Compiler Version Constraints") self.gen.h1("Compiler Version Constraints")

View file

@ -64,23 +64,23 @@ error(100, multiple_values_error, Attribute, Package)
% Versions are declared with a weight and an origin, which indicates where the % Versions are declared with a weight and an origin, which indicates where the
% version was declared (e.g. "package_py" or "external"). % version was declared (e.g. "package_py" or "external").
version_declared(Package, Version, Weight) :- version_declared(Package, Version, Weight, _). facts(Package, version_declared(Version, Weight)) :- facts(Package, version_declared(Version, Weight, _)).
% We can't emit the same version **with the same weight** from two different sources % We can't emit the same version **with the same weight** from two different sources
:- version_declared(Package, Version, Weight, Origin1), :- facts(Package, version_declared(Version, Weight, Origin1)),
version_declared(Package, Version, Weight, Origin2), facts(Package, version_declared(Version, Weight, Origin2)),
Origin1 < Origin2, Origin1 < Origin2,
internal_error("Two versions with identical weights"). internal_error("Two versions with identical weights").
% We cannot use a version declared for an installed package if we end up building it % We cannot use a version declared for an installed package if we end up building it
:- version_declared(Package, Version, Weight, "installed"), :- facts(Package, version_declared(Version, Weight, "installed")),
attr("version", Package, Version), attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
not attr("hash", Package, _), not attr("hash", Package, _),
internal_error("Reuse version weight used for built package"). internal_error("Reuse version weight used for built package").
% versions are declared w/priority -- declared with priority implies declared % versions are declared w/priority -- declared with priority implies declared
version_declared(Package, Version) :- version_declared(Package, Version, _). facts(Package, version_declared(Version)) :- facts(Package, version_declared(Version, _)).
% a spec with a git hash version is equivalent to one with the same matched version % a spec with a git hash version is equivalent to one with the same matched version
version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package, Constraint, EquivalentVersion), version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package, Constraint, EquivalentVersion),
@ -93,7 +93,7 @@ version_satisfies(Package, Constraint, HashVersion) :- version_satisfies(Package
% is not precisely one version chosen. Error facts are heavily optimized % is not precisely one version chosen. Error facts are heavily optimized
% against to ensure they cannot be inferred when a non-error solution is % against to ensure they cannot be inferred when a non-error solution is
% possible % possible
{ attr("version", Package, Version) : version_declared(Package, Version) } { attr("version", Package, Version) : facts(Package, version_declared(Version)) }
:- attr("node", Package). :- attr("node", Package).
% A virtual package may or may not have a version, but never has more than one % A virtual package may or may not have a version, but never has more than one
@ -104,17 +104,17 @@ error(100, "Cannot select a single version for virtual '{0}'", Virtual)
% If we select a deprecated version, mark the package as deprecated % If we select a deprecated version, mark the package as deprecated
attr("deprecated", Package, Version) :- attr("deprecated", Package, Version) :-
attr("version", Package, Version), attr("version", Package, Version),
deprecated_version(Package, Version). facts(Package, deprecated_version(Version)).
possible_version_weight(Package, Weight) possible_version_weight(Package, Weight)
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_declared(Package, Version, Weight). facts(Package, version_declared(Version, Weight)).
% we can't use the weight for an external version if we don't use the % we can't use the weight for an external version if we don't use the
% corresponding external spec. % corresponding external spec.
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
version_declared(Package, Version, Weight, "external"), facts(Package, version_declared(Version, Weight, "external")),
not external(Package), not external(Package),
internal_error("External weight used for built package"). internal_error("External weight used for built package").
@ -122,17 +122,17 @@ possible_version_weight(Package, Weight)
% and vice-versa % and vice-versa
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
version_declared(Package, Version, Weight, "installed"), facts(Package, version_declared(Version, Weight, "installed")),
build(Package), build(Package),
internal_error("Reuse version weight used for build package"). internal_error("Reuse version weight used for build package").
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
not version_declared(Package, Version, Weight, "installed"), not facts(Package, version_declared(Version, Weight, "installed")),
not build(Package), not build(Package),
internal_error("Build version weight used for reused package"). internal_error("Build version weight used for reused package").
1 { version_weight(Package, Weight) : version_declared(Package, Version, Weight) } 1 1 { version_weight(Package, Weight) : facts(Package, version_declared(Version, Weight)) } 1
:- attr("version", Package, Version), :- attr("version", Package, Version),
attr("node", Package). attr("node", Package).
@ -141,24 +141,24 @@ possible_version_weight(Package, Weight)
% While this choice rule appears redundant with the initial choice rule for % While this choice rule appears redundant with the initial choice rule for
% versions, virtual nodes with version constraints require this rule to be % versions, virtual nodes with version constraints require this rule to be
% able to choose versions % able to choose versions
{ attr("version", Package, Version) : version_satisfies(Package, Constraint, Version) } { attr("version", Package, Version) : facts(Package, version_satisfies(Constraint, Version)) }
:- attr("node_version_satisfies", Package, Constraint). :- attr("node_version_satisfies", Package, Constraint).
% If there is at least a version that satisfy the constraint, impose a lower % If there is at least a version that satisfy the constraint, impose a lower
% bound on the choice rule to avoid false positives with the error below % bound on the choice rule to avoid false positives with the error below
1 { attr("version", Package, Version) : version_satisfies(Package, Constraint, Version) } 1 { attr("version", Package, Version) : facts(Package, version_satisfies(Constraint, Version)) }
:- attr("node_version_satisfies", Package, Constraint), :- attr("node_version_satisfies", Package, Constraint),
version_satisfies(Package, Constraint, _). facts(Package, version_satisfies(Constraint, _)).
% More specific error message if the version cannot satisfy some constraint % More specific error message if the version cannot satisfy some constraint
% Otherwise covered by `no_version_error` and `versions_conflict_error`. % Otherwise covered by `no_version_error` and `versions_conflict_error`.
error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint) error(10, "Cannot satisfy '{0}@{1}'", Package, Constraint)
:- attr("node_version_satisfies", Package, Constraint), :- attr("node_version_satisfies", Package, Constraint),
attr("version", Package, Version), attr("version", Package, Version),
not version_satisfies(Package, Constraint, Version). not facts(Package, version_satisfies(Constraint, Version)).
attr("node_version_satisfies", Package, Constraint) attr("node_version_satisfies", Package, Constraint)
:- attr("version", Package, Version), version_satisfies(Package, Constraint, Version). :- attr("version", Package, Version), facts(Package, version_satisfies(Constraint, Version)).
#defined version_satisfies/3. #defined version_satisfies/3.
#defined deprecated_version/2. #defined deprecated_version/2.
@ -175,22 +175,22 @@ attr("node_version_satisfies", Package, Constraint)
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% conditions are specified with `condition_requirement` and hold when % conditions are specified with `condition_requirement` and hold when
% corresponding spec attributes hold. % corresponding spec attributes hold.
condition_holds(ID) :- condition_holds(ID, Package) :-
condition(ID, _); facts(Package, condition(ID));
attr(Name, A1) : condition_requirement(ID, Name, A1); attr(Name, A1) : condition_requirement(ID, Name, A1);
attr(Name, A1, A2) : condition_requirement(ID, Name, A1, A2); attr(Name, A1, A2) : condition_requirement(ID, Name, A1, A2);
attr(Name, A1, A2, A3) : condition_requirement(ID, Name, A1, A2, A3); attr(Name, A1, A2, A3) : condition_requirement(ID, Name, A1, A2, A3);
attr(Name, A1, A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4). attr(Name, A1, A2, A3, A4) : condition_requirement(ID, Name, A1, A2, A3, A4).
% condition_holds(ID) implies all imposed_constraints, unless do_not_impose(ID) % condition_holds(ID, Package) implies all imposed_constraints, unless do_not_impose(ID, Package)
% is derived. This allows imposed constraints to be canceled in special cases. % is derived. This allows imposed constraints to be canceled in special cases.
impose(ID) :- condition_holds(ID), not do_not_impose(ID). impose(ID, Package) :- condition_holds(ID, Package), not do_not_impose(ID, Package).
% conditions that hold impose constraints on other specs % conditions that hold impose constraints on other specs
attr(Name, A1) :- impose(ID), imposed_constraint(ID, Name, A1). attr(Name, A1) :- impose(ID, Package), imposed_constraint(ID, Name, A1).
attr(Name, A1, A2) :- impose(ID), imposed_constraint(ID, Name, A1, A2). attr(Name, A1, A2) :- impose(ID, Package), imposed_constraint(ID, Name, A1, A2).
attr(Name, A1, A2, A3) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A3). attr(Name, A1, A2, A3) :- impose(ID, Package), imposed_constraint(ID, Name, A1, A2, A3).
attr(Name, A1, A2, A3, A4) :- impose(ID), imposed_constraint(ID, Name, A1, A2, A3, A4). attr(Name, A1, A2, A3, A4) :- impose(ID, Package), imposed_constraint(ID, Name, A1, A2, A3, A4).
% we cannot have additional variant values when we are working with concrete specs % we cannot have additional variant values when we are working with concrete specs
:- attr("node", Package), attr("hash", Package, Hash), :- attr("node", Package), attr("hash", Package, Hash),
@ -231,17 +231,17 @@ depends_on(Package, Dependency) :- attr("depends_on", Package, Dependency, _).
% concrete specs don't need to be resolved -- they arise from the concrete % concrete specs don't need to be resolved -- they arise from the concrete
% specs themselves. % specs themselves.
dependency_holds(Package, Dependency, Type) :- dependency_holds(Package, Dependency, Type) :-
dependency_condition(ID, Package, Dependency), facts(Package, dependency_condition(ID, Dependency)),
dependency_type(ID, Type), dependency_type(ID, Type),
build(Package), build(Package),
not external(Package), not external(Package),
condition_holds(ID). condition_holds(ID, Package).
% We cut off dependencies of externals (as we don't really know them). % We cut off dependencies of externals (as we don't really know them).
% Don't impose constraints on dependencies that don't exist. % Don't impose constraints on dependencies that don't exist.
do_not_impose(ID) :- do_not_impose(ID, Package) :-
not dependency_holds(Package, Dependency, _), not dependency_holds(Package, Dependency, _),
dependency_condition(ID, Package, Dependency). facts(Package, dependency_condition(ID, Dependency)).
% declared dependencies are real if they're not virtual AND % declared dependencies are real if they're not virtual AND
% the package is not an external. % the package is not an external.
@ -281,9 +281,9 @@ error(100, "Cyclic dependency detected between '{0}' and '{1}' (consider changin
% Conflicts % Conflicts
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
error(1, Msg) :- attr("node", Package), error(1, Msg) :- attr("node", Package),
conflict(Package, TriggerID, ConstraintID, Msg), facts(Package, conflict(TriggerID, ConstraintID, Msg)),
condition_holds(TriggerID), condition_holds(TriggerID, Package),
condition_holds(ConstraintID), condition_holds(ConstraintID, Package),
not external(Package), % ignore conflicts for externals not external(Package), % ignore conflicts for externals
not attr("hash", Package, _). % ignore conflicts for installed packages not attr("hash", Package, _). % ignore conflicts for installed packages
@ -312,7 +312,7 @@ attr("virtual_node", Virtual)
% If there's a virtual node, we must select one and only one provider. % If there's a virtual node, we must select one and only one provider.
% The provider must be selected among the possible providers. % The provider must be selected among the possible providers.
{ provider(Package, Virtual) : possible_provider(Package, Virtual) } { provider(Package, Virtual) : facts(Package, possible_provider(Virtual)) }
:- attr("virtual_node", Virtual). :- attr("virtual_node", Virtual).
error(100, "Cannot find valid provider for virtual {0}", Virtual) error(100, "Cannot find valid provider for virtual {0}", Virtual)
@ -339,8 +339,8 @@ provider(Package, Virtual) :- attr("node", Package), virtual_condition_holds(Pac
% The provider provides the virtual if some provider condition holds. % The provider provides the virtual if some provider condition holds.
virtual_condition_holds(Provider, Virtual) :- virtual_condition_holds(Provider, Virtual) :-
provider_condition(ID, Provider, Virtual), facts(Provider, provider_condition(ID, Virtual)),
condition_holds(ID), condition_holds(ID, Provider),
virtual(Virtual). virtual(Virtual).
% A package cannot be the actual provider for a virtual if it does not % A package cannot be the actual provider for a virtual if it does not
@ -374,7 +374,7 @@ possible_provider_weight(Dependency, Virtual, 0, "external")
possible_provider_weight(Dependency, Virtual, Weight, "packages_yaml") possible_provider_weight(Dependency, Virtual, Weight, "packages_yaml")
:- provider(Dependency, Virtual), :- provider(Dependency, Virtual),
depends_on(Package, Dependency), depends_on(Package, Dependency),
pkg_provider_preference(Package, Virtual, Dependency, Weight). facts(Package, provider_preference(Virtual, Dependency, Weight)).
% A provider mentioned in the default configuration can use a weight % A provider mentioned in the default configuration can use a weight
% according to its priority in the list of providers % according to its priority in the list of providers
@ -408,7 +408,7 @@ possible_provider_weight(Dependency, Virtual, 100, "fallback") :- provider(Depen
% if a package is external its version must be one of the external versions % if a package is external its version must be one of the external versions
{ external_version(Package, Version, Weight): { external_version(Package, Version, Weight):
version_declared(Package, Version, Weight, "external") } facts(Package, version_declared(Version, Weight, "external")) }
:- external(Package). :- external(Package).
error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package) error(100, "Attempted to use external for '{0}' which does not satisfy any configured external spec", Package)
:- external(Package), :- external(Package),
@ -436,7 +436,7 @@ external(Package) :- attr("external_spec_selected", Package, _).
% corresponding external spec. % corresponding external spec.
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
version_declared(Package, Version, Weight, "external"), facts(Package, version_declared(Version, Weight, "external")),
not external(Package), not external(Package),
internal_error("External weight used for internal spec"). internal_error("External weight used for internal spec").
@ -447,7 +447,7 @@ attr("external_spec_selected", Package, LocalIndex) :-
not attr("hash", Package, _). not attr("hash", Package, _).
external_conditions_hold(Package, LocalIndex) :- external_conditions_hold(Package, LocalIndex) :-
possible_external(ID, Package, LocalIndex), condition_holds(ID). facts(Package, possible_external(ID, LocalIndex)), condition_holds(ID, Package).
% it cannot happen that a spec is external, but none of the external specs % it cannot happen that a spec is external, but none of the external specs
% conditions hold. % conditions hold.
@ -477,24 +477,24 @@ activate_requirement(Package, X) :-
activate_requirement(Package, X) :- activate_requirement(Package, X) :-
package_in_dag(Package), package_in_dag(Package),
requirement_group(Package, X), requirement_group(Package, X),
condition_holds(Y), condition_holds(Y, Package),
requirement_conditional(Package, X, Y). requirement_conditional(Package, X, Y).
requirement_group_satisfied(Package, X) :- requirement_group_satisfied(Package, X) :-
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } 1, 1 { condition_holds(Y, Package) : requirement_group_member(Y, Package, X) } 1,
requirement_policy(Package, X, "one_of"), requirement_policy(Package, X, "one_of"),
activate_requirement(Package, X), activate_requirement(Package, X),
requirement_group(Package, X). requirement_group(Package, X).
requirement_weight(Package, Group, W) :- requirement_weight(Package, Group, W) :-
condition_holds(Y), condition_holds(Y, Package),
requirement_has_weight(Y, W), requirement_has_weight(Y, W),
requirement_group_member(Y, Package, Group), requirement_group_member(Y, Package, Group),
requirement_policy(Package, Group, "one_of"), requirement_policy(Package, Group, "one_of"),
requirement_group_satisfied(Package, Group). requirement_group_satisfied(Package, Group).
requirement_group_satisfied(Package, X) :- requirement_group_satisfied(Package, X) :-
1 { condition_holds(Y) : requirement_group_member(Y, Package, X) } , 1 { condition_holds(Y, Package) : requirement_group_member(Y, Package, X) } ,
requirement_policy(Package, X, "any_of"), requirement_policy(Package, X, "any_of"),
activate_requirement(Package, X), activate_requirement(Package, X),
requirement_group(Package, X). requirement_group(Package, X).
@ -514,7 +514,7 @@ requirement_group_satisfied(Package, X) :-
requirement_weight(Package, Group, W) :- requirement_weight(Package, Group, W) :-
W = #min { W = #min {
Z : requirement_has_weight(Y, Z), condition_holds(Y), requirement_group_member(Y, Package, Group); Z : requirement_has_weight(Y, Z), condition_holds(Y, Package), requirement_group_member(Y, Package, Group);
% We need this to avoid an annoying warning during the solve % We need this to avoid an annoying warning during the solve
% concretize.lp:1151:5-11: info: tuple ignored: % concretize.lp:1151:5-11: info: tuple ignored:
% #sup@73 % #sup@73
@ -549,8 +549,12 @@ error(10, Message) :-
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% a variant is a variant of a package if it is a variant under some condition % a variant is a variant of a package if it is a variant under some condition
% and that condition holds % and that condition holds
variant(Package, Variant) :- variant_condition(ID, Package, Variant), node_has_variant(Package, variant(Variant)) :-
condition_holds(ID). facts(Package, conditional_variant(ID, Variant)),
condition_holds(ID, Package).
node_has_variant(Package, variant(Variant)) :- facts(Package, variant(Variant)).
attr("variant_propagate", Package, Variant, Value, Source) :- attr("variant_propagate", Package, Variant, Value, Source) :-
attr("node", Package), attr("node", Package),
@ -560,56 +564,56 @@ attr("variant_propagate", Package, Variant, Value, Source) :-
attr("variant_value", Package, Variant, Value) :- attr("variant_value", Package, Variant, Value) :-
attr("node", Package), attr("node", Package),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
attr("variant_propagate", Package, Variant, Value, _), attr("variant_propagate", Package, Variant, Value, _),
variant_possible_value(Package, Variant, Value). facts(Package, variant_possible_value(Variant, Value)).
error(100, "{0} and {1} cannot both propagate variant '{2}' to package {3} with values '{4}' and '{5}'", Source1, Source2, Variant, Package, Value1, Value2) :- error(100, "{0} and {1} cannot both propagate variant '{2}' to package {3} with values '{4}' and '{5}'", Source1, Source2, Variant, Package, Value1, Value2) :-
attr("variant_propagate", Package, Variant, Value1, Source1), attr("variant_propagate", Package, Variant, Value1, Source1),
attr("variant_propagate", Package, Variant, Value2, Source2), attr("variant_propagate", Package, Variant, Value2, Source2),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
Value1 < Value2. Value1 < Value2.
% a variant cannot be set if it is not a variant on the package % a variant cannot be set if it is not a variant on the package
error(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package) error(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
:- attr("variant_set", Package, Variant), :- attr("variant_set", Package, Variant),
not variant(Package, Variant), not node_has_variant(Package, variant(Variant)),
build(Package). build(Package).
% a variant cannot take on a value if it is not a variant of the package % a variant cannot take on a value if it is not a variant of the package
error(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package) error(100, "Cannot set variant '{0}' for package '{1}' because the variant condition cannot be satisfied for the given spec", Variant, Package)
:- attr("variant_value", Package, Variant, _), :- attr("variant_value", Package, Variant, _),
not variant(Package, Variant), not node_has_variant(Package, variant(Variant)),
build(Package). build(Package).
% if a variant is sticky and not set its value is the default value % if a variant is sticky and not set its value is the default value
attr("variant_value", Package, Variant, Value) :- attr("variant_value", Package, Variant, Value) :-
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
not attr("variant_set", Package, Variant), not attr("variant_set", Package, Variant),
variant_sticky(Package, Variant), facts(Package, variant_sticky(Variant)),
variant_default_value(Package, Variant, Value), variant_default_value(Package, Variant, Value),
build(Package). build(Package).
% at most one variant value for single-valued variants. % at most one variant value for single-valued variants.
{ {
attr("variant_value", Package, Variant, Value) attr("variant_value", Package, Variant, Value)
: variant_possible_value(Package, Variant, Value) : facts(Package, variant_possible_value(Variant, Value))
} }
:- attr("node", Package), :- attr("node", Package),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
build(Package). build(Package).
error(100, "'{0}' required multiple values for single-valued variant '{1}'", Package, Variant) error(100, "'{0}' required multiple values for single-valued variant '{1}'", Package, Variant)
:- attr("node", Package), :- attr("node", Package),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
variant_single_value(Package, Variant), facts(Package, variant_single_value(Variant)),
build(Package), build(Package),
2 { attr("variant_value", Package, Variant, Value) }. 2 { attr("variant_value", Package, Variant, Value) }.
error(100, "No valid value for variant '{1}' of package '{0}'", Package, Variant) error(100, "No valid value for variant '{1}' of package '{0}'", Package, Variant)
:- attr("node", Package), :- attr("node", Package),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
build(Package), build(Package),
not attr("variant_value", Package, Variant, _). not attr("variant_value", Package, Variant, _).
@ -621,7 +625,7 @@ attr("variant_set", Package, Variant) :- attr("variant_set", Package, Variant, _
% have been built w/different variants from older/different package versions. % have been built w/different variants from older/different package versions.
error(10, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Package, Variant, Value) error(10, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Package, Variant, Value)
:- attr("variant_value", Package, Variant, Value), :- attr("variant_value", Package, Variant, Value),
not variant_possible_value(Package, Variant, Value), not facts(Package, variant_possible_value(Variant, Value)),
build(Package). build(Package).
% Some multi valued variants accept multiple values from disjoint sets. % Some multi valued variants accept multiple values from disjoint sets.
@ -630,8 +634,8 @@ error(10, "'Spec({1}={2})' is not a valid value for '{0}' variant '{1}'", Packag
error(100, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come from disjoint value sets", Package, Variant, Value1, Value2) error(100, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come from disjoint value sets", Package, Variant, Value1, Value2)
:- attr("variant_value", Package, Variant, Value1), :- attr("variant_value", Package, Variant, Value1),
attr("variant_value", Package, Variant, Value2), attr("variant_value", Package, Variant, Value2),
variant_value_from_disjoint_sets(Package, Variant, Value1, Set1), facts(Package, variant_value_from_disjoint_sets(Variant, Value1, Set1)),
variant_value_from_disjoint_sets(Package, Variant, Value2, Set2), facts(Package, variant_value_from_disjoint_sets(Variant, Value2, Set2)),
Set1 < Set2, % see[1] Set1 < Set2, % see[1]
build(Package). build(Package).
@ -639,7 +643,7 @@ error(100, "{0} variant '{1}' cannot have values '{2}' and '{3}' as they come fr
% we revert to the default value. If it is set, we force the set value % we revert to the default value. If it is set, we force the set value
attr("variant_value", Package, Variant, Value) attr("variant_value", Package, Variant, Value)
:- attr("node", Package), :- attr("node", Package),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
attr("variant_set", Package, Variant, Value). attr("variant_set", Package, Variant, Value).
% The rules below allow us to prefer default values for variants % The rules below allow us to prefer default values for variants
@ -662,7 +666,7 @@ variant_not_default(Package, Variant, Value)
% A default variant value that is not used % A default variant value that is not used
variant_default_not_used(Package, Variant, Value) variant_default_not_used(Package, Variant, Value)
:- variant_default_value(Package, Variant, Value), :- variant_default_value(Package, Variant, Value),
variant(Package, Variant), node_has_variant(Package, variant(Variant)),
not attr("variant_value", Package, Variant, Value), not attr("variant_value", Package, Variant, Value),
attr("node", Package). attr("node", Package).
@ -670,7 +674,7 @@ variant_default_not_used(Package, Variant, Value)
external_with_variant_set(Package, Variant, Value) external_with_variant_set(Package, Variant, Value)
:- attr("variant_value", Package, Variant, Value), :- attr("variant_value", Package, Variant, Value),
condition_requirement(ID, "variant_value", Package, Variant, Value), condition_requirement(ID, "variant_value", Package, Variant, Value),
possible_external(ID, Package, _), facts(Package, possible_external(ID, _)),
external(Package), external(Package),
attr("node", Package). attr("node", Package).
@ -682,7 +686,7 @@ external_with_variant_set(Package, Variant, Value)
% packages.yaml and the command line) % packages.yaml and the command line)
% %
variant_default_value(Package, Variant, Value) variant_default_value(Package, Variant, Value)
:- variant_default_value_from_package_py(Package, Variant, Value), :- facts(Package, variant_default_value_from_package_py(Variant, Value)),
not variant_default_value_from_packages_yaml(Package, Variant, _), not variant_default_value_from_packages_yaml(Package, Variant, _),
not attr("variant_default_value_from_cli", Package, Variant, _). not attr("variant_default_value_from_cli", Package, Variant, _).
@ -706,9 +710,11 @@ error(100, "{0} variant '{1}' cannot have values '{2}' and 'none'", Package, Var
% when assigned a value. % when assigned a value.
auto_variant("dev_path"). auto_variant("dev_path").
auto_variant("patches"). auto_variant("patches").
variant(Package, Variant)
node_has_variant(Package, variant(Variant))
:- attr("variant_set", Package, Variant, _), auto_variant(Variant). :- attr("variant_set", Package, Variant, _), auto_variant(Variant).
variant_single_value(Package, "dev_path")
facts(Package, variant_single_value("dev_path"))
:- attr("variant_set", Package, "dev_path", _). :- attr("variant_set", Package, "dev_path", _).
% suppress warnings about this atom being unset. It's only set if some % suppress warnings about this atom being unset. It's only set if some
@ -848,7 +854,7 @@ attr("node_target", Package, Target)
node_target_weight(Package, Weight) node_target_weight(Package, Weight)
:- attr("node", Package), :- attr("node", Package),
attr("node_target", Package, Target), attr("node_target", Package, Target),
target_weight(Package, Target, Weight). facts(Package, target_weight(Target, Weight)).
% compatibility rules for targets among nodes % compatibility rules for targets among nodes
node_target_match(Parent, Dependency) node_target_match(Parent, Dependency)
@ -866,8 +872,6 @@ error(100, "'{0} target={1}' is not compatible with this machine", Package, Targ
attr("node_target", Package, Target), attr("node_target", Package, Target),
not target(Target). not target(Target).
#defined package_target_weight/3.
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
% Compiler semantics % Compiler semantics
%----------------------------------------------------------------------------- %-----------------------------------------------------------------------------
@ -981,18 +985,18 @@ compiler_weight(Package, Weight)
:- node_compiler(Package, CompilerID), :- node_compiler(Package, CompilerID),
compiler_name(CompilerID, Compiler), compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, V), compiler_version(CompilerID, V),
node_compiler_preference(Package, Compiler, V, Weight). facts(Package, node_compiler_preference(Compiler, V, Weight)).
compiler_weight(Package, Weight) compiler_weight(Package, Weight)
:- node_compiler(Package, CompilerID), :- node_compiler(Package, CompilerID),
compiler_name(CompilerID, Compiler), compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, V), compiler_version(CompilerID, V),
not node_compiler_preference(Package, Compiler, V, _), not facts(Package, node_compiler_preference(Compiler, V, _)),
default_compiler_preference(CompilerID, Weight). default_compiler_preference(CompilerID, Weight).
compiler_weight(Package, 100) compiler_weight(Package, 100)
:- node_compiler(Package, CompilerID), :- node_compiler(Package, CompilerID),
compiler_name(CompilerID, Compiler), compiler_name(CompilerID, Compiler),
compiler_version(CompilerID, V), compiler_version(CompilerID, V),
not node_compiler_preference(Package, Compiler, V, _), not facts(Package, node_compiler_preference(Compiler, V, _)),
not default_compiler_preference(CompilerID, _). not default_compiler_preference(CompilerID, _).
% For the time being, be strict and reuse only if the compiler match one we have on the system % For the time being, be strict and reuse only if the compiler match one we have on the system
@ -1085,7 +1089,7 @@ attr("no_flags", Package, FlagType)
:- attr("node", Package), 2 { attr("hash", Package, Hash) }. :- attr("node", Package), 2 { attr("hash", Package, Hash) }.
% if a hash is selected, we impose all the constraints that implies % if a hash is selected, we impose all the constraints that implies
impose(Hash) :- attr("hash", Package, Hash). impose(Hash, Package) :- attr("hash", Package, Hash).
% if we haven't selected a hash for a package, we'll be building it % if we haven't selected a hash for a package, we'll be building it
build(Package) :- not attr("hash", Package, _), attr("node", Package). build(Package) :- not attr("hash", Package, _), attr("node", Package).
@ -1124,7 +1128,7 @@ build_priority(Package, 0) :- attr("node", Package), not optimize_for_reuse().
% build deps in the solve, consider using them as a preference to resolve this. % build deps in the solve, consider using them as a preference to resolve this.
:- attr("version", Package, Version), :- attr("version", Package, Version),
version_weight(Package, Weight), version_weight(Package, Weight),
version_declared(Package, Version, Weight, "installed"), facts(Package, version_declared(Version, Weight, "installed")),
not optimize_for_reuse(). not optimize_for_reuse().
#defined installed_hash/2. #defined installed_hash/2.
@ -1339,10 +1343,10 @@ opt_criterion(5, "non-preferred targets").
#heuristic literal_solved(ID) : literal(ID). [50, init] #heuristic literal_solved(ID) : literal(ID). [50, init]
#heuristic attr("hash", Package, Hash) : attr("root", Package). [45, init] #heuristic attr("hash", Package, Hash) : attr("root", Package). [45, init]
#heuristic attr("version", Package, Version) : version_declared(Package, Version, 0), attr("root", Package). [40, true] #heuristic attr("version", Package, Version) : facts(Package, version_declared(Version, 0)), attr("root", Package). [40, true]
#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), attr("root", Package). [40, true] #heuristic version_weight(Package, 0) : facts(Package, version_declared(Version, 0)), attr("root", Package). [40, true]
#heuristic attr("variant_value", Package, Variant, Value) : variant_default_value(Package, Variant, Value), attr("root", Package). [40, true] #heuristic attr("variant_value", Package, Variant, Value) : variant_default_value(Package, Variant, Value), attr("root", Package). [40, true]
#heuristic attr("node_target", Package, Target) : package_target_weight(Target, Package, 0), attr("root", Package). [40, true] #heuristic attr("node_target", Package, Target) : facts(Package, target_weight(Target, 0)), attr("root", Package). [40, true]
#heuristic node_target_weight(Package, 0) : attr("root", Package). [40, true] #heuristic node_target_weight(Package, 0) : attr("root", Package). [40, true]
#heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("root", Package). [40, true] #heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("root", Package). [40, true]
@ -1350,10 +1354,10 @@ opt_criterion(5, "non-preferred targets").
#heuristic provider_weight(Package, Virtual, 0, R) : possible_provider_weight(Package, Virtual, 0, R), attr("virtual_node", Virtual). [30, true] #heuristic provider_weight(Package, Virtual, 0, R) : possible_provider_weight(Package, Virtual, 0, R), attr("virtual_node", Virtual). [30, true]
#heuristic attr("node", Package) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [30, true] #heuristic attr("node", Package) : possible_provider_weight(Package, Virtual, 0, _), attr("virtual_node", Virtual). [30, true]
#heuristic attr("version", Package, Version) : version_declared(Package, Version, 0), attr("node", Package). [20, true] #heuristic attr("version", Package, Version) : facts(Package, version_declared(Version, 0)), attr("node", Package). [20, true]
#heuristic version_weight(Package, 0) : version_declared(Package, Version, 0), attr("node", Package). [20, true] #heuristic version_weight(Package, 0) : facts(Package, version_declared(Version, 0)), attr("node", Package). [20, true]
#heuristic attr("node_target", Package, Target) : package_target_weight(Target, Package, 0), attr("node", Package). [20, true] #heuristic attr("node_target", Package, Target) : facts(Package, target_weight(Target, 0)), attr("node", Package). [20, true]
#heuristic node_target_weight(Package, 0) : attr("node", Package). [20, true] #heuristic node_target_weight(Package, 0) : attr("node", Package). [20, true]
#heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("node", Package). [15, true] #heuristic node_compiler(Package, CompilerID) : default_compiler_preference(ID, 0), compiler_id(ID), attr("node", Package). [15, true]

View file

@ -152,7 +152,7 @@ class Root(Package):
version("1.0", sha256="abcde") version("1.0", sha256="abcde")
depends_on("changing") depends_on("changing")
conflicts("changing~foo") conflicts("^changing~foo")
""" """
packages_dir.join("root", "package.py").write(root_pkg_str, ensure=True) packages_dir.join("root", "package.py").write(root_pkg_str, ensure=True)
@ -1599,7 +1599,7 @@ def test_installed_version_is_selected_only_for_reuse(
pytest.xfail("Known failure of the original concretizer") pytest.xfail("Known failure of the original concretizer")
# Install a dependency that cannot be reused with "root" # Install a dependency that cannot be reused with "root"
# because of a conflict a variant, then delete its version # because of a conflict in a variant, then delete its version
dependency = Spec("changing@1.0~foo").concretized() dependency = Spec("changing@1.0~foo").concretized()
dependency.package.do_install(fake=True, explicit=True) dependency.package.do_install(fake=True, explicit=True)
repo_with_changing_recipe.change({"delete_version": True}) repo_with_changing_recipe.change({"delete_version": True})