view third_party/emsdk/bazel/emscripten_toolchain/wasm_cc_binary.bzl @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 8d17f6e6e290
children
line wrap: on
line source

"""wasm_cc_binary rule for compiling C++ targets to WebAssembly.
"""

def _wasm_transition_impl(settings, attr):
    _ignore = (settings, attr)

    features = list(settings["//command_line_option:features"])
    linkopts = list(settings["//command_line_option:linkopt"])

    if attr.threads == "emscripten":
        # threads enabled
        features.append("use_pthreads")
    elif attr.threads == "off":
        # threads disabled
        features.append("-use_pthreads")

    if attr.exit_runtime == True:
        features.append("exit_runtime")

    if attr.backend == "llvm":
        features.append("llvm_backend")
    elif attr.backend == "emscripten":
        features.append("-llvm_backend")

    if attr.simd:
        features.append("wasm_simd")

    platform = "@emsdk//:platform_wasm"
    if attr.standalone:
        platform = "@emsdk//:platform_wasi"
        features.append("wasm_standalone")

    return {
        "//command_line_option:compiler": "emscripten",
        "//command_line_option:cpu": "wasm",
        "//command_line_option:features": features,
        "//command_line_option:dynamic_mode": "off",
        "//command_line_option:linkopt": linkopts,
        "//command_line_option:platforms": [platform],
        # This is hardcoded to an empty cc_library because the malloc library
        # is implicitly added by the emscripten toolchain
        "//command_line_option:custom_malloc": "@emsdk//emscripten_toolchain:malloc",
    }

_wasm_transition = transition(
    implementation = _wasm_transition_impl,
    inputs = [
        "//command_line_option:features",
        "//command_line_option:linkopt",
    ],
    outputs = [
        "//command_line_option:compiler",
        "//command_line_option:cpu",
        "//command_line_option:features",
        "//command_line_option:dynamic_mode",
        "//command_line_option:linkopt",
        "//command_line_option:platforms",
        "//command_line_option:custom_malloc",
    ],
)

_ALLOW_OUTPUT_EXTNAMES = [
    ".js",
    ".wasm",
    ".wasm.map",
    ".data",
    ".js.symbols",
    ".wasm.debug.wasm",
    ".html",
]

_WASM_BINARY_COMMON_ATTRS = {
    "backend": attr.string(
        default = "_default",
        values = ["_default", "emscripten", "llvm"],
    ),
    "cc_target": attr.label(
        cfg = _wasm_transition,
        mandatory = True,
    ),
    "exit_runtime": attr.bool(
        default = False,
    ),
    "threads": attr.string(
        default = "_default",
        values = ["_default", "emscripten", "off"],
    ),
    "simd": attr.bool(
        default = False,
    ),
    "standalone": attr.bool(
        default = False,
    ),
    "_allowlist_function_transition": attr.label(
        default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
    ),
    "_wasm_binary_extractor": attr.label(
        executable = True,
        allow_files = True,
        cfg = "exec",
        default = Label("@emsdk//emscripten_toolchain:wasm_binary"),
    ),
}

def _wasm_cc_binary_impl(ctx):
    args = ctx.actions.args()
    cc_target = ctx.attr.cc_target[0]

    for output in ctx.outputs.outputs:
        valid_extname = False
        for allowed_extname in _ALLOW_OUTPUT_EXTNAMES:
            if output.path.endswith(allowed_extname):
                valid_extname = True
                break
        if not valid_extname:
            fail("Invalid output '{}'. Allowed extnames: {}".format(output.basename, ", ".join(_ALLOW_OUTPUT_EXTNAMES)))

    args.add_all("--archive", ctx.files.cc_target)
    args.add_joined("--outputs", ctx.outputs.outputs, join_with = ",")

    ctx.actions.run(
        inputs = ctx.files.cc_target,
        outputs = ctx.outputs.outputs,
        arguments = [args],
        executable = ctx.executable._wasm_binary_extractor,
    )

    return [
        DefaultInfo(
            files = depset(ctx.outputs.outputs),
            # This is needed since rules like web_test usually have a data
            # dependency on this target.
            data_runfiles = ctx.runfiles(transitive_files = depset(ctx.outputs.outputs)),
        ),
        OutputGroupInfo(_wasm_tar = cc_target.files),
    ]

def _wasm_cc_binary_legacy_impl(ctx):
    cc_target = ctx.attr.cc_target[0]
    outputs = [
        ctx.outputs.loader,
        ctx.outputs.wasm,
        ctx.outputs.map,
        ctx.outputs.data,
        ctx.outputs.symbols,
        ctx.outputs.dwarf,
        ctx.outputs.html,
    ]

    args = ctx.actions.args()
    args.add("--allow_empty_outputs")
    args.add_all("--archive", ctx.files.cc_target)
    args.add_joined("--outputs", outputs, join_with = ",")

    ctx.actions.run(
        inputs = ctx.files.cc_target,
        outputs = outputs,
        arguments = [args],
        executable = ctx.executable._wasm_binary_extractor,
    )

    return [
        DefaultInfo(
            executable = ctx.outputs.wasm,
            files = depset(outputs),
            # This is needed since rules like web_test usually have a data
            # dependency on this target.
            data_runfiles = ctx.runfiles(transitive_files = depset(outputs)),
        ),
        OutputGroupInfo(_wasm_tar = cc_target.files),
    ]

_wasm_cc_binary = rule(
    implementation = _wasm_cc_binary_impl,
    attrs = dict(
        _WASM_BINARY_COMMON_ATTRS,
        outputs = attr.output_list(
            allow_empty = False,
            mandatory = True,
        ),
    ),
)

def _wasm_binary_legacy_outputs(name, cc_target):
    basename = cc_target.name
    basename = basename.split(".")[0]
    outputs = {
        "loader": "{}/{}.js".format(name, basename),
        "wasm": "{}/{}.wasm".format(name, basename),
        "map": "{}/{}.wasm.map".format(name, basename),
        "data": "{}/{}.data".format(name, basename),
        "symbols": "{}/{}.js.symbols".format(name, basename),
        "dwarf": "{}/{}.wasm.debug.wasm".format(name, basename),
        "html": "{}/{}.html".format(name, basename),
    }

    return outputs

_wasm_cc_binary_legacy = rule(
    implementation = _wasm_cc_binary_legacy_impl,
    attrs = _WASM_BINARY_COMMON_ATTRS,
    outputs = _wasm_binary_legacy_outputs,
)

# Wraps a C++ Blaze target, extracting the appropriate files.
#
# This rule will transition to the emscripten toolchain in order
# to build the the cc_target as a WebAssembly binary.
#
# Args:
#   name: The name of the rule.
#   cc_target: The cc_binary or cc_library to extract files from.
def wasm_cc_binary(outputs = None, **kwargs):
    # for backwards compatibility if no outputs are set the deprecated
    # implementation is used.
    if not outputs:
        _wasm_cc_binary_legacy(**kwargs)
    else:
        _wasm_cc_binary(outputs = outputs, **kwargs)