mongo/bazel/wasm_rules/embed_binary.bzl
Andrew Bradshaw 0b44af130b SERVER-122827 Get binary embedding working on mac (#50586)
GitOrigin-RevId: b0d75e7a3e0cb6c09e9d2a500355b71854c487c4
2026-03-27 16:49:31 +00:00

206 lines
6.6 KiB
Python

"""Rule to embed a binary file as a linkable object (.o) in .rodata using .incbin.
Generates a small assembly file that uses the .incbin directive to include the
binary data, then compiles it with the CC toolchain's assembler. This works with
both GCC and Clang on all platforms (Linux, macOS, Windows cross-compile).
Provides CcInfo so dependents can link the .o via deps (recommended) in addition
to using the output in linkopts/additional_linker_inputs.
"""
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
_ASM_TEMPLATE_LINUX = """\
.section .rodata,"a"
.global {start_sym}
.global {end_sym}
.global {size_sym}
.balign 16
{start_sym}:
.incbin "{input_path}"
{end_sym}:
{size_sym} = {end_sym} - {start_sym}
"""
# macOS uses Mach-O which prefixes C symbols with an underscore, so assembly
# symbols need the extra leading underscore. The .const section is Mach-O's
# equivalent of .rodata.
_ASM_TEMPLATE_MACOS = """\
.section __TEXT,__const
.global _{start_sym}
.global _{end_sym}
.global _{size_sym}
.balign 16
_{start_sym}:
.incbin "{input_path}"
_{end_sym}:
_{size_sym} = _{end_sym} - _{start_sym}
"""
def _embed_binary_obj_impl(ctx):
cc_toolchain = find_cpp_toolchain(ctx)
input_file = ctx.file.src
output_file = ctx.outputs.out
prefix = "_" + ctx.attr.symbol_prefix
macos = ctx.attr._macos[platform_common.ConstraintValueInfo]
is_macos = ctx.target_platform_has_constraint(macos)
template = _ASM_TEMPLATE_MACOS if is_macos else _ASM_TEMPLATE_LINUX
asm_file = ctx.actions.declare_file(ctx.attr.name + "_embed.S")
ctx.actions.write(
output = asm_file,
content = template.format(
start_sym = prefix + "_start",
end_sym = prefix + "_end",
size_sym = prefix + "_size",
input_path = input_file.path,
),
)
feature_configuration = cc_common.configure_features(
ctx = ctx,
cc_toolchain = cc_toolchain,
)
# Use cc_common.compile to assemble the .S file. This properly sets up
# the toolchain environment (e.g. GCC's libexec path for cc1) unlike
# a raw ctx.actions.run with the compiler path.
(_compilation_context, compilation_outputs) = cc_common.compile(
actions = ctx.actions,
feature_configuration = feature_configuration,
cc_toolchain = cc_toolchain,
name = ctx.label.name,
srcs = [asm_file],
additional_inputs = [input_file],
)
# cc_common.compile produces objects in its own location; copy to the
# expected output path.
objs = compilation_outputs.objects
if not objs:
objs = compilation_outputs.pic_objects
ctx.actions.symlink(output = output_file, target_file = objs[0])
linker_input = cc_common.create_linker_input(
owner = ctx.label,
user_link_flags = [output_file.path],
additional_inputs = depset([output_file]),
)
linking_context = cc_common.create_linking_context(
linker_inputs = depset(direct = [linker_input]),
)
return [
DefaultInfo(files = depset([output_file])),
CcInfo(linking_context = linking_context),
]
embed_binary_obj = rule(
implementation = _embed_binary_obj_impl,
attrs = {
"src": attr.label(
allow_single_file = True,
mandatory = True,
doc = "Binary file to embed (e.g. .wasm or .cwasm).",
),
"out": attr.output(
mandatory = True,
doc = "Output linkable object (.o) with content in .rodata.",
),
"symbol_prefix": attr.string(
mandatory = True,
doc = "Short name for embedded symbols; produces _<symbol_prefix>_start, _end, _size.",
),
"_cc_toolchain": attr.label(
default = "@bazel_tools//tools/cpp:current_cc_toolchain",
),
"_macos": attr.label(default = "@platforms//os:macos"),
},
doc = "Embeds a binary file as a linkable object using .incbin via the CC toolchain.",
toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
fragments = ["cpp"],
)
"""Rule to embed a binary file as a Windows resource using rc.exe.
Generates an .rc file referencing the binary, compiles it to a .res with rc.exe,
and provides CcInfo so dependents can link it via deps.
At runtime, use FindResource/LoadResource/LockResource/SizeofResource to access
the data, or use the helper in embedded_resource.h.
"""
def _embed_binary_rc_impl(ctx):
input_file = ctx.file.src
rc_file = ctx.actions.declare_file(ctx.attr.name + ".rc")
res_file = ctx.outputs.out
# The resource type and name used to look up the data at runtime.
resource_name = ctx.attr.resource_name
# Generate an .rc file that references the binary as RCDATA.
ctx.actions.write(
output = rc_file,
content = '{resource_name} RCDATA "{input_path}"\n'.format(
resource_name = resource_name,
input_path = input_file.path.replace("/", "\\\\"),
),
)
args = ctx.actions.args()
args.add("/nologo")
args.add("/fo" + res_file.path)
args.add(rc_file.path)
ctx.actions.run(
executable = ctx.executable.rc,
arguments = [args],
inputs = [rc_file, input_file],
outputs = [res_file],
mnemonic = "EmbedBinaryRC",
progress_message = "Embedding %s as Windows resource" % input_file.short_path,
)
linker_input = cc_common.create_linker_input(
owner = ctx.label,
user_link_flags = [res_file.path],
additional_inputs = depset([res_file]),
)
linking_context = cc_common.create_linking_context(
linker_inputs = depset(direct = [linker_input]),
)
return [
DefaultInfo(files = depset([res_file])),
CcInfo(linking_context = linking_context),
]
embed_binary_rc = rule(
implementation = _embed_binary_rc_impl,
attrs = {
"src": attr.label(
allow_single_file = True,
mandatory = True,
doc = "Binary file to embed (e.g. .wasm or .cwasm).",
),
"out": attr.output(
mandatory = True,
doc = "Output .res file.",
),
"resource_name": attr.string(
mandatory = True,
doc = "Resource name used in the .rc file and at runtime with FindResource.",
),
"rc": attr.label(
executable = True,
cfg = "exec",
allow_files = True,
default = "@mongo_windows_toolchain//:rc",
doc = "The rc.exe compiler.",
),
},
provides = [CcInfo],
doc = "Embeds a binary file as a Windows resource (.res) using rc.exe.",
)