Skip to content

Instantly share code, notes, and snippets.

@rrbutani
Last active September 5, 2023 04:47
Show Gist options
  • Save rrbutani/2b5a1ee5ae22dcc13204511915e451de to your computer and use it in GitHub Desktop.
Save rrbutani/2b5a1ee5ae22dcc13204511915e451de to your computer and use it in GitHub Desktop.
"""Workarounds for https://github.com/bazelbuild/bazel/issues/19055."""
# TODO(build, bazel, blzmod): if new APIs are introduced resolving this issue:
# https://github.com/bazelbuild/bazel/issues/19055, use them in lieu of this
# function.
def get_sibling_repo_in_module_extension(rctx, repo_name):
"""Gets the _canonical_ name for a sibling repo in the module extension.
## Background
This function is a workaround for [this issue].
See:
- [here][canonical-repo-name] for information on canonical and apparent
repository names
- [here][module-extensions] for information on module extensions
As detailed in the linked issue, the repo mapping applied to repositories
created within a module extension (i.e. the thing that makes it so that uses
of _apparent_ repository names are mapped to _canonical_ repository names)
is not in effect during the _execution_ of the repository rule.
This makes it so that, within a repository rule invoked from a module
extension, uses of labels pointing to repositories created within the same
module extension (sibling repos) will not resolve when apparent names are
used (see the linked issue for more details).
This function uses the first workaround detailed in the issue linked above:
it relies on details about bzlmod's repository naming convention to get the
_canonical_ name of the sibling repository that is being referenced.
## Assumptions
We assume that:
- you are calling this function from within a repository rule that has
been invoked from a module extension
+ if it looks like this function has been called from outside a module
extension we'll just return `repo_name`
- no repositories involved (the calling repository rule's repo and
`repo_name`) have a `~` in their name
- `repo_name` really is a repository that exists under the module
extension that invoked the repository rule this function was invoked
from
None of these are really correctness issues (you'll just end up with a Label
that's not well-formed or doesn't exist — at worst it'll point to the wrong
thing).
---
Args:
rctx: [repository context object][rctx]
This function must be invoked from a [repository rule] impl function.
repo_name: apparent name of the sibling repository
This should be the apparent name of a repository created by the module
extension that invoked the current repository rule.
Returns:
The canonical name of `repo_name`, subject to the caveats listed above.
[this issue]: https://github.com/bazelbuild/bazel/issues/19055
[canonical-repo-name]: https://bazel.build/external/overview#canonical-repo-name
[module-extensions]: https://bazel.build/external/extension
[rctx]: https://bazel.build/rules/lib/builtins/repository_ctx
[repository rule]: https://bazel.build/extending/repo
"""
mangled_repository_name = rctx.name.split("~")
if len(mangled_repository_name) < 3:
# We should need at least
# `{calling_repo_name}~{module extension name}~{current repo name}`.
#
# If we don't have that, assume that we're not being invoked from within
# a module extension.
return repo_name
else:
mangled_repository_name[-1] = repo_name
return "~".join(mangled_repository_name)
def get_sibling_label_in_module_extension_from_parts(rctx, repo, pkg = "", target = None):
# Emulates the default `@foo` and `@foo//bar/baz` label shorthand syntax
if target == None:
# @foo//bar/baz -> @foo//bar/baz:baz
if pkg != "": target = pkg.split("/")[-1]
else: target = repo # @foo -> @foo//:foo
if "~" in repo: fail(
"Label must contain an _apparent_ repo name and must not contain `~`"
)
if repo == "": fail("main repo cannot be a sibling repo")
repo = get_sibling_repo_in_module_extension(rctx, repo)
return Label("@@{repo}//{pkg}:{target}".format(
repo = repo,
pkg = pkg,
target = target,
))
def _parse_string_label(str_label):
# This is bad hacky string Label parsing. Fortunately it doesn't have to be
# rigorous...
if str_label.startswith("@@") or not str_label.startswith("@"): fail(
"invalid string label; must start with `@` and an _apparent_ repo name",
str_label
)
repo, sep, rest = str_label[1:].partition("//")
if sep != "//":
# assume there's no pkg or target, just return
return repo, "", None
pkg, sep, target = rest.partition(":")
if sep != ":":
# assume no target:
return repo, pkg, None
return repo, pkg, target
def get_sibling_label_in_module_extension_from_string(rctx, str_label):
# If the repository isn't in the mapping, you can't do
# `Label("@repo//...").workspace_name`; it'll error.
#
# So you have to either pass the label as a string (this function) or by
# parts (`_from_parts`).
return get_sibling_label_in_module_extension_from_parts(
rctx,
*_parse_string_label(str_label),
)
def get_sibling_label_in_module_extension(rctx, label):
"""Canonicalizes a label pointing to a sibling repo in the module extension.
This function is a wrapper over `get_sibling_repo_in_module_extension` that
operates on `Label`s instead of bare repo names. See that function's docs
for more details.
Args:
rctx: repository context object
label: [label] with the apparent repo name of a sibling repository
Returns:
A new `Label`, with the repo name canonicalized.
[label]: https://bazel.build/rules/lib/builtins/Label
"""
if type(label) != "Label": fail("must be a Label")
return get_sibling_label_in_module_extension_from_parts(
rctx, label.workspace_name, label.package, label.name
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment