diff --git a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java index 9746f780f100b6..968c70a1fecde4 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/starlark/StarlarkRuleContext.java @@ -57,6 +57,7 @@ import com.google.devtools.build.lib.analysis.test.InstrumentedFilesCollector; import com.google.devtools.build.lib.analysis.test.InstrumentedFilesInfo; import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.LabelSyntaxException; import com.google.devtools.build.lib.collect.nestedset.Depset; import com.google.devtools.build.lib.collect.nestedset.Depset.TypeException; import com.google.devtools.build.lib.collect.nestedset.NestedSet; @@ -1154,7 +1155,7 @@ public Tuple resolveCommand( String attribute = Type.STRING.convertOptional(attributeUnchecked, "attribute"); if (expandLocations) { command = - helper.resolveCommandAndExpandLabels(command, attribute, /*allowDataInLabel=*/ false); + helper.resolveCommandAndExpandLabels(command, attribute, /* allowDataInLabel= */ false); } if (!Starlark.isNullOrNone(makeVariablesUnchecked)) { Map makeVariables = @@ -1212,6 +1213,23 @@ private void checkResolveToolsAllowed() throws EvalException { } } + @Override + public Label packageRelativeLabel(Object input) throws EvalException { + checkMutable("package_relative_label"); + if (input instanceof Label inputLabel) { + return inputLabel; + } + try { + return Label.parseWithPackageContext( + (String) input, + Label.PackageContext.of( + ruleContext.getLabel().getPackageIdentifier(), + ruleContext.getRule().getPackageMetadata().repositoryMapping())); + } catch (LabelSyntaxException e) { + throw Starlark.errorf("invalid label in ctx.package_relative_label: %s", e.getMessage()); + } + } + @Override public StarlarkSemantics getStarlarkSemantics() { return ruleContext.getAnalysisEnvironment().getStarlarkSemantics(); diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java index 560bed104f4121..7aeee046c593b3 100644 --- a/src/main/java/com/google/devtools/build/lib/cmdline/Label.java +++ b/src/main/java/com/google/devtools/build/lib/cmdline/Label.java @@ -566,6 +566,7 @@ public Label getSamePackageLabel(String targetName) throws LabelSyntaxException + " containing an apparent repo name. Prefer Label.same_package_label(), native.package_relative_label()," + + " ctx.package_relative_label()," + " or Label() instead.

Resolves a label that" + " is either absolute (starts with //) or relative to the current" + " package. If this label is in a remote repository, the argument will be resolved" diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java index 7f0c45c6299340..65259e5b70668f 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java @@ -292,7 +292,10 @@ NoneType exportsFiles(Sequence srcs, Object visibility, Object licenses, Star + " supplied by the BUILD file to a Label object. (There is no way to" + " convert a string to a Label in the context of a package other than" + " the BUILD file or the calling .bzl file. For that reason, outer macros should" - + " always prefer to pass Label objects to inner macros rather than label strings.)", + + " always prefer to pass Label objects to inner macros rather than label strings.)" + + "ctx.package_relative_label()" + + " provides the same functionality within a rule or aspect implementation" + + " function.", parameters = { @Param( name = "input", diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleContextApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleContextApi.java index fe74341bd78812..2e5490177b2f75 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleContextApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleContextApi.java @@ -330,11 +330,7 @@ public interface StarlarkRuleContextApi tokenize(String optionString) throws EvalException; @@ -664,4 +660,32 @@ Tuple resolveCommand( doc = "List of tools (list of targets)."), }) Tuple resolveTools(Sequence tools) throws EvalException; + + @StarlarkMethod( + name = "package_relative_label", + doc = + """ + Converts the input string into a Label object, in \ + the context of the package of the target currently being analyzed. If the input is \ + already a Label, it is returned unchanged.

The result of this function is \ + the same Label value as would be produced by passing the given string to a \ + label-valued attribute of the rule and accessing the corresponding \ + Label() is \ + that Label() uses the context of the package of the .bzl file \ + that called it, not the package of the target currently being analyzed. This function \ + has the same behavior as + native.package_relative_label(), which cannot be used in a rule or + aspect implementation function. + """, + parameters = { + @Param( + name = "input", + allowedTypes = {@ParamType(type = String.class), @ParamType(type = Label.class)}, + doc = + "The input label string or Label object. If a Label object is passed, it's" + + " returned as is.") + }) + Label packageRelativeLabel(Object input) throws EvalException; } diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java index 8a9be9a5ff22dd..381dc0b78ac9d8 100644 --- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java +++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java @@ -1036,8 +1036,11 @@ StarlarkAspectApi aspect( + " function, native.package_relative_label()," + " converts the input into a Label in the context of the package" - + " currently being constructed. Use that function to mimic the string-to-label" - + " conversion that is automatically done by label-valued rule attributes.", + + " currently being constructed. For rule and aspect implementation functions, ctx.package_relative_label()" + + " can be used for the same purpose. Use these functions to mimic the" + + " string-to-label conversion that is automatically done by label-valued rule" + + " attributes.", parameters = { @Param( name = "input", diff --git a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java index 224d2674b57805..bd3d75266bf2ec 100644 --- a/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java +++ b/src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleContextTest.java @@ -52,6 +52,7 @@ import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; import com.google.devtools.build.lib.analysis.util.MockRule; import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.cmdline.RepositoryMapping; import com.google.devtools.build.lib.collect.nestedset.Depset; import com.google.devtools.build.lib.packages.Provider; @@ -2792,8 +2793,7 @@ public void testNoAccessToDependencyActionsWithoutStarlarkTest() throws Exceptio @Test public void testAbstractActionInterface() throws Exception { - setBuildLanguageOptions( - "--incompatible_no_rule_outputs_param=false"); + setBuildLanguageOptions("--incompatible_no_rule_outputs_param=false"); scratch.file( "test/rules.bzl", "load('//test:providers.bzl', 'AInfo')", @@ -2838,8 +2838,7 @@ public void testAbstractActionInterface() throws Exception { @Test public void testCreatedActions() throws Exception { - setBuildLanguageOptions( - "--incompatible_no_rule_outputs_param=false"); + setBuildLanguageOptions("--incompatible_no_rule_outputs_param=false"); // createRuleContext() gives us the context for a rule upon entry into its analysis function. // But we need to inspect the result of calling created_actions() after the rule context has // been modified by creating actions. So we'll call created_actions() from within the analysis @@ -2927,8 +2926,7 @@ public void testSpawnActionInterface() throws Exception { @Test public void testRunShellUsesHelperScriptForLongCommand() throws Exception { - setBuildLanguageOptions( - "--incompatible_no_rule_outputs_param=false"); + setBuildLanguageOptions("--incompatible_no_rule_outputs_param=false"); // createRuleContext() gives us the context for a rule upon entry into its analysis function. // But we need to inspect the result of calling created_actions() after the rule context has // been modified by creating actions. So we'll call created_actions() from within the analysis @@ -4935,4 +4933,39 @@ def _impl(ctx): assertThat(buildSettingProvider.getDefaultValue()).isEqualTo(ImmutableSet.copyOf(defaultValue)); assertThat(buildSettingProvider.getType()).isEqualTo(Types.STRING_SET); } + + @Test + public void testPackageRelativeLabel() throws Exception { + scratch.file("rules/BUILD"); + scratch.file( + "rules/rules.bzl", + """ + MyProvider = provider() + + def _impl(ctx): + return MyProvider(result = ctx.package_relative_label(":some_target")) + + my_rule = rule( + implementation = _impl, + ) + """); + + scratch.file( + "test/BUILD", + """ + load("//rules:rules.bzl", "my_rule") + + my_rule( + name = "my_target", + ) + """); + + ConfiguredTarget myTarget = getConfiguredTarget("//test:my_target"); + Provider.Key myProviderKey = + new StarlarkProvider.Key( + keyForBuild(Label.create(PackageIdentifier.createInMainRepo("rules"), "rules.bzl")), + "MyProvider"); + var result = (Label) ((StarlarkInfo) myTarget.get(myProviderKey)).getValue("result"); + assertThat(result).isEqualTo(Label.parseCanonicalUnchecked("//test:some_target")); + } } diff --git a/src/test/py/bazel/bzlmod/bazel_module_test.py b/src/test/py/bazel/bzlmod/bazel_module_test.py index ea2b9de3db8c95..bba57ca5a332dc 100644 --- a/src/test/py/bazel/bzlmod/bazel_module_test.py +++ b/src/test/py/bazel/bzlmod/bazel_module_test.py @@ -622,6 +622,58 @@ def testNativePackageRelativeLabel(self): self.assertIn('5th: @@bleb//bleb:bleb', stderr) self.assertIn('6th: @@//bleb:bleb', stderr) + def testCtxPackageRelativeLabel(self): + self.ScratchFile( + 'MODULE.bazel', + [ + 'module(name="foo")', + 'bazel_dep(name="bar")', + 'local_path_override(module_name="bar",path="bar")', + ], + ) + self.ScratchFile('BUILD') + self.ScratchFile( + 'defs.bzl', + [ + 'def _my_rule_impl(ctx):', + ' print("1st: " + str(ctx.package_relative_label(":bleb")))', + ' print("2nd: " + str(ctx.package_relative_label(' + + '"//bleb:bleb")))', + ' print("3rd: " + str(ctx.package_relative_label(' + + '"@bleb//bleb:bleb")))', + ' print("4th: " + str(ctx.package_relative_label("//bleb")))', + ' print("5th: " + str(ctx.package_relative_label(' + + '"@@bleb//bleb:bleb")))', + ' print("6th: " + str(ctx.package_relative_label(Label(' + + '"//bleb"))))', + 'my_rule = rule(_my_rule_impl)', + ], + ) + + self.ScratchFile( + 'bar/MODULE.bazel', + [ + 'module(name="bar")', + 'bazel_dep(name="foo", repo_name="bleb")', + ], + ) + self.ScratchFile( + 'bar/quux/BUILD', + [ + 'load("@bleb//:defs.bzl", "my_rule")', + 'my_rule(name="book")', + ], + ) + + _, _, stderr = self.RunBazel(['build', '@bar//quux:book']) + stderr = '\n'.join(stderr) + self.assertIn('1st: @@bar+//quux:bleb', stderr) + self.assertIn('2nd: @@bar+//bleb:bleb', stderr) + self.assertIn('3rd: @@//bleb:bleb', stderr) + self.assertIn('4th: @@bar+//bleb:bleb', stderr) + self.assertIn('5th: @@bleb//bleb:bleb', stderr) + self.assertIn('6th: @@//bleb:bleb', stderr) + def testArchiveWithArchiveType(self): # make the archive without the .zip extension self.main_registry.createShModule(