Skip to content

Commit b859571

Browse files
Wyveraldcopybara-github
authored andcommitted
Add native.package_relative_label function
This essentially brings back the old `relative_to_caller_repository` param to the Label constructor, but with an arguably better API. It converts a label string into a Label object using the context of the calling package; this is useful for macro authors that want to convert string inputs into labels for reasons such as deduping. Also made `Label()` accept relative labels (like `:foo`) because there really isn't a good reason not to do so. Short design doc about the proposed API: https://docs.google.com/document/d/1_kMVWRHSBVkSsw1SLWPf3e-3RNSt55ZZYlzFzJkkwMo/edit Fixes bazelbuild#17260 RELNOTES: Added a `native.package_relative_label()` function, which converts a label string to a Label object in the context of the calling package, in contrast to `Label()`, which does so in the context of the current .bzl file. Both functions now also accept relative labels such as `:foo`, and are idempotent. PiperOrigin-RevId: 507836895 Change-Id: Ic870fe564d96a77f05dd7258d32c031fca8cacb1
1 parent 4ed6327 commit b859571

File tree

8 files changed

+146
-15
lines changed

8 files changed

+146
-15
lines changed

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -997,8 +997,11 @@ public boolean isImmutable() {
997997
.build();
998998

999999
@Override
1000-
public Label label(String labelString, StarlarkThread thread) throws EvalException {
1001-
// The label string is interpeted with respect to the .bzl module containing the call to
1000+
public Label label(Object input, StarlarkThread thread) throws EvalException {
1001+
if (input instanceof Label) {
1002+
return (Label) input;
1003+
}
1004+
// The label string is interpreted with respect to the .bzl module containing the call to
10021005
// `Label()`. An alternative to this approach that avoids stack inspection is to have each .bzl
10031006
// module define its own copy of the `Label()` builtin embedding the module's own name. This
10041007
// would lead to peculiarities like foo.bzl being able to call bar.bzl's `Label()` symbol to
@@ -1007,9 +1010,9 @@ public Label label(String labelString, StarlarkThread thread) throws EvalExcepti
10071010
BazelModuleContext moduleContext =
10081011
BazelModuleContext.of(Module.ofInnermostEnclosingStarlarkFunction(thread));
10091012
try {
1010-
return Label.parseWithRepoContext(labelString, moduleContext.packageContext());
1013+
return Label.parseWithPackageContext((String) input, moduleContext.packageContext());
10111014
} catch (LabelSyntaxException e) {
1012-
throw Starlark.errorf("Illegal absolute label syntax: %s", e.getMessage());
1015+
throw Starlark.errorf("invalid label in Label(): %s", e.getMessage());
10131016
}
10141017
}
10151018

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

+14
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,20 @@ public String repositoryName(StarlarkThread thread) throws EvalException {
621621
return packageId.getRepository().getNameWithAt();
622622
}
623623

624+
@Override
625+
public Label packageRelativeLabel(Object input, StarlarkThread thread) throws EvalException {
626+
BazelStarlarkContext.from(thread).checkLoadingPhase("native.package_relative_label");
627+
if (input instanceof Label) {
628+
return (Label) input;
629+
}
630+
try {
631+
String s = (String) input;
632+
return PackageFactory.getContext(thread).getBuilder().getLabelConverter().convert(s);
633+
} catch (LabelSyntaxException e) {
634+
throw Starlark.errorf("invalid label in native.package_relative_label: %s", e.getMessage());
635+
}
636+
}
637+
624638
private static Dict<String, Object> getRuleDict(Rule rule, Mutability mu) throws EvalException {
625639
Dict.Builder<String, Object> values = Dict.builder();
626640

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkNativeModuleApi.java

+33
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.devtools.build.lib.starlarkbuildapi;
1616

1717
import com.google.devtools.build.docgen.annot.DocCategory;
18+
import com.google.devtools.build.lib.cmdline.Label;
1819
import net.starlark.java.annot.Param;
1920
import net.starlark.java.annot.ParamType;
2021
import net.starlark.java.annot.StarlarkBuiltin;
@@ -241,6 +242,38 @@ NoneType exportsFiles(Sequence<?> srcs, Object visibility, Object licenses, Star
241242
useStarlarkThread = true)
242243
String repositoryName(StarlarkThread thread) throws EvalException;
243244

245+
@StarlarkMethod(
246+
name = "package_relative_label",
247+
doc =
248+
"Converts the input string into a <a href='Label'>Label</a> object, in the context of the"
249+
+ " package currently being initialized (that is, the <code>BUILD</code> file for"
250+
+ " which the current macro is executing). If the input is already a"
251+
+ " <code>Label</code>, it is returned unchanged.<p>This function may only be called"
252+
+ " while evaluating a BUILD file and the macros it directly or indirectly calls; it"
253+
+ " may not be called in (for instance) a rule implementation function. <p>The result"
254+
+ " of this function is the same <code>Label</code> value as would be produced by"
255+
+ " passing the given string to a label-valued attribute of a target declared in the"
256+
+ " BUILD file. <p><i>Usage note:</i> The difference between this function and <a"
257+
+ " href='Label#Label'>Label()</a></code> is that <code>Label()</code> uses the"
258+
+ " context of the package of the <code>.bzl</code> file that called it, not the"
259+
+ " package of the <code>BUILD</code> file. Use <code>Label()</code> when you need to"
260+
+ " refer to a fixed target that is hardcoded into the macro, such as a compiler. Use"
261+
+ " <code>package_relative_label()</code> when you need to normalize a label string"
262+
+ " supplied by the BUILD file to a <code>Label</code> object. (There is no way to"
263+
+ " convert a string to a <code>Label</code> in the context of a package other than"
264+
+ " the BUILD file or the calling .bzl file. For that reason, outer macros should"
265+
+ " always prefer to pass Label objects to inner macros rather than label strings.)",
266+
parameters = {
267+
@Param(
268+
name = "input",
269+
allowedTypes = {@ParamType(type = String.class), @ParamType(type = Label.class)},
270+
doc =
271+
"The input label string or Label object. If a Label object is passed, it's"
272+
+ " returned as is.")
273+
},
274+
useStarlarkThread = true)
275+
Label packageRelativeLabel(Object input, StarlarkThread thread) throws EvalException;
276+
244277
@StarlarkMethod(
245278
name = "subpackages",
246279
doc =

src/main/java/com/google/devtools/build/lib/starlarkbuildapi/StarlarkRuleFunctionsApi.java

+16-8
Original file line numberDiff line numberDiff line change
@@ -701,18 +701,26 @@ StarlarkAspectApi aspect(
701701
@StarlarkMethod(
702702
name = "Label",
703703
doc =
704-
"Creates a Label referring to a BUILD target. Use this function when you want to give a"
705-
+ " default value for the label attributes of a rule or when referring to a target"
706-
+ " via an absolute label from a macro. The argument must refer to an absolute label."
707-
+ " The repo part of the label (or its absence) is interpreted in the context of the"
708-
+ " repo where this Label() call appears. Example: <br><pre"
709-
+ " class=language-python>Label(\"//tools:default\")</pre>",
704+
"Converts a label string into a <code>Label</code> object, in the context of the package"
705+
+ " where the calling <code>.bzl</code> source file lives. If the given value is"
706+
+ " already a <code>Label</code>, it is returned unchanged."
707+
+ "<p>For macros, a related function,"
708+
+ " <code><a"
709+
+ " href='native#package_relative_label'>native.package_relative_label()</a></code>,"
710+
+ " converts the input into a <code>Label</code> in the context of the package"
711+
+ " currently being constructed. Use that function to mimic the string-to-label"
712+
+ " conversion that is automatically done by label-valued rule attributes.",
710713
parameters = {
711-
@Param(name = "label_string", doc = "the label string."),
714+
@Param(
715+
name = "input",
716+
allowedTypes = {@ParamType(type = String.class), @ParamType(type = Label.class)},
717+
doc =
718+
"The input label string or Label object. If a Label object is passed, it's"
719+
+ " returned as is.")
712720
},
713721
useStarlarkThread = true)
714722
@StarlarkConstructor
715-
Label label(String labelString, StarlarkThread thread) throws EvalException;
723+
Label label(Object input, StarlarkThread thread) throws EvalException;
716724

717725
@StarlarkMethod(
718726
name = "exec_group",

src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkNativeModuleApi.java

+6
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import com.google.common.collect.ImmutableCollection;
1818
import com.google.common.collect.ImmutableList;
19+
import com.google.devtools.build.lib.cmdline.Label;
1920
import com.google.devtools.build.lib.starlarkbuildapi.StarlarkNativeModuleApi;
2021
import javax.annotation.Nullable;
2122
import net.starlark.java.eval.Dict;
@@ -77,6 +78,11 @@ public String repositoryName(StarlarkThread thread) {
7778
return "";
7879
}
7980

81+
@Override
82+
public Label packageRelativeLabel(Object input, StarlarkThread thread) throws EvalException {
83+
return Label.parseCanonicalUnchecked("//:fake_label");
84+
}
85+
8086
@Override
8187
public Sequence<?> subpackages(
8288
Sequence<?> include, Sequence<?> exclude, boolean allowEmpty, StarlarkThread thread)

src/main/java/com/google/devtools/build/skydoc/fakebuildapi/FakeStarlarkRuleFunctionsApi.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,14 @@ public StarlarkCallable rule(
171171
}
172172

173173
@Override
174-
public Label label(String labelString, StarlarkThread thread) throws EvalException {
174+
public Label label(Object input, StarlarkThread thread) throws EvalException {
175+
if (input instanceof Label) {
176+
return (Label) input;
177+
}
175178
try {
176-
return Label.parseCanonical(labelString);
179+
return Label.parseCanonical((String) input);
177180
} catch (LabelSyntaxException e) {
178-
throw Starlark.errorf("Illegal absolute label syntax: %s", labelString);
181+
throw Starlark.errorf("Illegal absolute label syntax: %s", input);
179182
}
180183
}
181184

src/test/java/com/google/devtools/build/lib/starlark/StarlarkRuleClassFunctionsTest.java

+7
Original file line numberDiff line numberDiff line change
@@ -1119,6 +1119,13 @@ public void testLabel() throws Exception {
11191119
assertThat(result.toString()).isEqualTo("//foo/foo:foo");
11201120
}
11211121

1122+
@Test
1123+
public void testLabelIdempotence() throws Exception {
1124+
Object result = ev.eval("Label(Label('//foo/foo:foo'))");
1125+
assertThat(result).isInstanceOf(Label.class);
1126+
assertThat(result.toString()).isEqualTo("//foo/foo:foo");
1127+
}
1128+
11221129
@Test
11231130
public void testLabelSameInstance() throws Exception {
11241131
Object l1 = ev.eval("Label('//foo/foo:foo')");

src/test/py/bazel/bzlmod/bazel_module_test.py

+57
Original file line numberDiff line numberDiff line change
@@ -884,5 +884,62 @@ def testJavaRunfilesLibraryRepoMapping(self):
884884
env_add={'RUNFILES_LIB_DEBUG': '1'})
885885
self.AssertExitCode(exit_code, 0, stderr, stdout)
886886

887+
def testNativePackageRelativeLabel(self):
888+
self.ScratchFile(
889+
'MODULE.bazel',
890+
[
891+
'module(name="foo")',
892+
'bazel_dep(name="bar")',
893+
'local_path_override(module_name="bar",path="bar")',
894+
],
895+
)
896+
self.ScratchFile('WORKSPACE')
897+
self.ScratchFile('BUILD')
898+
self.ScratchFile(
899+
'defs.bzl',
900+
[
901+
'def mac(name):',
902+
' native.filegroup(name=name)',
903+
' print("1st: " + str(native.package_relative_label(":bleb")))',
904+
' print("2nd: " + str(native.package_relative_label('
905+
+ '"//bleb:bleb")))',
906+
' print("3rd: " + str(native.package_relative_label('
907+
+ '"@bleb//bleb:bleb")))',
908+
' print("4th: " + str(native.package_relative_label("//bleb")))',
909+
' print("5th: " + str(native.package_relative_label('
910+
+ '"@@bleb//bleb:bleb")))',
911+
' print("6th: " + str(native.package_relative_label(Label('
912+
+ '"//bleb"))))',
913+
],
914+
)
915+
916+
self.ScratchFile(
917+
'bar/MODULE.bazel',
918+
[
919+
'module(name="bar")',
920+
'bazel_dep(name="foo", repo_name="bleb")',
921+
],
922+
)
923+
self.ScratchFile('bar/WORKSPACE')
924+
self.ScratchFile(
925+
'bar/quux/BUILD',
926+
[
927+
'load("@bleb//:defs.bzl", "mac")',
928+
'mac(name="book")',
929+
],
930+
)
931+
932+
_, _, stderr = self.RunBazel(
933+
['build', '@bar//quux:book'], allow_failure=False
934+
)
935+
stderr = '\n'.join(stderr)
936+
self.assertIn('1st: @@bar~override//quux:bleb', stderr)
937+
self.assertIn('2nd: @@bar~override//bleb:bleb', stderr)
938+
self.assertIn('3rd: @@//bleb:bleb', stderr)
939+
self.assertIn('4th: @@bar~override//bleb:bleb', stderr)
940+
self.assertIn('5th: @@bleb//bleb:bleb', stderr)
941+
self.assertIn('6th: @@//bleb:bleb', stderr)
942+
943+
887944
if __name__ == '__main__':
888945
unittest.main()

0 commit comments

Comments
 (0)