summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@gmail.com>2024-08-05 18:24:57 +0200
committerGitHub <noreply@github.com>2024-08-05 09:24:57 -0700
commitb9125ae3e73c6c8d24b4a3b5670fb2005394ba5d (patch)
tree1e0b3ac08e21345a8b0e8848032ea34786dda178
parent0a2b63b0324f1e606c626459215703b63dc11962 (diff)
downloadspack-b9125ae3e73c6c8d24b4a3b5670fb2005394ba5d.tar.gz
spack-b9125ae3e73c6c8d24b4a3b5670fb2005394ba5d.tar.bz2
spack-b9125ae3e73c6c8d24b4a3b5670fb2005394ba5d.tar.xz
spack-b9125ae3e73c6c8d24b4a3b5670fb2005394ba5d.zip
Do not halt concretization on unknown variants in externals (#45326)
* Do not halt concretization on unkwnown variants in externals
-rw-r--r--lib/spack/spack/solver/asp.py57
-rw-r--r--lib/spack/spack/test/concretize.py49
2 files changed, 82 insertions, 24 deletions
diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py
index c4251bc220..53d5aa8cfb 100644
--- a/lib/spack/spack/solver/asp.py
+++ b/lib/spack/spack/solver/asp.py
@@ -1438,16 +1438,14 @@ class SpackSolverSetup:
# caller, we won't emit partial facts.
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(
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(
fn.pkg_fact(required_spec.name, fn.condition_trigger(condition_id, trigger_id))
)
-
if not imposed_spec:
return condition_id
@@ -1696,20 +1694,44 @@ class SpackSolverSetup:
spack.spec.parse_with_version_concrete(x["spec"]) for x in externals
]
- external_specs = []
+ selected_externals = set()
if spec_filters:
for current_filter in spec_filters:
current_filter.factory = lambda: candidate_specs
- external_specs.extend(current_filter.selected_specs())
- else:
- external_specs.extend(candidate_specs)
+ selected_externals.update(current_filter.selected_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
# 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 = [
(v, idx, external_id)
for idx, (v, external_id) in enumerate(sorted(external_versions, reverse=True))
]
@@ -1718,19 +1740,6 @@ class SpackSolverSetup:
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.effect_rules()
diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py
index e1076abd10..f18e1f6854 100644
--- a/lib/spack/spack/test/concretize.py
+++ b/lib/spack/spack/test/concretize.py
@@ -2609,6 +2609,55 @@ class TestConcretize:
assert len(result.specs) == 1
assert result.specs[0] == snd
+ @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()
def duplicates_test_repository():