"""This module encapsulates logic to create emscripten_cc_toolchain_config rule."""

load(
    "@bazel_tools//tools/cpp: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("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES")

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

    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"),

        # 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 (-g4).
        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"],
        ),
        flag_set(
            actions = all_cpp_compile_actions,
            flags = [
                "-fexceptions",
            ],
            features = ["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 = [
                "-g4",
                "-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 = ["-g4"],
            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/20/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,
                ),
            ],
        ),
        # 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/20/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"),
        "script_extension": attr.string(mandatory = True, values = ["sh", "bat"]),
    },
    provides = [CcToolchainConfigInfo],
)
