Do not halt concretization on unknown variants in externals (#45326)
* Do not halt concretization on unkwnown variants in externals
This commit is contained in:
parent
4354288e44
commit
22c815f3d4
2 changed files with 82 additions and 24 deletions
|
@ -1433,16 +1433,14 @@ def condition(
|
||||||
# caller, we won't emit partial facts.
|
# caller, we won't emit partial facts.
|
||||||
|
|
||||||
condition_id = next(self._id_counter)
|
condition_id = next(self._id_counter)
|
||||||
self.gen.fact(fn.pkg_fact(required_spec.name, fn.condition(condition_id)))
|
|
||||||
self.gen.fact(fn.condition_reason(condition_id, msg))
|
|
||||||
|
|
||||||
trigger_id = self._get_condition_id(
|
trigger_id = self._get_condition_id(
|
||||||
required_spec, cache=self._trigger_cache, body=True, transform=transform_required
|
required_spec, cache=self._trigger_cache, body=True, transform=transform_required
|
||||||
)
|
)
|
||||||
|
self.gen.fact(fn.pkg_fact(required_spec.name, fn.condition(condition_id)))
|
||||||
|
self.gen.fact(fn.condition_reason(condition_id, msg))
|
||||||
self.gen.fact(
|
self.gen.fact(
|
||||||
fn.pkg_fact(required_spec.name, fn.condition_trigger(condition_id, trigger_id))
|
fn.pkg_fact(required_spec.name, fn.condition_trigger(condition_id, trigger_id))
|
||||||
)
|
)
|
||||||
|
|
||||||
if not imposed_spec:
|
if not imposed_spec:
|
||||||
return condition_id
|
return condition_id
|
||||||
|
|
||||||
|
@ -1691,19 +1689,43 @@ def external_packages(self):
|
||||||
spack.spec.parse_with_version_concrete(x["spec"]) for x in externals
|
spack.spec.parse_with_version_concrete(x["spec"]) for x in externals
|
||||||
]
|
]
|
||||||
|
|
||||||
external_specs = []
|
selected_externals = set()
|
||||||
if spec_filters:
|
if spec_filters:
|
||||||
for current_filter in spec_filters:
|
for current_filter in spec_filters:
|
||||||
current_filter.factory = lambda: candidate_specs
|
current_filter.factory = lambda: candidate_specs
|
||||||
external_specs.extend(current_filter.selected_specs())
|
selected_externals.update(current_filter.selected_specs())
|
||||||
else:
|
|
||||||
external_specs.extend(candidate_specs)
|
# Emit facts for externals specs. Note that "local_idx" is the index of the spec
|
||||||
|
# in packages:<pkg_name>:externals. This means:
|
||||||
|
#
|
||||||
|
# packages:<pkg_name>:externals[local_idx].spec == spec
|
||||||
|
external_versions = []
|
||||||
|
for local_idx, spec in enumerate(candidate_specs):
|
||||||
|
msg = f"{spec.name} available as external when satisfying {spec}"
|
||||||
|
|
||||||
|
if spec_filters and spec not in selected_externals:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not spec.versions.concrete:
|
||||||
|
warnings.warn(f"cannot use the external spec {spec}: needs a concrete version")
|
||||||
|
continue
|
||||||
|
|
||||||
|
def external_imposition(input_spec, requirements):
|
||||||
|
return requirements + [
|
||||||
|
fn.attr("external_conditions_hold", input_spec.name, local_idx)
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.condition(spec, spec, msg=msg, transform_imposed=external_imposition)
|
||||||
|
except (spack.error.SpecError, RuntimeError) as e:
|
||||||
|
warnings.warn(f"while setting up external spec {spec}: {e}")
|
||||||
|
continue
|
||||||
|
external_versions.append((spec.version, local_idx))
|
||||||
|
self.possible_versions[spec.name].add(spec.version)
|
||||||
|
self.gen.newline()
|
||||||
|
|
||||||
# Order the external versions to prefer more recent versions
|
# Order the external versions to prefer more recent versions
|
||||||
# even if specs in packages.yaml are not ordered that way
|
# even if specs in packages.yaml are not ordered that way
|
||||||
external_versions = [
|
|
||||||
(x.version, external_id) for external_id, x in enumerate(external_specs)
|
|
||||||
]
|
|
||||||
external_versions = [
|
external_versions = [
|
||||||
(v, idx, external_id)
|
(v, idx, external_id)
|
||||||
for idx, (v, external_id) in enumerate(sorted(external_versions, reverse=True))
|
for idx, (v, external_id) in enumerate(sorted(external_versions, reverse=True))
|
||||||
|
@ -1713,19 +1735,6 @@ def external_packages(self):
|
||||||
DeclaredVersion(version=version, idx=idx, origin=Provenance.EXTERNAL)
|
DeclaredVersion(version=version, idx=idx, origin=Provenance.EXTERNAL)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Declare external conditions with a local index into packages.yaml
|
|
||||||
for local_idx, spec in enumerate(external_specs):
|
|
||||||
msg = "%s available as external when satisfying %s" % (spec.name, spec)
|
|
||||||
|
|
||||||
def external_imposition(input_spec, requirements):
|
|
||||||
return requirements + [
|
|
||||||
fn.attr("external_conditions_hold", input_spec.name, local_idx)
|
|
||||||
]
|
|
||||||
|
|
||||||
self.condition(spec, spec, msg=msg, transform_imposed=external_imposition)
|
|
||||||
self.possible_versions[spec.name].add(spec.version)
|
|
||||||
self.gen.newline()
|
|
||||||
|
|
||||||
self.trigger_rules()
|
self.trigger_rules()
|
||||||
self.effect_rules()
|
self.effect_rules()
|
||||||
|
|
||||||
|
|
|
@ -2574,6 +2574,55 @@ def test_can_reuse_concrete_externals_for_dependents(self, mutable_config, tmp_p
|
||||||
sombrero = result.specs[0]
|
sombrero = result.specs[0]
|
||||||
assert sombrero["externaltool"].dag_hash() == external_spec.dag_hash()
|
assert sombrero["externaltool"].dag_hash() == external_spec.dag_hash()
|
||||||
|
|
||||||
|
@pytest.mark.regression("45321")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"corrupted_str",
|
||||||
|
[
|
||||||
|
"cmake@3.4.3 foo=bar", # cmake has no variant "foo"
|
||||||
|
"mvdefaults@1.0 foo=a,d", # variant "foo" has no value "d"
|
||||||
|
"cmake %gcc", # spec has no version
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_corrupted_external_does_not_halt_concretization(self, corrupted_str, mutable_config):
|
||||||
|
"""Tests that having a wrong variant in an external spec doesn't stop concretization"""
|
||||||
|
corrupted_spec = Spec(corrupted_str)
|
||||||
|
packages_yaml = {
|
||||||
|
f"{corrupted_spec.name}": {
|
||||||
|
"externals": [{"spec": corrupted_str, "prefix": "/dev/null"}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutable_config.set("packages", packages_yaml)
|
||||||
|
# Assert we don't raise due to the corrupted external entry above
|
||||||
|
s = Spec("pkg-a").concretized()
|
||||||
|
assert s.concrete
|
||||||
|
|
||||||
|
@pytest.mark.regression("44828")
|
||||||
|
@pytest.mark.not_on_windows("Tests use linux paths")
|
||||||
|
def test_correct_external_is_selected_from_packages_yaml(self, mutable_config):
|
||||||
|
"""Tests that when filtering external specs, the correct external is selected to
|
||||||
|
reconstruct the prefix, and other external attributes.
|
||||||
|
"""
|
||||||
|
packages_yaml = {
|
||||||
|
"cmake": {
|
||||||
|
"externals": [
|
||||||
|
{"spec": "cmake@3.23.1 %gcc", "prefix": "/tmp/prefix1"},
|
||||||
|
{"spec": "cmake@3.23.1 %clang", "prefix": "/tmp/prefix2"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
concretizer_yaml = {
|
||||||
|
"reuse": {"roots": True, "from": [{"type": "external", "exclude": ["%gcc"]}]}
|
||||||
|
}
|
||||||
|
mutable_config.set("packages", packages_yaml)
|
||||||
|
mutable_config.set("concretizer", concretizer_yaml)
|
||||||
|
|
||||||
|
s = Spec("cmake").concretized()
|
||||||
|
|
||||||
|
# Check that we got the properties from the right external
|
||||||
|
assert s.external
|
||||||
|
assert s.satisfies("%clang")
|
||||||
|
assert s.prefix == "/tmp/prefix2"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def duplicates_test_repository():
|
def duplicates_test_repository():
|
||||||
|
|
Loading…
Reference in a new issue