2023-10-10 19:16:13 +00:00
|
|
|
"""Utility functions for bzlmod extensions"""
|
|
|
|
|
|
|
|
def _toolchain_repos_bfs(mctx, get_tag_fn, toolchain_name, toolchain_repos_fn, default_repository = None, get_name_fn = None, get_version_fn = None):
|
|
|
|
"""Create toolchain repositories from bzlmod extensions using a breadth-first resolution strategy.
|
|
|
|
|
|
|
|
Toolchains are assumed to have a "default" or canonical repository name so that across
|
|
|
|
all invocations of the module extension with that name only a single toolchain repository
|
|
|
|
is created. As such, it is recommended to default the toolchain name in the extension's
|
|
|
|
tag class attributes so that diverging from the canonical name is a special case.
|
|
|
|
|
|
|
|
The resolved toolchain version will be the one invoked closest to the root module, following
|
|
|
|
Bazel's breadth-first ordering of modules in the dependency graph.
|
|
|
|
|
|
|
|
For example, given the module extension usage in a MODULE file:
|
|
|
|
|
|
|
|
```starlark
|
|
|
|
ext = use_extension("@my_lib//lib:extensions.bzl", "ext")
|
|
|
|
|
|
|
|
ext.foo_toolchain(version = "1.2.3") # Default `name = "foo"`
|
|
|
|
|
|
|
|
use_repo(ext, "foo")
|
|
|
|
|
|
|
|
register_toolchains(
|
|
|
|
"@foo//:all",
|
|
|
|
)
|
|
|
|
```
|
|
|
|
|
|
|
|
This macro would be used in the module extension implementation as follows:
|
|
|
|
|
|
|
|
```starlark
|
|
|
|
extension_utils.toolchain_repos(
|
|
|
|
mctx = mctx,
|
|
|
|
get_tag_fn = lambda tags: tags.foo_toolchain,
|
|
|
|
toolchain_name = "foo",
|
|
|
|
toolchain_repos_fn = lambda name, version: register_foo_toolchains(name = name, register = False),
|
|
|
|
get_version_fn = lambda attr: None,
|
|
|
|
)
|
|
|
|
```
|
|
|
|
|
|
|
|
Where `register_foo_toolchains` is a typical WORKSPACE macro used to register
|
|
|
|
the foo toolchain for a particular version, minus the actual registration step
|
|
|
|
which is done separately in the MODULE file.
|
|
|
|
|
|
|
|
This macro enforces that only root MODULEs may use a different name for the toolchain
|
|
|
|
in case several versions of the toolchain repository is desired.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
mctx: The module context
|
|
|
|
get_tag_fn: A function that takes in `module.tags` and returns the tag used for the toolchain.
|
|
|
|
For example, `tag: lambda tags: tags.foo_toolchain`. This is required because `foo_toolchain`
|
|
|
|
cannot be accessed as a simple string key from `module.tags`.
|
|
|
|
toolchain_name: Name of the toolchain to use in error messages
|
|
|
|
toolchain_repos_fn: A function that takes (name, version) and creates a toolchain repository. This lambda
|
|
|
|
should call a typical reposotiory rule to create toolchains.
|
|
|
|
default_repository: Default name of the toolchain repository to pass to the repos_fn.
|
|
|
|
By default, it equals `toolchain_name`.
|
|
|
|
get_name_fn: A function that extracts the module name from the toolchain tag's attributes. Defaults
|
|
|
|
to grabbing the `name` attribute.
|
|
|
|
get_version_fn: A function that extracts the module version from the a tag's attributes. Defaults
|
|
|
|
to grabbing the `version` attribute. Override this to a lambda that returns `None` if
|
|
|
|
version isn't used as an attribute.
|
|
|
|
"""
|
|
|
|
if default_repository == None:
|
|
|
|
default_repository = toolchain_name
|
|
|
|
|
|
|
|
if get_name_fn == None:
|
|
|
|
get_name_fn = lambda attr: attr.name
|
|
|
|
if get_version_fn == None:
|
|
|
|
get_version_fn = lambda attr: attr.version
|
|
|
|
|
|
|
|
registrations = {}
|
|
|
|
for mod in mctx.modules:
|
|
|
|
for attr in get_tag_fn(mod.tags):
|
|
|
|
name = get_name_fn(attr)
|
|
|
|
version = get_version_fn(attr)
|
|
|
|
if name != default_repository and not mod.is_root:
|
|
|
|
fail("Only the root module may provide a name for the {} toolchain.".format(toolchain_name))
|
|
|
|
|
|
|
|
if name in registrations.keys():
|
|
|
|
if name == default_repository:
|
|
|
|
# Prioritize the root-most registration of the default toolchain version and
|
|
|
|
# ignore any further registrations (modules are processed breadth-first)
|
|
|
|
continue
|
|
|
|
if version == registrations[name]:
|
|
|
|
# No problem to register a matching toolchain twice
|
|
|
|
continue
|
|
|
|
fail("Multiple conflicting {} toolchains declared for name {} ({} and {})".format(
|
|
|
|
toolchain_name,
|
|
|
|
name,
|
|
|
|
version,
|
|
|
|
registrations[name],
|
|
|
|
))
|
|
|
|
else:
|
|
|
|
registrations[name] = version
|
|
|
|
|
2023-11-16 14:57:29 +00:00
|
|
|
for name, version in registrations.items():
|
|
|
|
toolchain_repos_fn(
|
|
|
|
name = name,
|
|
|
|
version = version,
|
|
|
|
)
|
2023-10-10 19:16:13 +00:00
|
|
|
|
|
|
|
extension_utils = struct(
|
|
|
|
toolchain_repos_bfs = _toolchain_repos_bfs,
|
|
|
|
)
|