Skip to content

Commit 1d341f6

Browse files
committed
Add apparent repo name utilities to modules.bzl
Adds the following macros to work with apparent repo names when running under Bzlmod. - `adjust_main_repo_prefix` - `apparent_repo_label_string` - `apparent_repo_name` Originally developed while updating rules_scala to support Bzlmod as part of bazel-contrib/rules_scala#1482. For examples of their use, see bazel-contrib/rules_scala#1621.
1 parent 56b235e commit 1d341f6

File tree

3 files changed

+296
-2
lines changed

3 files changed

+296
-2
lines changed

docs/modules_doc.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,80 @@
22

33
Skylib module containing utilities for Bazel modules and module extensions.
44

5+
<a id="modules.adjust_main_repo_prefix"></a>
6+
7+
## modules.adjust_main_repo_prefix
8+
9+
<pre>
10+
modules.adjust_main_repo_prefix(<a href="#modules.adjust_main_repo_prefix-target_pattern">target_pattern</a>)
11+
</pre>
12+
13+
Updates the main repo prefix to match the current Bazel version.
14+
15+
Used to automatically update strings representing include/exclude target
16+
patterns so that they match actual main repo target Labels correctly. The
17+
main repo prefix will be "@//" for Bazel < 7.1.0, and "@@//" for Bazel >=
18+
7.1.0 under Bzlmod.
19+
20+
21+
**PARAMETERS**
22+
23+
24+
| Name | Description | Default Value |
25+
| :------------- | :------------- | :------------- |
26+
| <a id="modules.adjust_main_repo_prefix-target_pattern"></a>target_pattern | a string used to match a BUILD target pattern | none |
27+
28+
**RETURNS**
29+
30+
the string with any main repository prefix updated to match the current
31+
Bazel version
32+
33+
34+
<a id="modules.apparent_repo_label_string"></a>
35+
36+
## modules.apparent_repo_label_string
37+
38+
<pre>
39+
modules.apparent_repo_label_string(<a href="#modules.apparent_repo_label_string-label">label</a>)
40+
</pre>
41+
42+
Return a Label string starting with its apparent repo name.
43+
44+
**PARAMETERS**
45+
46+
47+
| Name | Description | Default Value |
48+
| :------------- | :------------- | :------------- |
49+
| <a id="modules.apparent_repo_label_string-label"></a>label | a Label instance | none |
50+
51+
**RETURNS**
52+
53+
str(label) with its canonical repository name replaced with its apparent
54+
repository name
55+
56+
57+
<a id="modules.apparent_repo_name"></a>
58+
59+
## modules.apparent_repo_name
60+
61+
<pre>
62+
modules.apparent_repo_name(<a href="#modules.apparent_repo_name-label_or_name">label_or_name</a>)
63+
</pre>
64+
65+
Return a repository's apparent repository name.
66+
67+
**PARAMETERS**
68+
69+
70+
| Name | Description | Default Value |
71+
| :------------- | :------------- | :------------- |
72+
| <a id="modules.apparent_repo_name-label_or_name"></a>label_or_name | a Label or repository name string | none |
73+
74+
**RETURNS**
75+
76+
The apparent repository name
77+
78+
579
<a id="modules.as_extension"></a>
680

781
## modules.as_extension

lib/modules.bzl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,78 @@ def _use_all_repos(module_ctx, reproducible = False):
114114
**extension_metadata_kwargs
115115
)
116116

