Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,30 @@ archive_override(
urls = ["https://github.com/bazel-contrib/toolchains_llvm/archive/ffe407ca2423f4d171b7a0f3897c23bbb7e40310.tar.gz"],
)

#################################
## Tools that ship with the OS ##
#################################

find_macos_codesign = use_repo_rule("//bazel/toolchains/codesign:configure.bzl", "find_macos_codesign")

find_macos_codesign(
name = "macos_codesign",
exec_compatible_with = ["@platforms//os:macos"],
)

find_macos_pkgbuild = use_repo_rule("//bazel/toolchains/pkgbuild:configure.bzl", "find_macos_pkgbuild")

find_macos_pkgbuild(
name = "macos_pkgbuild",
exec_compatible_with = ["@platforms//os:macos"],
)

register_toolchains(
"@macos_codesign//:all",
"@macos_pkgbuild//:all",
dev_dependency = True,
)

#########################
## Prebuilt binaries ##
#########################
Expand Down
9 changes: 9 additions & 0 deletions bazel/toolchains/codesign/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""toolchain to wrap the codesign binary.

Type: @codesign//:codesign_toolchain_type

Toolchains:
- @codesign//:codesign_toolchain: provides the tool
- @codesign//:codesign_missing_toolchain: provides a fallback toolchain for
exec platforms where codesign might not be available.
"""
8 changes: 8 additions & 0 deletions bazel/toolchains/codesign/configure.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Repository rule to autoconfigure a toolchain using the system codesign."""

load("//bazel/toolchains/common:defs.bzl", "make_repo_builder")

# This must match the repository name used by register_toolchains in consuming MODULE.bazel files.
NAME = "macos_codesign"

find_macos_codesign = make_repo_builder(name = NAME, tool_name = "codesign")
12 changes: 12 additions & 0 deletions bazel/toolchains/common/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# //bazel/toolchains.

package(default_visibility = ["//visibility:private"])

filegroup(
name = "common",
srcs = [
"toolchain_BUILD.tpl",
"toolchain_defs.bzl.tpl",
],
visibility = ["//bazel/toolchains:__subpackages__"],
)
Comment thread
aiuto marked this conversation as resolved.
Outdated
63 changes: 63 additions & 0 deletions bazel/toolchains/common/defs.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Utilitites for creating toolchains to wrap system provided tools."""

def _write_toolchain_repo(rctx, repo_name, tool_name, tool_path, tool_version = "<unknown>", exec_compatible_with = None):
if not tool_path:
tool_path = ""
rctx.template(
"BUILD",
Label("@@//bazel/toolchains/common:toolchain_BUILD.tpl"),
substitutions = {
"{AVAILABLE}": "1" if tool_path else "0",
"{EXEC_COMPATIBLE_WITH}": repr(exec_compatible_with),
"{GENERATOR}": "//bazel/toolchains/common:defs.bzl",
"{REPO_NAME}": repo_name,
"{TOOL_NAME}": tool_name,
"{TOOL_PATH}": str(tool_path),
"{TOOL_VERSION}": tool_version,
},
executable = False,
)
rctx.template(
"defs.bzl",
Label("@@//bazel/toolchains/common:toolchain_defs.bzl.tpl"),
substitutions = {
"{AVAILABLE}": "1" if tool_path else "0",
"{GENERATOR}": "//bazel/toolchains/common:defs.bzl",
"{REPO_NAME}": repo_name,
"{TOOL_NAME}": tool_name,
},
executable = False,
)

def _default_repo_builder_impl(rctx):
tool_name = rctx.attr.tool_name
tool_path = rctx.which(tool_name)
if rctx.attr.verbose:
if tool_path:
print("Found %s at '%s'" % (tool_name, tool_path)) # buildifier: disable=print
else:
print("No system %s found." % tool_name) # buildifier: disable=print
_write_toolchain_repo(
rctx = rctx,
repo_name = rctx.original_name,
tool_name = rctx.attr.tool_name,
tool_path = tool_path,
exec_compatible_with = rctx.attr.exec_compatible_with,
)

def make_repo_builder(name, tool_name, impl = _default_repo_builder_impl):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This name seems to describe the function poorly. Probably something like local_toolchain_repository would be more apt.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not that either, because it doesn't make the repository. It returns a rule, that you have to call to make the thing you want. So it's really a builder pattern. Think of the expansion in MODULE.bazel. It would be

# This next line is the effect of load(..., "find_macos_pkgbuild")
find_macos_pkgbuild = make_repo_builder(name = NAME, tool_name = "pkgbuild")
# This calls the function to make the repository.
find_macos_pkgbuild(
    name = "macos_pkgbuild",
    exec_compatible_with = ["@platforms//os:macos"],
)

So, maybe we rename find_macos_pkgbuild. Maybe "os_provided_macos_pkgbuild".
That's wordy, but more accurate.

@alopezz alopezz Jul 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, but the name is still a bit too generic, it could be something like make_local_toolchain_repository_rule or make_os_provided_tool_repo_rule or similar, at least something that points at this being intended to wrap local tools would be desirable in my opinion.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to make_local_toolchain_repository_rule

return repository_rule(
implementation = impl,
doc = """Create a repository that defines a {name} toolchain based on tool in the default $PATH.""".format(name = name),
local = True,
environ = ["PATH"],
attrs = {
"tool_name": attr.string(doc = "The name of the tool to find.", default = tool_name),
"exec_compatible_with": attr.string_list(
doc = "exec_compatible_with list to apply to the created toolchain.",
),
"verbose": attr.bool(
doc = "If true, print status messages.",
),
},
)
51 changes: 51 additions & 0 deletions bazel/toolchains/common/toolchain_BUILD.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# This content is generated by {GENERATOR}
load(":defs.bzl", "is_{TOOL_NAME}_available", "{TOOL_NAME}_toolchain")

toolchain_type(
name = "{TOOL_NAME}_toolchain_type",
visibility = ["//visibility:public"],
)

{TOOL_NAME}_toolchain(
name = "{TOOL_NAME}_auto",
path = "{TOOL_PATH}",
version = "{TOOL_VERSION}",
)

toolchain(
name = "{TOOL_NAME}_toolchain",
toolchain = ":{TOOL_NAME}_auto",
toolchain_type = ":{TOOL_NAME}_toolchain_type",
exec_compatible_with = {EXEC_COMPATIBLE_WITH},
)
Comment thread
alopezz marked this conversation as resolved.

# {TOOL_NAME}_missing_toolchain provides a fallback toolchain so that toolchain
# resolution can succeed even on platforms that do not have that tool.
# If this toolchain is selected, the constraint ":have_{TOOL_NAME}" will not be satistifed,
# so that can be used with with exec_compatible_with clauses.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you check whether it can actually be used in exec_compatible_with clauses?

According to https://bazel.build/reference/be/platforms-and-toolchains#toolchain_args, only constraint_setting's can be passed here, and the attribute is nonconfigurable.

@aiuto aiuto Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right it can't be used there. I got ahead of myself. We were going to unify constraint_value and config_setting but never got around to it. A rule using it would have to examine the valid field.
I fixed the comment.

{TOOL_NAME}_toolchain(
name = "no_{TOOL_NAME}",
)

toolchain(
name = "zzz_{TOOL_NAME}_missing_toolchain", # keep name lexicographically last
toolchain = ":no_{TOOL_NAME}",
toolchain_type = ":{TOOL_NAME}_toolchain_type",
)

# Expose the availability of the actual tool as a flag, so we can
# create a config_setting from it.
is_{TOOL_NAME}_available(
Comment thread
alopezz marked this conversation as resolved.
name = "is_{TOOL_NAME}_available",
build_setting_default = {AVAILABLE},
Comment thread
alopezz marked this conversation as resolved.
)

# Expose the availability of the toolchain as a config_setting, so we can
# select() on it.
config_setting(
name = "have_{TOOL_NAME}",
flag_values = {
":is_{TOOL_NAME}_available": "1",
},
visibility = ["//visibility:public"],
)
48 changes: 48 additions & 0 deletions bazel/toolchains/common/toolchain_defs.bzl.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""toolchain to provide the {TOOL_NAME} binary."""

