Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add apparent_repo_name utility to modules.bzl #548

Closed
wants to merge 8 commits into from

Conversation

mbland
Copy link

@mbland mbland commented Oct 14, 2024

Adds the apparent_repo_name macro to derive the apparent name from repository_ctx.name when running under Bzlmod. This enables repository rules to generate their own top level default target without requiring a redundant parameter.

Originally developed while updating rules_scala to support Bzlmod as part of bazelbuild/rules_scala#1482.

For examples of its use, see bazelbuild/rules_scala#1621.


BTW, I happened to put it in the modules lib, as that seemed like the best fit at the time. Happy to move it elsewhere.

cc: @BillyAutrey @jayconrod @benjaminp @TheGrizzlyDev

mbland added a commit to mbland/bazel-skylib that referenced this pull request Oct 14, 2024
mbland added a commit to mbland/bazel-skylib that referenced this pull request Oct 14, 2024
The Bazel 5 build from bazelbuild#548 broke because:

```txt
(21:43:53) ERROR: Traceback (most recent call last):
  File "/workdir/lib/modules.bzl", line 162, column 30, in <toplevel>
    _main_repo_prefix = str(Label("@@//:all")).split(":")[0]
Error in Label: Illegal absolute label syntax: @@//:all
```

Then things broke locally because `Label.repo_name` isn't available in
Bazel 5.

Changed the `@@//` prefixes to `//` and added the new `repo_name`
utility to adapt to Bazel 5. Confirmed locally that everything now works
with Bazel 5.4.1, 6.5.0, and 7.3.2.
mbland added a commit to mbland/rules_scala that referenced this pull request Oct 14, 2024
I don't think we really need them, as I don't think we support Bazel 5.
But better to have and not need, I guess.
mbland added a commit to mbland/rules_scala that referenced this pull request Oct 15, 2024
Backported from bazelbuild/bazel-skylib#548, whereby I realized
`Label(//:all)` would not produce the main repo prefix when imported
into other repos.
@fmeum
Copy link
Contributor

fmeum commented Oct 15, 2024

Thanks for sending such a well-executed PR (and migrating rules_scala)!

Since Bzlmod introduces a number of new concepts, I want to make sure that they are applied and named correctly as well as avoid XY problems. So please bear with me being nitpicky and delving into the individual proposed helpers below.

adjust_main_repo_prefix

In rules_scala, this is used to turn label strings provided by users into label string prefixes that are then checked for to determine targets that are included or excluded from certain functionality (say strict deps). Matching labels as strings is usually pretty error-prone and I think that rules_scala may suffer from some issues here caused by this:

  • //foo:foo would also match //foo:foo1, //foo would match //foo1.
  • @some_other_module// wouldn't match as expected as the label of targets in this module would start with the canonical repository name, not the apparent repository name seen from the main module.

It's generally preferable to accept target patterns as attr.label_list or convert to Label as early as possible (using native.package_relative_label in a macro) and only ever pass Label instances around in code. Depending on your use case, you could also have users supply the label of a package_group and then use PackageSpecificationInfo#contains.
cc_shared_library in Bazel and go_sdk.nogo provide different examples of this.

In any of these cases, manually adjusting the main repo prefix wouldn't be needed. Happy to help rules_scala migrate to another mechanism and thus add another example to the list!

is_bzlmod_enabled

This already exists in bazel_features and is arguably better placed in a library dedicated to feature detection that becomes less relevant over time.

repo_name

Label#workspace_name is still available at HEAD, so a project that needs to support Bazel 5 should probably just keep using it for now. The flag will probably be flipped for Bazel 9, at which point Bazel 5 will no longer be officially supported and projects could switch to the new method name at that point.

If a project wants to adopt it early for some reason, I would personally consider it more descriptive to keep the simple logic close to the code that uses it. This prevents a casual reader from assuming that the helper does more than it actually does. In fact, if made type-safe, it could even be simplified down to just getattr(label, "repo_name" if hasattr(label, "repo_name") else "workspace_name").

