Concretizer should respect namespace of reused specs (#45538)

* concretize.lp: improve coverage of internal_error facts
* concretizer: track namespaces for reused packages
* regression test
This commit is contained in:
Greg Becker 2024-08-09 13:51:34 -07:00 committed by GitHub
parent da33c12ad4
commit 7a83cdbcc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 94 additions and 29 deletions

View File

@ -1851,6 +1851,8 @@ def _spec_clauses(
if spec.name: if spec.name:
clauses.append(f.node(spec.name) if not spec.virtual else f.virtual_node(spec.name)) clauses.append(f.node(spec.name) if not spec.virtual else f.virtual_node(spec.name))
if spec.namespace:
clauses.append(f.namespace(spec.name, spec.namespace))
clauses.extend(self.spec_versions(spec)) clauses.extend(self.spec_versions(spec))
@ -2748,6 +2750,7 @@ class _Head:
"""ASP functions used to express spec clauses in the HEAD of a rule""" """ASP functions used to express spec clauses in the HEAD of a rule"""
node = fn.attr("node") node = fn.attr("node")
namespace = fn.attr("namespace_set")
virtual_node = fn.attr("virtual_node") virtual_node = fn.attr("virtual_node")
node_platform = fn.attr("node_platform_set") node_platform = fn.attr("node_platform_set")
node_os = fn.attr("node_os_set") node_os = fn.attr("node_os_set")
@ -2763,6 +2766,7 @@ class _Body:
"""ASP functions used to express spec clauses in the BODY of a rule""" """ASP functions used to express spec clauses in the BODY of a rule"""
node = fn.attr("node") node = fn.attr("node")
namespace = fn.attr("namespace")
virtual_node = fn.attr("virtual_node") virtual_node = fn.attr("virtual_node")
node_platform = fn.attr("node_platform") node_platform = fn.attr("node_platform")
node_os = fn.attr("node_os") node_os = fn.attr("node_os")

View File

@ -18,38 +18,79 @@
{ attr("virtual_node", node(0..X-1, Package)) } :- max_dupes(Package, X), virtual(Package). { attr("virtual_node", node(0..X-1, Package)) } :- max_dupes(Package, X), virtual(Package).
% Integrity constraints on DAG nodes % Integrity constraints on DAG nodes
:- attr("root", PackageNode), not attr("node", PackageNode). :- attr("root", PackageNode),
:- attr("version", PackageNode, _), not attr("node", PackageNode), not attr("virtual_node", PackageNode). not attr("node", PackageNode),
:- attr("node_version_satisfies", PackageNode, _), not attr("node", PackageNode), not attr("virtual_node", PackageNode). internal_error("Every root must be a node").
:- attr("hash", PackageNode, _), not attr("node", PackageNode). :- attr("version", PackageNode, _),
:- attr("node_platform", PackageNode, _), not attr("node", PackageNode). not attr("node", PackageNode),
:- attr("node_os", PackageNode, _), not attr("node", PackageNode). not attr("virtual_node", PackageNode),
:- attr("node_target", PackageNode, _), not attr("node", PackageNode). internal_error("Only nodes and virtual_nodes can have versions").
:- attr("node_compiler_version", PackageNode, _, _), not attr("node", PackageNode). :- attr("node_version_satisfies", PackageNode, _),
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode). not attr("node", PackageNode),
:- attr("node_flag_compiler_default", PackageNode), not attr("node", PackageNode). not attr("virtual_node", PackageNode),
:- attr("node_flag", PackageNode, _, _), not attr("node", PackageNode). internal_error("Only nodes and virtual_nodes can have version satisfaction").
:- attr("external_spec_selected", PackageNode, _), not attr("node", PackageNode). :- attr("hash", PackageNode, _),
:- attr("depends_on", ParentNode, _, _), not attr("node", ParentNode). not attr("node", PackageNode),
:- attr("depends_on", _, ChildNode, _), not attr("node", ChildNode). internal_error("Only nodes can have hashes").
:- attr("node_flag_source", ParentNode, _, _), not attr("node", ParentNode). :- attr("node_platform", PackageNode, _),
:- attr("node_flag_source", _, _, ChildNode), not attr("node", ChildNode). not attr("node", PackageNode),
:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode), internal_error("virtual node with no provider"). internal_error("Only nodes can have platforms").
:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode), internal_error("provider with no virtual node"). :- attr("node_os", PackageNode, _), not attr("node", PackageNode),
:- provider(PackageNode, _), not attr("node", PackageNode), internal_error("provider with no real node"). internal_error("Only nodes can have node_os").
:- attr("node_target", PackageNode, _), not attr("node", PackageNode),
internal_error("Only nodes can have node_target").
:- attr("node_compiler_version", PackageNode, _, _), not attr("node", PackageNode),
internal_error("Only nodes can have node_compiler_version").
:- attr("variant_value", PackageNode, _, _), not attr("node", PackageNode),
internal_error("variant_value true for a non-node").
:- attr("node_flag_compiler_default", PackageNode), not attr("node", PackageNode),
internal_error("node_flag_compiler_default true for non-node").
:- attr("node_flag", PackageNode, _, _), not attr("node", PackageNode),
internal_error("node_flag assigned for non-node").
:- attr("external_spec_selected", PackageNode, _), not attr("node", PackageNode),
internal_error("external_spec_selected for non-node").
:- attr("depends_on", ParentNode, _, _), not attr("node", ParentNode),
internal_error("non-node depends on something").
:- attr("depends_on", _, ChildNode, _), not attr("node", ChildNode),
internal_error("something depends_on a non-node").
:- attr("node_flag_source", Node, _, _), not attr("node", Node),
internal_error("node_flag_source assigned for a non-node").
:- attr("node_flag_source", _, _, SourceNode), not attr("node", SourceNode),
internal_error("node_flag_source assigned with a non-node source").
:- attr("virtual_node", VirtualNode), not provider(_, VirtualNode),
internal_error("virtual node with no provider").
:- provider(_, VirtualNode), not attr("virtual_node", VirtualNode),
internal_error("provider with no virtual node").
:- provider(PackageNode, _), not attr("node", PackageNode),
internal_error("provider with no real node").
:- attr("root", node(ID, PackageNode)), ID > min_dupe_id, internal_error("root with a non-minimal duplicate ID"). :- attr("root", node(ID, PackageNode)), ID > min_dupe_id,
internal_error("root with a non-minimal duplicate ID").
% Nodes in the "root" unification set cannot depend on non-root nodes if the dependency is "link" or "run" % Nodes in the "root" unification set cannot depend on non-root nodes if the dependency is "link" or "run"
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("link dependency out of the root unification set"). :- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "link"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("link dependency out of the root unification set").
:- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("run dependency out of the root unification set"). :- attr("depends_on", node(min_dupe_id, Package), node(ID, _), "run"), ID != min_dupe_id, unification_set("root", node(min_dupe_id, Package)), internal_error("run dependency out of the root unification set").
% Namespaces are statically assigned by a package fact % Namespaces are statically assigned by a package fact if not otherwise set
attr("namespace", node(ID, Package), Namespace) :- attr("node", node(ID, Package)), pkg_fact(Package, namespace(Namespace)). error(100, "{0} does not have a namespace", Package) :- attr("node", node(ID, Package)),
not attr("namespace", node(ID, Package), _),
internal_error("A node must have a namespace").
error(100, "{0} cannot come from both {1} and {2} namespaces", Package, NS1, NS2) :- attr("node", node(ID, Package)),
attr("namespace", node(ID, Package), NS1),
attr("namespace", node(ID, Package), NS2),
NS1 != NS2,
internal_error("A node cannot have two namespaces").
attr("namespace", node(ID, Package), Namespace) :- attr("namespace_set", node(ID, Package), Namespace).
attr("namespace", node(ID, Package), Namespace)
:- attr("node", node(ID, Package)),
not attr("namespace_set", node(ID, Package), _),
pkg_fact(Package, namespace(Namespace)).
% Rules on "unification sets", i.e. on sets of nodes allowing a single configuration of any given package % Rules on "unification sets", i.e. on sets of nodes allowing a single configuration of any given package
unify(SetID, PackageName) :- unification_set(SetID, node(_, PackageName)). unify(SetID, PackageName) :- unification_set(SetID, node(_, PackageName)).
:- 2 { unification_set(SetID, node(_, PackageName)) }, unify(SetID, PackageName). :- 2 { unification_set(SetID, node(_, PackageName)) }, unify(SetID, PackageName),
internal_error("Cannot have multiple unification sets IDs for one set").
unification_set("root", PackageNode) :- attr("root", PackageNode). unification_set("root", PackageNode) :- attr("root", PackageNode).
unification_set(SetID, ChildNode) :- attr("depends_on", ParentNode, ChildNode, Type), Type != "build", unification_set(SetID, ParentNode). unification_set(SetID, ChildNode) :- attr("depends_on", ParentNode, ChildNode, Type), Type != "build", unification_set(SetID, ParentNode).
@ -75,7 +116,8 @@ unification_set(SetID, VirtualNode)
% as a build dependency. % as a build dependency.
% %
% We'll need to relax the rule before we get to actual cross-compilation % We'll need to relax the rule before we get to actual cross-compilation
:- depends_on(ParentNode, node(X, Dependency)), depends_on(ParentNode, node(Y, Dependency)), X < Y. :- depends_on(ParentNode, node(X, Dependency)), depends_on(ParentNode, node(Y, Dependency)), X < Y,
internal_error("Cannot split link/build deptypes for a single edge (yet)").
#defined multiple_unification_sets/1. #defined multiple_unification_sets/1.
@ -131,7 +173,8 @@ mentioned_in_literal(Root, Mentioned) :- mentioned_in_literal(TriggerID, Root, M
condition_set(node(min_dupe_id, Root), node(min_dupe_id, Root)) :- mentioned_in_literal(Root, Root). condition_set(node(min_dupe_id, Root), node(min_dupe_id, Root)) :- mentioned_in_literal(Root, Root).
1 { condition_set(node(min_dupe_id, Root), node(0..Y-1, Mentioned)) : max_dupes(Mentioned, Y) } 1 :- 1 { condition_set(node(min_dupe_id, Root), node(0..Y-1, Mentioned)) : max_dupes(Mentioned, Y) } 1 :-
mentioned_in_literal(Root, Mentioned), Mentioned != Root. mentioned_in_literal(Root, Mentioned), Mentioned != Root,
internal_error("must have exactly one condition_set for literals").
% Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots" % Discriminate between "roots" that have been explicitly requested, and roots that are deduced from "virtual roots"
explicitly_requested_root(node(min_dupe_id, Package)) :- explicitly_requested_root(node(min_dupe_id, Package)) :-
@ -151,7 +194,8 @@ associated_with_root(RootNode, ChildNode) :-
:- attr("root", RootNode), :- attr("root", RootNode),
condition_set(RootNode, node(X, Package)), condition_set(RootNode, node(X, Package)),
not virtual(Package), not virtual(Package),
not associated_with_root(RootNode, node(X, Package)). not associated_with_root(RootNode, node(X, Package)),
internal_error("nodes in root condition set must be associated with root").
#defined concretize_everything/0. #defined concretize_everything/0.
#defined literal/1. #defined literal/1.
@ -385,8 +429,10 @@ imposed_nodes(ConditionID, PackageNode, node(X, A1))
condition_set(PackageNode, node(X, A1)), condition_set(PackageNode, node(X, A1)),
attr("hash", PackageNode, ConditionID). attr("hash", PackageNode, ConditionID).
:- imposed_packages(ID, A1), impose(ID, PackageNode), not condition_set(PackageNode, node(_, A1)). :- imposed_packages(ID, A1), impose(ID, PackageNode), not condition_set(PackageNode, node(_, A1)),
:- imposed_packages(ID, A1), impose(ID, PackageNode), not imposed_nodes(ID, PackageNode, node(_, A1)). internal_error("Imposing constraint outside of condition set").
:- imposed_packages(ID, A1), impose(ID, PackageNode), not imposed_nodes(ID, PackageNode, node(_, A1)),
internal_error("Imposing constraint outside of imposed_nodes").
% Conditions that hold impose may impose constraints on other specs % Conditions that hold impose may impose constraints on other specs
attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)). attr(Name, node(X, A1)) :- impose(ID, PackageNode), imposed_constraint(ID, Name, A1), imposed_nodes(ID, PackageNode, node(X, A1)).
@ -416,7 +462,8 @@ provider(ProviderNode, VirtualNode) :- attr("provider_set", ProviderNode, Virtua
% satisfy the dependency. % satisfy the dependency.
1 { attr("depends_on", node(X, A1), node(0..Y-1, A2), A3) : max_dupes(A2, Y) } 1 1 { attr("depends_on", node(X, A1), node(0..Y-1, A2), A3) : max_dupes(A2, Y) } 1
:- impose(ID, node(X, A1)), :- impose(ID, node(X, A1)),
imposed_constraint(ID, "depends_on", A1, A2, A3). imposed_constraint(ID, "depends_on", A1, A2, A3),
internal_error("Build deps must land in exactly one duplicate").
% Reconstruct virtual dependencies for reused specs % Reconstruct virtual dependencies for reused specs
attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual) attr("virtual_on_edge", node(X, A1), node(Y, A2), Virtual)

View File

@ -1761,6 +1761,20 @@ def test_reuse_with_unknown_namespace_dont_raise(
s = Spec("pkg-c").concretized() s = Spec("pkg-c").concretized()
assert s.namespace == "builtin.mock" assert s.namespace == "builtin.mock"
@pytest.mark.regression("45538")
def test_reuse_from_other_namespace_no_raise(self, tmpdir, temporary_store, monkeypatch):
myrepo = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("mock.repo"), namespace="myrepo")
myrepo.add_package("zlib")
builtin = Spec("zlib").concretized()
builtin.package.do_install(fake=True, explicit=True)
with spack.repo.use_repositories(myrepo.root, override=False):
with spack.config.override("concretizer:reuse", True):
myrepo = Spec("myrepo.zlib").concretized()
assert myrepo.namespace == "myrepo"
@pytest.mark.regression("28259") @pytest.mark.regression("28259")
def test_reuse_with_unknown_package_dont_raise(self, tmpdir, temporary_store, monkeypatch): def test_reuse_with_unknown_package_dont_raise(self, tmpdir, temporary_store, monkeypatch):
builder = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("mock.repo"), namespace="myrepo") builder = spack.repo.MockRepositoryBuilder(tmpdir.mkdir("mock.repo"), namespace="myrepo")