Skip to content

Commit bd9eb4f

Browse files
aoeuicopybara-github
authored andcommitted
Sort out of order transitive packages before adding. (#18666)
PiperOrigin-RevId: 541656611 Change-Id: Ie6c365275003343dc2bf0520a305e005ec48550b
1 parent 1fe02c4 commit bd9eb4f

26 files changed

+687
-291
lines changed

src/main/java/com/google/devtools/build/lib/analysis/BUILD

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2361,19 +2361,19 @@ java_library(
23612361
srcs = ["constraints/IncompatibleTargetChecker.java"],
23622362
deps = [
23632363
":analysis_cluster",
2364+
":config/build_configuration",
2365+
":config/config_conditions",
2366+
":configured_target",
2367+
":configured_target_value",
23642368
":constraints/environment_collection",
23652369
":constraints/supported_environments",
2370+
":dependency_kind",
23662371
":file_provider",
23672372
":incompatible_platform_provider",
2373+
":target_and_configuration",
23682374
":test/test_configuration",
2375+
":transitive_dependency_state",
23692376
":transitive_info_provider_map_builder",
2370-
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
2371-
"//src/main/java/com/google/devtools/build/lib/analysis:config/build_configuration",
2372-
"//src/main/java/com/google/devtools/build/lib/analysis:config/config_conditions",
2373-
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
2374-
"//src/main/java/com/google/devtools/build/lib/analysis:configured_target_value",
2375-
"//src/main/java/com/google/devtools/build/lib/analysis:dependency_kind",
2376-
"//src/main/java/com/google/devtools/build/lib/analysis:target_and_configuration",
23772377
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
23782378
"//src/main/java/com/google/devtools/build/lib/analysis/platform:utils",
23792379
"//src/main/java/com/google/devtools/build/lib/cmdline",
@@ -2782,3 +2782,20 @@ java_library(
27822782
"//third_party:guava",
27832783
],
27842784
)
2785+
2786+
java_library(
2787+
name = "transitive_dependency_state",
2788+
srcs = ["TransitiveDependencyState.java"],
2789+
deps = [
2790+
"//src/main/java/com/google/devtools/build/lib/causes",
2791+
"//src/main/java/com/google/devtools/build/lib/cmdline",
2792+
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
2793+
"//src/main/java/com/google/devtools/build/lib/packages",
2794+
"//src/main/java/com/google/devtools/build/lib/skyframe:aspect_key_creator",
2795+
"//src/main/java/com/google/devtools/build/lib/skyframe:build_configuration",
2796+
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
2797+
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
2798+
"//third_party:guava",
2799+
"//third_party:jsr305",
2800+
],
2801+
)
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
// Copyright 2023 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
package com.google.devtools.build.lib.analysis;
15+
16+
import static com.google.common.collect.Comparators.lexicographical;
17+
import static java.util.Comparator.comparing;
18+
import static java.util.Comparator.naturalOrder;
19+
import static java.util.Comparator.nullsFirst;
20+
21+
import com.google.devtools.build.lib.causes.Cause;
22+
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
23+
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
24+
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
25+
import com.google.devtools.build.lib.packages.AspectDescriptor;
26+
import com.google.devtools.build.lib.packages.Package;
27+
import com.google.devtools.build.lib.packages.Target;
28+
import com.google.devtools.build.lib.skyframe.AspectKeyCreator.AspectKey;
29+
import com.google.devtools.build.lib.skyframe.BuildConfigurationKey;
30+
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
31+
import com.google.devtools.build.lib.skyframe.ConfiguredTargetKey;
32+
import java.util.ArrayList;
33+
import java.util.Collections;
34+
import java.util.Comparator;
35+
import java.util.HashSet;
36+
import java.util.TreeMap;
37+
import java.util.concurrent.ConcurrentHashMap;
38+
import javax.annotation.Nullable;
39+
40+
/** Groups state associated with transitive dependencies. */
41+
public final class TransitiveDependencyState {
42+
private final NestedSetBuilder<Cause> transitiveRootCauses;
43+
44+
/**
45+
* State for constructing the packages transitively loaded for the value being built.
46+
*
47+
* <p>See {@link
48+
* com.google.devtools.build.lib.analysis.ConfiguredObjectValue#getTransitivePackages}.
49+
*
50+
* <p>Non-null when transitive packages are tracked, determined by {@link
51+
* com.google.devtools.build.lib.skyframe.SkyframeExecutor#shouldStoreTransitivePackagesInLoadingAndAnalysis}.
52+
*/
53+
@Nullable private final PackageCollector packageCollector;
54+
55+
/**
56+
* Contains packages that were previously requested by transitive dependencies.
57+
*
58+
* <p>When the {@link ConfiguredTargetFunction} computes a value, it depends on properties of its
59+
* dependencies. In some cases, those values are read directly out of the dependency's underlying
60+
* {@link Target}. All instances of this are to be restricted to where {@link
61+
* ConfiguredTargetAndData#target} is read.
62+
*
63+
* <p>More ideally, those properties would be conveyed via providers of those dependencies, but
64+
* doing so would adversely affect resting heap usage whereas {@link ConfiguredTargetAndData} is
65+
* ephemeral. Distributed implementations will include these properties in an extra providers. It
66+
* won't affect memory because the underlying package won't exist on the node loading it remotely.
67+
*
68+
* <p>It's valid to obtain {@link Package}s of dependencies from this map instead of creating an
69+
* edge in {@code Skyframe} due to the transitive dependency through the {@link ConfiguredTarget}
70+
* if it is scoped to the invocation. Invalidation of the {@link Package} propagates upwards
71+
* through the dependency. This is compatible with bottom-up change pruning because {@link
72+
* ConfiguredTargetValue} uses identity equals.
73+
*/
74+
@Nullable // TODO(b/261521010): make this non-null.
75+
private final ConcurrentHashMap<PackageIdentifier, Package> prerequisitePackages;
76+
77+
public TransitiveDependencyState(
78+
boolean storeTransitivePackages,
79+
@Nullable ConcurrentHashMap<PackageIdentifier, Package> prerequisitePackages) {
80+
this.transitiveRootCauses = NestedSetBuilder.stableOrder();
81+
this.packageCollector = storeTransitivePackages ? new PackageCollector() : null;
82+
this.prerequisitePackages = prerequisitePackages;
83+
}
84+
85+
public static TransitiveDependencyState createForTesting() {
86+
return new TransitiveDependencyState(
87+
/* storeTransitivePackages= */ false,
88+
// Passing an empty map here means that there will be few, if any, existing prerequisite
89+
// Packages. This causes the underlying code to fall back on declaring Package edges for
90+
// prerequisites.
91+
new ConcurrentHashMap<>());
92+
}
93+
94+
public NestedSetBuilder<Cause> transitiveRootCauses() {
95+
return transitiveRootCauses;
96+
}
97+
98+
@Nullable
99+
public NestedSet<Package> transitivePackages() {
100+
if (packageCollector == null) {
101+
return null;
102+
}
103+
return packageCollector.buildSet();
104+
}
105+
106+
public void addTransitiveCauses(NestedSet<Cause> transitiveCauses) {
107+
transitiveRootCauses.addTransitive(transitiveCauses);
108+
}
109+
110+
public void addTransitiveCause(Cause cause) {
111+
transitiveRootCauses.add(cause);
112+
}
113+
114+
public boolean storeTransitivePackages() {
115+
return packageCollector != null;
116+
}
117+
118+
/** Adds to the set of transitive packages if {@link #storeTransitivePackages} is true. */
119+
public void updateTransitivePackages(Package pkg) {
120+
if (packageCollector == null) {
121+
return;
122+
}
123+
packageCollector.packages.add(pkg);
124+
}
125+
126+
/** Adds to the set of transitive packages if {@link #storeTransitivePackages} is true. */
127+
public void updateTransitivePackages(ConfiguredTargetKey key, NestedSet<Package> packages) {
128+
if (packageCollector == null) {
129+
return;
130+
}
131+
packageCollector.configuredTargetPackages.put(key, packages);
132+
}
133+
134+
/** Adds to the set of transitive packages if {@link #storeTransitivePackages} is true. */
135+
public void updateTransitivePackages(AspectKey key, NestedSet<Package> packages) {
136+
if (packageCollector == null) {
137+
return;
138+
}
139+
packageCollector.aspectPackages.put(key, packages);
140+
}
141+
142+
/** Sets a batch of transitive packages to be added. */
143+
public void setTransitivePackagesBatch(NestedSet<Package> packages) {
144+
if (packageCollector == null) {
145+
return;
146+
}
147+
packageCollector.transitivePackagesBatch = packages;
148+
}
149+
150+
@Nullable
151+
public Package getDependencyPackage(PackageIdentifier packageId) {
152+
if (prerequisitePackages == null) {
153+
return null;
154+
}
155+
return prerequisitePackages.get(packageId);
156+
}
157+
158+
public void putDependencyPackageIfAbsent(PackageIdentifier packageId, Package pkg) {
159+
if (prerequisitePackages == null) {
160+
return;
161+
}
162+
prerequisitePackages.putIfAbsent(packageId, pkg);
163+
}
164+
165+
/**
166+
* Collects packages of dependencies to be unified in a {@link NestedSet}.
167+
*
168+
* <p>Performs bookkeeping so the result is deterministic.
169+
*
170+
* <p>Work in Skyframe may complete in arbitrary order due to missing values and restarts. For
171+
* example, if a client requests {@code //foo} and {@code //bar}, it could receive any of the
172+
* following: {@code (//foo, null), (null, //bar), (//foo, //bar) or (null, null)}.
173+
*
174+
* <p>This class tracks how the {@link Package}s are added so they can be given a deterministic
175+
* order. This is required for determinism of {@link
176+
* com.google.devtools.build.lib.analysis.RepoMappingManifestAction#computeKey}.
177+
*/
178+
private static class PackageCollector {
179+
/**
180+
* Keeps packages that were added directly as a list.
181+
*
182+
* <p>These will be sorted.
183+
*/
184+
private final ArrayList<Package> packages = new ArrayList<>();
185+
186+
/** Stores transitive {@link Package}s of {@link ConfiguredTargetValues}s. */
187+
private final TreeMap<ConfiguredTargetKey, NestedSet<Package>> configuredTargetPackages =
188+
new TreeMap<>(CONFIGURED_TARGET_KEY_ORDERING);
189+
190+
/** Stores transitive {@link Package}s of {@link AspectValue}s. */
191+
private final TreeMap<AspectKey, NestedSet<Package>> aspectPackages =
192+
new TreeMap<>(ASPECT_KEY_ORDERING);
193+
194+
/**
195+
* Stores a single batch of packages computed by {@link
196+
* PrerequisiteProducer#resolveConfiguredTargetDependencies}.
197+
*/
198+
// TODO(b/261521010): delete this when the corresponding method is deleted.
199+
private NestedSet<Package> transitivePackagesBatch;
200+
201+
/**
202+
* Constructs the deterministically ordered result.
203+
*
204+
* <p>It's safe to call this multiple times.
205+
*/
206+
private NestedSet<Package> buildSet() {
207+
var result = NestedSetBuilder.<Package>stableOrder();
208+
209+
Collections.sort(packages, comparing(Package::getPackageIdentifier));
210+
result.addAll(packages);
211+
212+
for (NestedSet<Package> packageSet : configuredTargetPackages.values()) {
213+
result.addTransitive(packageSet);
214+
}
215+
for (NestedSet<Package> packageSet : aspectPackages.values()) {
216+
result.addTransitive(packageSet);
217+
}
218+
219+
if (transitivePackagesBatch != null) {
220+
result.addTransitive(transitivePackagesBatch);
221+
}
222+
223+
return result.build();
224+
}
225+
}
226+
227+
private static final Comparator<ConfiguredTargetKey> CONFIGURED_TARGET_KEY_ORDERING =
228+
comparing(ConfiguredTargetKey::getLabel)
229+
.thenComparing(ConfiguredTargetKey::getExecutionPlatformLabel, nullsFirst(naturalOrder()))
230+
.thenComparing(
231+
ConfiguredTargetKey::getConfigurationKey,
232+
nullsFirst(comparing(BuildConfigurationKey::getOptionsChecksum)));
233+
234+
private static final Comparator<AspectKey> ASPECT_KEY_ORDERING =
235+
comparing(AspectKey::getBaseConfiguredTargetKey, CONFIGURED_TARGET_KEY_ORDERING)
236+
.thenComparing(
237+
(left, right) -> new AspectKeyDescriptorGraphComparator().compare(left, right));
238+
239+
/**
240+
* Compares the {@link AspectKey} graph structure for specific dependencies.
241+
*
242+
* <p>An {@link AspectKey} for a dependency is determined by {@link
243+
* AspectCollection#buildAspectKey}. This means that the {@link AspectKey} is structured like a
244+
* DAG with the following properties.
245+
*
246+
* <ul>
247+
* <li>The {@link AspectKey#getBaseConfiguredTargetKey} is the same across all nodes.
248+
* <li>Each DAG node has a unique {@link AspectKey#getAspectDescriptor}.
249+
* </ul>
250+
*
251+
* <p>Given the above, it's sufficient to traverse unique {@link AspectDescriptor}s to understand
252+
* the toplogy of both graphs.
253+
*
254+
* <p>NB: a new instance of this comparator must be constructed for each comparison.
255+
*/
256+
private static class AspectKeyDescriptorGraphComparator implements Comparator<AspectKey> {
257+
private final HashSet<AspectDescriptor> visited = new HashSet<>();
258+
259+
@Override
260+
public int compare(AspectKey left, AspectKey right) {
261+
AspectDescriptor leftDescriptor = left.getAspectDescriptor();
262+
AspectDescriptor rightDescriptor = right.getAspectDescriptor();
263+
if (!leftDescriptor.equals(rightDescriptor)) {
264+
return leftDescriptor.getDescription().compareTo(rightDescriptor.getDescription());
265+
}
266+
if (!visited.add(leftDescriptor)) {
267+
return 0;
268+
}
269+
270+
return lexicographical(this).compare(left.getBaseKeys(), right.getBaseKeys());
271+
}
272+
}
273+
}

0 commit comments

Comments
 (0)