load("@@//bazel/toolchains:toolchain_info.bzl", "ToolInfo")

def _{TOOL_NAME}_toolchain_impl(ctx):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And another question that I have is whether we actually need to be templated with the TOOL_NAME baked in or whether a single common rule can be used to define all these toolchains instead.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For just the two I have done, yes. But that gets in the way of future expansion, where I want different things to customize the template. pkgbuild and codesign, do not have version commands, but for tools that do, I want the repo rule to extract the version (which has to be tool specific) and put that in as metadata so we can have the SBOM say things like otool: cctools-1030.6.3

I have that ready in another PR, but it's not needed yet. Essentially the repo_builder call will use a private impl function instead of the default, and your private impl will call do the lookup and then call write_toolchain_repo to emit the repo.

if ctx.attr.label and ctx.attr.path:
fail("{TOOL_NAME}_toolchain must not specify both label and path.")
valid = bool(ctx.attr.label) or bool(ctx.attr.path)
toolchain_info = platform_common.ToolchainInfo(
{TOOL_NAME} = ToolInfo(
name = str(ctx.label),
valid = valid,
label = ctx.attr.label,
path = ctx.attr.path,
version = ctx.attr.version,
),
)
return [toolchain_info]

{TOOL_NAME}_toolchain = rule(
implementation = _{TOOL_NAME}_toolchain_impl,
attrs = {
"label": attr.label(
doc = "A valid label of a target to build or a prebuilt binary. Mutually exclusive with path.",
cfg = "exec",
executable = True,
allow_files = True,
),
"path": attr.string(
doc = "The path to the executable. Mutually exclusive with label.",
),
"version": attr.string(
doc = "The version string of the executable. This should be manually set.",
),
},
)

# Expose the presence of {TOOL_NAME} as a flag.
def _is_{TOOL_NAME}_available_impl(ctx):
return [config_common.FeatureFlagInfo(
value = ("1" if ctx.build_setting_value else "0"),
)]

is_{TOOL_NAME}_available = rule(
implementation = _is_{TOOL_NAME}_available_impl,
attrs = {},
build_setting = config.bool(flag = False),
)
Comment thread
aiuto marked this conversation as resolved.
8 changes: 8 additions & 0 deletions bazel/toolchains/pkgbuild/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Toolchain to wrap the pkgbuild binary.

Type: @macos_pkgbuild//:tool_toolchain_type

Toolchains:
- @macos_pkgbuild//:macos_pkgbuild_toolchain: provides the tool
- @macos_pkgbuild//:macos_pkgbuild_missing_toolchain: provides a fallback toolchain for exec platforms where pkgbuild might not be available.
"""
8 changes: 8 additions & 0 deletions bazel/toolchains/pkgbuild/configure.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Repository rule to autoconfigure a toolchain using the system pkgbuild."""

load("//bazel/toolchains/common:defs.bzl", "make_repo_builder")

# This must match the name used by register_toolchains in MODULE.bazel.
NAME = "macos_pkgbuild"

find_macos_pkgbuild = make_repo_builder(name = NAME, tool_name = "pkgbuild")
Loading