Skip to content

Commit 2c076f2

Browse files
committed
Add repo name macros for Bzlmod compatibility
Part of bazel-contrib#1482. These helper macros fix various repository name related errors when building under Bzlmod, while remaining backwards compatible with `WORKSPACE`. Without Bzlmod, these macros return original repo names. With Bzlmod enabled, they avoid the problems described below. I've prepared a change for bazelbuild/bazel-skylib containing these macros with full unit tests. If the maintainers accept that change, we can bump our bazel_skylib version to use the macros from there, and remove the `bzlmod.bzl` file. --- Also includes a couple of other minor touch-ups: - Updated the `runtime_deps` attribute in `repositories()` to add the Scala version suffix, just like `deps`. - Added a `fail()` message to `repositories()` to make it more clear which Scala version dictionary is missing an artifact. - Removed unnecessary internal uses of the `@io_bazel_rules_scala` repo name, applying `Label()` where necessary. - Updated the construction of `dep_providers` in `_default_dep_providers` to remove the repo name, reduce duplication, and make the upcoming toolchain update slightly cleaner. --- Before this change, `repositories()` would originally emit `BUILD` targets including canonical repo names: ```py scala_import( name = "_main~scala_deps~io_bazel_rules_scala_scala_compiler", jars = ["scala-compiler-2.12.18.jar"], ) ``` resulting in errors like: ```txt ERROR: .../_main~_repo_rules~io_bazel_rules_scala/scala/BUILD: no such target '@@_main~scala_deps~io_bazel_rules_scala_scala_compiler//:io_bazel_rules_scala_scala_compiler': target 'io_bazel_rules_scala_scala_compiler' not declared in package '' defined by .../_main~scala_deps~io_bazel_rules_scala_scala_compiler/BUILD and referenced by '@@_main~_repo_rules~io_bazel_rules_scala//scala:default_toolchain_scala_compile_classpath_provider' ``` --- Attaching resources from custom repos to targets under Bzlmod, like in `scalarules.test.resources.ScalaLibResourcesFromExternalDepTest`, would break with: ```txt $ bazel test //test/src/main/scala/scalarules/test/resources:all 1) Scala library depending on resources from external resource-only jar::allow to load resources(scalarules.test.resources.ScalaLibResourcesFromExternalDepTest) java.lang.NullPointerException at scalarules.test.resources.ScalaLibResourcesFromExternalDepTest.get(ScalaLibResourcesFromExternalDepTest.scala:17) at scalarules.test.resources.ScalaLibResourcesFromExternalDepTest.$anonfun$new$3(ScalaLibResourcesFromExternalDepTest.scala:11) at scalarules.test.resources.ScalaLibResourcesFromExternalDepTest.$anonfun$new$2(ScalaLibResourcesFromExternalDepTest.scala:11) ``` `_update_external_target_path` in `resources.bzl` fixes this problem. --- Fixes `test_strict_deps_filter_included_target` from `test/shell/test_strict_dependency.sh` when run under Bzlmod. The `dependency_tracking_strict_deps_patterns` attribute of //test_expect_failure/missing_direct_deps/filtering:plus_one_strict_deps_filter_a_impl contains patterns starting with `@//`. However, in `_phase_dependency()` from `scala/private/phases/phase_dependency.bzl`, these patterns were compared against a stringified Label. Under Bazel < 7.1.0, this works for root target Labels. Under Bazel >= 7.1.0, this breaks for root target Labels under Bzlmod, which start with `@@//`. `adjust_main_repo_prefix` updates the patterns accordingly in `_partition_patterns` from `scala_toolchain.bzl`. `apparent_repo_label_string` makes `_phase_dependency()` more resilient when comparing target Labels against filters containing external apparent repo names. --- Fixes the `alias` targets generated by `_jvm_import_external` from `scala_maven_import_external.bzl` by setting the `target` to the correct apparent repo name. Added `apparent_repo_name(repository_ctx.name)` to `_jvm_import_external` to avoid this familiar error when running `dt_patches/test_dt_patches` tests: ```txt $ bazel build //... ERROR: .../external/_main~compiler_source_repos~scala_reflect/BUILD: no such target '@@_main~compiler_source_repos~scala_reflect//:scala_reflect': target 'scala_reflect' not declared in package '' defined by .../external/_main~compiler_source_repos~scala_reflect/BUILD ERROR: .../dt_patches/test_dt_patches/BUILD:11:22: no such target '@@_main~compiler_source_repos~scala_reflect//:scala_reflect': target 'scala_reflect' not declared in package '' defined by .../external/_main~compiler_source_repos~scala_reflect/BUILD and referenced by '//:dt_scala_toolchain_scala_compile_classpath_provider' ERROR: Analysis of target '//:dt_scala_toolchain_scala_compile_classpath_provider' failed; build aborted: Analysis failed ``` --- As for why we need these macros, we can't rely on hacking the specific canonical repository name format: > Repos generated by extensions have canonical names in the form of > `module_repo_canonical_name+extension_name+repo_name`. Note that the > canonical name format is not an API you should depend on — it's > subject to change at any time. > > - https://bazel.build/external/extension#repository_names_and_visibility The change to no longer encode module versions in canonical repo names in Bazel 7.1.0 is a recent example of Bazel maintainers altering the format: - bazelbuild/bazel#21316 And the maintainers recently replaced the `~` delimiter with `+` in the upcoming Bazel 8 release due to build performance issues on Windows: - bazelbuild/bazel#22865 The core `apparent_repo_name` function assumes the only valid repo name characters are letters, numbers, '_', '-', and '.'. This is valid so long as this condition holds: - https://github.com/bazelbuild/bazel/blob/7.3.2/src/main/java/com/google/devtools/build/lib/cmdline/RepositoryName.java#L159-L162
1 parent 1aeaad4 commit 2c076f2

