From 39ace5fc45f67a870fef352ca5592892c54cf114 Mon Sep 17 00:00:00 2001 From: Harmen Stoppels Date: Thu, 30 May 2024 07:31:28 +0200 Subject: [PATCH] concretizer: enforce host compat when reuse only (#44428) Fixes a bug in the concretizer where specs depending on a host incompatible libc would be used. This bug triggers when nothing is built. In the case where everything is reused, there is no libc provider from the perspective of the solver, there is only compatible_libc. This commit ensures that we require a host compatible libc on any reused spec, additionally to requiring compat with the chosen libc provider. Co-authored-by: Massimiliano Culpo --- lib/spack/spack/solver/asp.py | 2 +- lib/spack/spack/solver/libc_compatibility.lp | 12 ++++++++-- lib/spack/spack/test/concretize.py | 25 +++++++++++++++++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/spack/spack/solver/asp.py b/lib/spack/spack/solver/asp.py index 094ebaf170e..49b5c626ce1 100644 --- a/lib/spack/spack/solver/asp.py +++ b/lib/spack/spack/solver/asp.py @@ -2440,7 +2440,7 @@ def setup( if using_libc_compatibility(): for libc in self.libcs: - self.gen.fact(fn.allowed_libc(libc.name, libc.version)) + self.gen.fact(fn.host_libc(libc.name, libc.version)) if not allow_deprecated: self.gen.fact(fn.deprecated_versions_not_allowed()) diff --git a/lib/spack/spack/solver/libc_compatibility.lp b/lib/spack/spack/solver/libc_compatibility.lp index 3e2c00e3721..6c324936568 100644 --- a/lib/spack/spack/solver/libc_compatibility.lp +++ b/lib/spack/spack/solver/libc_compatibility.lp @@ -24,12 +24,20 @@ has_built_packages() :- build(X), not external(X). % A libc is needed in the DAG :- has_built_packages(), not provider(_, node(0, "libc")). -% The libc must be chosen among available ones +% Non-libc reused specs must be host libc compatible. In case we build packages, we get a +% host compatible libc provider from other rules. If nothing is built, there is no libc provider, +% since it's pruned from reusable specs, meaning we have to explicitly impose reused specs are host +% compatible. +:- attr("hash", node(R, ReusedPackage), Hash), + not provider(node(R, ReusedPackage), node(0, "libc")), + not attr("compatible_libc", node(R, ReusedPackage), _, _). + +% The libc provider must be one that a compiler can target :- has_built_packages(), provider(node(X, LibcPackage), node(0, "libc")), attr("node", node(X, LibcPackage)), attr("version", node(X, LibcPackage), LibcVersion), - not allowed_libc(LibcPackage, LibcVersion). + not host_libc(LibcPackage, LibcVersion). % A built node must depend on libc :- build(PackageNode), diff --git a/lib/spack/spack/test/concretize.py b/lib/spack/spack/test/concretize.py index 0b4ce5b42a8..b1cd35bff94 100644 --- a/lib/spack/spack/test/concretize.py +++ b/lib/spack/spack/test/concretize.py @@ -28,7 +28,7 @@ import spack.variant as vt from spack.concretize import find_spec from spack.spec import CompilerSpec, Spec -from spack.version import Version, ver +from spack.version import Version, VersionList, ver def check_spec(abstract, concrete): @@ -2570,6 +2570,29 @@ def test_can_reuse_concrete_externals_for_dependents(self, mutable_config, tmp_p sombrero = result.specs[0] assert sombrero["externaltool"].dag_hash() == external_spec.dag_hash() + @pytest.mark.only_clingo("Original concretizer cannot reuse") + def test_cannot_reuse_host_incompatible_libc(self): + """Test whether reuse concretization correctly fails to reuse a spec with a host + incompatible libc.""" + if not spack.solver.asp.using_libc_compatibility(): + pytest.skip("This test requires libc nodes") + + # We install b@1 ^glibc@2.30, and b@0 ^glibc@2.28. The former is not host compatible, the + # latter is. + fst = Spec("b@1").concretized() + fst._mark_concrete(False) + fst.dependencies("glibc")[0].versions = VersionList(["=2.30"]) + fst._mark_concrete(True) + snd = Spec("b@0").concretized() + + # The spec b@1 ^glibc@2.30 is "more optimal" than b@0 ^glibc@2.28, but due to glibc + # incompatibility, it should not be reused. + solver = spack.solver.asp.Solver() + setup = spack.solver.asp.SpackSolverSetup() + result, _, _ = solver.driver.solve(setup, [Spec("b")], reuse=[fst, snd]) + assert len(result.specs) == 1 + assert result.specs[0] == snd + @pytest.fixture() def duplicates_test_repository():