diff third_party/emsdk/bazel/emscripten_toolchain/toolchain.bzl @ 186:8cf4ec5e2191 hg-web

Fixed merge conflict.
author MrJuneJune <me@mrjunejune.com>
date Fri, 23 Jan 2026 22:38:59 -0800
parents 8d17f6e6e290
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/third_party/emsdk/bazel/emscripten_toolchain/toolchain.bzl	Fri Jan 23 22:38:59 2026 -0800
@@ -0,0 +1,1158 @@
+"""This module encapsulates logic to create emscripten_cc_toolchain_config rule."""
+
+load(
+    "@rules_cc//cc:cc_toolchain_config_lib.bzl",
+    "action_config",
+    "env_entry",
+    "env_set",
+    "feature",
+    "feature_set",
+    "flag_group",
+    "tool",
+    "tool_path",
+    "variable_with_value",
+    "with_feature_set",
+    _flag_set = "flag_set",
+)
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+
+def flag_set(flags = None, features = None, not_features = None, **kwargs):
+    """Extension to flag_set which allows for a "simple" form.
+
+    The simple form allows specifying flags as a simple list instead of a flag_group
+    if enable_if or expand_if semantics are not required.
+
+    Similarly, the simple form allows passing features/not_features if they are a simple
+    list of semantically "and" features.
+    (i.e. "asan" and "dbg", rather than "asan" or "dbg")
+
+    Args:
+      flags: list, set of flags
+      features: list, set of features required to be enabled.
+      not_features: list, set of features required to not be enabled.
+      **kwargs: The rest of the args for flag_set.
+
+    Returns:
+      flag_set
+    """
+    if flags:
+        if kwargs.get("flag_groups"):
+            fail("Cannot set flags and flag_groups")
+        else:
+            kwargs["flag_groups"] = [flag_group(flags = flags)]
+
+    if features or not_features:
+        if kwargs.get("with_features"):
+            fail("Cannot set features/not_feature and with_features")
+        kwargs["with_features"] = [with_feature_set(
+            features = features or [],
+            not_features = not_features or [],
+        )]
+    return _flag_set(**kwargs)
+
+CROSSTOOL_DEFAULT_WARNINGS = [
+    "-Wall",
+]
+
+def _impl(ctx):
+    target_cpu = ctx.attr.cpu
+    toolchain_identifier = "emscripten-" + target_cpu
+    target_system_name = target_cpu + "-unknown-emscripten"
+
+    host_system_name = "i686-unknown-linux-gnu"
+
+    target_libc = "musl/js"
+
+    abi_version = "emscripten_syscalls"
+
+    compiler = "emscripten"
+    abi_libc_version = "default"
+
+    cc_target_os = "emscripten"
+
+    emscripten_dir = ctx.attr.emscripten_binaries.label.workspace_root
+
+    nodejs_path = ctx.file.nodejs_bin.path
+
+    builtin_sysroot = emscripten_dir + "/emscripten/cache/sysroot"
+
+    emcc_script = "emcc.%s" % ctx.attr.script_extension
+    emcc_link_script = "emcc_link.%s" % ctx.attr.script_extension
+    emar_script = "emar.%s" % ctx.attr.script_extension
+
+    ################################################################
+    # Tools
+    ################################################################
+    clang_tool = tool(path = emcc_script)
+    clif_match_tool = tool(path = "dummy_clif_matcher")
+    link_tool = tool(path = emcc_link_script)
+    archive_tool = tool(path = emar_script)
+    strip_tool = tool(path = "NOT_USED_STRIP_TOOL")
+
+    #### Legacy tool paths (much of this is redundant with action_configs, but
+    #### these are still used for some things)
+    tool_paths = [
+        tool_path(name = "ar", path = emar_script),
+        tool_path(name = "cpp", path = "/bin/false"),
+        tool_path(name = "gcc", path = emcc_script),
+        tool_path(name = "gcov", path = "/bin/false"),
+        tool_path(name = "ld", path = emcc_link_script),
+        tool_path(name = "nm", path = "NOT_USED"),
+        tool_path(name = "objdump", path = "/bin/false"),
+        tool_path(name = "strip", path = "NOT_USED"),
+    ]
+
+    ################################################################
+    # Action Configs
+    ################################################################
+
+    cpp_compile_action = action_config(
+        action_name = ACTION_NAMES.cpp_compile,
+        tools = [clang_tool],
+    )
+
+    cpp_module_compile_action = action_config(
+        action_name = ACTION_NAMES.cpp_module_compile,
+        tools = [clang_tool],
+    )
+
+    cpp_module_codegen_action = action_config(
+        action_name = ACTION_NAMES.cpp_module_codegen,
+        tools = [clang_tool],
+    )
+
+    clif_match_action = action_config(
+        action_name = ACTION_NAMES.clif_match,
+        tools = [clif_match_tool],
+    )
+
+    cpp_link_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_dynamic_library,
+        tools = [link_tool],
+    )
+
+    strip_action = action_config(
+        action_name = ACTION_NAMES.strip,
+        tools = [strip_tool],
+    )
+
+    preprocess_assemble_action = action_config(
+        action_name = ACTION_NAMES.preprocess_assemble,
+        tools = [clang_tool],
+    )
+
+    cpp_header_parsing_action = action_config(
+        action_name = ACTION_NAMES.cpp_header_parsing,
+        tools = [clang_tool],
+    )
+
+    cpp_link_static_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_static_library,
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        flags = ["rcsD", "%{output_execpath}"],
+                        expand_if_available = "output_execpath",
+                    ),
+                ],
+            ),
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        iterate_over = "libraries_to_link",
+                        flag_groups = [
+                            flag_group(
+                                flags = ["%{libraries_to_link.name}"],
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file",
+                                ),
+                            ),
+                            flag_group(
+                                flags = ["%{libraries_to_link.object_files}"],
+                                iterate_over = "libraries_to_link.object_files",
+                                expand_if_equal = variable_with_value(
+                                    name = "libraries_to_link.type",
+                                    value = "object_file_group",
+                                ),
+                            ),
+                        ],
+                        expand_if_available = "libraries_to_link",
+                    ),
+                ],
+            ),
+            flag_set(
+                flag_groups = [
+                    flag_group(
+                        flags = ["@%{linker_param_file}"],
+                        expand_if_available = "linker_param_file",
+                    ),
+                ],
+            ),
+        ],
+        tools = [archive_tool],
+    )
+
+    c_compile_action = action_config(
+        action_name = ACTION_NAMES.c_compile,
+        tools = [clang_tool],
+    )
+
+    linkstamp_compile_action = action_config(
+        action_name = ACTION_NAMES.linkstamp_compile,
+        tools = [clang_tool],
+    )
+
+    assemble_action = action_config(
+        action_name = ACTION_NAMES.assemble,
+        tools = [clang_tool],
+    )
+
+    cpp_link_executable_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_executable,
+        tools = [link_tool],
+    )
+
+    cpp_link_nodeps_dynamic_library_action = action_config(
+        action_name = ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+        tools = [link_tool],
+    )
+
+    action_configs = [
+        strip_action,
+        c_compile_action,
+        cpp_compile_action,
+        linkstamp_compile_action,
+        assemble_action,
+        preprocess_assemble_action,
+        cpp_header_parsing_action,
+        cpp_module_compile_action,
+        cpp_module_codegen_action,
+        cpp_link_executable_action,
+        cpp_link_dynamic_library_action,
+        cpp_link_nodeps_dynamic_library_action,
+        cpp_link_static_library_action,
+        clif_match_action,
+    ]
+
+    all_compile_actions = [
+        ACTION_NAMES.c_compile,
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.assemble,
+        ACTION_NAMES.preprocess_assemble,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.cpp_module_codegen,
+        ACTION_NAMES.clif_match,
+        ACTION_NAMES.lto_backend,
+    ]
+
+    all_cpp_compile_actions = [
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.cpp_module_codegen,
+        ACTION_NAMES.clif_match,
+    ]
+
+    preprocessor_compile_actions = [
+        ACTION_NAMES.c_compile,
+        ACTION_NAMES.cpp_compile,
+        ACTION_NAMES.linkstamp_compile,
+        ACTION_NAMES.preprocess_assemble,
+        ACTION_NAMES.cpp_header_parsing,
+        ACTION_NAMES.cpp_module_compile,
+        ACTION_NAMES.clif_match,
+    ]
+
+    all_link_actions = [
+        ACTION_NAMES.cpp_link_executable,
+        ACTION_NAMES.cpp_link_dynamic_library,
+        ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+    ]
+
+    ################################################################
+    # Features
+    ################################################################
+
+    features = [
+        # This set of magic "feature"s are important configuration information for blaze.
+        feature(name = "no_legacy_features", enabled = True),
+        feature(
+            name = "has_configured_linker_path",
+            enabled = True,
+        ),
+
+        # Blaze requests this feature by default, but we don't care.
+        feature(name = "dependency_file"),
+
+        # Blaze requests this feature by default, but we don't care.
+        feature(name = "random_seed"),
+
+        # Formerly "needsPic" attribute
+        feature(name = "supports_pic", enabled = False),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported, before setting the "pic" build-variable.
+        feature(name = "pic"),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported before setting preprocessor_defines
+        # (...but why?)
+        feature(name = "preprocessor_defines"),
+
+        # Blaze requests this feature by default.
+        # Blaze also tests if this feature is supported before setting includes. (...but why?)
+        feature(name = "include_paths"),
+
+        # Blaze tests if this feature is enabled in order to create implicit
+        # "nodeps" .so outputs from cc_library rules.
+        feature(name = "supports_dynamic_linker", enabled = False),
+
+        # Blaze requests this feature when linking a cc_binary which is
+        # "dynamic" aka linked against nodeps-dynamic-library cc_library
+        # outputs.
+        feature(name = "dynamic_linking_mode"),
+
+        #### Configuration features
+        feature(
+            name = "crosstool_cpu",
+            enabled = True,
+            implies = ["crosstool_cpu_" + target_cpu],
+        ),
+        feature(
+            name = "crosstool_cpu_asmjs",
+            provides = ["variant:crosstool_cpu"],
+        ),
+        feature(
+            name = "crosstool_cpu_wasm",
+            provides = ["variant:crosstool_cpu"],
+        ),
+
+        # These 3 features will be automatically enabled by blaze in the
+        # corresponding build mode.
+        feature(
+            name = "opt",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+        feature(
+            name = "dbg",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+        feature(
+            name = "fastbuild",
+            provides = ["variant:crosstool_build_mode"],
+        ),
+
+        # Feature to prevent 'command line too long' issues
+        feature(
+            name = "archive_param_file",
+            enabled = True,
+        ),
+        feature(
+            name = "compiler_param_file",
+            enabled = True,
+        ),
+
+        #### User-settable features
+
+        # Set if enabling exceptions.
+        feature(name = "exceptions"),
+
+        # Set if enabling wasm_exceptions.
+        feature(name = "wasm_exceptions"),
+
+        # This feature overrides the default optimization to prefer execution speed
+        # over binary size (like clang -O3).
+        feature(
+            name = "optimized_for_speed",
+            provides = ["variant:crosstool_optimization_mode"],
+        ),
+
+        # This feature overrides the default optimization to prefer binary size over
+        # execution speed (like clang -Oz).
+        feature(
+            name = "optimized_for_size",
+            provides = ["variant:crosstool_optimization_mode"],
+        ),
+
+        # Convenience aliases / alt-spellings.
+        feature(
+            name = "optimize_for_speed",
+            implies = ["optimized_for_speed"],
+        ),
+        feature(
+            name = "optimize_for_size",
+            implies = ["optimized_for_size"],
+        ),
+
+        # This feature allows easier use of profiling tools by preserving mangled
+        # C++ names. This does everything profiling_funcs does and more.
+        feature(name = "profiling"),
+
+        # This feature emits only enough debug info for function names to appear
+        # in profiles.
+        feature(name = "profiling_funcs"),
+
+        # This feature allows source maps to be generated.
+        feature(
+            name = "source_maps",
+            implies = ["full_debug_info"],
+        ),
+        feature(
+            name = "dwarf_debug_info",
+            implies = ["profiling"],
+        ),
+
+        # Turns on full debug info (-g3).
+        feature(name = "full_debug_info"),
+
+        # Enables the use of "Emscripten" Pthread implementation.
+        # https://kripken.github.io/emscripten-site/docs/porting/pthreads.html
+        # https://github.com/kripken/emscripten/wiki/Pthreads-with-WebAssembly
+        feature(name = "use_pthreads"),
+
+        # If enabled, the runtime will exit when main() completes.
+        feature(name = "exit_runtime"),
+
+        # Primarily for toolchain maintainers:
+        feature(name = "emcc_debug"),
+        feature(name = "emcc_debug_link"),
+        feature(
+            name = "llvm_backend",
+            requires = [feature_set(features = ["crosstool_cpu_wasm"])],
+            enabled = True,
+        ),
+
+        # Remove once flag is flipped.
+        # See https://github.com/bazelbuild/bazel/issues/7687
+        feature(
+            name = "do_not_split_linking_cmdline",
+        ),
+
+        # Adds simd support, only available with the llvm backend.
+        feature(
+            name = "wasm_simd",
+            requires = [feature_set(features = ["llvm_backend"])],
+        ),
+        # Adds relaxed-simd support, only available with the llvm backend.
+        feature(
+            name = "wasm_relaxed_simd",
+            requires = [feature_set(features = ["llvm_backend"])],
+        ),
+        feature(
+            name = "precise_long_double_printf",
+            enabled = True,
+        ),
+        feature(
+            name = "wasm_warnings_as_errors",
+            enabled = False,
+        ),
+
+        # ASan and UBSan. See also:
+        # https://emscripten.org/docs/debugging/Sanitizers.html
+        feature(name = "wasm_asan"),
+        feature(name = "wasm_ubsan"),
+        feature(
+            name = "output_format_js",
+            enabled = True,
+        ),
+        feature(
+            name = "wasm_standalone",
+        ),
+    ]
+
+    crosstool_default_flag_sets = [
+        # Compile, Link, and CC_FLAGS make variable
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+                ACTION_NAMES.cpp_link_executable,
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["--sysroot=%{sysroot}"],
+                    expand_if_available = "sysroot",
+                ),
+            ],
+        ),
+        # Compile + Link
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+                ACTION_NAMES.cpp_link_executable,
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            # This forces color diagnostics even on Forge (where we don't have an
+            # attached terminal).
+            flags = [
+                "-fdiagnostics-color",
+            ],
+        ),
+        # C++ compiles (and implicitly link)
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = [
+                "-fno-exceptions",
+            ],
+            not_features = ["exceptions", "wasm_exceptions"],
+        ),
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = [
+                "-fexceptions",
+            ],
+            features = ["exceptions"],
+        ),
+        flag_set(
+            actions = all_cpp_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-fwasm-exceptions",
+            ],
+            features = ["wasm_exceptions"],
+        ),
+        # All compiles (and implicitly link)
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-fno-strict-aliasing",
+                "-funsigned-char",
+                "-no-canonical-prefixes",
+            ],
+        ),
+        # Language Features
+        flag_set(
+            actions = all_cpp_compile_actions,
+            flags = ["-std=gnu++17", "-nostdinc", "-nostdinc++"],
+        ),
+
+        # Emscripten-specific settings:
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-s", "WASM=0"],
+            features = ["crosstool_cpu_asmjs"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-s", "USE_PTHREADS=1"],
+            features = ["use_pthreads"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-s", "EXIT_RUNTIME=1"],
+            features = ["exit_runtime"],
+        ),
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-pthread"],
+            features = ["llvm_backend", "use_pthreads"],
+        ),
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-msimd128"],
+            features = ["wasm_simd"],
+        ),
+        flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flags = ["-msimd128", "-mrelaxed-simd"],
+            features = ["wasm_relaxed_simd"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-s", "PRINTF_LONG_DOUBLE=1"],
+            features = ["precise_long_double_printf"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["--oformat=js"],
+            features = ["output_format_js"],
+        ),
+
+        # Opt
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flags = ["-DNDEBUG"],
+            features = ["opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fomit-frame-pointer"],
+            features = ["opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O2"],
+            features = ["opt"],
+        ),
+
+        # Users can override opt-level with semantic names...
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-Oz"],
+            features = ["optimized_for_size", "opt"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O3"],
+            features = ["optimized_for_speed", "opt"],
+        ),
+
+        # Fastbuild
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fomit-frame-pointer"],
+            features = ["fastbuild"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-O0"],
+            features = ["fastbuild"],
+        ),
+
+        # Dbg
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-fno-omit-frame-pointer"],
+            features = ["dbg"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-g", "-O0"],
+            features = ["dbg"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-g",
+                "-fsanitize=address",
+                "-O1",
+                "-DADDRESS_SANITIZER=1",
+                "-fno-omit-frame-pointer",
+            ],
+            features = ["wasm_asan"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = [
+                "-g3",
+                "-fsanitize=undefined",
+                "-O1",
+                "-DUNDEFINED_BEHAVIOR_SANITIZER=1",
+                "-fno-omit-frame-pointer",
+                "-fno-sanitize=vptr",
+            ],
+            features = ["wasm_ubsan"],
+        ),
+
+        # Profiling provides full debug info and a special --profiling flag
+        # to control name mangling
+        flag_set(
+            actions = all_link_actions,
+            flags = ["--profiling"],
+            features = ["profiling"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["--profiling_funcs"],
+            features = ["profiling_funcs"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-g3"],
+            features = ["full_debug_info"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-gseparate-dwarf"],
+            features = ["dwarf_debug_info"],
+        ),
+        flag_set(
+            actions = all_compile_actions +
+                      all_link_actions,
+            flags = ["-fdebug-compilation-dir=."],
+            features = ["dwarf_debug_info"],
+        ),
+        # Generic warning flag list
+        flag_set(
+            actions = all_compile_actions,
+            flags = CROSSTOOL_DEFAULT_WARNINGS,
+        ),
+
+        # Defines and Includes and Paths and such
+        flag_set(
+            actions = all_compile_actions,
+            flag_groups = [
+                flag_group(flags = ["-fPIC"], expand_if_available = "pic"),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-D%{preprocessor_defines}"],
+                    iterate_over = "preprocessor_defines",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-include", "%{includes}"],
+                    iterate_over = "includes",
+                    expand_if_available = "includes",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = preprocessor_compile_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-iquote", "%{quote_include_paths}"],
+                    iterate_over = "quote_include_paths",
+                ),
+                flag_group(
+                    flags = ["-I%{include_paths}"],
+                    iterate_over = "include_paths",
+                ),
+                flag_group(
+                    flags = ["-isystem", "%{system_include_paths}"],
+                    iterate_over = "system_include_paths",
+                ),
+            ],
+        ),
+
+        ## Linking options (not libs -- those go last)
+
+        # Generic link options
+        flag_set(
+            actions = [
+                ACTION_NAMES.cpp_link_dynamic_library,
+                ACTION_NAMES.cpp_link_nodeps_dynamic_library,
+            ],
+            flags = ["-shared"],
+        ),
+
+        # Linker search paths and objects:
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    iterate_over = "runtime_library_search_directories",
+                    flag_groups = [
+                        flag_group(
+                            flags = [
+                                "-Wl,-rpath,$EXEC_ORIGIN/%{runtime_library_search_directories}",
+                            ],
+                            expand_if_true = "is_cc_test",
+                        ),
+                        flag_group(
+                            flags = [
+                                "-Wl,-rpath,$ORIGIN/%{runtime_library_search_directories}",
+                            ],
+                            expand_if_false = "is_cc_test",
+                        ),
+                    ],
+                    expand_if_available = "runtime_library_search_directories",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-L%{library_search_directories}"],
+                    iterate_over = "library_search_directories",
+                    expand_if_available = "library_search_directories",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    # This is actually a list of object files from the linkstamp steps
+                    flags = ["%{linkstamp_paths}"],
+                    iterate_over = "linkstamp_paths",
+                    expand_if_available = "linkstamp_paths",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["@%{thinlto_param_file}"],
+                    expand_if_available = "libraries_to_link",
+                    expand_if_true = "thinlto_param_file",
+                ),
+                flag_group(
+                    iterate_over = "libraries_to_link",
+                    flag_groups = [
+                        flag_group(
+                            flags = ["-Wl,--start-lib"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-Wl,-whole-archive"],
+                            expand_if_true = "libraries_to_link.is_whole_archive",
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.object_files}"],
+                            iterate_over = "libraries_to_link.object_files",
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "interface_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "static_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-l%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "dynamic_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-l:%{libraries_to_link.name}"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "versioned_dynamic_library",
+                            ),
+                        ),
+                        flag_group(
+                            flags = ["-Wl,-no-whole-archive"],
+                            expand_if_true = "libraries_to_link.is_whole_archive",
+                        ),
+                        flag_group(
+                            flags = ["-Wl,--end-lib"],
+                            expand_if_equal = variable_with_value(
+                                name = "libraries_to_link.type",
+                                value = "object_file_group",
+                            ),
+                        ),
+                    ],
+                    expand_if_available = "libraries_to_link",
+                ),
+            ],
+        ),
+
+        # Configure the header parsing and preprocessing.
+        flag_set(
+            actions = [ACTION_NAMES.cpp_header_parsing],
+            flags = ["-xc++-header", "-fsyntax-only"],
+            features = ["parse_headers"],
+        ),
+
+        # Note: user compile flags should be nearly last -- you probably
+        # don't want to put any more features after this!
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["%{user_compile_flags}"],
+                    iterate_over = "user_compile_flags",
+                    expand_if_available = "user_compile_flags",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["%{user_link_flags}"],
+                    iterate_over = "user_link_flags",
+                    expand_if_available = "user_link_flags",
+                ),
+            ],
+        ),
+        ## Options which need to go late -- after all the user options -- go here.
+        flag_set(
+            # One might hope that these options would only be needed for C++
+            # compiles. But, sadly, users compile ".c" files with custom
+            # copts=["-x", "c++"], and expect that to be able to find C++ stdlib
+            # headers. It might be worth pondering how blaze could support this sort
+            # of use-case better.
+            actions = preprocessor_compile_actions +
+                      [ACTION_NAMES.cc_flags_make_variable],
+            flags = [
+                "-iwithsysroot" + "/include/c++/v1",
+                "-iwithsysroot" + "/include/compat",
+                "-iwithsysroot" + "/include",
+                "-isystem",
+                emscripten_dir + "/lib/clang/22/include",
+            ],
+        ),
+        # Inputs and outputs
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-MD", "-MF", "%{dependency_file}"],
+                    expand_if_available = "dependency_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-c", "%{source_file}"],
+                    expand_if_available = "source_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = [
+                ACTION_NAMES.c_compile,
+                ACTION_NAMES.cpp_compile,
+                ACTION_NAMES.linkstamp_compile,
+                ACTION_NAMES.assemble,
+                ACTION_NAMES.preprocess_assemble,
+                ACTION_NAMES.cpp_header_parsing,
+                ACTION_NAMES.cpp_module_compile,
+                ACTION_NAMES.cpp_module_codegen,
+                ACTION_NAMES.clif_match,
+            ],
+            flag_groups = [
+                flag_group(
+                    flags = ["-S"],
+                    expand_if_available = "output_assembly_file",
+                ),
+                flag_group(
+                    flags = ["-E"],
+                    expand_if_available = "output_preprocess_file",
+                ),
+                flag_group(
+                    flags = ["-o", "%{output_file}"],
+                    expand_if_available = "output_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["-o", "%{output_execpath}"],
+                    expand_if_available = "output_execpath",
+                ),
+            ],
+        ),
+        # And finally, the params file!
+        flag_set(
+            actions = all_link_actions,
+            flag_groups = [
+                flag_group(
+                    flags = ["@%{linker_param_file}"],
+                    expand_if_available = "linker_param_file",
+                ),
+            ],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = [
+                "-Wno-builtin-macro-redefined",
+                # Genrules may not escape quotes enough for these, so
+                # don't put them into $(CC_FLAGS):
+                '-D__DATE__="redacted"',
+                '-D__TIMESTAMP__="redacted"',
+                '-D__TIME__="redacted"',
+            ],
+        ),
+        flag_set(
+            actions = all_compile_actions,
+            flags = ["-Werror"],
+            features = ["wasm_warnings_as_errors"],
+        ),
+        flag_set(
+            actions = all_link_actions,
+            flags = ["-sSTANDALONE_WASM"],
+            features = ["wasm_standalone"],
+        ),
+    ]
+
+    crosstool_default_env_sets = [
+        # Globals
+        env_set(
+            actions = all_compile_actions +
+                      all_link_actions +
+                      [ACTION_NAMES.cpp_link_static_library],
+            env_entries = [
+                env_entry(
+                    key = "EM_BIN_PATH",
+                    value = emscripten_dir,
+                ),
+                env_entry(
+                    key = "EM_CONFIG_PATH",
+                    value = ctx.file.em_config.path,
+                ),
+                env_entry(
+                    key = "NODE_JS_PATH",
+                    value = nodejs_path,
+                ),
+            ],
+        ),
+        # Use llvm backend.  Off by default, enabled via --features=llvm_backend
+        env_set(
+            actions = all_compile_actions +
+                      all_link_actions +
+                      [ACTION_NAMES.cpp_link_static_library],
+            env_entries = [env_entry(key = "EMCC_WASM_BACKEND", value = "1")],
+            with_features = [with_feature_set(features = ["llvm_backend"])],
+        ),
+        # Debug compile and link. Off by default, enabled via --features=emcc_debug
+        env_set(
+            actions = all_compile_actions,
+            env_entries = [env_entry(key = "EMCC_DEBUG", value = "1")],
+            with_features = [with_feature_set(features = ["emcc_debug"])],
+        ),
+
+        # Debug only link step. Off by default, enabled via --features=emcc_debug_link
+        env_set(
+            actions = all_link_actions,
+            env_entries = [env_entry(key = "EMCC_DEBUG", value = "1")],
+            with_features = [
+                with_feature_set(features = ["emcc_debug"]),
+                with_feature_set(features = ["emcc_debug_link"]),
+            ],
+        ),
+    ]
+
+    crosstool_default_flags_feature = feature(
+        name = "crosstool_default_flags",
+        enabled = True,
+        flag_sets = crosstool_default_flag_sets,
+        env_sets = crosstool_default_env_sets,
+    )
+
+    features.append(crosstool_default_flags_feature)
+
+    cxx_builtin_include_directories = [
+        emscripten_dir + "/emscripten/cache/sysroot/include/c++/v1",
+        emscripten_dir + "/emscripten/cache/sysroot/include/compat",
+        emscripten_dir + "/emscripten/cache/sysroot/include",
+        emscripten_dir + "/lib/clang/21/include",
+    ]
+
+    artifact_name_patterns = []
+
+    make_variables = []
+
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        features = features,
+        action_configs = action_configs,
+        artifact_name_patterns = artifact_name_patterns,
+        cxx_builtin_include_directories = cxx_builtin_include_directories,
+        toolchain_identifier = toolchain_identifier,
+        host_system_name = host_system_name,
+        target_system_name = target_system_name,
+        target_cpu = target_cpu,
+        target_libc = target_libc,
+        compiler = compiler,
+        abi_version = abi_version,
+        abi_libc_version = abi_libc_version,
+        tool_paths = tool_paths,
+        make_variables = make_variables,
+        builtin_sysroot = builtin_sysroot,
+        cc_target_os = cc_target_os,
+    )
+
+emscripten_cc_toolchain_config_rule = rule(
+    implementation = _impl,
+    attrs = {
+        "cpu": attr.string(mandatory = True, values = ["asmjs", "wasm"]),
+        "em_config": attr.label(mandatory = True, allow_single_file = True),
+        "emscripten_binaries": attr.label(mandatory = True, cfg = "exec"),
+        "nodejs_bin": attr.label(mandatory = True, allow_single_file = True),
+        "script_extension": attr.string(mandatory = True, values = ["sh", "bat"]),
+    },
+)