117+
def _apparent_repo_name(label_or_name):
118+
"""Return a repository's apparent repository name.
119+
120+
Args:
121+
label_or_name: a Label or repository name string
122+
123+
Returns:
124+
The apparent repository name
125+
"""
126+
repo_name = getattr(label_or_name, "repo_name", label_or_name).lstrip("@")
127+
delimiter_indices = []
128+
129+
# Bazed on this pattern from the Bazel source:
130+
# com.google.devtools.build.lib.cmdline.RepositoryName.VALID_REPO_NAME
131+
for i in range(len(repo_name)):
132+
c = repo_name[i]
133+
if not (c.isalnum() or c in "_-."):
134+
delimiter_indices.append(i)
135+
136+
if len(delimiter_indices) == 0:
137+
# Already an apparent repo name, apparently.
138+
return repo_name
139+
140+
if len(delimiter_indices) == 1:
141+
# The name is for a top level module, possibly containing a version ID.
142+
return repo_name[:delimiter_indices[0]]
143+
144+
return repo_name[delimiter_indices[-1] + 1:]
145+
146+
def _apparent_repo_label_string(label):
147+
"""Return a Label string starting with its apparent repo name.
148+
149+
Args:
150+
label: a Label instance
151+
152+
Returns:
153+
str(label) with its canonical repository name replaced with its apparent
154+
repository name
155+
"""
156+
if len(label.repo_name) == 0:
157+
return str(label)
158+
159+
label_str = "@" + str(label).lstrip("@")
160+
return label_str.replace(label.repo_name, _apparent_repo_name(label))
161+
162+
_main_repo_prefix = str(Label("@@//:all")).split(":")[0]
163+
164+
def _adjust_main_repo_prefix(target_pattern):
165+
"""Updates the main repository prefix to match the current Bazel version.
166+
167+
The main repo prefix will be "@//" for Bazel < 7.1.0, and "@@//" for Bazel
168+
>= 7.1.0 under Bzlmod. This macro automatically updates strings representing
169+
include/exclude target patterns so that they match actual main repository
170+
target Labels correctly.
171+
172+
Args:
173+
target_pattern: a string used to match a BUILD target pattern
174+
175+
Returns:
176+
the string with any main repository prefix updated to match the current
177+
Bazel version
178+
"""
179+
if target_pattern.startswith("@//") or target_pattern.startswith("@@//"):
180+
return _main_repo_prefix + target_pattern.lstrip("@/")
181+
182+
return target_pattern
183+
117184
modules = struct(
118185
as_extension = _as_extension,
119186
use_all_repos = _use_all_repos,
187+
apparent_repo_name = _apparent_repo_name,
188+
apparent_repo_label_string = _apparent_repo_label_string,
189+
main_repo_prefix = _main_repo_prefix,
190+
adjust_main_repo_prefix = _adjust_main_repo_prefix
120191
)

tests/modules_test.bzl

Lines changed: 151 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
"""Test usage of modules.bzl."""
1616

1717
load("//lib:modules.bzl", "modules")
18+
load("//lib:unittest.bzl", "asserts", "unittest")
1819
load("//rules:build_test.bzl", "build_test")
1920

21+
_is_bzlmod_enabled = str(Label("//tests:module_tests.bzl")).startswith("@@")
22+
2023
def _repo_rule_impl(repository_ctx):
2124
repository_ctx.file("WORKSPACE")
2225
repository_ctx.file("BUILD", """exports_files(["hello"])""")
@@ -45,12 +48,158 @@ use_all_repos_test_ext = module_extension(
4548
doc = "Only used for testing modules.use_all_repos().",
4649
)
4750

