| """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 |
| |
| for name, version in registrations.items(): |
| toolchain_repos_fn( |
| name = name, |
| version = version, |
| ) |
| |
| extension_utils = struct( |
| toolchain_repos_bfs = _toolchain_repos_bfs, |
| ) |