Skip to content

Commit 671986c

Browse files
committed
Stringify Labels in display form in Args
`Label`s added to `Args` are now formatted in display form, which means that they will be repo-relative if referring to the main repo and use an apparent repository name if possible. Previously, labels were formatted in repo-relative form if referring to the main repo and using canonical repository names exclusively for external repos. At the same time, `Args` docs explicitly stated that the exact stringification of any type other than `File` is unspecified. The previous behavior was problematic since it neither always used canonical labels (which could be useful for writing tests that check these labels against an allowlist) nor provided labels that could be included in BUILD files (canonical names are explicitly unstable). Furthermore, whether a label resulted in a string prefixed with a single or two `@`s already dependended on a user choice, namely the value of `--enable_bzlmod`. Thus, this change is not considered to be breaking. It makes it so that existing rulesets relying on default stringification to return a BUILD- file compatible label in WORKSPACE continue to do so with Bzlmod with no code changes. This change aims to be as memory efficient as possible: * Single labels or sequences of labels that reference targets in the main repo incur no memory overhead. * Labels referring to external repos as well as `NestedSet`s of labels result in an additional Skyframe dependency for each target using the command line as well as one additional reference (4 bytes) stored in the command line's `arguments`.
1 parent 68a484e commit 671986c

16 files changed

+226
-44
lines changed

src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java

+3
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,8 @@ Artifact.DerivedArtifact getDerivedArtifact(
168168

169169
ActionKeyContext getActionKeyContext();
170170

171+
/**
172+
* Returns and registers a Skyframe dependency on the {@link RepositoryMapping} of the main repo.
173+
*/
171174
RepositoryMapping getMainRepoMapping() throws InterruptedException;
172175
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ java_library(
411411
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
412412
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
413413
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_value_creation_exception",
414+
"//src/main/java/com/google/devtools/build/lib/skyframe:repository_mapping_value",
414415
"//src/main/java/com/google/devtools/build/lib/skyframe:starlark_builtins_value",
415416
"//src/main/java/com/google/devtools/build/lib/skyframe:workspace_status_value",
416417
"//src/main/java/com/google/devtools/build/lib/skyframe/config",
@@ -2442,6 +2443,7 @@ java_library(
24422443
"//src/main/java/com/google/devtools/build/lib/actions",
24432444
"//src/main/java/com/google/devtools/build/lib/actions:artifacts",
24442445
"//src/main/java/com/google/devtools/build/lib/actions:commandline_item",
2446+
"//src/main/java/com/google/devtools/build/lib/cmdline",
24452447
"//src/main/java/com/google/devtools/build/lib/collect/nestedset",
24462448
"//src/main/java/com/google/devtools/build/lib/concurrent",
24472449
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",

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

+37-7
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import com.google.devtools.build.lib.actions.ParamFileInfo;
2424
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
2525
import com.google.devtools.build.lib.actions.SingleStringArgFormatter;
26+
import com.google.devtools.build.lib.cmdline.Label;
27+
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
2628
import com.google.devtools.build.lib.collect.nestedset.Depset;
2729
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
2830
import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
@@ -70,7 +72,8 @@ public void repr(Printer printer) {
7072
@Override
7173
public void debugPrint(Printer printer, StarlarkSemantics semantics) {
7274
try {
73-
printer.append(Joiner.on(" ").join(build().arguments()));
75+
printer.append(
76+
Joiner.on(" ").join(build(/* mainRepoMappingSupplier */ () -> null).arguments()));
7477
} catch (CommandLineExpansionException e) {
7578
printer.append("Cannot expand command line: " + e.getMessage());
7679
} catch (InterruptedException e) {
@@ -101,8 +104,13 @@ public void debugPrint(Printer printer, StarlarkSemantics semantics) {
101104
*/
102105
public abstract ImmutableSet<Artifact> getDirectoryArtifacts();
103106

107+
public interface InterruptibleRepoMappingSupplier {
108+
RepositoryMapping get() throws InterruptedException;
109+
}
110+
104111
/** Returns the command line built by this {@link Args} object. */
105-
public abstract CommandLine build();
112+
public abstract CommandLine build(InterruptibleRepoMappingSupplier mainRepoMappingSupplier)
113+
throws InterruptedException;
106114

107115
/**
108116
* Returns a frozen {@link Args} representation corresponding to an already-registered action.
@@ -157,7 +165,7 @@ public ImmutableSet<Artifact> getDirectoryArtifacts() {
157165
}
158166

159167
@Override
160-
public CommandLine build() {
168+
public CommandLine build(InterruptibleRepoMappingSupplier mainRepoMappingSupplier) {
161169
return commandLine;
162170
}
163171

@@ -259,6 +267,12 @@ private static class MutableArgs extends Args implements StarlarkValue, Mutabili
259267
*/
260268
private boolean flagPerLine = false;
261269

270+
/**
271+
* True if the command line needs to stringify any {@link Label}s without an explicit 'map_each'
272+
* function.
273+
*/
274+
private boolean mayStringifyExternalLabel = false;
275+
262276
// May be set explicitly once -- if unset defaults to ParameterFileType.SHELL_QUOTED.
263277
private ParameterFileType parameterFileType = null;
264278
private String flagFormatString;
@@ -307,6 +321,9 @@ public CommandLineArgsApi addArgument(
307321
"Args.add() doesn't accept vectorized arguments. Please use Args.add_all() or"
308322
+ " Args.add_joined() instead.");
309323
}
324+
if (value instanceof Label label && !label.getRepository().isMain()) {
325+
mayStringifyExternalLabel = true;
326+
}
310327
addSingleArg(value, format != Starlark.NONE ? (String) format : null);
311328
return this;
312329
}
@@ -436,8 +453,12 @@ private void addVectorArg(
436453
validateFormatString("format_each", formatEach);
437454
validateFormatString("format_joined", formatJoined);
438455
StarlarkCustomCommandLine.VectorArg.Builder vectorArg;
439-
if (value instanceof Depset) {
440-
Depset starlarkNestedSet = (Depset) value;
456+
if (value instanceof Depset starlarkNestedSet) {
457+
if (mapEach == null && Label.class.equals(starlarkNestedSet.getElementClass())) {
458+
// We don't want to eagerly check whether all labels reference targets in the main repo,
459+
// so just assume they might not. Nested sets of labels should be rare.
460+
mayStringifyExternalLabel = true;
461+
}
441462
NestedSet<?> nestedSet = starlarkNestedSet.getSet();
442463
if (nestedSet.isEmpty() && omitIfEmpty) {
443464
return;
@@ -454,6 +475,13 @@ private void addVectorArg(
454475
if (expandDirectories) {
455476
scanForDirectories(starlarkList);
456477
}
478+
// Labels referencing targets in the main repo are stringified as //pkg:name and thus
479+
// don't require a RepositoryMapping. If a map_each function is provided, default
480+
// stringification via Label#toString() is not used.
481+
mayStringifyExternalLabel |=
482+
(mapEach != null)
483+
&& starlarkList.stream()
484+
.anyMatch(o -> o instanceof Label label && !label.getRepository().isMain());
457485
vectorArg = new StarlarkCustomCommandLine.VectorArg.Builder(starlarkList);
458486
}
459487
commandLine.recordArgStart();
@@ -573,8 +601,10 @@ private MutableArgs(@Nullable Mutability mutability, StarlarkSemantics starlarkS
573601
}
574602

575603
@Override
576-
public CommandLine build() {
577-
return commandLine.build(flagPerLine);
604+
public CommandLine build(InterruptibleRepoMappingSupplier mainRepoMappingSupplier)
605+
throws InterruptedException {
606+
return commandLine.build(
607+
flagPerLine, mayStringifyExternalLabel ? mainRepoMappingSupplier.get() : null);
578608
}
579609

580610
@Override

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

+13-9
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,8 @@ public void symlink(
332332
}
333333

334334
@Override
335-
public void write(FileApi output, Object content, Boolean isExecutable) throws EvalException {
335+
public void write(FileApi output, Object content, Boolean isExecutable)
336+
throws EvalException, InterruptedException {
336337
context.checkMutable("actions.write");
337338
RuleContext ruleContext = getRuleContext();
338339

@@ -347,7 +348,7 @@ public void write(FileApi output, Object content, Boolean isExecutable) throws E
347348
ruleContext.getActionOwner(),
348349
NestedSetBuilder.wrap(Order.STABLE_ORDER, args.getDirectoryArtifacts()),
349350
(Artifact) output,
350-
args.build(),
351+
args.build(context.getRuleContext().getAnalysisEnvironment()::getMainRepoMapping),
351352
args.getParameterFileType());
352353
} else {
353354
throw new AssertionError("Unexpected type: " + content.getClass().getSimpleName());
@@ -373,7 +374,7 @@ public void run(
373374
Object shadowedActionUnchecked,
374375
Object resourceSetUnchecked,
375376
Object toolchainUnchecked)
376-
throws EvalException {
377+
throws EvalException, InterruptedException {
377378
context.checkMutable("actions.run");
378379
execGroupUnchecked = context.maybeOverrideExecGroup(execGroupUnchecked);
379380
toolchainUnchecked = context.maybeOverrideToolchain(toolchainUnchecked);
@@ -382,7 +383,7 @@ public void run(
382383
boolean useAutoExecGroups = ruleContext.useAutoExecGroups();
383384

384385
StarlarkAction.Builder builder = new StarlarkAction.Builder();
385-
buildCommandLine(builder, arguments);
386+
buildCommandLine(builder, arguments, ruleContext.getAnalysisEnvironment()::getMainRepoMapping);
386387
if (executableUnchecked instanceof Artifact) {
387388
Artifact executable = (Artifact) executableUnchecked;
388389
FilesToRunProvider provider = context.getExecutableRunfiles(executable, "executable");
@@ -560,15 +561,15 @@ public void runShell(
560561
Object shadowedActionUnchecked,
561562
Object resourceSetUnchecked,
562563
Object toolchainUnchecked)
563-
throws EvalException {
564+
throws EvalException, InterruptedException {
564565
context.checkMutable("actions.run_shell");
565566
execGroupUnchecked = context.maybeOverrideExecGroup(execGroupUnchecked);
566567
toolchainUnchecked = context.maybeOverrideToolchain(toolchainUnchecked);
567568

568569
RuleContext ruleContext = getRuleContext();
569570

570571
StarlarkAction.Builder builder = new StarlarkAction.Builder();
571-
buildCommandLine(builder, arguments);
572+
buildCommandLine(builder, arguments, ruleContext.getAnalysisEnvironment()::getMainRepoMapping);
572573

573574
// When we use a shell command, add an empty argument before other arguments.
574575
// e.g. bash -c "cmd" '' 'arg1' 'arg2'
@@ -631,8 +632,11 @@ public void runShell(
631632
builder);
632633
}
633634

634-
private static void buildCommandLine(SpawnAction.Builder builder, Sequence<?> argumentsList)
635-
throws EvalException {
635+
private static void buildCommandLine(
636+
SpawnAction.Builder builder,
637+
Sequence<?> argumentsList,
638+
Args.InterruptibleRepoMappingSupplier repoMappingSupplier)
639+
throws EvalException, InterruptedException {
636640
ImmutableList.Builder<String> stringArgs = null;
637641
for (Object value : argumentsList) {
638642
if (value instanceof String) {
@@ -647,7 +651,7 @@ private static void buildCommandLine(SpawnAction.Builder builder, Sequence<?> ar
647651
}
648652
Args args = (Args) value;
649653
ParamFileInfo paramFileInfo = args.getParamFileInfo();
650-
builder.addCommandLine(args.build(), paramFileInfo);
654+
builder.addCommandLine(args.build(repoMappingSupplier), paramFileInfo);
651655
} else {
652656
throw Starlark.errorf(
653657
"expected list of strings or ctx.actions.args() for arguments instead of %s",

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

+68-15
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.common.collect.ImmutableList;
2121
import com.google.common.collect.ImmutableMap;
2222
import com.google.common.collect.Interner;
23+
import com.google.common.collect.Maps;
2324
import com.google.common.collect.Sets;
2425
import com.google.devtools.build.lib.actions.AbstractCommandLine;
2526
import com.google.devtools.build.lib.actions.ActionKeyContext;
@@ -36,6 +37,8 @@
3637
import com.google.devtools.build.lib.actions.PathMapper;
3738
import com.google.devtools.build.lib.actions.SingleStringArgFormatter;
3839
import com.google.devtools.build.lib.cmdline.Label;
40+
import com.google.devtools.build.lib.cmdline.RepositoryMapping;
41+
import com.google.devtools.build.lib.cmdline.RepositoryName;
3942
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
4043
import com.google.devtools.build.lib.concurrent.BlazeInterners;
4144
import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
@@ -182,7 +185,8 @@ private int eval(
182185
int argi,
183186
List<String> builder,
184187
@Nullable ArtifactExpander artifactExpander,
185-
PathMapper pathMapper)
188+
PathMapper pathMapper,
189+
@Nullable RepositoryMapping mainRepoMapping)
186190
throws CommandLineExpansionException, InterruptedException {
187191
StarlarkCallable mapEach = null;
188192
StarlarkSemantics starlarkSemantics = null;
@@ -219,7 +223,7 @@ private int eval(
219223
int count = expandedValues.size();
220224
stringValues = new ArrayList<>(expandedValues.size());
221225
for (int i = 0; i < count; ++i) {
222-
stringValues.add(expandToCommandLine(expandedValues.get(i), pathMapper));
226+
stringValues.add(expandToCommandLine(expandedValues.get(i), pathMapper, mainRepoMapping));
223227
}
224228
}
225229
// It's safe to uniquify at this stage, any transformations after this
@@ -607,9 +611,15 @@ static void push(List<Object> arguments, Object object, String format) {
607611
arguments.add(format);
608612
}
609613

610-
static int eval(List<Object> arguments, int argi, List<String> builder, PathMapper pathMapper) {
614+
static int eval(
615+
List<Object> arguments,
616+
int argi,
617+
List<String> builder,
618+
PathMapper pathMapper,
619+
@Nullable RepositoryMapping mainRepoMapping) {
611620
Object object = arguments.get(argi++);
612-
String stringValue = StarlarkCustomCommandLine.expandToCommandLine(object, pathMapper);
621+
String stringValue =
622+
StarlarkCustomCommandLine.expandToCommandLine(object, pathMapper, mainRepoMapping);
613623
String formatStr = (String) arguments.get(argi++);
614624
builder.add(SingleStringArgFormatter.format(formatStr, stringValue));
615625
return argi;
@@ -662,10 +672,13 @@ Builder addFormatted(Object object, String format) {
662672
return this;
663673
}
664674

665-
CommandLine build(boolean flagPerLine) {
675+
CommandLine build(boolean flagPerLine, @Nullable RepositoryMapping mainRepoMapping) {
666676
if (arguments.isEmpty()) {
667677
return CommandLine.empty();
668678
}
679+
if (mainRepoMapping != null) {
680+
arguments.add(mainRepoMapping);
681+
}
669682
Object[] args = arguments.toArray();
670683
return flagPerLine
671684
? new StarlarkCustomCommandLineWithIndexes(args, argStartIndexes.build())
@@ -700,20 +713,39 @@ public Iterable<String> arguments(
700713
List<String> result = new ArrayList<>();
701714
List<Object> arguments = rawArgsAsList();
702715

703-
for (int argi = 0; argi < arguments.size(); ) {
716+
RepositoryMapping mainRepoMapping;
717+
int size;
718+
// Added in #build() if any labels in the command line require this to be formatted with an
719+
// apparent repository name.
720+
if (arguments.getLast() instanceof RepositoryMapping) {
721+
mainRepoMapping = (RepositoryMapping) arguments.getLast();
722+
size = arguments.size() - 1;
723+
} else {
724+
mainRepoMapping = null;
725+
size = arguments.size();
726+
}
727+
728+
for (int argi = 0; argi < size; ) {
704729
Object arg = arguments.get(argi++);
705730
if (arg instanceof VectorArg) {
706-
argi = ((VectorArg) arg).eval(arguments, argi, result, artifactExpander, pathMapper);
731+
argi =
732+
((VectorArg) arg)
733+
.eval(arguments, argi, result, artifactExpander, pathMapper, mainRepoMapping);
707734
} else if (arg == SingleFormattedArg.MARKER) {
708-
argi = SingleFormattedArg.eval(arguments, argi, result, pathMapper);
735+
argi = SingleFormattedArg.eval(arguments, argi, result, pathMapper, mainRepoMapping);
709736
} else {
710-
result.add(expandToCommandLine(arg, pathMapper));
737+
result.add(expandToCommandLine(arg, pathMapper, mainRepoMapping));
711738
}
712739
}
713740
return ImmutableList.copyOf(pathMapper.mapCustomStarlarkArgs(result));
714741
}
715742

716-
private static String expandToCommandLine(Object object, PathMapper pathMapper) {
743+
private static String expandToCommandLine(
744+
Object object, PathMapper pathMapper, @Nullable RepositoryMapping mainRepoMapping) {
745+
if (mainRepoMapping != null && object instanceof Label label) {
746+
return label.getDisplayForm(mainRepoMapping);
747+
}
748+
717749
// It'd be nice to build this into DerivedArtifact's CommandLine interface so we don't have
718750
// to explicitly check if an object is a DerivedArtifact. Unfortunately that would require
719751
// a lot more dependencies on the Java library DerivedArtifact is built into.
@@ -751,7 +783,17 @@ public Iterable<String> arguments(
751783
Iterator<Integer> startIndexIterator = argStartIndexes.iterator();
752784
int nextStartIndex = startIndexIterator.hasNext() ? startIndexIterator.next() : -1;
753785

754-
for (int argi = 0; argi < arguments.size(); ) {
786+
RepositoryMapping mainRepoMapping;
787+
int size;
788+
if (arguments.getLast() instanceof RepositoryMapping) {
789+
mainRepoMapping = (RepositoryMapping) arguments.getLast();
790+
size = arguments.size() - 1;
791+
} else {
792+
mainRepoMapping = null;
793+
size = arguments.size();
794+
}
795+
796+
for (int argi = 0; argi < size; ) {
755797

756798
// Record the actual beginning of each group.
757799
if (argi == nextStartIndex) {
@@ -761,11 +803,14 @@ public Iterable<String> arguments(
761803

762804
Object arg = arguments.get(argi++);
763805
if (arg instanceof VectorArg) {
764-
argi = ((VectorArg) arg).eval(arguments, argi, result, artifactExpander, pathMapper);
806+
argi =
807+
((VectorArg) arg)
808+
.eval(arguments, argi, result, artifactExpander, pathMapper, mainRepoMapping);
765809
} else if (arg == SingleFormattedArg.MARKER) {
766-
argi = SingleFormattedArg.eval(arguments, argi, result, pathMapper);
810+
argi = SingleFormattedArg.eval(arguments, argi, result, pathMapper, mainRepoMapping);
767811
} else {
768-
result.add(StarlarkCustomCommandLine.expandToCommandLine(arg, pathMapper));
812+
result.add(
813+
StarlarkCustomCommandLine.expandToCommandLine(arg, pathMapper, mainRepoMapping));
769814
}
770815
}
771816

@@ -796,7 +841,15 @@ public void addToFingerprint(
796841
Fingerprint fingerprint)
797842
throws CommandLineExpansionException, InterruptedException {
798843
List<Object> arguments = rawArgsAsList();
799-
for (int argi = 0; argi < arguments.size(); ) {
844+
int size;
845+
if (arguments.getLast() instanceof RepositoryMapping mainRepoMapping) {
846+
fingerprint.addStringMap(
847+
Maps.transformValues(mainRepoMapping.entries(), RepositoryName::getName));
848+
size = arguments.size() - 1;
849+
} else {
850+
size = arguments.size();
851+
}
852+
for (int argi = 0; argi < size; ) {
800853
Object arg = arguments.get(argi++);
801854
if (arg instanceof VectorArg) {
802855
argi =

src/main/java/com/google/devtools/build/lib/cmdline/RepositoryMapping.java

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@
3131
* <p>For repositories from the WORKSPACE file, if the requested repo doesn't exist in the mapping,
3232
* we fallback to the requested name. For repositories from Bzlmod, we return null to let the caller
3333
* decide what to do.
34+
*
35+
* <p>This class must not implement {@link net.starlark.java.eval.StarlarkValue} since instances of
36+
* this class are used as markers by {@link
37+
* com.google.devtools.build.lib.analysis.starlark.StarlarkCustomCommandLine}.
3438
*/
3539
public class RepositoryMapping {
3640
/* A repo mapping that always falls back to using the apparent name as the canonical name. */

0 commit comments

Comments
 (0)