Skip to content

Commit

Permalink
feat: backport 2.x features for interoperability (#657)
Browse files Browse the repository at this point in the history
  • Loading branch information
kormide committed Nov 16, 2023
1 parent 7dbb96e commit 78ed16a
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 9 deletions.
10 changes: 8 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ bazel_dep(name = "platforms", version = "0.0.7")
# 0.5.4 is the first version with bzlmod support
bazel_dep(name = "stardoc", version = "0.5.4", repo_name = "io_bazel_stardoc")

ext = use_extension("@aspect_bazel_lib//lib:extensions.bzl", "ext")
use_repo(ext, "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "expand_template_toolchains", "jq_toolchains", "yq_toolchains")
bazel_lib_toolchains = use_extension("@aspect_bazel_lib//lib:extensions.bzl", "toolchains")
bazel_lib_toolchains.copy_directory()
bazel_lib_toolchains.copy_to_directory()
bazel_lib_toolchains.jq()
bazel_lib_toolchains.yq()
bazel_lib_toolchains.coreutils()
bazel_lib_toolchains.expand_template()
use_repo(bazel_lib_toolchains, "copy_directory_toolchains", "copy_to_directory_toolchains", "coreutils_toolchains", "expand_template_toolchains", "jq_toolchains", "yq_toolchains")

register_toolchains(
"@copy_directory_toolchains//:all",
Expand Down
12 changes: 12 additions & 0 deletions docs/copy_file.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions docs/copy_to_bin.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/copy_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,11 @@ query --@aspect_bazel_lib//lib:copy_use_local_execution=false

load(
"//lib/private:copy_file.bzl",
_COPY_FILE_TOOLCHAINS = "COPY_FILE_TOOLCHAINS",
_copy_file = "copy_file",
_copy_file_action = "copy_file_action",
)

copy_file = _copy_file
copy_file_action = _copy_file_action
COPY_FILE_TOOLCHAINS = _COPY_FILE_TOOLCHAINS
2 changes: 2 additions & 0 deletions lib/copy_to_bin.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ https://github.com/bazelbuild/rules_nodejs/blob/8b5d27400db51e7027fe95ae413eeabe

load(
"//lib/private:copy_to_bin.bzl",
_COPY_FILE_TO_BIN_TOOLCHAINS = "COPY_FILE_TO_BIN_TOOLCHAINS",
_copy_file_to_bin_action = "copy_file_to_bin_action",
_copy_files_to_bin_actions = "copy_files_to_bin_actions",
_copy_to_bin = "copy_to_bin",
Expand All @@ -17,3 +18,4 @@ load(
copy_file_to_bin_action = _copy_file_to_bin_action
copy_files_to_bin_actions = _copy_files_to_bin_actions
copy_to_bin = _copy_to_bin
COPY_FILE_TO_BIN_TOOLCHAINS = _COPY_FILE_TO_BIN_TOOLCHAINS
88 changes: 88 additions & 0 deletions lib/extensions.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

load(
"@aspect_bazel_lib//lib:repositories.bzl",
"DEFAULT_COPY_DIRECTORY_REPOSITORY",
"DEFAULT_COPY_TO_DIRECTORY_REPOSITORY",
"DEFAULT_COREUTILS_REPOSITORY",
"DEFAULT_COREUTILS_VERSION",
"DEFAULT_EXPAND_TEMPLATE_REPOSITORY",
"DEFAULT_JQ_REPOSITORY",
"DEFAULT_JQ_VERSION",
"DEFAULT_YQ_REPOSITORY",
"DEFAULT_YQ_VERSION",
"register_copy_directory_toolchains",
"register_copy_to_directory_toolchains",
"register_coreutils_toolchains",
"register_expand_template_toolchains",
"register_jq_toolchains",
"register_yq_toolchains",
)
load("//lib/private:extension_utils.bzl", "extension_utils")
load("//lib/private:host_repo.bzl", "host_repo")

def _toolchain_extension(mctx):
Expand All @@ -32,3 +42,81 @@ ext = module_extension(
implementation = _toolchain_extension,
tag_classes = {"host": tag_class(attrs = {})},
)

# Backport the new host extension from bazel-lib 2.x so that downstream rulesets
# are compatible with 1.x and 2.x
def _host_extension_impl(mctx):
create_host_repo = False
for module in mctx.modules:
if len(module.tags.host) > 0:
create_host_repo = True

if create_host_repo:
host_repo(name = "aspect_bazel_lib_host")

host = module_extension(
implementation = _host_extension_impl,
tag_classes = {
"host": tag_class(attrs = {}),
},
)

# Backport the new toolchains extension from bazel-lib 2.x so that downstream rulesets
# are compatible with 1.x and 2.x
def _toolchains_extension_impl(mctx):
extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.copy_directory,
toolchain_name = "copy_directory",
toolchain_repos_fn = lambda name, version: register_copy_directory_toolchains(name = name, register = False),
get_version_fn = lambda attr: None,
)

extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.copy_to_directory,
toolchain_name = "copy_to_directory",
toolchain_repos_fn = lambda name, version: register_copy_to_directory_toolchains(name = name, register = False),
get_version_fn = lambda attr: None,
)

extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.jq,
toolchain_name = "jq",
toolchain_repos_fn = lambda name, version: register_jq_toolchains(name = name, version = version, register = False),
)

extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.yq,
toolchain_name = "yq",
toolchain_repos_fn = lambda name, version: register_yq_toolchains(name = name, version = version, register = False),
)

extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.coreutils,
toolchain_name = "coreutils",
toolchain_repos_fn = lambda name, version: register_coreutils_toolchains(name = name, version = version, register = False),
)