apparent_repo_name and apparent_repo_label_string

My main problem with this function is that a repository doesn't have a single "apparent name".
Rather, it has one apparent name from the point of view of each other repository that has visibility into it.
Since this is one of the key concepts that makes Bzlmod tick (by avoiding the need for well-known names), we need to be careful with naming here.

From what I can tell, this function returns:

  1. the name of a module when called on a label pointing to a target in that module's repo
  2. the name of a repo as given by a module extension if the label points to a target in a module extension repo
  3. the name of a WORKSPACE repo if the label points to a target in a WORKSPACE repo
  4. the empty string if the label points to a target in the main repository

In all but the third case, which is going away with Bazel 9, it doesn't return an apparent name, so we would definitely need to rename or break this function up into multiple functions with smaller scope.

Could you briefly describe the use cases you encountered while migrating rules_scala that led to this "catch-all" function?
I'm happy to suggest alternatives based on that and we can look into where to document them so that they are more easily discovered in the future.

CC @Wyverald @meteorcloudy

mbland added a commit to mbland/bazel-skylib that referenced this pull request Oct 15, 2024
Based @fmeum's review comments on bazelbuild#548, removed the following functions
from `lib/modules.bzl`:

- `adjust_main_repo_prefix`: prefer solutions based on
  `attr.label_list`, `Label`, and `native.package_relative_label` to
  pass `Label` values in the code

- `is_bzlmod_enabled`: available in `@bazel_features`, only added
  because of `repo_name`

- `repo_name`: replaced with a `getattr` + `hasattr` expression, since
  Bazel 5 is near end of life and it's easier to search and replace this
  expression
@mbland
Copy link
Author

mbland commented Oct 15, 2024

No need to bear with you—this is exactly the kind of thoughtful and helpful response I was fishing for! 😉 Thanks for responding so quickly as well.

As noted below, I've already withdrawn most of this PR, with the exception of apparent_repo_name. But now I'm going to try a couple of ideas to see if I can get away without it as well, and will ping again once I have.

BTW, my last two commits removed all but apparent_repo_name, but broke the "Last Green Bazel" builds. I'm certain the failure has nothing to do with this PR:

