Skip to content

Commit d0b37d5

Browse files
Wyveraldaiuto
authored andcommitted
Fix a ridiculous bug with Starlark-defined transitions on label-list-typed flags
Thanks to @fmeum for spotting this. When we have a Starlark-defined transition on a label-list-typed flag (for example, --platforms), we need to somehow stringify the "label list" object we get in Starlark, so that it might get parsed back into a list of labels by the flag parsing mechanism. This stringification is done using Label#getUnambiguousCanonicalForm (see https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/analysis/starlark/FunctionTransitionUtil.java;l=329;drc=1d4cecf9eab592b1e949185fa1cc850bfe42058d), which does *not* respect the --enable_bzlmod flag, and hence does not use the canonical label literal syntax. This means that the label gets stringified as "@foo//bar", which in turns means that when it gets parsed back using the flag parsing mechanism, it might trigger a strict deps violation. The fix is simply to have Label#getUnambiguousCanonicalForm always use the canonical label literal syntax. This method isn't exposed to users so it doesn't change any visible behavior, so doing this only improves correctness. Work towards bazelbuild#15916 PiperOrigin-RevId: 470203401 Change-Id: I664e15422cf74f2ce427924d8d79e3dee41c07f6
1 parent ed37226 commit d0b37d5

File tree

5 files changed

+98
-12
lines changed

5 files changed