File tree

6 files changed

+118
-24
lines changed

6 files changed

+118
-24
lines changed

scala/private/macros/bzlmod.bzl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Utilities for working with Bazel modules"""
2+
3+
def apparent_repo_name(label_or_name):
4+
"""Return a repository's apparent repository name.
5+
6+
Can be replaced with a future bazel-skylib implementation, if accepted into
7+
that repo.
8+
9+
Args:
10+
label_or_name: a Label or repository name string
11+
12+
Returns:
13+
The apparent repository name
14+
"""
15+
repo_name = getattr(label_or_name, "repo_name", label_or_name).lstrip("@")
16+
delimiter_indices = []
17+
18+
# Bazed on this pattern from the Bazel source:
19+
# com.google.devtools.build.lib.cmdline.RepositoryName.VALID_REPO_NAME
20+
for i in range(len(repo_name)):
21+
c = repo_name[i]
22+
if not (c.isalnum() or c in "_-."):
23+
delimiter_indices.append(i)
24+
25+
if len(delimiter_indices) == 0:
26+
# Already an apparent repo name, apparently.
27+
return repo_name
28+
29+
if len(delimiter_indices) == 1:
30+
# The name is for a top level module, possibly containing a version ID.
31+
return repo_name[:delimiter_indices[0]]
32+
33+
return repo_name[delimiter_indices[-1] + 1:]
34+
35+
def apparent_repo_label_string(label):
36+
"""Return a Label string starting with its apparent repo name.
37+
38+
Args:
39+
label: a Label instance
40+
41+
Returns:
42+
str(label) with its canonical repository name replaced with its apparent
43+
repository name
44+
"""
45+
if len(label.repo_name) == 0:
46+
return str(label)
47+
48+
label_str = "@" + str(label).lstrip("@")
49+
return label_str.replace(label.repo_name, apparent_repo_name(label))
50+
51+
_MAIN_REPO_PREFIX = str(Label("@@//:all")).split(":")[0]
52+
53+
def adjust_main_repo_prefix(target_pattern):
54+
"""Updates the main repo prefix to match the current Bazel version.
55+
56+
The main repo prefix will be "@//" for Bazel < 7.1.0, and "@@//" for Bazel
57+
>= 7.1.0 under Bzlmod. This macro automatically updates strings representing
58+
include/exclude target patterns so that they match actual main repository
59+
target Labels correctly.
60+
61+
Args:
62+
target_pattern: a string used to match a BUILD target pattern
63+
64+
Returns:
65+
the string with any main repository prefix updated to match the current
66+
Bazel version
67+
"""
68+
if target_pattern.startswith("@//") or target_pattern.startswith("@@//"):
69+
return _MAIN_REPO_PREFIX + target_pattern.lstrip("@/")
70+
71+
return target_pattern

scala/private/phases/phase_dependency.bzl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# Gathers information about dependency mode and analysis
22

33
load(
4-
"@io_bazel_rules_scala//scala/private:dependency.bzl",
4+
"//scala/private:dependency.bzl",
55
"get_compiler_deps_mode",
66
"get_strict_deps_mode",
77
"new_dependency_info",
88
)
9+
load("//scala/private:macros/bzlmod.bzl", "apparent_repo_label_string")
910
load(
10-
"@io_bazel_rules_scala//scala/private:paths.bzl",
11+
"//scala/private:paths.bzl",
1112
_get_files_with_extension = "get_files_with_extension",
1213
_java_extension = "java_extension",
1314
)
@@ -35,9 +36,9 @@ def _phase_dependency(
3536
p,
3637
unused_deps_always_off,
3738
strict_deps_always_off):
38-
toolchain = ctx.toolchains["@io_bazel_rules_scala//scala:toolchain_type"]
39+
toolchain = ctx.toolchains[Label("//scala:toolchain_type")]
3940

40-
target_label = str(ctx.label)
41+
target_label = apparent_repo_label_string(ctx.label)
4142

4243
included_in_strict_deps_analysis = _is_target_included(
4344
target_label,

scala/private/resources.bzl

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
load(":macros/bzlmod.bzl", "apparent_repo_name")
2+
13
def paths(resources, resource_strip_prefix):
24
"""Return a list of path tuples (target, source) where:
35
target - is a path in the archive (with given prefix stripped off)
@@ -13,7 +15,13 @@ def paths(resources, resource_strip_prefix):
1315