(15:21:56) ERROR: Traceback (most recent call last):
	File "/workdir/docs/BUILD", line 182, column 12, in <toplevel>
		update_docs(
	File "/workdir/docs/private/stardoc_with_diff_test.bzl", line 107, column 11, in update_docs
		native.sh_binary(
Error: no native function or rule 'sh_binary'

adjust_main_repo_prefix: Withdrawn

I agree, the target filtering mechanism as currently implemented in rules_scala is a bit wonky for the reasons you cite. I'll run it by the current maintainers to see if they've the appetite to change it while I'm doing my Bzlmod conversion; it would be a potentially breaking change from WORKSPACE, so there'd be a major version bump. (I may have another reason to recommend a major bump, after toolchainizing its dependencies.) If they don't want me to do it now, I can file a bug to update it in the next major release.

I've withdrawn this one, and will only keep it in rules_scala if the maintainers don't want to update it right away.

is_bzlmod_enabled: Withdrawn

Now that you mention it, I saw the @bazel_features implementation at some point but forgot about it. I only hoisted it out for the sake of main_repo_prefix, used by adjust_main_repo_prefix.

Withdrew this as well.

repo_name: Withdrawn

Heh, this wasn't part of my original commit; I'd only added it when the bazel-skylib CI broke on the Bazel 5 build. Withdrawn and using the recommended one liner in its place. (Though I did extract a private _repo_attr variable to hold the result of the hasattr expression.)

apparent_repo_label_string: Withdrawn

Withdrawing this, too, as I was using it on the other end of the filtering operation that applied adjust_main_repo_prefix. If we filter using proper Label() values instead of strings, the problem apparent_repo_label_string solves goes away.

apparent_repo_name

All those return value cases are correct. In practice, I'm solving primarily for case 2 (with case 3 for pre-Bzlmod compatibility). However, I tried to ensure cases 1 and 4 were covered to avoid potential bugs.

I was surprised at first that these wouldn't be considered apparent names, but I see the point regarding repo mapping.

The use cases break down to two particular problems. See the message for commit bazelbuild/rules_scala@25e8aa8 from bazelbuild/rules_scala#1621 for more info.

But now that I'm thinking about it more, I've a couple ideas in mind that might help me avoid the need even for apparent_repo_name, noted below. I'll ping again once I've given them a try.

Repository rule generated BUILD target names

I'm wondering if I can use Label.name for this class of problems instead.

The jvm_import_external repository rule emits default BUILD targets for JAR files, so dependencies can depend on these repos using only the canonical repo name, like @io_bazel_rules_scala_compiler.

(Aside: I don't know enough to know why rules_jvm_external wouldn't work for this repo. All I know is that the repo JARs can be versioned for specific Scala versions, which can also appear in the apparent repo name; I'm using a non-versioned default repo name here. Also, different toolchain-related repos are included for different user-configurable Scala versions.)

Without using apparent_repo_name, the default target names would look like this under Bzlmod:

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

Then a (macro generated) target listing @io_bazel_rules_scala_compiler in its dependencies produced 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'

You can see in bazelbuild/rules_scala#1621 that I used apparent_repo_name to solve a handful of other similar problems when generating BUILD targets.

Attaching repository assets as embedded resources

I'm wondering if I should just use the common resource_strip_prefix attribute of affected rules, or possibly use the Java runfiles library (or contribute a Scala version).

Under Bzlmod, accessing resources from external repos, like in the ScalaLibResourcesFromExternalDepTest.scala file from //test/src/main/scala/scalarules/test/resources:TestScalaLibResourcesFromExternalDep, would break with:

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

The BUILD target is:

scala_specs2_junit_test(
    name = "TestScalaLibResourcesFromExternalDep",
    size = "small",
    srcs = ["ScalaLibResourcesFromExternalDepTest.scala"],
    resources = ["@test_new_local_repo//:data"],
    suffixes = ["Test"],
)

@test_new_local_repo is defined under a module extension as:

    new_local_repository(
        name = "test_new_local_repo",
        build_file_content = """
filegroup(
    name = "data",
    srcs = glob(["**/*.txt"]),
    visibility = ["//visibility:public"],
)
""",
        path = "third_party/test/new_local_repo",
    )

The source file attempts to access a file in this repo as:

get("/external/test_new_local_repo/resource.txt")

So I updated _target_path from scala/private/resources.bzl to use a new _update_external_target_path function, so the Scala code could use the apparent repo name in the /external/ 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])

mbland added a commit to mbland/rules_scala that referenced this pull request Oct 15, 2024
Replaced `apparent_repo_label_string` and `adjust_main_repo_prefix`
based on an idea from @fmeum during his review of
bazelbuild/bazel-skylib#548.

Added `_expand_patterns`, which uses `native.package_relative_label` to
expand the `dependency_tracking_*_deps_patterns` attributes to full,
correct `Label` strings.

All `test/shell/test_{strict,unused}_dependency.sh` test cases pass.
@fmeum
Copy link
Contributor

fmeum commented Oct 15, 2024

The best way to get a pretty name into a repo rule is to just pass it in via an attr.string (could be optional for backwards compatibility with WORKSPACE and fall back to ctx.attr.name).

mbland added a commit to mbland/rules_scala that referenced this pull request Oct 16, 2024
Replaced `apparent_repo_label_string` and `adjust_main_repo_prefix`
based on an idea from @fmeum during his review of
bazelbuild/bazel-skylib#548.

Added `_expand_patterns`, which uses `native.package_relative_label` to
expand the `dependency_tracking_*_deps_patterns` attributes to full,
correct `Label` strings.

All `test/shell/test_{strict,unused}_dependency.sh` test cases pass.
@mbland
Copy link
Author

mbland commented Oct 16, 2024

OK, some significant updates. I've updated this PR to now only contain def apparent_repo_name(repository_ctx), specifically to allow repo rules to inject their own apparent repo name into a default BUILD target. As suggested, we could add a redundant attr.string to each repo rule that needs to do this, but that seems like a suboptimal user experience.

That said, I've updated bazelbuild/rules_scala#1621 to solve all the other problems without apparent_repo_name. I appreciate the thoughtful feedback that inspired me to find better solutions to those problems!

FWIW, I don't know if something like _expand_patterns would be useful to contribute in any way. I suppose asking people to write rules with separate attr.label_lists for includes and excludes would be preferable, but my goal here was to preserve existing behavior in rules_scala.

@mbland mbland changed the title Add apparent repo name utilities to modules.bzl Add apparent_repo_name utility to modules.bzl Oct 17, 2024
mbland added a commit to mbland/rules_scala that referenced this pull request Oct 21, 2024
I don't think we really need them, as I don't think we support Bazel 5.
But better to have and not need, I guess.
mbland added a commit to mbland/rules_scala that referenced this pull request Oct 21, 2024
Backported from bazelbuild/bazel-skylib#548, whereby I realized
`Label(//:all)` would not produce the main repo prefix when imported
into other repos.
mbland added a commit to mbland/rules_scala that referenced this pull request Oct 21, 2024
Replaced `apparent_repo_label_string` and `adjust_main_repo_prefix`
based on an idea from @fmeum during his review of
bazelbuild/bazel-skylib#548.

Added `_expand_patterns`, which uses `native.package_relative_label` to
expand the `dependency_tracking_*_deps_patterns` attributes to full,
correct `Label` strings.

All `test/shell/test_{strict,unused}_dependency.sh` test cases pass.
simuons pushed a commit to bazelbuild/rules_scala that referenced this pull request Oct 24, 2024
* Add repo name macros for Bzlmod compatibility

Part of #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

* Bazel 5 updates from bazelbuild/bazel-skylib#548

I don't think we really need them, as I don't think we support Bazel 5.
But better to have and not need, I guess.

* Fix _MAIN_REPO_PREFIX in bzlmod.bzl

Backported from bazelbuild/bazel-skylib#548, whereby I realized
`Label(//:all)` would not produce the main repo prefix when imported
into other repos.

* Expand dependency tracking patterns using Label

Replaced `apparent_repo_label_string` and `adjust_main_repo_prefix`
based on an idea from @fmeum during his review of
bazelbuild/bazel-skylib#548.

Added `_expand_patterns`, which uses `native.package_relative_label` to
expand the `dependency_tracking_*_deps_patterns` attributes to full,
correct `Label` strings.

All `test/shell/test_{strict,unused}_dependency.sh` test cases pass.

* Fix `scala_toolchains` lint error

* Use `apparent_repo_name` only on `repository_ctx`

Repurposed `apparent_repo_name` to only work for `repository_ctx`
objects, not repository or module names in general. Removed
`generated_rule_name` from `repositories.bzl`, since it's no longer
necessary.

Technically we could eliminate `apparent_repo_name` by making
`generated_rule_name` a mandatory attribute of `_jvm_import_external`.
However, this feels ultimately clunky and unnecessary.

This update to `apparent_repo_name` required removing
`_update_external_target_path` and updating
`_target_path_by_default_prefixes` to remove
`external/<canonical_repo_name>` prefixes. This represents a breaking
change for files referencing `external/<repo_name>` paths, but the quick
fix is to delete that prefix in the code. This matches the behavior in
the same function regarding `resources/` and `java/` prefixes.

* Update `_jvm_import_external` JAR alias target

Changes the target for the `//jar:jar` alias in `_jvm_import_scala` to
the top level repo target directly (`//:%s`) instead of the repo name
(`@%s`).

Functionally the same, but seems a bit cleaner than referencing the
target as though it were external to the repo.

* Fix typo in apparent_repo_name comment

Caught by @simuons in #1621, thankfully. I hate sloppy comments, and
hate it more when I write them!
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 bazelbuild/rules_scala#1482.

For examples of their use, see bazelbuild/rules_scala#1621.
The Bazel 5 build from bazelbuild#548 broke because:

```txt
(21:43:53) ERROR: Traceback (most recent call last):
  File "/workdir/lib/modules.bzl", line 162, column 30, in <toplevel>
    _main_repo_prefix = str(Label("@@//:all")).split(":")[0]
Error in Label: Illegal absolute label syntax: @@//:all
```

Then things broke locally because `Label.repo_name` isn't available in
Bazel 5.

Changed the `@@//` prefixes to `//` and added the new `repo_name`
utility to adapt to Bazel 5. Confirmed locally that everything now works
with Bazel 5.4.1, 6.5.0, and 7.3.2.
After thinking it through, I realized `Label(//:all)` would not produce
the main repo prefix when imported into other repos.

I also realized taking a variation of the `_is_bzlmod_enabled` test from
`tests/modules_test.bzl` would make the correct implementation easier.

So I made `is_bzlmod_enabled` part of the `modules` exports, and used it
internally to set `_main_repo_prefix`. All the tests continue to pass.
Based @fmeum's review comments on bazelbuild#548, removed the following functions
from `lib/modules.bzl`:

- `adjust_main_repo_prefix`: prefer solutions based on
  `attr.label_list`, `Label`, and `native.package_relative_label` to
  pass `Label` values in the code

- `is_bzlmod_enabled`: available in `@bazel_features`, only added
  because of `repo_name`

- `repo_name`: replaced with a `getattr` + `hasattr` expression, since
  Bazel 5 is near end of life and it's easier to search and replace this
  expression
After removing `modules.adjust_main_repo_prefix` in the previous commit,
there's no need for `modules.apparent_repo_label_string`. In other
words, if the solution to the problem is to replace `string` inputs with
`Label`s, there's no need for a special `Label` stringifier.
Repurposed `apparent_repo_name` to only work with `repository_ctx`
objects after finding solutions to other repository name related
problems in bazelbuild/rules_scala#1621.

The one problem I couldn't solve (elegantly) without this function was
setting a default target name for a generated repo. Technically, such
repos could require an attribute to mirror `name`, but that seems like a
terrible user experience.
@mbland mbland force-pushed the apparent-repo-name branch from 9a7fbc9 to 73da047 Compare November 6, 2024 16:42
mbland added a commit to mbland/rules_scala that referenced this pull request Nov 15, 2024
This presents the same API, remains compatible with both `WORKSPACE` and
Bzlmod, and avoids relying on the canonical repository name format at
all. Part of bazelbuild#1482, and supersedes the addition of `apparent_repo_name`
from bazelbuild#1621.

Also adds the `_SCALA_IMPORT_RULE_LOAD` constant in
`scala/scala_maven_import_external.bzl`, which also removes
`@io_bazel_rules_scala` from the `load`ed file path. It now generates
the correct path with a `Label` instead.

---

With bazelbuild/bazel-skylib#548 going nowhere, I eventually realized a
macro wrapper could generate a default target name for a repository_rule
by duplicating the `name` attr. This isn't as easy as adding
`apparent_repo_name(repository_ctx.name)` to a repo rule's
implementation, but it's also not that much more work.

One small downside is that the macro can't be imported into a
`MODULE.bazel` file directly via `use_repo_rule`, if one wanted to do
that. If you did, you'd have to add a module extension to call it, or
use `modules.as_extension` from the `bazel_skylib` module. I suppose
that's a small price to pay for squashing a canonical repo name format
dependency forever.

The other small downside may be that the documentation from the original
`repository_rule` doesn't automatically convey to the repo wrapper. In
this case, `_alias_repository` is already internal, and the original
`jvm_import_external` didn't have docstrings to begin with.
mbland added a commit to mbland/rules_scala that referenced this pull request Nov 15, 2024
This presents the same API, remains compatible with both `WORKSPACE` and
Bzlmod, and avoids relying on the canonical repository name format at
all. Part of bazelbuild#1482, and supersedes the addition of `apparent_repo_name`
from bazelbuild#1621.

Also adds the `_SCALA_IMPORT_RULE_LOAD` constant in
`scala/scala_maven_import_external.bzl`, which also removes
`@io_bazel_rules_scala` from the `load`ed file path. It now generates
the correct path with a `Label` instead.

---

The goal is to maintain the ability to generate default target names
from a repository name under Bzlmod. While not documented on
https://bazel.build, it is documented in the Bazel source itself, even
under the current 9.0.0-pre.20241105.2 prerelease:

- https://github.com/bazelbuild/bazel/blob/9.0.0-pre.20241105.2/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java#L99

Under `WORKSPACE`, the `repository_ctx.name` value served this purpose
directly, as it would reflect the `name` value as provided by the
caller. Under Bzlmod, this value now contains the canonical repo name,
which is different than that provided by the `name` parameter. The
`apparent_repo_name` utility was my first attempt to resolve this, by
parsing the original `name` parameter from the canonicalized
`repository_ctx.name`.

I also created bazelbuild/bazel-skylib#548 to contribute
`apparent_repo_name` to `bazel_skylib`, along with other proposed
utilities. I quickly found better solutions to obviate the other
utilities, but hung onto `apparent_repo_name`.

After that pull request stagnated, I eventually realized a macro wrapper
could generate a default target name for a repository_rule by
duplicating the `name` attr. This isn't as easy as adding
`apparent_repo_name(repository_ctx.name)` to a repo rule's
implementation, but it's also not that much more work. See also this
thread from the #bzlmod channel in the Bazel Slack workspace about
generating default repo names:

- https://bazelbuild.slack.com/archives/C014RARENH0/p1730909824656159

---

One small downside of this technique is that the macro can't be imported
into a `MODULE.bazel` file directly via `use_repo_rule`. If you did want
to use it in `MODULE.bazel`, you'd have to write a module extension to
call it, or use `modules.as_extension` from `bazel_skylib`. I suppose
that's a small price to pay for squashing a canonical repo name format
dependency forever.

The other small downside may be that the documentation from the original
`repository_rule` doesn't automatically convey to the repo wrapper. In
this case, `_alias_repository` is already internal, and the original
`jvm_import_external` didn't have docstrings to begin with.
mbland added a commit to mbland/rules_scala that referenced this pull request Nov 15, 2024
This presents the same API, remains compatible with both `WORKSPACE` and
Bzlmod, and avoids relying on the canonical repository name format at
all. Part of bazelbuild#1482, and supersedes the addition of `apparent_repo_name`
from bazelbuild#1621.

Also adds the `_SCALA_IMPORT_RULE_LOAD` constant in
`scala/scala_maven_import_external.bzl`, which also removes
`@io_bazel_rules_scala` from the `load`ed file path. It now generates
the correct path with a `Label` instead.

---

The goal is to maintain the ability to generate default target names
from a repository name under Bzlmod. While not documented on
https://bazel.build, it is documented in the Bazel source itself, even
under the current 9.0.0-pre.20241105.2 prerelease:

- https://github.com/bazelbuild/bazel/blob/9.0.0-pre.20241105.2/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java#L99

Under `WORKSPACE`, the `repository_ctx.name` value served this purpose
directly, as it would reflect the `name` value as provided by the
caller. Under Bzlmod, this value now contains the canonical repo name,
which is different than that provided by the `name` parameter. The
`apparent_repo_name` utility was my first attempt to resolve this, by
parsing the original `name` parameter from the canonicalized
`repository_ctx.name`.

I also created bazelbuild/bazel-skylib#548 to contribute
`apparent_repo_name` to `bazel_skylib`, along with other proposed
utilities. I quickly found better solutions to obviate the other
utilities, but hung onto `apparent_repo_name`.

After that pull request stagnated, I eventually realized a macro wrapper
could generate a default target name for a repository_rule by
duplicating the `name` attr. This isn't as easy as adding
`apparent_repo_name(repository_ctx.name)` to a repo rule's
implementation, but it's also not that much more work. See also this
thread from the #bzlmod channel in the Bazel Slack workspace about
generating default repo names:

- https://bazelbuild.slack.com/archives/C014RARENH0/p1730909824656159

---

One small downside of this technique is that the macro can't be imported
into a `MODULE.bazel` file directly via `use_repo_rule`. If you did want
to use it in `MODULE.bazel`, you'd have to write a module extension to
call it, or use `modules.as_extension` from `bazel_skylib`. I suppose
that's a small price to pay for squashing a canonical repo name format
dependency forever.

The other small downside may be that the documentation from the original
`repository_rule` doesn't automatically convey to the repo wrapper. In
this case, `_alias_repository` is already internal, and the original
`jvm_import_external` didn't have docstrings to begin with.
mbland added a commit to mbland/rules_scala that referenced this pull request Nov 15, 2024
This presents the same API, remains compatible with both `WORKSPACE` and
Bzlmod, and avoids relying on the canonical repository name format at
all. Part of bazelbuild#1482, and supersedes the addition of `apparent_repo_name`
from bazelbuild#1621.

Also adds the `_SCALA_IMPORT_RULE_LOAD` constant in
`scala/scala_maven_import_external.bzl`, which also removes
`@io_bazel_rules_scala` from the `load`ed file path. It now generates
the correct path with a `Label` instead.

---

The goal is to maintain the ability to generate default target names
from a repository name under Bzlmod. While not documented on
https://bazel.build, it is documented in the Bazel source itself, even
under the current 9.0.0-pre.20241105.2 prerelease:

- https://github.com/bazelbuild/bazel/blob/9.0.0-pre.20241105.2/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java#L99

Under `WORKSPACE`, the `repository_ctx.name` value served this purpose
directly, as it would reflect the `name` value as provided by the
caller. Under Bzlmod, this value now contains the canonical repo name,
which is different than that provided by the `name` parameter. The
`apparent_repo_name` utility was my first attempt to resolve this, by
parsing the original `name` parameter from the canonicalized
`repository_ctx.name`.

I also created bazelbuild/bazel-skylib#548 to contribute
`apparent_repo_name` to `bazel_skylib`, along with other proposed
utilities. I quickly found better solutions to obviate the other
utilities, but hung onto `apparent_repo_name`.

After that pull request stagnated, I eventually realized a macro wrapper
could generate a default target name for a repository_rule by
duplicating the `name` attr. This isn't as easy as adding
`apparent_repo_name(repository_ctx.name)` to a repo rule's
implementation, but it's also not that much more work. See also this
thread from the #bzlmod channel in the Bazel Slack workspace about
generating default repo names:

- https://bazelbuild.slack.com/archives/C014RARENH0/p1730909824656159

---

One small downside of this technique is that the macro can't be imported
into a `MODULE.bazel` file directly via `use_repo_rule`. If you did want
to use it in `MODULE.bazel`, you'd have to write a module extension to
call it, or use `modules.as_extension` from `bazel_skylib`. I suppose
that's a small price to pay for squashing a canonical repo name format
dependency forever.

The other small downside may be that the documentation from the original
`repository_rule` doesn't automatically convey to the repo wrapper. In
this case, `_alias_repository` is already internal, and the original
`jvm_import_external` didn't have docstrings to begin with.
@mbland
Copy link
Author

mbland commented Nov 18, 2024

Closing this now, as my subconscious concocted another approach to the "default repo target name" problem last Friday. Basically, you can use a macro to wrap the actual repository_rule and duplicate the name argument for the additional parameter. I've opened bazelbuild/rules_scala#1650 to replace apparent_repo_name with this macro pattern (note this is for a completely internal repo rule, but the pattern applies to public ones, too):

_alias_repository = repository_rule(
    implementation = _alias_repository_impl,
    attrs = {
        "default_target_name": attr.string(mandatory = True),
        "target": attr.string(mandatory = True),
    },
)

def _alias_repository_wrapper(**kwargs):
    """Wraps `_alias_repository` to pass `name` as `default_target_name`."""
    default_target_name = kwargs.pop("default_target_name", kwargs.get("name"))
    _alias_repository(default_target_name = default_target_name, **kwargs)

From that PR: One small downside of this technique is that the macro can't be imported into a MODULE.bazel file directly via use_repo_rule. If you did want to use it in MODULE.bazel, you'd have to write a module extension to call it, or use modules.as_extension from bazel_skylib. I suppose that's a small price to pay for squashing a canonical repo name format dependency forever.

The other small downside may be that the documentation from the original repository_rule doesn't automatically convey to the repo wrapper.

@mbland mbland closed this Nov 18, 2024
liucijus pushed a commit to bazelbuild/rules_scala that referenced this pull request Nov 27, 2024
This presents the same API, remains compatible with both `WORKSPACE` and
Bzlmod, and avoids relying on the canonical repository name format at
all. Part of #1482, and supersedes the addition of `apparent_repo_name`
from #1621.

Also adds the `_SCALA_IMPORT_RULE_LOAD` constant in
`scala/scala_maven_import_external.bzl`, which also removes
`@io_bazel_rules_scala` from the `load`ed file path. It now generates
the correct path with a `Label` instead.

---

The goal is to maintain the ability to generate default target names
from a repository name under Bzlmod. While not documented on
https://bazel.build, it is documented in the Bazel source itself, even
under the current 9.0.0-pre.20241105.2 prerelease:

- https://github.com/bazelbuild/bazel/blob/9.0.0-pre.20241105.2/src/main/java/com/google/devtools/build/lib/cmdline/LabelParser.java#L99

Under `WORKSPACE`, the `repository_ctx.name` value served this purpose
directly, as it would reflect the `name` value as provided by the
caller. Under Bzlmod, this value now contains the canonical repo name,
which is different than that provided by the `name` parameter. The
`apparent_repo_name` utility was my first attempt to resolve this, by
parsing the original `name` parameter from the canonicalized
`repository_ctx.name`.

I also created bazelbuild/bazel-skylib#548 to contribute
`apparent_repo_name` to `bazel_skylib`, along with other proposed
utilities. I quickly found better solutions to obviate the other
utilities, but hung onto `apparent_repo_name`.

After that pull request stagnated, I eventually realized a macro wrapper
could generate a default target name for a repository_rule by
duplicating the `name` attr. This isn't as easy as adding
`apparent_repo_name(repository_ctx.name)` to a repo rule's
implementation, but it's also not that much more work. See also this
thread from the #bzlmod channel in the Bazel Slack workspace about
generating default repo names:

- https://bazelbuild.slack.com/archives/C014RARENH0/p1730909824656159

---

One small downside of this technique is that the macro can't be imported
into a `MODULE.bazel` file directly via `use_repo_rule`. If you did want
to use it in `MODULE.bazel`, you'd have to write a module extension to
call it, or use `modules.as_extension` from `bazel_skylib`. I suppose
that's a small price to pay for squashing a canonical repo name format
dependency forever.

The other small downside may be that the documentation from the original
`repository_rule` doesn't automatically convey to the repo wrapper. In
this case, `_alias_repository` is already internal, and the original
`jvm_import_external` didn't have docstrings to begin with.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants