Skip to content

Commit

Permalink
Add apparent_repo_name macro to prepare for Bzlmod
Browse files Browse the repository at this point in the history
Part of bazelbuild#1482.

Splits the last component off of canonical repo names to produce the
expected repo name.

Without Bzlmod, it returns the original name. With Bzlmod enabled, it
avoids generating output like:

    scala_import(
        name = "_main~scala_deps~io_bazel_rules_scala_scala_compiler",
        jars = ["scala-compiler-2.12.18.jar"],
    )

resulting in errors like:

```
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'
```

Also fixes the following error when attaching resources from custom repos to
targets under Bzlmod:

```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)
```

Can be replaced with a future bazel-skylib implementation, if accepted
into that repo.

---

We can't rely on 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`. For extensions
> hosted in the root module, the `module_repo_canonical_name` part is
> replaced with the string `_main`. 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

This function assumes the only valid `repo_name` characters are letters,
numbers, '_', '-', and '.'. It finds the last character not in this set, and
returns the contents of `name` following this character. 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
  • Loading branch information
mbland committed Oct 7, 2024
1 parent 3da60a8 commit 7f27266
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 4 deletions.
23 changes: 23 additions & 0 deletions scala/private/macros/bzlmod.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""Utilities for working with Bazel modules"""

def apparent_repo_name(label_or_name):
"""Return a repository's apparent repository name.
Can be replaced with a future bazel-skylib implementation, if accepted into
that repo.
Args:
label_or_name: a Label or repository name string
Returns:
The apparent repository name
"""
repo_name = getattr(label_or_name, "repo_name", label_or_name)

# Bazed on this pattern from the Bazel source:
# com.google.devtools.build.lib.cmdline.RepositoryName.VALID_REPO_NAME
for i in range(len(repo_name) - 1, -1, -1):
c = repo_name[i]
if not (c.isalnum() or c in "_-."):
return repo_name[i + 1:]
return repo_name
10 changes: 9 additions & 1 deletion scala/private/resources.bzl
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load(":macros/bzlmod.bzl", "apparent_repo_name")

def paths(resources, resource_strip_prefix):
"""Return a list of path tuples (target, source) where:
target - is a path in the archive (with given prefix stripped off)
Expand All @@ -13,7 +15,13 @@ def paths(resources, resource_strip_prefix):

def _target_path(resource, resource_strip_prefix):
path = _target_path_by_strip_prefix(resource, resource_strip_prefix) if resource_strip_prefix else _target_path_by_default_prefixes(resource)
return _strip_prefix(path, "/")
return _update_external_target_path(_strip_prefix(path, "/"))

def _update_external_target_path(target_path):
if not target_path.startswith("external/"):
return target_path
prefix, repo_name, rest = target_path.split("/")
return "/".join([prefix, apparent_repo_name(repo_name), rest])

def _target_path_by_strip_prefix(resource, resource_strip_prefix):
# Start from absolute resource path and then strip roots so we get to correct short path
Expand Down
3 changes: 2 additions & 1 deletion scala/scala_maven_import_external.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ the following macros are defined below that utilize jvm_import_external:
- java_import_external - to demonstrate that the original functionality of `java_import_external` stayed intact.
"""

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

# https://github.com/bazelbuild/bazel/issues/13709#issuecomment-1336699672
Expand Down Expand Up @@ -136,7 +137,7 @@ def _jvm_import_external(repository_ctx):
"",
"alias(",
" name = \"jar\",",
" actual = \"@%s\"," % repository_ctx.name,
" actual = \"@%s\"," % apparent_repo_name(repository_ctx.name),
")",
"",
]))
Expand Down
7 changes: 5 additions & 2 deletions third_party/repositories/repositories.bzl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
load("//scala/private:macros/bzlmod.bzl", "apparent_repo_name")
load(
"//third_party/repositories:scala_2_11.bzl",
_artifacts_2_11 = "artifacts",
Expand Down Expand Up @@ -95,8 +96,10 @@ def repositories(
default_artifacts = artifacts_by_major_scala_version[major_scala_version]
artifacts = dict(default_artifacts.items() + overriden_artifacts.items())
for id in for_artifact_ids:
generated_rule_name = apparent_repo_name(id) + suffix
_scala_maven_import_external(
name = id + suffix,
generated_rule_name = generated_rule_name,
artifact = artifacts[id]["artifact"],
artifact_sha256 = artifacts[id]["sha256"],
licenses = ["notice"],
Expand All @@ -111,13 +114,13 @@ def repositories(
# See: https://github.com/bazelbuild/rules_scala/pull/1573
# Hopefully we can deprecate and remove it one day.
if suffix and scala_version == SCALA_VERSION:
_alias_repository(name = id, target = id + suffix)
_alias_repository(name = id, target = generated_rule_name)

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

format_kwargs = {
"name": rctx.name,
"name": apparent_repo_name(rctx.name),
"target": rctx.attr.target,
}
rctx.file("BUILD", """alias(
Expand Down

0 comments on commit 7f27266

Please sign in to comment.