1416
def _target_path(resource, resource_strip_prefix):
1517
path = _target_path_by_strip_prefix(resource, resource_strip_prefix) if resource_strip_prefix else _target_path_by_default_prefixes(resource)
16-
return _strip_prefix(path, "/")
18+
return _update_external_target_path(_strip_prefix(path, "/"))
19+
20+
def _update_external_target_path(target_path):
21+
if not target_path.startswith("external/"):
22+
return target_path
23+
prefix, repo_name, rest = target_path.split("/")
24+
return "/".join([prefix, apparent_repo_name(repo_name), rest])
1725

1826
def _target_path_by_strip_prefix(resource, resource_strip_prefix):
1927
# Start from absolute resource path and then strip roots so we get to correct short path

scala/scala_maven_import_external.bzl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ the following macros are defined below that utilize jvm_import_external:
3535
- java_import_external - to demonstrate that the original functionality of `java_import_external` stayed intact.
3636
"""
3737

38+
load("//scala/private:macros/bzlmod.bzl", "apparent_repo_name")
3839
load("@bazel_tools//tools/build_defs/repo:utils.bzl", "read_netrc", "read_user_netrc", "use_netrc")
3940

4041
# https://github.com/bazelbuild/bazel/issues/13709#issuecomment-1336699672
@@ -64,7 +65,10 @@ def _jvm_import_external(repository_ctx):
6465
if (repository_ctx.attr.generated_linkable_rule_name and
6566
not repository_ctx.attr.neverlink):
6667
fail("Only use generated_linkable_rule_name if neverlink is set")
67-
name = repository_ctx.attr.generated_rule_name or repository_ctx.name
68+
name = (
69+
repository_ctx.attr.generated_rule_name or
70+
apparent_repo_name(repository_ctx.name)
71+
)
6872
urls = repository_ctx.attr.jar_urls
6973
if repository_ctx.attr.jar_sha256:
7074
print("'jar_sha256' is deprecated. Please use 'artifact_sha256'")
@@ -136,7 +140,7 @@ def _jvm_import_external(repository_ctx):
136140
"",
137141
"alias(",
138142
" name = \"jar\",",
139-
" actual = \"@%s\"," % repository_ctx.name,
143+
" actual = \"@%s\"," % apparent_repo_name(repository_ctx.name),
140144
")",
141145
"",
142146
]))

scala/scala_toolchain.bzl

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
load(
2-
"@io_bazel_rules_scala//scala:providers.bzl",
3-
_DepsInfo = "DepsInfo",
4-
)
1+
load("//scala/private:macros/bzlmod.bzl", "adjust_main_repo_prefix")
2+
load("//scala:providers.bzl", _DepsInfo = "DepsInfo")
53
load(
64
"@io_bazel_rules_scala_config//:config.bzl",
75
"ENABLE_COMPILER_DEPENDENCY_TRACKING",
@@ -31,12 +29,12 @@ def _compute_dependency_tracking_method(
3129

3230
def _partition_patterns(patterns):
3331
includes = [
34-
pattern
32+
adjust_main_repo_prefix(pattern)
3533
for pattern in patterns
3634
if not pattern.startswith("-")
3735
]
3836
excludes = [
39-
pattern.lstrip("-")
37+
adjust_main_repo_prefix(pattern.lstrip("-"))
4038
for pattern in patterns
4139
if pattern.startswith("-")
4240
]
@@ -108,15 +106,15 @@ def _scala_toolchain_impl(ctx):
108106

109107
def _default_dep_providers():
110108
dep_providers = [
111-
"@io_bazel_rules_scala//scala:scala_xml_provider",
112-
"@io_bazel_rules_scala//scala:parser_combinators_provider",
113-
"@io_bazel_rules_scala//scala:scala_compile_classpath_provider",
114-
"@io_bazel_rules_scala//scala:scala_library_classpath_provider",
115-
"@io_bazel_rules_scala//scala:scala_macro_classpath_provider",
109+
"scala_xml",
110+
"parser_combinators",
111+
"scala_compile_classpath",
112+
"scala_library_classpath",
113+
"scala_macro_classpath",
116114
]
117-
if SCALA_MAJOR_VERSION.startswith("2"):
118-
dep_providers.append("@io_bazel_rules_scala//scala:semanticdb_provider")
119-
return dep_providers
115+
if SCALA_MAJOR_VERSION.startswith("2."):
116+
dep_providers.append("semanticdb")
117+
return [Label("//scala:%s_provider" % p) for p in dep_providers]
120118

121119
scala_toolchain = rule(
122120
_scala_toolchain_impl,

third_party/repositories/repositories.bzl

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
load("//scala/private:macros/bzlmod.bzl", "apparent_repo_name")
12
load(
23
"//third_party/repositories:scala_2_11.bzl",
34
_artifacts_2_11 = "artifacts",
@@ -102,14 +103,25 @@ def repositories(
102103
default_artifacts = artifacts_by_major_scala_version[major_scala_version]
103104
artifacts = dict(default_artifacts.items() + overriden_artifacts.items())
104105
for id in for_artifact_ids:
106+
if id not in artifacts:
107+
fail("artifact %s not in third_party/repositories/scala_%s.bzl" % (
108+
id,
109+
major_scala_version.replace(".", "_"),
110+
))
111+
112+
generated_rule_name = apparent_repo_name(id) + suffix
105113
_scala_maven_import_external(
106114
name = id + suffix,
115+
generated_rule_name = generated_rule_name,
107116
artifact = artifacts[id]["artifact"],
108117
artifact_sha256 = artifacts[id]["sha256"],
109118
licenses = ["notice"],
110119
server_urls = maven_servers,
111120
deps = [dep + suffix for dep in artifacts[id].get("deps", [])],
112-
runtime_deps = artifacts[id].get("runtime_deps", []),
121+
runtime_deps = [
122+
dep + suffix
123+
for dep in artifacts[id].get("runtime_deps", [])
124+
],
113125
testonly_ = artifacts[id].get("testonly", False),
114126
fetch_sources = fetch_sources,
115127
)
@@ -118,13 +130,13 @@ def repositories(
118130
# See: https://github.com/bazelbuild/rules_scala/pull/1573
119131
# Hopefully we can deprecate and remove it one day.
120132
if suffix and scala_version == SCALA_VERSION:
121-
_alias_repository(name = id, target = id + suffix)
133+
_alias_repository(name = id, target = generated_rule_name)
122134

123135
def _alias_repository_impl(rctx):
124136
""" Builds a repository containing just two aliases to the Scala Maven artifacts in the `target` repository. """
125137

126138
format_kwargs = {
127-
"name": rctx.name,
139+
"name": apparent_repo_name(rctx.name),
128140
"target": rctx.attr.target,
129141
}
130142
rctx.file("BUILD", """alias(

0 commit comments

Comments
 (0)