Skip to content

Commit f8e7d61

Browse files
authored
[8.2.0] Set generator_name, generator_function, generator_location, full Starlark stack for rules in symbolic macros (#25772)
Before, we did not set generator_* pseudo-attributes for rules generated in a symbolic macro. However, it turns out that there are existing tools which expect to be able to filter rule targets by generator_name in query expressions; and such tools get broken when legacy macros are migrated to symbolic. The easiest solution is to set generator_name and friends consistently for both legacy and symbolic macro rule targets. In the long term, we wish to deprecate and remove generator_* pseudo-attrs, since the API has numerous problems and doesn't expose the information users really need: But for now, however, we ought to support the broken API symmetrically for all varieties of macro. In addition, take the opportunity to concatenate the Starlark stacks of BUILD and symbolic macro threads for rule targets in symbolic macros, because (1) the generator_* psudo-attributes are derived from the BUILD thread's stack, and (2) the stack is shown in some query output, and truncating it at symbolic macro boundaries is likely to confuse users who rely on it for debugging. RELNOTES: Set generator_name, generator_function, generator_location, and the full Starlark stack for rule targets instantiated in a symbolic macro. PiperOrigin-RevId: 744110163 Change-Id: I5216b05f22ee0663343bd4468f4f299d095bb739 Commit d6b07eb
1 parent e597d86 commit f8e7d61

File tree

11 files changed

+398
-59
lines changed

11 files changed

+398
-59
lines changed

src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleClassFunctions.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,8 @@ public Object call(StarlarkThread thread, Tuple args, Dict<String, Object> kwarg
14671467
throw Starlark.errorf("unexpected positional arguments");
14681468
}
14691469

1470-
MacroInstance macroInstance = macroClass.instantiateAndAddMacro(pkgBuilder, kwargs);
1470+
MacroInstance macroInstance =
1471+
macroClass.instantiateAndAddMacro(pkgBuilder, kwargs, thread.getCallStack());
14711472

14721473
// Evaluate the macro now, if it's not a finalizer. Finalizer evaluation will be deferred to
14731474
// the end of the BUILD file evaluation.

src/main/java/com/google/devtools/build/lib/packages/CallStack.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
1919
import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
2020
import com.google.devtools.build.lib.util.HashCodes;
21+
import java.util.ArrayDeque;
22+
import java.util.Deque;
2123
import java.util.List;
2224
import java.util.Objects;
2325
import javax.annotation.Nullable;
@@ -124,20 +126,52 @@ public int hashCode() {
124126
private static final Interner<Node> nodeInterner = BlazeInterners.newWeakInterner();
125127

126128
/**
127-
* Returns a compact representation of the <em>interior</em> of the given call stack.
129+
* Returns a compact representation of the given call stack, optionally ignoring the outermost
130+
* frame.
128131
*
129-
* <p>The outermost frame of the call stack is not reflected in the compact representation because
130-
* it is already stored as {@link Rule#getLocation}.
131-
*
132-
* <p>Returns {@code null} for call stacks with fewer than two frames.
132+
* @param start index of frame at which to start; in other words, skip this many outermost frames.
133+
* This is useful for skipping the outermost frame in BUILD file thread stacks, since the
134+
* BUILD file location is already stored in {@link Rule#getLocation} and {@link
135+
* MacroInstance#getBuildFileLocation}.
136+
* @return {@code null} for call stacks with fewer than two frames.
133137
*/
134138
@Nullable
135-
static Node compactInterior(List<StarlarkThread.CallStackEntry> stack) {
139+
static Node compact(List<StarlarkThread.CallStackEntry> stack, int start) {
136140
Node node = null;
137-
for (int i = stack.size() - 1; i > 0; i--) {
141+
for (int i = stack.size() - 1; i >= start; i--) {
138142
StarlarkThread.CallStackEntry entry = stack.get(i);
139143
node = nodeInterner.intern(new Node(entry.name, entry.location, node));
140144
}
141145
return node;
142146
}
147+
148+
/**
149+
* Returns a concatenation of two compact call stacks.
150+
*
151+
* <p>The result will contain {@code inner} stack appended unmodified to a new copy of the {@code
152+
* outer} stack.
153+
*
154+
* @return {@code null} if both of the inputs are {@code null} - in other words, if both of the
155+
* inputs are empty stacks.
156+
*/
157+
@Nullable
158+
static Node concatenate(@Nullable Node outer, @Nullable Node inner) {
159+
Deque<Node> outerReversed = new ArrayDeque<>();
160+
while (outer != null) {
161+
outerReversed.addFirst(outer);
162+
outer = outer.next();
163+
}
164+
Node node = inner;
165+
for (Node origOuterNode : outerReversed) {
166+
node =
167+
nodeInterner.intern(
168+
new Node(
169+
origOuterNode.name,
170+
origOuterNode.file,
171+
origOuterNode.line,
172+
origOuterNode.col,
173+
node));
174+
}
175+
return node;
176+
}
143177
}

src/main/java/com/google/devtools/build/lib/packages/MacroClass.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,10 @@ public MacroClass build() {
178178
*/
179179
// TODO(#19922): Consider reporting multiple events instead of failing on the first one. See
180180
// analogous implementation in RuleClass#populateDefinedRuleAttributeValues.
181-
private MacroInstance instantiateMacro(Package.Builder pkgBuilder, Map<String, Object> kwargs)
181+
private MacroInstance instantiateMacro(
182+
Package.Builder pkgBuilder,
183+
Map<String, Object> kwargs,
184+
ImmutableList<StarlarkThread.CallStackEntry> parentThreadCallStack)
182185
throws EvalException {
183186
// A word on edge cases:
184187
// - If an attr is implicit but does not have a default specified, its value is just the
@@ -319,7 +322,7 @@ private MacroInstance instantiateMacro(Package.Builder pkgBuilder, Map<String, O
319322
? 1
320323
: parentMacroFrame.macroInstance.getSameNameDepth() + 1;
321324

322-
return pkgBuilder.createMacro(this, attrValues, sameNameDepth);
325+
return pkgBuilder.createMacro(this, attrValues, sameNameDepth, parentThreadCallStack);
323326
}
324327

325328
/**
@@ -346,10 +349,16 @@ private static boolean shouldForceDefaultToNone(Attribute attr) {
346349
* @param kwargs A map from attribute name to its given Starlark value, such as passed in a BUILD
347350
* file (i.e., prior to attribute type conversion, {@code select()} promotion, default value
348351
* substitution, or even validation that the attribute exists).
352+
* @param parentThreadCallStack The call stack of the Starlark thread in whose context the macro
353+
* instance is being constructed. This is *not* the thread that will execute the macro's
354+
* implementation function.
349355
*/
350356
public MacroInstance instantiateAndAddMacro(
351-
Package.Builder pkgBuilder, Map<String, Object> kwargs) throws EvalException {
352-
MacroInstance macroInstance = instantiateMacro(pkgBuilder, kwargs);
357+
Package.Builder pkgBuilder,
358+
Map<String, Object> kwargs,
359+
ImmutableList<StarlarkThread.CallStackEntry> parentThreadCallStack)
360+
throws EvalException {
361+
MacroInstance macroInstance = instantiateMacro(pkgBuilder, kwargs, parentThreadCallStack);
353362
try {
354363
pkgBuilder.addMacro(macroInstance);
355364
} catch (NameConflictException e) {

src/main/java/com/google/devtools/build/lib/packages/MacroInstance.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static java.util.Objects.requireNonNull;
1818

19+
import com.google.common.annotations.VisibleForTesting;
1920
import com.google.common.base.Preconditions;
2021
import com.google.common.collect.ImmutableList;
2122
import com.google.common.collect.ImmutableMap;
@@ -29,6 +30,8 @@
2930
import javax.annotation.Nullable;
3031
import net.starlark.java.eval.EvalException;
3132
import net.starlark.java.eval.Starlark;
33+
import net.starlark.java.eval.StarlarkThread;
34+
import net.starlark.java.syntax.Location;
3235

3336
/**
3437
* Represents a use of a symbolic macro in a package.
@@ -48,6 +51,10 @@ public final class MacroInstance {
4851

4952
@Nullable private final MacroInstance parent;
5053

54+
private final Location buildFileLocation;
55+
56+
private final CallStack.Node parentCallStack;
57+
5158
private final MacroClass macroClass;
5259

5360
private final int sameNameDepth;
@@ -80,12 +87,16 @@ public final class MacroInstance {
8087
public MacroInstance(
8188
Package pkg,
8289
@Nullable MacroInstance parent,
90+
Location buildFileLocation,
91+
CallStack.Node parentCallStack,
8392
MacroClass macroClass,
8493
Map<String, Object> attrValues,
8594
int sameNameDepth)
8695
throws EvalException {
8796
this.pkg = pkg;
8897
this.parent = parent;
98+
this.buildFileLocation = buildFileLocation;
99+
this.parentCallStack = parentCallStack;
89100
this.macroClass = macroClass;
90101
this.attrValues = ImmutableMap.copyOf(attrValues);
91102
Preconditions.checkArgument(sameNameDepth > 0);
@@ -107,6 +118,44 @@ public MacroInstance getParent() {
107118
return parent;
108119
}
109120

121+
/**
122+
* Returns the location in the BUILD file at which this macro was created or its outermost
123+
* enclosing symbolic or legacy macro was called.
124+
*/
125+
@VisibleForTesting
126+
public Location getBuildFileLocation() {
127+
return buildFileLocation;
128+
}
129+
130+
/**
131+
* Returns the call stack of the Starlark thread that created this macro instance.
132+
*
133+
* <p>If this macro was instantiated in a BUILD file thread (as contrasted with a symbolic macro
134+
* thread), the call stack does not include the frame for the BUILD file top level, since it's
135+
* redundant with {@link #getBuildFileLocation}.
136+
*/
137+
CallStack.Node getParentCallStack() {
138+
return parentCallStack;
139+
}
140+
141+
/**
142+
* Returns the call stack of the Starlark thread that created this macro instance.
143+
*
144+
* <p>Requires reconstructing the call stack from a compact representation, so should only be
145+
* called when the full call stack is needed.
146+
*/
147+
@VisibleForTesting
148+
public ImmutableList<StarlarkThread.CallStackEntry> reconstructParentCallStack() {
149+
ImmutableList.Builder<StarlarkThread.CallStackEntry> stack = ImmutableList.builder();
150+
if (parent == null) {
151+
stack.add(StarlarkThread.callStackEntry(StarlarkThread.TOP_LEVEL, buildFileLocation));
152+
}
153+
for (CallStack.Node node = parentCallStack; node != null; node = node.next()) {
154+
stack.add(node.toCallStackEntry());
155+
}
156+
return stack.build();
157+
}
158+
110159
/** Returns the {@link MacroClass} (i.e. schema info) that this instance parameterizes. */
111160
public MacroClass getMacroClass() {
112161
return macroClass;

src/main/java/com/google/devtools/build/lib/packages/Rule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,7 @@ public <T> BuildType.SelectorList<T> getSelectorList(String attributeName, Type<
826826
* Returns whether this rule was created by a macro.
827827
*/
828828
public boolean wasCreatedByMacro() {
829+
// TODO(bazel-team): do we really need the `hasStringAttribute(GENERATOR_NAME)` check?
829830
return interiorCallStack != null || hasStringAttribute(GENERATOR_NAME);
830831
}
831832

src/main/java/com/google/devtools/build/lib/packages/RuleFactory.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ private RuleFactory() {} // uninstantiable
5050
*
5151
* <p>It is the caller's responsibility to add the rule to the package (the caller may choose not
5252
* to do so if, for example, the rule has errors).
53+
*
54+
* @param callstack the stack of the Starlark thread where the rule is created. In the case of
55+
* rules instantiated by a symbolic macro, this is the inner macro's stack, and does not
56+
* include frames for enclosing macros or the BUILD file.
5357
*/
5458
public static Rule createRule(
5559
Package.Builder pkgBuilder,
@@ -227,26 +231,28 @@ public boolean isExplicitlySpecified(Map.Entry<String, Object> attributeAccessor
227231
* (and which are not also within a symbolic macro). Its value is the name argument of the
228232
* top-level macro on the call stack, if its value can be determined statically (see {@link
229233
* PackageFactory#checkBuildSyntax}), or just the name of the target otherwise.
234+
*
235+
* @param callstack the stack of the Starlark thread where the rule is created. In the case of
236+
* rules instantiated by a symbolic macro, this is the inner macro's stack, and does not
237+
* include frames for enclosing macros or the BUILD file.
230238
*/
231-
// TODO: #19922 - Should we set generator_name on targets created by a symbolic macro instantiated
232-
// within a legacy macro? Otherwise tooling may think those targets were not created in a macro.
233239
@Nullable
234240
private static String getGeneratorName(
235241
Package.Builder pkgBuilder,
236242
BuildLangTypedAttributeValuesMap args,
237-
ImmutableList<CallStackEntry> stack) {
238-
// The "generator" of a rule is the function outermost in the call stack (regardless of whether
239-
// or not it was passed a "name" parameter). For rules with generators, the stack must contain
240-
// at least two entries:
241-
// 0: the outermost function (e.g. a BUILD file),
242-
// 1: the function called by it (e.g. a "macro" in a .bzl file).
243-
// optionally followed by other Starlark or built-in functions, and finally the rule
244-
// instantiation function.
245-
if (stack.size() < 2 || !stack.get(1).location.file().endsWith(".bzl")) {
246-
// Not instantiated by a legacy macro.
247-
// TODO: #19922 - This stack inspection logic doesn't work for symbolic macros, where it will
248-
// likely incorrectly discriminate between targets created in the implementation function
249-
// directly and targets created in a helper function called from the implementation function.
243+
ImmutableList<CallStackEntry> callstack) {
244+
@Nullable MacroInstance macro = pkgBuilder.currentMacro();
245+
// The "generator" of a rule is the function outermost in the BUILD file thread's call stack
246+
// (regardless of whether or not it was passed a "name" parameter). For rules with generators,
247+
// the BUILD file thread's call stack must contain at least two entries:
248+
// 0: the outermost function (BUILD file top level),
249+
// 1: the function called by it (e.g. a macro in a .bzl file),
250+
// optionally followed by other Starlark or built-in functions, and finally - if the rule is
251+
// instantiated in the BUILD file thread - the rule instantiation function.
252+
if (macro == null
253+
&& (callstack.size() < 2 || !callstack.get(1).location.file().endsWith(".bzl"))) {
254+
// We are in a BUILD file thread, and the rule is being instantiated directly at the top
255+
// level of the BUILD file.
250256
// TODO(bazel-team): Tolerate ".scl" extension in the above if? An .scl file can instantiate a
251257
// rule if the rule function is passed as an argument.
252258
return null;
@@ -258,7 +264,9 @@ private static String getGeneratorName(
258264
return null;
259265
}
260266

261-
String generatorName = pkgBuilder.getGeneratorNameByLocation(stack.get(0).location);
267+
String generatorName =
268+
pkgBuilder.getGeneratorNameByLocation(
269+
macro != null ? macro.getBuildFileLocation() : callstack.get(0).location);
262270
if (generatorName == null) {
263271
// Fall back on target name (meh).
264272
generatorName = (String) args.getAttributeValue("name");

src/main/java/com/google/devtools/build/lib/packages/TargetDefinitionContext.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -364,13 +364,27 @@ public MacroInstance currentMacro() {
364364
*
365365
* <p>The created {@link Rule} will have no output files and therefore will be in an invalid
366366
* state.
367+
*
368+
* @param threadCallStack the call stack of the thread that created the rule. Call stacks for
369+
* threads of enclosing symbolic macros (if any) will be prepended to it automatically to form
370+
* the rule's full call stack.
367371
*/
368-
Rule createRule(Label label, RuleClass ruleClass, List<StarlarkThread.CallStackEntry> callstack) {
369-
return createRule(
370-
label,
371-
ruleClass,
372-
callstack.isEmpty() ? Location.BUILTIN : callstack.get(0).location,
373-
CallStack.compactInterior(callstack));
372+
Rule createRule(
373+
Label label, RuleClass ruleClass, List<StarlarkThread.CallStackEntry> threadCallStack) {
374+
CallStack.Node fullInteriorCallStack;
375+
final Location location;
376+
if (currentMacro() != null) {
377+
location = currentMacro().getBuildFileLocation();
378+
fullInteriorCallStack = CallStack.compact(threadCallStack, /* start= */ 0);
379+
for (MacroInstance macro = currentMacro(); macro != null; macro = macro.getParent()) {
380+
fullInteriorCallStack =
381+
CallStack.concatenate(macro.getParentCallStack(), fullInteriorCallStack);
382+
}
383+
} else {
384+
location = threadCallStack.isEmpty() ? Location.BUILTIN : threadCallStack.get(0).location;
385+
fullInteriorCallStack = CallStack.compact(threadCallStack, /* start= */ 1);
386+
}
387+
return createRule(label, ruleClass, location, fullInteriorCallStack);
374388
}
375389

376390
Rule createRule(
@@ -386,10 +400,23 @@ Rule createRule(
386400
* Package} associated with this {@link Builder}.
387401
*/
388402
MacroInstance createMacro(
389-
MacroClass macroClass, Map<String, Object> attrValues, int sameNameDepth)
403+
MacroClass macroClass,
404+
Map<String, Object> attrValues,
405+
int sameNameDepth,
406+
List<StarlarkThread.CallStackEntry> parentCallStack)
390407
throws EvalException {
391408
MacroInstance parent = currentMacro();
392-
return new MacroInstance(pkg, parent, macroClass, attrValues, sameNameDepth);
409+
final Location location;
410+
final CallStack.Node compactParentCallStack;
411+
if (parent != null) {
412+
location = parent.getBuildFileLocation();
413+
compactParentCallStack = CallStack.compact(parentCallStack, /* start= */ 0);
414+
} else {
415+
location = parentCallStack.isEmpty() ? Location.BUILTIN : parentCallStack.get(0).location;
416+
compactParentCallStack = CallStack.compact(parentCallStack, /* start= */ 1);
417+
}
418+
return new MacroInstance(
419+
pkg, parent, location, compactParentCallStack, macroClass, attrValues, sameNameDepth);
393420
}
394421

395422
@Nullable

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,8 @@ java_test(
375375
"//src/main/java/com/google/devtools/build/lib/analysis:test/analysis_failure_info",
376376
"//src/main/java/com/google/devtools/build/lib/cmdline",
377377
"//src/main/java/com/google/devtools/build/lib/packages",
378+
"//src/main/java/net/starlark/java/eval",
379+
"//src/main/java/net/starlark/java/syntax",
378380
"//src/test/java/com/google/devtools/build/lib/analysis/util",
379381
"//third_party:error_prone_annotations",
380382
"//third_party:guava",

0 commit comments

Comments
 (0)