Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
7 changes: 5 additions & 2 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ module(
bazel_dep(name = "bazel_features", version = "1.27.0")
bazel_dep(name = "bazel_skylib", version = "1.3.0")
bazel_dep(name = "platforms", version = "0.0.9")
bazel_dep(name = "rules_shell", version = "0.3.0")
bazel_dep(name = "rules_cc", version = "0.2.15")

apple_cc_configure = use_extension("//crosstool:setup.bzl", "apple_cc_configure_extension")
use_repo(apple_cc_configure, "local_config_apple_cc", "local_config_apple_cc_toolchains")

register_toolchains("@local_config_apple_cc_toolchains//:all")
register_toolchains(
"@local_config_apple_cc_toolchains//:all",
"//rules/install_name_tool:default_toolchain",
)

bazel_dep(name = "rules_shell", version = "0.3.0", dev_dependency = True)
bazel_dep(name = "stardoc", version = "0.8.0", dev_dependency = True)

# TODO: Remove when transitives bump past this
Expand Down
53 changes: 53 additions & 0 deletions rules/install_name_tool/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load(":xcode_toolchain.bzl", "xcode_install_name_tool_toolchain")

licenses(["notice"])

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

toolchain_type(name = "toolchain_type")

sh_binary(
name = "install_name_tool",
srcs = ["install_name_tool_wrapper.sh"],
visibility = ["//visibility:private"],
)

xcode_install_name_tool_toolchain(
name = "install_name_tool_toolchain",
tool = ":install_name_tool",
)

toolchain(
name = "default_toolchain",
exec_compatible_with = ["@platforms//os:macos"],
toolchain = ":install_name_tool_toolchain",
toolchain_type = ":toolchain_type",
)

bzl_library(
name = "install_name_tool_bzl",
srcs = ["install_name_tool.bzl"],
)

bzl_library(
name = "toolchain_bzl",
srcs = ["toolchain.bzl"],
)

bzl_library(
name = "xcode_toolchain_bzl",
srcs = ["xcode_toolchain.bzl"],
deps = [
":toolchain_bzl",
"//lib:apple_support",
],
)

filegroup(
name = "for_bazel_tests",
testonly = 1,
srcs = glob(["**"]),
visibility = ["//:__pkg__"],
)
115 changes: 115 additions & 0 deletions rules/install_name_tool/install_name_tool.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright 2026 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Rule for modifying Mach-O binaries with install_name_tool."""

_TOOLCHAIN_TYPE = "//rules/install_name_tool:toolchain_type"

def _install_name_tool_impl(ctx):
args = ctx.actions.args()
if ctx.attr.install_name:
args.add("-id", ctx.attr.install_name)

for rpath in ctx.attr.add_rpath:
args.add("-add_rpath", rpath)

for rpath in ctx.attr.prepend_rpath:
args.add("-prepend_rpath", rpath)

for rpath in ctx.attr.delete_rpath:
args.add("-delete_rpath", rpath)

for old, new in ctx.attr.change_library.items():
args.add("-change")
args.add(old)
args.add(new)

for old, new in ctx.attr.change_rpath.items():
args.add("-rpath")
args.add(old)
args.add(new)

if not args:
fail("No modifications specified for install_name_tool.")

toolchain_info = ctx.toolchains[_TOOLCHAIN_TYPE].install_name_tool_info
output = ctx.actions.declare_file(ctx.label.name)
args.add(output)

ctx.actions.run_shell(
inputs = [ctx.file.src],
outputs = [output],
tools = [toolchain_info.tool],
command = "cp \"$1\" \"$2\" && chmod u+w \"$2\" && shift 2 && exec \"$@\"",
arguments = [ctx.file.src.path, output.path, toolchain_info.tool.executable.path, args],
mnemonic = "InstallNameTool",
progress_message = "Editing load commands %{output}",
env = toolchain_info.env,
execution_requirements = toolchain_info.execution_requirements,
use_default_shell_env = True,
)

return [DefaultInfo(
files = depset([output]),
)]

install_name_tool = rule(
doc = """\
Modifies a Mach-O binary using `install_name_tool`.

This rule copies the input binary and applies the requested modifications to
the copy. It uses a toolchain to resolve the `install_name_tool` binary,
allowing users to provide their own implementation if needed.

Example usage:

```build
load("@build_bazel_apple_support//rules/install_name_tool:install_name_tool.bzl", "install_name_tool")

install_name_tool(
name = "patched_lib",
src = ":my_dylib",
install_name = "@rpath/libfoo.dylib",
add_rpath = ["@loader_path/../Frameworks"],
)
```
""",
implementation = _install_name_tool_impl,
attrs = {
"src": attr.label(
mandatory = True,
allow_single_file = True,
doc = "The Mach-O binary to modify.",
),
"install_name": attr.string(
doc = "The new install name (`-id`) for the binary.",
),
"add_rpath": attr.string_list(
doc = "Rpaths to add (`-add_rpath`).",
),
"prepend_rpath": attr.string_list(
doc = "Rpaths to prepend (`-prepend_rpath`).",
),
"delete_rpath": attr.string_list(
doc = "Rpaths to delete (`-delete_rpath`).",
),
"change_library": attr.string_dict(
doc = "Library paths to change (`-change old new`). Keys are old paths, values are new paths.",
),
"change_rpath": attr.string_dict(
doc = "Rpaths to change (`-rpath old new`). Keys are old rpaths, values are new rpaths.",
),
},
toolchains = [_TOOLCHAIN_TYPE],
)
5 changes: 5 additions & 0 deletions rules/install_name_tool/install_name_tool_wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env bash

set -euo pipefail

exec /usr/bin/install_name_tool "$@"
63 changes: 63 additions & 0 deletions rules/install_name_tool/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2026 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Toolchain rule for providing a custom `install_name_tool` tool."""

InstallNameToolInfo = provider(
doc = "Provides an `install_name_tool` for modifying Mach-O binaries.",
fields = {
"tool": "A `FilesToRunProvider` for the `install_name_tool` tool.",
"env": "A `dict` of environment variables to set when running the tool.",
"execution_requirements": """\
A `dict` of execution requirements for the action (e.g. `requires-darwin`).
""",
},
)

def _install_name_tool_toolchain_impl(ctx):
return [
platform_common.ToolchainInfo(
install_name_tool_info = InstallNameToolInfo(
tool = ctx.attr.tool[DefaultInfo].files_to_run,
env = ctx.attr.env,
execution_requirements = ctx.attr.execution_requirements,
),
),
]

install_name_tool_toolchain = rule(
attrs = {
"tool": attr.label(
doc = "The `install_name_tool` binary.",
mandatory = True,
allow_files = True,
executable = True,
cfg = "exec",
),
"env": attr.string_dict(
doc = "Additional environment variables to set when running install_name_tool.",
default = {},
),
"execution_requirements": attr.string_dict(
doc = "Additional execution requirements for the action.",
default = {},
),
},
doc = """\
Defines a toolchain for `install_name_tool` used to modify Mach-O binaries.
Use this to provide a custom `install_name_tool` implementation by defining an
`install_name_tool_toolchain` target and registering it as a toolchain.
""",
implementation = _install_name_tool_toolchain_impl,
)
67 changes: 67 additions & 0 deletions rules/install_name_tool/xcode_toolchain.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Copyright 2026 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Xcode-aware toolchain rule for providing an `install_name_tool` tool."""

load("//lib:apple_support.bzl", "apple_support")
load(":toolchain.bzl", "InstallNameToolInfo")

def _xcode_install_name_tool_toolchain_impl(ctx):
env = dict(ctx.attr.env)
execution_requirements = dict(ctx.attr.execution_requirements)

xcode_config = ctx.attr._xcode_config[apple_common.XcodeVersionConfig]
if xcode_config:
env.update(apple_common.apple_host_system_env(xcode_config))
env.update(
apple_common.target_apple_env(xcode_config, ctx.fragments.apple.single_arch_platform),
)
execution_requirements.update(xcode_config.execution_info())

return [
platform_common.ToolchainInfo(
install_name_tool_info = InstallNameToolInfo(
tool = ctx.attr.tool[DefaultInfo].files_to_run,
env = env,
execution_requirements = execution_requirements,
),
),
]

xcode_install_name_tool_toolchain = rule(
attrs = apple_support.action_required_attrs() | {
"tool": attr.label(
doc = "The `install_name_tool` binary.",
mandatory = True,
allow_files = True,
executable = True,
cfg = "exec",
),
"env": attr.string_dict(
doc = "Additional environment variables to set when running install_name_tool.",
default = {},
),
"execution_requirements": attr.string_dict(
doc = "Additional execution requirements for the action.",
default = {},
),
},
doc = """\
Defines a toolchain for `install_name_tool` used to modify Mach-O binaries.
This toolchain automatically sets environment variables and execution
requirements required to run Xcode's install_name_tool hermetically.
""",
fragments = ["apple"],
implementation = _xcode_install_name_tool_toolchain_impl,
)
32 changes: 32 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ load("@rules_cc//cc:cc_shared_library.bzl", "cc_shared_library")
load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test", "objc_library")
load("@rules_shell//shell:sh_test.bzl", "sh_test")
load("//rules:apple_genrule.bzl", "apple_genrule")
load("//rules/install_name_tool:install_name_tool.bzl", "install_name_tool")
load(":apple_support_test.bzl", "apple_support_test")
load(":available_xcodes_test.bzl", "available_xcodes_test")
load(":binary_tests.bzl", "binary_test_suite")
Expand Down Expand Up @@ -97,6 +98,37 @@ universal_binary_test(
target_under_test = "//test/test_data:multi_arch_cc_binary",
)

install_name_tool(
name = "patched_dylib",
testonly = True,
src = "//test/test_data:test_dylib",
add_rpath = ["CUSTOM_RPATH"],
install_name = "CUSTOM_INSTALL_NAME",
)

sh_test(
name = "install_name_tool_test",
size = "small",
srcs = ["install_name_tool_test.sh"],
args = ["$(location :patched_dylib)"],
data = [":patched_dylib"],
)

install_name_tool(
name = "patched_dylib_changed_rpath",
testonly = True,
src = ":patched_dylib",
change_rpath = {"CUSTOM_RPATH": "CUSTOM_CHANGED_RPATH"},
)

sh_test(
name = "install_name_tool_rpath_test",
size = "small",
srcs = ["install_name_tool_rpath_test.sh"],
args = ["$(location :patched_dylib_changed_rpath)"],
data = [":patched_dylib_changed_rpath"],
)

# Consumed by bazel tests.
filegroup(
name = "for_bazel_tests",
Expand Down
Loading