51+
def _apparent_repo_name_test(ctx):
52+
"""Unit tests for modules.apparent_repo_name."""
53+
env = unittest.begin(ctx)
54+
55+
asserts.equals(
56+
env, "", modules.apparent_repo_name(""),
57+
msg = "Handles the empty string as input",
58+
)
59+
60+
asserts.equals(
61+
env, "foo", modules.apparent_repo_name("foo"),
62+
msg = (
63+
"Return the original name unchanged if it doesn't start with `@`.",
64+
),
65+
)
66+
67+
asserts.equals(
68+
env, "foo", modules.apparent_repo_name("@foo"),
69+
msg = "Return the original name without `@` if already apparent.",
70+
)
71+
72+
asserts.equals(
73+
env, "foo", modules.apparent_repo_name(Label("@foo").repo_name),
74+
msg = "Return the apparent name from a canonical name string.",
75+
)
76+
77+
asserts.equals(
78+
env, "", modules.apparent_repo_name(Label("@@//:all")),
79+
msg = "Returns the empty string for a main repository Label.",
80+
)
81+
82+
asserts.equals(
83+
env, "", modules.apparent_repo_name(Label("@bazel_skylib//:all")),
84+
msg = " ".join([
85+
"Returns the empty string for a Label containing the main",
86+
"repository's module name.",
87+
]),
88+
)
89+
90+
asserts.equals(
91+
env, "foo", modules.apparent_repo_name(Label("@foo")),
92+
msg = "Return the apparent name from a Label.",
93+
)
94+
95+
asserts.equals(
96+
env, "rules_pkg", modules.apparent_repo_name(Label("@rules_pkg")),
97+
msg = " ".join([
98+
"Top level module repos have the canonical name delimiter at the",
99+
"end. Therefore, this should not return the empty string, but the",
100+
"name without the leading `@` and trailing delimiter.",
101+
]),
102+
)
103+
104+
asserts.equals(
105+
env,
106+
"stardoc" if _is_bzlmod_enabled else "io_bazel_stardoc",
107+
modules.apparent_repo_name(Label("@io_bazel_stardoc")),
108+
msg = " ".join([
109+
"Label values will already map bazel_dep repo_names to",
110+
"actual repo names under Bzlmod (no-op under WORKSPACE)."
111+
])
112+
)
113+
114+
asserts.equals(
115+
env, "foo", modules.apparent_repo_name("foo+1.2.3"),
116+
msg = "Ignores version numbers in canonical repo names",
117+
)
118+
119+
return unittest.end(env)
120+
121+
apparent_repo_name_test = unittest.make(_apparent_repo_name_test)
122+
123+
def _apparent_repo_label_string_test(ctx):
124+
"""Unit tests for modules.apparent_repo_label_string."""
125+
env = unittest.begin(ctx)
126+
127+
main_repo = str(Label("//:all"))
128+
asserts.equals(
129+
env, main_repo, modules.apparent_repo_label_string(Label("//:all")),
130+
msg = "Returns top level target with leading `@` or `@@`",
131+
)
132+
133+
main_module_label = Label("@bazel_skylib//:all")
134+
asserts.equals(
135+
env, main_repo, modules.apparent_repo_label_string(main_module_label),
136+
msg = " ".join([
137+
"Returns top level target with leading `@` or `@@`",
138+
"for a Label containing the main module's name",
139+
]),
140+
)
141+
142+
rules_pkg = "@rules_pkg//:all"
143+
asserts.equals(
144+
env, rules_pkg, modules.apparent_repo_label_string(Label(rules_pkg)),
145+
msg = "Returns original repo name",
146+
)
147+
148+
asserts.equals(
149+
env,
150+
"@%s//:all" % ("stardoc" if _is_bzlmod_enabled else "io_bazel_stardoc"),
151+
modules.apparent_repo_label_string(Label("@io_bazel_stardoc//:all")),
152+
msg = " ".join([
153+
"Returns the actual module name instead of",
154+
"repo_name from bazel_dep() (no-op under WORKSPACE).",
155+
]),
156+
)
157+
158+
return unittest.end(env)
159+
160+
apparent_repo_label_string_test = unittest.make(
161+
_apparent_repo_label_string_test
162+
)
163+
164+
def _adjust_main_repo_prefix_test(ctx):
165+
"""Unit tests for modules.apparent_repo_label_string."""
166+
env = unittest.begin(ctx)
167+
168+
expected = modules.main_repo_prefix + ":all"
169+
asserts.equals(
170+
env, expected, modules.adjust_main_repo_prefix("@//:all"),
171+
msg = "Normalizes a target pattern starting with `@//`.",
172+
)
173+
174+
asserts.equals(
175+
env, expected, modules.adjust_main_repo_prefix("@@//:all"),
176+
msg = "Normalizes a target pattern starting with `@@//`.",
177+
)
178+
179+
original = "@not_the_main_repo"
180+
asserts.equals(
181+
env, original, modules.adjust_main_repo_prefix(original),
182+
msg = "Returns non main repo target patterns unchanged.",
183+
)
184+
185+
return unittest.end(env)
186+
187+
adjust_main_repo_prefix_test = unittest.make(
188+
_adjust_main_repo_prefix_test
189+
)
190+
48191
# buildifier: disable=unnamed-macro
49192
def modules_test_suite():
50193
"""Creates the tests for modules.bzl if Bzlmod is enabled."""
51194

52-
is_bzlmod_enabled = str(Label("//tests:module_tests.bzl")).startswith("@@")
53-
if not is_bzlmod_enabled:
195+
unittest.suite(
196+
"modules_tests",
197+
apparent_repo_name_test,
198+
apparent_repo_label_string_test,
199+
adjust_main_repo_prefix_test,
200+
)
201+
202+
if not _is_bzlmod_enabled:
54203
return
55204

56205
build_test(

0 commit comments

Comments
 (0)