+98
-12
lines changed

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,9 @@ public String getName() {
390390
/**
391391
* Renders this label in canonical form.
392392
*
393-
* <p>invariant: {@code parseAbsolute(x.toString(), false).equals(x)}
393+
* <p>invariant: {@code parseCanonical(x.toString()).equals(x)}. Note that using {@link
394+
* #parseWithPackageContext} or {@link #parseWithRepoContext} on the returned string might not
395+
* yield the same label! For that, use {@link #getUnambiguousCanonicalForm()}.
394396
*/
395397
@Override
396398
public String toString() {
@@ -400,18 +402,23 @@ public String toString() {
400402
/**
401403
* Renders this label in canonical form.
402404
*
403-
* <p>invariant: {@code parseAbsolute(x.getCanonicalForm(), false).equals(x)}
405+
* <p>invariant: {@code parseCanonical(x.getCanonicalForm()).equals(x)}. Note that using {@link
406+
* #parseWithPackageContext} or {@link #parseWithRepoContext} on the returned string might not
407+
* yield the same label! For that, use {@link #getUnambiguousCanonicalForm()}.
404408
*/
405409
public String getCanonicalForm() {
406410
return packageIdentifier.getCanonicalForm() + ":" + name;
407411
}
408412

413+
/**
414+
* Returns an absolutely unambiguous canonical form for this label. Parsing this string in any
415+
* environment should yield the same label (as in {@code
416+
* Label.parse*(x.getUnambiguousCanonicalForm(), ...).equals(x)}).
417+
*/
409418
public String getUnambiguousCanonicalForm() {
410-
return packageIdentifier.getRepository().getNameWithAt()
411-
+ "//"
412-
+ packageIdentifier.getPackageFragment()
413-
+ ":"
414-
+ name;
419+
return String.format(
420+
"@@%s//%s:%s",
421+
packageIdentifier.getRepository().getName(), packageIdentifier.getPackageFragment(), name);
415422
}
416423

417424
/** Return the name of the repository label refers to without the leading `at` symbol. */
@@ -638,11 +645,17 @@ public void str(Printer printer, StarlarkSemantics semantics) {
638645
// If Bzlmod is enabled, we use canonical label literal syntax here and prepend an extra '@'.
639646
// So the result looks like "@@//foo:bar" for the main repo and "@@foo~1.0//bar:quux" for
640647
// other repos.
641-
printer.append('@');
648+
printer.append(getUnambiguousCanonicalForm());
649+
return;
642650
}
643651
// If Bzlmod is not enabled, we just use a single '@'.
644652
// So the result looks like "@//foo:bar" for the main repo and "@foo//bar:quux" for other repos.
645-
printer.append(getUnambiguousCanonicalForm());
653+
printer.append(
654+
String.format(
655+
"@%s//%s:%s",
656+
packageIdentifier.getRepository().getName(),
657+
packageIdentifier.getPackageFragment(),
658+
name));
646659
}
647660

648661
@Override

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ java_library(
121121
"//src/main/java/com/google/devtools/build/lib/analysis:view_creation_failed_exception",
122122
"//src/main/java/com/google/devtools/build/lib/analysis/platform",
123123
"//src/main/java/com/google/devtools/build/lib/analysis/stringtemplate",
124+
"//src/main/java/com/google/devtools/build/lib/bazel/bzlmod:resolution_impl",
125+
"//src/main/java/com/google/devtools/build/lib/bazel/repository:repository_options",
124126
"//src/main/java/com/google/devtools/build/lib/buildeventstream",
125127
"//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
126128
"//src/main/java/com/google/devtools/build/lib/causes",
@@ -143,6 +145,7 @@ java_library(
143145
"//src/main/java/com/google/devtools/build/lib/skyframe:build_configuration",
144146
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_and_data",
145147
"//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
148+
"//src/main/java/com/google/devtools/build/lib/skyframe:precomputed_value",
146149
"//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
147150
"//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_context_key",
148151
"//src/main/java/com/google/devtools/build/lib/skyframe:toolchain_exception",
@@ -170,6 +173,7 @@ java_library(
170173
"//src/test/java/com/google/devtools/build/lib/actions/util",
171174
"//src/test/java/com/google/devtools/build/lib/analysis/testing",
172175
"//src/test/java/com/google/devtools/build/lib/analysis/util",
176+
"//src/test/java/com/google/devtools/build/lib/bazel/bzlmod:util",
173177
"//src/test/java/com/google/devtools/build/lib/exec/util",
174178
"//src/test/java/com/google/devtools/build/lib/packages:testutil",
175179
"//src/test/java/com/google/devtools/build/lib/rules/platform:testutil",

src/test/java/com/google/devtools/build/lib/analysis/StarlarkRuleTransitionProviderTest.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
package com.google.devtools.build.lib.analysis;
1515

1616
import static com.google.common.truth.Truth.assertThat;
17+
import static com.google.devtools.build.lib.bazel.bzlmod.BzlmodTestUtil.createModuleKey;
1718

19+
import com.google.common.collect.ImmutableList;
1820
import com.google.common.collect.ImmutableMap;
1921
import com.google.common.collect.ImmutableSet;
2022
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
@@ -23,18 +25,25 @@
2325
import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
2426
import com.google.devtools.build.lib.analysis.util.DummyTestFragment;
2527
import com.google.devtools.build.lib.analysis.util.DummyTestFragment.DummyTestOptions;
28+
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleResolutionFunction;
29+
import com.google.devtools.build.lib.bazel.bzlmod.FakeRegistry;
30+
import com.google.devtools.build.lib.bazel.bzlmod.ModuleFileFunction;
31+
import com.google.devtools.build.lib.bazel.repository.RepositoryOptions.CheckDirectDepsMode;
2632
import com.google.devtools.build.lib.cmdline.Label;
2733
import com.google.devtools.build.lib.packages.Rule;
2834
import com.google.devtools.build.lib.packages.RuleTransitionData;
2935
import com.google.devtools.build.lib.rules.cpp.CppOptions;
3036
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
37+
import com.google.devtools.build.lib.skyframe.PrecomputedValue;
38+
import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
3139
import com.google.devtools.build.lib.testutil.TestConstants;
3240
import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
3341
import com.google.devtools.build.lib.vfs.ModifiedFileSet;
3442
import com.google.devtools.build.lib.vfs.PathFragment;
3543
import com.google.devtools.build.lib.vfs.Root;
3644
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
3745
import com.google.testing.junit.testparameterinjector.TestParameters;
46+
import java.io.IOException;
3847
import java.util.List;
3948
import java.util.Map;
4049
import java.util.regex.Pattern;
@@ -44,6 +53,23 @@
4453
/** Tests for StarlarkRuleTransitionProvider. */
4554
@RunWith(TestParameterInjector.class)
4655
public final class StarlarkRuleTransitionProviderTest extends BuildViewTestCase {
56+
private FakeRegistry registry;
57+
58+
@Override
59+
protected ImmutableList<Injected> extraPrecomputedValues() {
60+
try {
61+
registry =
62+
FakeRegistry.DEFAULT_FACTORY.newFakeRegistry(scratch.dir("modules").getPathString());
63+
} catch (IOException e) {
64+
throw new IllegalStateException(e);
65+
}
66+
return ImmutableList.of(
67+
PrecomputedValue.injected(
68+
ModuleFileFunction.REGISTRIES, ImmutableList.of(registry.getUrl())),
69+
PrecomputedValue.injected(ModuleFileFunction.IGNORE_DEV_DEPS, false),
70+
PrecomputedValue.injected(
71+
BazelModuleResolutionFunction.CHECK_DIRECT_DEPENDENCIES, CheckDirectDepsMode.WARNING));
72+
}
4773

4874
@Override
4975
protected ConfiguredRuleClassProvider createRuleClassProvider() {
@@ -1272,6 +1298,50 @@ public void successfulTypeConversionOfNativeListOption() throws Exception {
12721298
assertNoEvents();
12731299
}
12741300

1301+
@Test
1302+
public void successfulTypeConversionOfNativeListOption_unambiguousLabels() throws Exception {
1303+
setBuildLanguageOptions("--enable_bzlmod", "--incompatible_unambiguous_label_stringification");
1304+
1305+
scratch.overwriteFile("MODULE.bazel", "bazel_dep(name='rules_x',version='1.0')");
1306+
registry.addModule(createModuleKey("rules_x", "1.0"), "module(name='rules_x', version='1.0')");
1307+
scratch.file("modules/rules_x~1.0/WORKSPACE");
1308+
scratch.file("modules/rules_x~1.0/BUILD");
1309+
scratch.file(
1310+
"modules/rules_x~1.0/defs.bzl",
1311+
"def _tr_impl(settings, attr):",
1312+
" return {'//command_line_option:platforms': [Label('@@//test:my_platform')]}",
1313+
"my_transition = transition(implementation = _tr_impl, inputs = [],",
1314+
" outputs = ['//command_line_option:platforms'])",
1315+
"def _impl(ctx):",
1316+
" pass",
1317+
"my_rule = rule(",
1318+
" implementation = _impl,",
1319+
" cfg = my_transition,",
1320+
" attrs = {",
1321+
" '_allowlist_function_transition': attr.label(",
1322+
" default = '@@//tools/allowlists/function_transition_allowlist',",
1323+
" ),",
1324+
" })");
1325+
1326+
scratch.overwriteFile(
1327+
"tools/allowlists/function_transition_allowlist/BUILD",
1328+
"package_group(",
1329+
" name = 'function_transition_allowlist',",
1330+
" packages = [",
1331+
" '//...',",
1332+
" ],",
1333+
")");
1334+
1335+
scratch.file(
1336+
"test/BUILD",
1337+
"load('@rules_x//:defs.bzl', 'my_rule')",
1338+
"platform(name = 'my_platform')",
1339+
"my_rule(name = 'test')");
1340+
1341+
getConfiguredTarget("//test");
1342+
assertNoEvents();
1343+
}
1344+
12751345
// Regression test for b/170729565
12761346
@Test
12771347
public void testSetBooleanNativeOptionWithStarlarkBoolean() throws Exception {

src/test/java/com/google/devtools/build/lib/analysis/StarlarkTransitionTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void testDupeSettingsInInputsThrowsError() throws Exception {
7878
reporter.removeHandler(failFastHandler);
7979
getConfiguredTarget("//test:arizona");
8080
assertContainsEvent(
81-
"Transition declares duplicate build setting '@//test:formation' in INPUTS");
81+
"Transition declares duplicate build setting '@@//test:formation' in INPUTS");
8282
}
8383

8484
@Test
@@ -117,7 +117,7 @@ public void testDupeSettingsInOutputsThrowsError() throws Exception {
117117
reporter.removeHandler(failFastHandler);
118118
getConfiguredTarget("//test:arizona");
119119
assertContainsEvent(
120-
"Transition declares duplicate build setting '@//test:formation' in OUTPUTS");
120+
"Transition declares duplicate build setting '@@//test:formation' in OUTPUTS");
121121
}
122122

123123
@Test

src/test/java/com/google/devtools/build/lib/rules/LabelBuildSettingTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,6 @@ public void transitionOutput_otherRepo() throws Exception {
325325
"load('//test:rules.bzl', 'rule_with_transition')",
326326
"label_flag(name = 'my_flag1', build_setting_default = ':first_rule')",
327327
"label_flag(name = 'my_flag2', build_setting_default = ':first_rule')",
328-
"label_flag(name = 'my_flag3', build_setting_default = ':first_rule')",
329328
"filegroup(name = 'first_rule')",
330329
"rule_with_transition(name = 'buildme')");
331330
assertThat(getConfiguredTarget("//test:buildme")).isNotNull();

0 commit comments

Comments
 (0)