extension_utils.toolchain_repos_bfs(
mctx = mctx,
get_tag_fn = lambda tags: tags.expand_template,
toolchain_name = "expand_template",
toolchain_repos_fn = lambda name, version: register_expand_template_toolchains(name = name, register = False),
get_version_fn = lambda attr: None,
)

toolchains = module_extension(
implementation = _toolchains_extension_impl,
tag_classes = {
"copy_directory": tag_class(attrs = {"name": attr.string(default = DEFAULT_COPY_DIRECTORY_REPOSITORY)}),
"copy_to_directory": tag_class(attrs = {"name": attr.string(default = DEFAULT_COPY_TO_DIRECTORY_REPOSITORY)}),
"jq": tag_class(attrs = {"name": attr.string(default = DEFAULT_JQ_REPOSITORY), "version": attr.string(default = DEFAULT_JQ_VERSION)}),
"yq": tag_class(attrs = {"name": attr.string(default = DEFAULT_YQ_REPOSITORY), "version": attr.string(default = DEFAULT_YQ_VERSION)}),
"coreutils": tag_class(attrs = {"name": attr.string(default = DEFAULT_COREUTILS_REPOSITORY), "version": attr.string(default = DEFAULT_COREUTILS_VERSION)}),
"expand_template": tag_class(attrs = {"name": attr.string(default = DEFAULT_EXPAND_TEMPLATE_REPOSITORY)}),
},
)
18 changes: 18 additions & 0 deletions lib/private/copy_file.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ load(":copy_common.bzl", "execution_requirements_for_copy", _progress_path = "pr
load(":directory_path.bzl", "DirectoryPathInfo")
load(":platform_utils.bzl", _platform_utils = "platform_utils")

# Declare toolchains used by copy file actions so that downstream rulesets can pass it into
# the `toolchains` attribute of their rule.
COPY_FILE_TOOLCHAINS = []

def _copy_cmd(ctx, src, src_path, dst, override_execution_requirements = None):
# Most Windows binaries built with MSVC use a certain argument quoting
# scheme. Bazel uses that scheme too to quote arguments. However,
Expand Down Expand Up @@ -93,6 +97,18 @@ def copy_file_action(ctx, src, dst, dir_path = None, is_windows = None):
This helper is used by copy_file. It is exposed as a public API so it can be used within
other rule implementations.
To use `copy_file_action` in your own rules, you need to include the toolchains it uses
in your rule definition. For example:
```starlark
load("@aspect_bazel_lib//lib:copy_file.bzl", "COPY_FILE_TOOLCHAINS")
my_rule = rule(
...,
toolchains = COPY_FILE_TOOLCHAINS,
)
```
Args:
ctx: The rule context.
src: The source file to copy or TreeArtifact to copy a single file out of.
Expand Down Expand Up @@ -164,13 +180,15 @@ _copy_file = rule(
implementation = _copy_file_impl,
provides = [DefaultInfo],
attrs = _ATTRS,
toolchains = COPY_FILE_TOOLCHAINS,
)

_copy_xfile = rule(
implementation = _copy_file_impl,
executable = True,
provides = [DefaultInfo],
attrs = _ATTRS,
toolchains = COPY_FILE_TOOLCHAINS,
)

def copy_file(name, src, out, is_executable = False, allow_symlink = False, **kwargs):
Expand Down
17 changes: 16 additions & 1 deletion lib/private/copy_to_bin.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"""Implementation of copy_to_bin macro and underlying rules."""

load("@bazel_skylib//lib:paths.bzl", "paths")
load(":copy_file.bzl", "copy_file_action")
load(":copy_file.bzl", "COPY_FILE_TOOLCHAINS", "copy_file_action")

COPY_FILE_TO_BIN_TOOLCHAINS = COPY_FILE_TOOLCHAINS

def copy_file_to_bin_action(ctx, file, is_windows = None):
"""Factory function that creates an action to copy a file to the output tree.
Expand All @@ -26,6 +28,18 @@ def copy_file_to_bin_action(ctx, file, is_windows = None):
If the file passed in is already in the output tree is then it is returned
without a copy action.
To use `copy_file_to_bin_action` in your own rules, you need to include the toolchains it uses
in your rule definition. For example:
```starlark
load("@aspect_bazel_lib//lib:copy_file.bzl", "COPY_FILE_TO_BIN_TOOLCHAINS")
my_rule = rule(
...,
toolchains = COPY_FILE_TO_BIN_TOOLCHAINS,
)
```
Args:
ctx: The rule context.
file: The file to copy.
Expand Down Expand Up @@ -125,6 +139,7 @@ _copy_to_bin = rule(
attrs = {
"srcs": attr.label_list(mandatory = True, allow_files = True),
},
toolchains = COPY_FILE_TO_BIN_TOOLCHAINS,
)

def copy_to_bin(name, srcs, **kwargs):
Expand Down
93 changes: 93 additions & 0 deletions lib/private/extension_utils.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""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,
)
Loading

0 comments on commit 78ed16a

Please sign in to comment.