From c658ca790ce2593ed8d482389d5a04c3aceaf7b0 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Wed, 20 May 2026 14:15:36 +0200 Subject: [PATCH 01/12] Consume hermetic MinGW --- MODULE.bazel | 16 ++- bazel/rules/windows_resources.bzl | 13 ++- bazel/toolchains/mingw/BUILD.bazel | 53 +-------- bazel/toolchains/mingw/paths.bzl | 4 - bazel/toolchains/mingw/toolchain.bzl | 118 ++++++++------------- bazel/toolchains/mingw/winlibs.BUILD.bazel | 82 ++++++++++++++ 6 files changed, 153 insertions(+), 133 deletions(-) delete mode 100644 bazel/toolchains/mingw/paths.bzl create mode 100644 bazel/toolchains/mingw/winlibs.BUILD.bazel diff --git a/MODULE.bazel b/MODULE.bazel index 3956e1d883c2..f46c1354812b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -282,11 +282,25 @@ llvm.toolchain( llvm_version = "19.1.7", # https://github.com/bazel-contrib/toolchains_llvm/blob/master/toolchain/internal/llvm_distributions.bzl ) +# Hermetic MinGW-w64 toolchain sourced from a pinned WinLibs distribution. +# Variant: GCC 14.2.0 + MinGW-w64 12.0.0, POSIX threads, MSVCRT runtime, +# no LLVM bundle. Bumping requires updating GCC_VERSION in +# //bazel/toolchains/mingw:winlibs.BUILD.bazel in lockstep. +http_archive( + name = "winlibs_mingw64", + build_file = "//bazel/toolchains/mingw:winlibs.BUILD.bazel", + sha256 = "ff475e985a98c5f3785129baf7460db14fee27708bce35f2833db5009507f1b9", + strip_prefix = "mingw64", + urls = [ + "https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-14.2.0-mingw-w64msvcrt-12.0.0-r3.zip", + ], +) + # TODO{agent-build}: Find a way to register platform-specific toolchains dynamically register_toolchains( "@gcc_toolchain_x86_64//:cc_toolchain", "@gcc_toolchain_aarch64//:cc_toolchain", - "//bazel/toolchains/mingw:mingw_cc_toolchain", + "@winlibs_mingw64//:mingw_cc_toolchain", "@llvm_toolchain//:all", # last to avoid taking precedence over GCC toolchains ) diff --git a/bazel/rules/windows_resources.bzl b/bazel/rules/windows_resources.bzl index 744aa23603d6..6d8beb013ad7 100644 --- a/bazel/rules/windows_resources.bzl +++ b/bazel/rules/windows_resources.bzl @@ -13,7 +13,9 @@ load("@rules_cc//cc:action_names.bzl", "C_COMPILE_ACTION_NAME") load("@rules_cc//cc:defs.bzl", "cc_common") load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_ATTRS", "find_cc_toolchain", "use_cc_toolchain") load("//bazel/rules:version_info.bzl", "agent_version_defines") -load("//bazel/toolchains/mingw:paths.bzl", "MINGW_PATH") + +_WINDRES = "@winlibs_mingw64//:windres" +_WINDMC = "@winlibs_mingw64//:windmc" def _cc_env(ctx): """Returns (env, cc_toolchain) from the resolved CC toolchain.""" @@ -48,7 +50,7 @@ def _win_messagetable_impl(ctx): windmc_args.add(src) ctx.actions.run( - executable = MINGW_PATH + "/bin/windmc", + executable = ctx.executable._windmc, arguments = [windmc_args], inputs = [src], outputs = [rc_out, h_out, bin_out], @@ -67,7 +69,7 @@ def _win_messagetable_impl(ctx): windres_args.add("-o", syso_out) ctx.actions.run( - executable = MINGW_PATH + "/bin/windres", + executable = ctx.executable._windres, arguments = [windres_args], env = env, inputs = depset([rc_out, bin_out], transitive = [cc_toolchain.all_files]), @@ -83,6 +85,8 @@ _win_messagetable = rule( doc = "Compiles a .mc message file into a .syso resource and .h header via windmc + windres.", attrs = { "src": attr.label(mandatory = True, allow_single_file = [".mc"]), + "_windmc": attr.label(default = _WINDMC, executable = True, cfg = "exec", allow_single_file = True), + "_windres": attr.label(default = _WINDRES, executable = True, cfg = "exec", allow_single_file = True), } | CC_TOOLCHAIN_ATTRS, toolchains = use_cc_toolchain(), fragments = ["cpp"], @@ -129,7 +133,7 @@ def _win_resource_impl(ctx): windres_args.add("-o", syso_out) ctx.actions.run( - executable = MINGW_PATH + "/bin/windres", + executable = ctx.executable._windres, arguments = [windres_args], env = env, inputs = depset([src] + ctx.files.deps, transitive = [cc_toolchain.all_files]), @@ -147,6 +151,7 @@ _win_resource = rule( "src": attr.label(mandatory = True, allow_single_file = [".rc"]), "deps": attr.label_list(allow_files = True), "defines": attr.string_dict(), + "_windres": attr.label(default = _WINDRES, executable = True, cfg = "exec", allow_single_file = True), } | CC_TOOLCHAIN_ATTRS, toolchains = use_cc_toolchain(), fragments = ["cpp"], diff --git a/bazel/toolchains/mingw/BUILD.bazel b/bazel/toolchains/mingw/BUILD.bazel index 92ea1f55f483..83739850964f 100644 --- a/bazel/toolchains/mingw/BUILD.bazel +++ b/bazel/toolchains/mingw/BUILD.bazel @@ -1,50 +1,3 @@ -load("@rules_cc//cc:defs.bzl", "cc_toolchain") -load("//bazel/toolchains/mingw:paths.bzl", "MINGW_PATH", "MSYS2_PATH") -load("//bazel/toolchains/mingw:toolchain.bzl", "mingw_cc_toolchain_config") - -mingw_cc_toolchain_config( - name = "mingw-cc-toolchain-config", - GCC_VERSION = "14.2.0", - MINGW_PATH = MINGW_PATH, - MSYS2_PATH = MSYS2_PATH, -) - -filegroup( - name = "toolchain_files", -) - -# Define our cc_toolchain -# (https://bazel.build/reference/be/c-cpp#cc_toolchain). -# The cc_toolchain rule is pre-defined by the C++ rule owners. It uses these -# parameters to construct a ToolchainInfo provider, as required by Bazel's -# platform/toolchain APIs. -cc_toolchain( - name = "mingw_cc_toolchain_definition", - all_files = ":toolchain_files", - compiler_files = ":toolchain_files", - dwp_files = ":toolchain_files", - linker_files = ":toolchain_files", - objcopy_files = ":toolchain_files", - strip_files = ":toolchain_files", - toolchain_config = ":mingw-cc-toolchain-config", -) - -# Bazel's platform/toolchain APIs require this wrapper around the actual -# toolchain defined above. It serves two purposes: declare which -# constraint_values it supports (which can be matched to appropriate platforms) -# and tell Bazel what language this toolchain is for. -# -# So when you're building a cc_binary, Bazel has all the info it needs to give -# that cc_binary the right toolchain: it knows cc_binary requires a "C++-type -# toolchain" (this is encoded in the cc_binary rule definition) and needs to -# use a toolchain that matches whatever you set --platforms to at the command -# line. -toolchain( - name = "mingw_cc_toolchain", - target_compatible_with = [ - "@platforms//os:windows", - "@platforms//cpu:x86_64", - ], - toolchain = ":mingw_cc_toolchain_definition", - toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", -) +# MinGW cc_toolchain lives in @winlibs_mingw64//: (see winlibs.BUILD.bazel). +# This file exists only to mark the package; the toolchain config rule is +# loaded from //bazel/toolchains/mingw:toolchain.bzl by the WinLibs repo. diff --git a/bazel/toolchains/mingw/paths.bzl b/bazel/toolchains/mingw/paths.bzl deleted file mode 100644 index a53c3e8c80d2..000000000000 --- a/bazel/toolchains/mingw/paths.bzl +++ /dev/null @@ -1,4 +0,0 @@ -"""MinGW/MSYS2 installation paths shared across toolchain and resource rules.""" - -MSYS2_PATH = "C:/tools/msys64" -MINGW_PATH = MSYS2_PATH + "/mingw64" diff --git a/bazel/toolchains/mingw/toolchain.bzl b/bazel/toolchains/mingw/toolchain.bzl index 7b8a803caea4..6859efbd2614 100644 --- a/bazel/toolchains/mingw/toolchain.bzl +++ b/bazel/toolchains/mingw/toolchain.bzl @@ -1,9 +1,18 @@ +"""Hermetic MinGW-w64 cc_toolchain_config. + +All tool paths are relative to the package containing the cc_toolchain target +(see @winlibs_mingw64//:winlibs.BUILD.bazel), so this rule has no host-path +attrs and works in sandboxed / remote-exec contexts. + +WinLibs gcc.exe locates its sibling tools (as, ld, cc1, ...) via relative path +lookup against argv[0], which is why no PATH env_entry is needed once the full +WinLibs tree is staged as the cc_toolchain's `all_files`. +""" + load( "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", "action_config", "artifact_name_pattern", - "env_entry", - "env_set", "feature", "flag_group", "flag_set", @@ -13,40 +22,27 @@ load( load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES") load("@rules_cc//cc:defs.bzl", "CcToolchainConfigInfo", "cc_common") -def _impl(ctx): - tools = [ - "ar", - "cpp", - "gcc", - "gcov", - "ld", - "nm", - "objdump", - "strip", - ] - - gpp_tool = tool( - path = ctx.attr.MINGW_PATH + "/bin/g++", - ) - - gcc_tool = tool( - path = ctx.attr.MINGW_PATH + "/bin/gcc", - ) - - # For assembly files, GCC can act as the assembler (preprocesses and assembles .S files) - as_tool = tool( - path = ctx.attr.MINGW_PATH + "/bin/as", - ) - - # For creating static libraries (.a files) - ar_tool = tool( - path = ctx.attr.MINGW_PATH + "/bin/ar", - ) +_TOOL_NAMES = [ + "ar", + "cpp", + "gcc", + "gcov", + "ld", + "nm", + "objdump", + "strip", +] - tool_paths = [] +def _impl(ctx): + gpp_tool = tool(path = "bin/g++.exe") + gcc_tool = tool(path = "bin/gcc.exe") + as_tool = tool(path = "bin/as.exe") + ar_tool = tool(path = "bin/ar.exe") - for mingw_tool in tools: - tool_paths.append(tool_path(name = mingw_tool, path = "{}/bin/{}".format(ctx.attr.MINGW_PATH, mingw_tool))) + tool_paths = [ + tool_path(name = name, path = "bin/{}.exe".format(name)) + for name in _TOOL_NAMES + ] default_feature = feature( name = "default_env_feature", @@ -68,25 +64,8 @@ def _impl(ctx): ], ), ], - env_sets = [ - env_set( - actions = [ - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.cpp_link_executable, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_static_library, - ], - env_entries = [ - env_entry("PATH", "{}/usr/bin;{}/bin".format(ctx.attr.MSYS2_PATH, ctx.attr.MINGW_PATH)), - ], - ), - ], ) - # Feature for archiver flags (ar) archiver_flags_feature = feature( name = "archiver_flags", enabled = True, @@ -96,9 +75,8 @@ def _impl(ctx): flag_groups = [ flag_group( flags = ["rcsD", "%{output_execpath}"], - # This is needed to make rules_foreign_cc work - # as it cannot find output_execpath. This way we - # don't expand this flag group if variable is not available. + # Needed for rules_foreign_cc which doesn't expose + # output_execpath; skip the group when absent. expand_if_available = "output_execpath", ), flag_group( @@ -125,37 +103,32 @@ def _impl(ctx): target_libc = "nothing", cc_target_os = "windows", compiler = "mingw-gcc", - abi_version = "gcc-" + ctx.attr.GCC_VERSION, + abi_version = "gcc-" + ctx.attr.gcc_version, abi_libc_version = "nothing", action_configs = [ - # C compilation - use gcc action_config( action_name = ACTION_NAMES.c_compile, enabled = True, tools = [gcc_tool], ), - # C++ compilation - use g++ action_config( action_name = ACTION_NAMES.cpp_compile, enabled = True, tools = [gpp_tool], ), - # Assembly actions - # preprocess_assemble: for .S files (assembly with C preprocessor) + # .S files (assembly with C preprocessor) go through gcc. action_config( action_name = ACTION_NAMES.preprocess_assemble, enabled = True, - tools = [gcc_tool], # GCC can preprocess and assemble .S files + tools = [gcc_tool], ), - # assemble: for .s files (pure assembly, no preprocessing) + # .s files (pure assembly) go straight to as. action_config( action_name = ACTION_NAMES.assemble, enabled = True, - tools = [as_tool], # Use 'as' directly for pure assembly + tools = [as_tool], ), - # Linking actions (shared between C and C++) - # For C projects, gcc is typically used; for C++ projects, g++ is used - # We'll use g++ for linking to handle both cases properly + # g++ for linking covers both C and C++ link cases. action_config( action_name = ACTION_NAMES.cpp_link_executable, enabled = True, @@ -166,7 +139,6 @@ def _impl(ctx): enabled = True, tools = [gpp_tool], ), - # Static library archiving - use ar (archiver) action_config( action_name = ACTION_NAMES.cpp_link_static_library, enabled = True, @@ -175,11 +147,11 @@ def _impl(ctx): ], tool_paths = tool_paths, cxx_builtin_include_directories = [ - ctx.attr.MINGW_PATH + "/include", - ctx.attr.MINGW_PATH + "/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.GCC_VERSION + "/include-fixed", - ctx.attr.MINGW_PATH + "/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.GCC_VERSION + "/include", - ctx.attr.MINGW_PATH + "/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.GCC_VERSION + "/install-tools/include", - ctx.attr.MINGW_PATH + "/x86_64-w64-mingw32/include", + "%package(@winlibs_mingw64//)%/include", + "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include-fixed", + "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include", + "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/install-tools/include", + "%package(@winlibs_mingw64//)%/x86_64-w64-mingw32/include", ], artifact_name_patterns = [ artifact_name_pattern( @@ -204,9 +176,7 @@ def _impl(ctx): mingw_cc_toolchain_config = rule( implementation = _impl, attrs = { - "MSYS2_PATH": attr.string(mandatory = True), - "MINGW_PATH": attr.string(mandatory = True), - "GCC_VERSION": attr.string(mandatory = True), + "gcc_version": attr.string(mandatory = True), }, provides = [CcToolchainConfigInfo], ) diff --git a/bazel/toolchains/mingw/winlibs.BUILD.bazel b/bazel/toolchains/mingw/winlibs.BUILD.bazel new file mode 100644 index 000000000000..c2465670b033 --- /dev/null +++ b/bazel/toolchains/mingw/winlibs.BUILD.bazel @@ -0,0 +1,82 @@ +"""BUILD file template applied to the @winlibs_mingw64 http_archive. + +The WinLibs zip contains a single top-level `mingw64/` directory which is +stripped by `strip_prefix = "mingw64"`, so files land at the repo root: +`bin/gcc.exe`, `include/...`, `lib/gcc/x86_64-w64-mingw32//...`. + +GCC version is hard-coded here because it must match the zip pinned in +MODULE.bazel; bumping one without the other is a build error waiting to +happen, so keep them in lockstep when bumping WinLibs. +""" + +load("@//bazel/toolchains/mingw:toolchain.bzl", "mingw_cc_toolchain_config") +load("@rules_cc//cc:defs.bzl", "cc_toolchain") + +package(default_visibility = ["//visibility:public"]) + +GCC_VERSION = "14.2.0" + +filegroup( + name = "all", + srcs = glob( + ["**"], + exclude = [ + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + ), +) + +[ + filegroup( + name = tool_name, + srcs = ["bin/" + tool_name + ".exe"], + ) + for tool_name in [ + "ar", + "as", + "cpp", + "dlltool", + "g++", + "gcc", + "gcov", + "ld", + "nm", + "objdump", + "strip", + "windmc", + "windres", + ] +] + +filegroup( + name = "make", + srcs = ["bin/mingw32-make.exe"], +) + +mingw_cc_toolchain_config( + name = "mingw-cc-toolchain-config", + gcc_version = GCC_VERSION, +) + +cc_toolchain( + name = "mingw_cc_toolchain_definition", + all_files = ":all", + compiler_files = ":all", + dwp_files = ":all", + linker_files = ":all", + objcopy_files = ":all", + strip_files = ":all", + toolchain_config = ":mingw-cc-toolchain-config", +) + +toolchain( + name = "mingw_cc_toolchain", + target_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], + toolchain = ":mingw_cc_toolchain_definition", + toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", +) From 7ecb59f614a087ac9955dbc409b40f7aabcd230f Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 09:07:27 +0200 Subject: [PATCH 02/12] Manage GCC version automatically --- MODULE.bazel | 17 ++++---- bazel/toolchains/mingw/winlibs.BUILD.bazel | 14 +++---- bazel/toolchains/mingw/winlibs.bzl | 48 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 bazel/toolchains/mingw/winlibs.bzl diff --git a/MODULE.bazel b/MODULE.bazel index f46c1354812b..26bd41535654 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -284,16 +284,17 @@ llvm.toolchain( # Hermetic MinGW-w64 toolchain sourced from a pinned WinLibs distribution. # Variant: GCC 14.2.0 + MinGW-w64 12.0.0, POSIX threads, MSVCRT runtime, -# no LLVM bundle. Bumping requires updating GCC_VERSION in -# //bazel/toolchains/mingw:winlibs.BUILD.bazel in lockstep. -http_archive( +# no LLVM bundle. URL, sha256 and gcc_version must move together when bumping. +winlibs_mingw_repository = use_repo_rule( + "//bazel/toolchains/mingw:winlibs.bzl", + "winlibs_mingw_repository", +) + +winlibs_mingw_repository( name = "winlibs_mingw64", - build_file = "//bazel/toolchains/mingw:winlibs.BUILD.bazel", + gcc_version = "14.2.0", sha256 = "ff475e985a98c5f3785129baf7460db14fee27708bce35f2833db5009507f1b9", - strip_prefix = "mingw64", - urls = [ - "https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-14.2.0-mingw-w64msvcrt-12.0.0-r3.zip", - ], + url = "https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-14.2.0-mingw-w64msvcrt-12.0.0-r3.zip", ) # TODO{agent-build}: Find a way to register platform-specific toolchains dynamically diff --git a/bazel/toolchains/mingw/winlibs.BUILD.bazel b/bazel/toolchains/mingw/winlibs.BUILD.bazel index c2465670b033..2217eb1f7272 100644 --- a/bazel/toolchains/mingw/winlibs.BUILD.bazel +++ b/bazel/toolchains/mingw/winlibs.BUILD.bazel @@ -1,12 +1,10 @@ -"""BUILD file template applied to the @winlibs_mingw64 http_archive. +"""BUILD template instantiated by winlibs_mingw_repository. -The WinLibs zip contains a single top-level `mingw64/` directory which is -stripped by `strip_prefix = "mingw64"`, so files land at the repo root: -`bin/gcc.exe`, `include/...`, `lib/gcc/x86_64-w64-mingw32//...`. +%GCC_VERSION% is substituted at fetch time so the URL pin in MODULE.bazel +stays the single source of truth for which WinLibs zip we use. -GCC version is hard-coded here because it must match the zip pinned in -MODULE.bazel; bumping one without the other is a build error waiting to -happen, so keep them in lockstep when bumping WinLibs. +After strip_prefix the layout is `bin/`, `include/`, `lib/`, +`x86_64-w64-mingw32/` at the repo root. """ load("@//bazel/toolchains/mingw:toolchain.bzl", "mingw_cc_toolchain_config") @@ -14,7 +12,7 @@ load("@rules_cc//cc:defs.bzl", "cc_toolchain") package(default_visibility = ["//visibility:public"]) -GCC_VERSION = "14.2.0" +GCC_VERSION = "%GCC_VERSION%" filegroup( name = "all", diff --git a/bazel/toolchains/mingw/winlibs.bzl b/bazel/toolchains/mingw/winlibs.bzl new file mode 100644 index 000000000000..50bee4a68024 --- /dev/null +++ b/bazel/toolchains/mingw/winlibs.bzl @@ -0,0 +1,48 @@ +"""Repository rule that materializes a hermetic MinGW-w64 toolchain from a +pinned WinLibs distribution. + +Pins URL, SHA256 and GCC version together in MODULE.bazel so bumping the zip +and the include-dir layout it implies stays a single atomic change. +""" + +def _winlibs_mingw_repository_impl(ctx): + ctx.download_and_extract( + url = ctx.attr.url, + sha256 = ctx.attr.sha256, + stripPrefix = ctx.attr.strip_prefix, + ) + ctx.template( + "BUILD.bazel", + ctx.attr._build_file_template, + substitutions = { + "%GCC_VERSION%": ctx.attr.gcc_version, + }, + executable = False, + ) + +winlibs_mingw_repository = repository_rule( + implementation = _winlibs_mingw_repository_impl, + doc = "Downloads a pinned WinLibs MinGW-w64 zip and exposes it as a hermetic cc_toolchain.", + attrs = { + "url": attr.string( + mandatory = True, + doc = "Direct URL to the WinLibs .zip release asset.", + ), + "sha256": attr.string( + mandatory = True, + doc = "SHA256 of the zip, pinned for supply-chain integrity.", + ), + "strip_prefix": attr.string( + default = "mingw64", + doc = "Top-level directory stripped from the zip (WinLibs ships everything under mingw64/).", + ), + "gcc_version": attr.string( + mandatory = True, + doc = "GCC version baked into the WinLibs zip; must match the URL or builtin include dirs will be wrong.", + ), + "_build_file_template": attr.label( + default = "//bazel/toolchains/mingw:winlibs.BUILD.bazel", + allow_single_file = True, + ), + }, +) From 1d06f18062bdeb542261d2fcfb77055121c85221 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 09:29:00 +0200 Subject: [PATCH 03/12] Fix missing label --- bazel/toolchains/mingw/toolchain.bzl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bazel/toolchains/mingw/toolchain.bzl b/bazel/toolchains/mingw/toolchain.bzl index 6859efbd2614..ede60f1c88db 100644 --- a/bazel/toolchains/mingw/toolchain.bzl +++ b/bazel/toolchains/mingw/toolchain.bzl @@ -146,12 +146,17 @@ def _impl(ctx): ), ], tool_paths = tool_paths, + # Paths are relative to the cc_toolchain's package (the root of + # @winlibs_mingw64//:), so rules_cc resolves them via crosstool_top + # and never constructs a Label("@winlibs_mingw64//...") from inside + # @@rules_cc+'s repo_mapping. Using %package(@winlibs_mingw64//)% + # here trips rules_cc 0.2.18 on Bazel 9 with an invalid-Label error. cxx_builtin_include_directories = [ - "%package(@winlibs_mingw64//)%/include", - "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include-fixed", - "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include", - "%package(@winlibs_mingw64//)%/lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/install-tools/include", - "%package(@winlibs_mingw64//)%/x86_64-w64-mingw32/include", + "include", + "lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include-fixed", + "lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/include", + "lib/gcc/x86_64-w64-mingw32/" + ctx.attr.gcc_version + "/install-tools/include", + "x86_64-w64-mingw32/include", ], artifact_name_patterns = [ artifact_name_pattern( From f8d5e573982d2fba4510a7c4db438a35491958fb Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 10:17:12 +0200 Subject: [PATCH 04/12] Introduce hermetic bash for Windows --- .adms/bazel/adms.mirror.cfg | 1 + .bazelrc | 8 ++- MODULE.bazel | 20 +++++++ bazel/toolchains/msys2/BUILD.bazel | 3 + bazel/toolchains/msys2/bash_shim.bat | 43 ++++++++++++++ bazel/toolchains/msys2/msys2.BUILD.bazel | 53 +++++++++++++++++ bazel/toolchains/msys2/msys2.bzl | 72 ++++++++++++++++++++++++ deps/repos.MODULE.bazel | 11 +--- 8 files changed, 199 insertions(+), 12 deletions(-) create mode 100644 bazel/toolchains/msys2/BUILD.bazel create mode 100644 bazel/toolchains/msys2/bash_shim.bat create mode 100644 bazel/toolchains/msys2/msys2.BUILD.bazel create mode 100644 bazel/toolchains/msys2/msys2.bzl diff --git a/.adms/bazel/adms.mirror.cfg b/.adms/bazel/adms.mirror.cfg index ea63b5e49c0e..d12436b44fc7 100644 --- a/.adms/bazel/adms.mirror.cfg +++ b/.adms/bazel/adms.mirror.cfg @@ -33,6 +33,7 @@ allow gnupg.org allow lua.org allow mirrors.edge.kernel.org allow mirrors.kernel.org +allow repo.msys2.org allow sourceware.org allow sqlite.org diff --git a/.bazelrc b/.bazelrc index 55c064271177..664e4a96a6f2 100644 --- a/.bazelrc +++ b/.bazelrc @@ -55,13 +55,17 @@ common:windows --strategy=standalone # Valid values are: [dynamic_worker, standa # rules_python 1.9.0 transitions enable_runfiles to `true` for every py_ target on Windows. Pre-setting it here makes # that transition a no-op, so Bazel deduplicates python_win and avoids 2 concurrent MSBuild racing on shared resources. common:windows --enable_runfiles -common:windows --repo_env=BAZEL_SH=C:/tools/msys64/usr/bin/bash.exe # for https://github.com/bazelbuild/bazel/pull/26927 common:windows --repo_env=PIP_CACHE_DIR # https://pip.pypa.io/en/stable/topics/caching/#default-paths common:windows --repo_env=SYSTEMDRIVE # needed by vswhere to locate the VS installer instance database common:windows --repo_env=SYSTEMROOT # used by COM to load system DLLs, needed by vswhere common:windows --repo_env=USERPROFILE # used by MSYS2 bash to emulate HOME, needed by git to fetch repositories common:windows --repo_env=VSTUDIO_ROOT # visual_studio(path_variable) in MODULE.bazel -common:windows --shell_executable=C:/tools/msys64/usr/bin/bash.exe +# Hermetic bash discovery for ctx.actions.run_shell / genrule / rules_foreign_cc. +# Bazel resolves --shell_executable relative to the workspace root at startup, +# and the .bat shim then chains to the bash.exe extracted by @msys2_base +# (see //bazel/toolchains/msys2:msys2.bzl). Needed until bazelbuild/bazel#21089 +# is fixed; sh_toolchain alone covers sh_binary / sh_test but not run_shell. +common:windows --shell_executable=bazel/toolchains/msys2/bash_shim.bat # Force the x86_64-pc-windows-gnu Rust toolchain (compact name for rust_windows_gnu_x86_64) # to take priority over the default MSVC toolchain, since the CI cc_toolchain is MinGW/GCC. diff --git a/MODULE.bazel b/MODULE.bazel index 26bd41535654..a2316ac1c28d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -297,11 +297,31 @@ winlibs_mingw_repository( url = "https://github.com/brechtsanders/winlibs_mingw/releases/download/14.2.0posix-19.1.7-12.0.0-msvcrt-r3/winlibs-x86_64-posix-seh-gcc-14.2.0-mingw-w64msvcrt-12.0.0-r3.zip", ) +# Hermetic MSYS2 sh_toolchain sourced from a pinned MSYS2 base archive. +# Replaces the previous --shell_executable=C:/tools/msys64/usr/bin/bash.exe +# pointer in .bazelrc so Windows shell actions no longer depend on a system +# MSYS2 install. Contains the `base` package only: bash + coreutils + sed + +# gawk + grep + tar + xz + msys-2.0.dll runtime. No make/perl/autotools yet; +# rules_foreign_cc workloads (openssl, krb5, ...) still need those overlaid +# in a follow-up. +msys2_base_repository = use_repo_rule( + "//bazel/toolchains/msys2:msys2.bzl", + "msys2_base_repository", +) + +msys2_base_repository( + name = "msys2_base", + sha256 = "7a3e5c0a728ebefd24dca6da179fe569dc98d6c7d20ce80981d8dc33fb15f7fc", + url = "https://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20260322.tar.zst", + version = "20260322", +) + # TODO{agent-build}: Find a way to register platform-specific toolchains dynamically register_toolchains( "@gcc_toolchain_x86_64//:cc_toolchain", "@gcc_toolchain_aarch64//:cc_toolchain", "@winlibs_mingw64//:mingw_cc_toolchain", + "@msys2_base//:sh_toolchain", "@llvm_toolchain//:all", # last to avoid taking precedence over GCC toolchains ) diff --git a/bazel/toolchains/msys2/BUILD.bazel b/bazel/toolchains/msys2/BUILD.bazel new file mode 100644 index 000000000000..2d8e40f58ce2 --- /dev/null +++ b/bazel/toolchains/msys2/BUILD.bazel @@ -0,0 +1,3 @@ +# Hermetic MSYS2 sh_toolchain lives in @msys2_base//: (see msys2.BUILD.bazel). +# This file exists only to mark the package; the BUILD template is loaded from +# //bazel/toolchains/msys2:msys2.BUILD.bazel by the MSYS2 repository rule. diff --git a/bazel/toolchains/msys2/bash_shim.bat b/bazel/toolchains/msys2/bash_shim.bat new file mode 100644 index 000000000000..bbce5691838e --- /dev/null +++ b/bazel/toolchains/msys2/bash_shim.bat @@ -0,0 +1,43 @@ +@echo off +:: Shell shim invoked via --shell_executable on Windows so that ctx.actions.run_shell +:: and friends (genrule, rules_foreign_cc, ...) reach the hermetic MSYS2 bash +:: provided by //bazel/toolchains/msys2:msys2.bzl instead of whatever bash.exe +:: happens to be on the action's PATH (typically C:\Windows\System32\bash.exe, +:: which is the WSL stub). +:: +:: Bazel resolves --shell_executable relative to the workspace root at startup, +:: then invokes us with the action's execroot (output_base\execroot\_main) as +:: CWD. External repos are only staged under execroot when declared as action +:: inputs; the make_tool, configure_make and similar rules_foreign_cc actions +:: don't declare @msys2_base, so we reach the canonical extraction path under +:: output_base by going two levels up from CWD. +:: +:: Reaching outside execroot is safe under --strategy=standalone (which the +:: workspace .bazelrc pins on Windows) but would fail under sandbox / RBE. +:: +:: Required because bazelbuild/bazel#21089 — ctx.actions.run_shell does not +:: consult sh_toolchain — is still open upstream. Once that lands the shim and +:: this .bazelrc flag can be dropped in favour of the toolchain registration +:: alone. +setlocal +set "MSYS2_ROOT=%cd%\..\..\external\+msys2_base_repository+msys2_base" +set "MINGW_ROOT=%cd%\..\..\external\+winlibs_mingw_repository+winlibs_mingw64" +set "BASH=%MSYS2_ROOT%\usr\bin\bash.exe" +if not exist "%BASH%" ( + echo bash_shim: hermetic bash not found at %BASH% 1>&2 + echo bash_shim: run 'bazelisk fetch @msys2_base//...' to materialise it 1>&2 + exit /b 1 +) +if not exist "%MINGW_ROOT%\bin\gcc.exe" ( + echo bash_shim: hermetic gcc not found at %MINGW_ROOT%\bin\gcc.exe 1>&2 + echo bash_shim: run 'bazelisk fetch @winlibs_mingw64//...' to materialise it 1>&2 + exit /b 1 +) +:: Prepend MSYS2's usr/bin (sed, gawk, grep, tar, ...) and WinLibs' bin/ (gcc, +:: g++, ld, ...) so bash and any tools it execs resolve to the hermetic copies +:: rather than whatever leaked in via the action's PATH. MSYS2 first so its +:: msys-2.0.dll-linked coreutils win over any same-name native Windows tools +:: that may ship in WinLibs. +set "PATH=%MSYS2_ROOT%\usr\bin;%MINGW_ROOT%\bin;%PATH%" +"%BASH%" %* +exit /b %ERRORLEVEL% diff --git a/bazel/toolchains/msys2/msys2.BUILD.bazel b/bazel/toolchains/msys2/msys2.BUILD.bazel new file mode 100644 index 000000000000..7216745da93a --- /dev/null +++ b/bazel/toolchains/msys2/msys2.BUILD.bazel @@ -0,0 +1,53 @@ +"""BUILD template instantiated by msys2_base_repository. + +%VERSION% and %BASH_ABSOLUTE_PATH% are substituted at fetch time so the URL +pin in MODULE.bazel stays the single source of truth for which MSYS2 archive +we use, and so the sh_toolchain points at the actual extracted bash.exe. + +After strip_prefix the layout is `usr/bin/`, `etc/`, `var/`, ... at the repo +root, matching what `msys2-base-x86_64-*.sfx.exe -y -oC:/` would produce. +""" + +load("@rules_shell//shell/toolchains:sh_toolchain.bzl", "sh_toolchain") + +package(default_visibility = ["//visibility:public"]) + +MSYS2_VERSION = "%VERSION%" + +filegroup( + name = "all", + srcs = glob( + ["**"], + exclude = [ + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + # MSYS2 archives contain symlinks (/bin -> /usr/bin etc.). Developer + # Mode on Windows handles them; without it Bazel would fail at fetch + # time, not here. + allow_empty = False, + ), +) + +# Bash plus the MSYS2 runtime DLLs it depends on. Globbing msys-*.dll captures +# future additions (msys-iconv-*.dll, msys-intl-*.dll, ...) without churn. +filegroup( + name = "bash_files", + srcs = ["usr/bin/bash.exe"] + glob(["usr/bin/msys-*.dll"]), +) + +sh_toolchain( + name = "msys2_bash", + path = "%BASH_ABSOLUTE_PATH%", +) + +toolchain( + name = "sh_toolchain", + target_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], + toolchain = ":msys2_bash", + toolchain_type = "@bazel_tools//tools/sh:toolchain_type", +) diff --git a/bazel/toolchains/msys2/msys2.bzl b/bazel/toolchains/msys2/msys2.bzl new file mode 100644 index 000000000000..961e715c4852 --- /dev/null +++ b/bazel/toolchains/msys2/msys2.bzl @@ -0,0 +1,72 @@ +"""Repository rule that materializes a hermetic MSYS2 base environment from a +pinned MSYS2 distribution archive. + +Pins URL, SHA256 and release date together in MODULE.bazel so bumping the +archive stays a single atomic change. + +The MSYS2 `base` package and its transitive dependencies are extracted as-is: +bash, coreutils, sed, gawk, grep, find, tar, gzip, xz, plus the msys-2.0.dll +runtime. No `make`/`perl`/autotools yet — those are separate pacman packages +and will be overlaid in a follow-up if rules_foreign_cc workloads require them. + +Skips the upstream first-run post-install (which generates /etc/passwd, +/etc/group and pacman gpg keyrings) because: + * passwd/group are auto-synthesised by the Cygwin/MSYS2 runtime on demand + since cygwin 1.7.34 (2015); + * gpg keyrings are only consumed by pacman, which we never run hermetically. + +`sh_toolchain.path` is a string attribute documented as "Absolute path to the +shell interpreter". We compute the absolute path to the extracted bash.exe at +fetch time and bake it into the generated BUILD.bazel via template +substitution — the standard pattern for hermetic-but-absolute-path toolchains +on Windows. +""" + +def _msys2_base_repository_impl(ctx): + ctx.download_and_extract( + url = ctx.attr.url, + sha256 = ctx.attr.sha256, + stripPrefix = ctx.attr.strip_prefix, + ) + + # Bazel passes this path verbatim to its action executor; forward slashes + # work for both cmd.exe and bash on Windows, native backslashes do not + # round-trip through Starlark string handling cleanly. + bash_abs_path = str(ctx.path("usr/bin/bash.exe")).replace("\\", "/") + + ctx.template( + "BUILD.bazel", + ctx.attr._build_file_template, + substitutions = { + "%VERSION%": ctx.attr.version, + "%BASH_ABSOLUTE_PATH%": bash_abs_path, + }, + executable = False, + ) + +msys2_base_repository = repository_rule( + implementation = _msys2_base_repository_impl, + doc = "Downloads a pinned MSYS2 base archive and exposes its bash as a hermetic sh_toolchain.", + attrs = { + "url": attr.string( + mandatory = True, + doc = "Direct URL to the msys2-base-x86_64-*.tar.zst release asset.", + ), + "sha256": attr.string( + mandatory = True, + doc = "SHA256 of the archive, pinned for supply-chain integrity.", + ), + "strip_prefix": attr.string( + default = "msys64", + doc = "Top-level directory stripped from the archive (MSYS2 ships everything under msys64/).", + ), + "version": attr.string( + mandatory = True, + doc = "MSYS2 release date (e.g. 20260322); must match the URL.", + ), + "_build_file_template": attr.label( + default = "//bazel/toolchains/msys2:msys2.BUILD.bazel", + allow_single_file = True, + ), + }, +) diff --git a/deps/repos.MODULE.bazel b/deps/repos.MODULE.bazel index 92955d8b19bb..ae144386c973 100644 --- a/deps/repos.MODULE.bazel +++ b/deps/repos.MODULE.bazel @@ -96,16 +96,7 @@ git_override( remote = "https://github.com/bazel-contrib/rules_foreign_cc", ) -register_toolchains( - "@rules_foreign_cc//toolchains:preinstalled_cmake_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_make_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_ninja_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_meson_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_autoconf_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_automake_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_m4_toolchain", - "@rules_foreign_cc//toolchains:preinstalled_pkgconfig_toolchain", -) + http_archive( name = "openssl", From 639e56f3bbe1b1b08e9afcae02a88fa2f350c063 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 11:00:03 +0200 Subject: [PATCH 05/12] Add configure make overlay for msys2 --- MODULE.bazel | 68 ++++++++++++++++++++++++++++++++ bazel/toolchains/msys2/msys2.bzl | 30 +++++++++++--- deps/repos.MODULE.bazel | 11 +++++- 3 files changed, 103 insertions(+), 6 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index a2316ac1c28d..a897a1e31632 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -314,6 +314,74 @@ msys2_base_repository( sha256 = "7a3e5c0a728ebefd24dca6da179fe569dc98d6c7d20ce80981d8dc33fb15f7fc", url = "https://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20260322.tar.zst", version = "20260322", + # Curated overlay of pacman packages from https://repo.msys2.org/msys/x86_64/. + # Together they satisfy rules_foreign_cc's preinstalled_{make,m4,autoconf, + # automake,pkgconfig}_toolchain lookups (see deps/repos.MODULE.bazel) plus + # the perl interpreter that openssl's Configure script needs, and the + # libiconv import lib that any glib/gettext-style configure scripts probe + # for. Pinned URL+SHA256 for supply-chain integrity; bumping a package is + # a single-line change here. + overlay_packages = { + "make": [ + "https://repo.msys2.org/msys/x86_64/make-4.4.1-2-x86_64.pkg.tar.zst", + "2408af61717dae87b00c855b132769a125c708907fc94a46bb16dae076113e5c", + ], + "m4": [ + "https://repo.msys2.org/msys/x86_64/m4-1.4.21-1-x86_64.pkg.tar.zst", + "b561e8871c7599502d9fae7196ee965b3fed3d7af09154eb8d2f5e4d9e5e5106", + ], + "libtool": [ + "https://repo.msys2.org/msys/x86_64/libtool-2.5.4-4-x86_64.pkg.tar.zst", + "c8e812a9a32dd86c53d6e9bd5ec94977a5fc66d2ce3e691d92196aa06f616775", + ], + # pkgconf ships pkg-config.exe as a co-installed copy of pkgconf.exe + # (drop-in CLI compatible). Preferred over the legacy pkg-config + # package, which is no longer in the MSYS2 repo. + "pkgconf": [ + "https://repo.msys2.org/msys/x86_64/pkgconf-2.5.1-1-x86_64.pkg.tar.zst", + "38efd4928ac0cb06e9bd558baa533fc923c0592115bf9bd44970250f3ae2cb7e", + ], + # MSYS2 splits autoconf/automake into a version-selecting wrapper + # (autoconf, automake, aclocal, ...) and per-series implementations. + # We ship one current series of each — bump these two together. + "autoconf-wrapper": [ + "https://repo.msys2.org/msys/x86_64/autoconf-wrapper-20260320-1-any.pkg.tar.zst", + "84c1f93d4450b3bf1ef567110b51c15ffb3f13670d656cc48fea70a01371b20c", + ], + "autoconf2.72": [ + "https://repo.msys2.org/msys/x86_64/autoconf2.72-2.72-4-any.pkg.tar.zst", + "65b3a37edf8bb4221d38db910ce1acc1ed879c7bcba33faa058066d06d35e7b0", + ], + "automake-wrapper": [ + "https://repo.msys2.org/msys/x86_64/automake-wrapper-20260320-1-any.pkg.tar.zst", + "693817c288c81bb9f94ac92e5b6f914402735b80bb33e326ccddd7c89cc6a719", + ], + "automake1.18": [ + "https://repo.msys2.org/msys/x86_64/automake1.18-1.18.1-1-any.pkg.tar.zst", + "c045d9eddf900dbacae06d9e0e19e250cc32d849def7d06b833253f85e3fc941", + ], + # `-devel` ships iconv.h + libiconv.dll.a needed by glib/gettext/krb5 + # configure scripts; the runtime msys-iconv-2.dll is already in base. + "libiconv-devel": [ + "https://repo.msys2.org/msys/x86_64/libiconv-devel-1.19-1-x86_64.pkg.tar.zst", + "b4836f02199a2ae02d5a836d5a6dc4a174a055da79c30e256635e638b52e2c5d", + ], + # perl is openssl's `Configure` runtime; libcrypt + libxcrypt are + # perl's link-time crypto deps (older + newer ABI both present so we + # don't have to track which one MSYS2's perl build linked against). + "perl": [ + "https://repo.msys2.org/msys/x86_64/perl-5.42.2-1-x86_64.pkg.tar.zst", + "7c96ef91b7395c12cfbd967ebf7daa8d3f950d1c0be23bb1dc2a635c95a07c01", + ], + "libcrypt": [ + "https://repo.msys2.org/msys/x86_64/libcrypt-2.1-5-x86_64.pkg.tar.zst", + "3e7822d392eed281cd6019b80078ab201eefa7c83fff2d5bab0491b01088f674", + ], + "libxcrypt": [ + "https://repo.msys2.org/msys/x86_64/libxcrypt-4.5.2-1-x86_64.pkg.tar.zst", + "e63db0aa2b708359e25f61f506e2d2c92c315febf5f88cc9f386e4ec0bdf4fa5", + ], + }, ) # TODO{agent-build}: Find a way to register platform-specific toolchains dynamically diff --git a/bazel/toolchains/msys2/msys2.bzl b/bazel/toolchains/msys2/msys2.bzl index 961e715c4852..27529b175bdf 100644 --- a/bazel/toolchains/msys2/msys2.bzl +++ b/bazel/toolchains/msys2/msys2.bzl @@ -1,13 +1,19 @@ """Repository rule that materializes a hermetic MSYS2 base environment from a -pinned MSYS2 distribution archive. +pinned MSYS2 distribution archive, plus a curated set of pacman packages +overlaid on top. Pins URL, SHA256 and release date together in MODULE.bazel so bumping the archive stays a single atomic change. -The MSYS2 `base` package and its transitive dependencies are extracted as-is: -bash, coreutils, sed, gawk, grep, find, tar, gzip, xz, plus the msys-2.0.dll -runtime. No `make`/`perl`/autotools yet — those are separate pacman packages -and will be overlaid in a follow-up if rules_foreign_cc workloads require them. +Layer 1 — `base` archive (msys2-base-*.tar.zst): + bash, coreutils, sed, gawk, grep, find, tar, gzip, xz, msys-2.0.dll runtime. + +Layer 2 — `overlay_packages` (.pkg.tar.zst from repo.msys2.org/msys/x86_64/): + Each pacman package is a tarball that ships its files under the same + `usr//` layout as the base, so extracting at the + same root merges cleanly. We use this to provide `make`, `perl`, `pkgconf`, + autotools, etc. without falling back to rules_foreign_cc's slow source + bootstrap (which on Windows ends up trying to compile glib for pkg-config). Skips the upstream first-run post-install (which generates /etc/passwd, /etc/group and pacman gpg keyrings) because: @@ -29,6 +35,17 @@ def _msys2_base_repository_impl(ctx): stripPrefix = ctx.attr.strip_prefix, ) + # Starlark dicts preserve insertion order, so the extract sequence is + # deterministic and matches the MODULE.bazel declaration order — useful + # when two packages own the same file (rare for MSYS2; last write wins). + for pkg_name, spec in ctx.attr.overlay_packages.items(): + if len(spec) != 2: + fail("overlay_packages[%r] must be [url, sha256], got %r" % (pkg_name, spec)) + ctx.download_and_extract( + url = spec[0], + sha256 = spec[1], + ) + # Bazel passes this path verbatim to its action executor; forward slashes # work for both cmd.exe and bash on Windows, native backslashes do not # round-trip through Starlark string handling cleanly. @@ -64,6 +81,9 @@ msys2_base_repository = repository_rule( mandatory = True, doc = "MSYS2 release date (e.g. 20260322); must match the URL.", ), + "overlay_packages": attr.string_list_dict( + doc = "Pacman packages overlaid on the base tree, as name -> [url, sha256].", + ), "_build_file_template": attr.label( default = "//bazel/toolchains/msys2:msys2.BUILD.bazel", allow_single_file = True, diff --git a/deps/repos.MODULE.bazel b/deps/repos.MODULE.bazel index ae144386c973..92955d8b19bb 100644 --- a/deps/repos.MODULE.bazel +++ b/deps/repos.MODULE.bazel @@ -96,7 +96,16 @@ git_override( remote = "https://github.com/bazel-contrib/rules_foreign_cc", ) - +register_toolchains( + "@rules_foreign_cc//toolchains:preinstalled_cmake_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_make_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_ninja_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_meson_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_autoconf_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_automake_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_m4_toolchain", + "@rules_foreign_cc//toolchains:preinstalled_pkgconfig_toolchain", +) http_archive( name = "openssl", From 660ea11157577d296c690a97a6c71094f188e3f4 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 12:21:02 +0200 Subject: [PATCH 06/12] Derive path to dlltool from the toolchain --- MODULE.bazel | 35 +++++++------------ MODULE.bazel.lock | 18 +++++----- .../rules_rust-crateinfo-env-leak.patch | 34 ------------------ ...ules_rust-derive-dlltool-from-linker.patch | 35 +++++++++++++++++++ bazel/toolchains/msys2/msys2.BUILD.bazel | 2 ++ 5 files changed, 58 insertions(+), 66 deletions(-) delete mode 100644 bazel/patches/rules_rust-crateinfo-env-leak.patch create mode 100644 bazel/patches/rules_rust-derive-dlltool-from-linker.patch diff --git a/MODULE.bazel b/MODULE.bazel index a897a1e31632..b0104c5f56de 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -93,25 +93,25 @@ git_override( remote = "https://github.com/bazelbuild/rules_pkg.git", ) -# Temporary until rules_rust > 0.69.0 ships with Windows GNU ABI support (PR #3940). -# The CI runner uses MinGW as its cc_toolchain; rules_rust 0.69.0 defaults to -# x86_64-pc-windows-msvc whose linker flags are incompatible with MinGW's g++. +# Pinned past the 0.69.0 release to pick up the Windows-GNU ABI support from +# bazelbuild/rules_rust#3940 (lib name resolution, stdlib link flags, PDB +# emission split by abi). Required because our Windows cc_toolchain is MinGW +# (@winlibs_mingw64), and the 0.69.0 stable release still hard-codes MSVC +# behavior for `target_os == "windows"`. git_override( module_name = "rules_rust", - commit = "82506df3f31240f97194b2e9453022125eaa07f4", # main as of April 21, 2026 + commit = "f31db8b6f124dd5eebdd0ed8de4daf20d4d9685f", # main as of April 27, 2026 (includes #3940 + #3990) patch_strip = 1, patches = [ - # Stops rustc_compile_action from leaking default_shell_env into - # CrateInfo.rustc_env, which otherwise clobbers cc_toolchain link_env - # for rust_test(crate = ...). Upstream issue pending. - "//bazel/patches:rules_rust-crateinfo-env-leak.patch", + # Derives `-Cdlltool=/dlltool.exe` automatically + "//bazel/patches:rules_rust-derive-dlltool-from-linker.patch", ], remote = "https://github.com/bazelbuild/rules_rust.git", ) git_override( module_name = "rules_rust_prost", - commit = "82506df3f31240f97194b2e9453022125eaa07f4", + commit = "f31db8b6f124dd5eebdd0ed8de4daf20d4d9685f", remote = "https://github.com/bazelbuild/rules_rust.git", strip_prefix = "extensions/prost", ) @@ -311,9 +311,6 @@ msys2_base_repository = use_repo_rule( msys2_base_repository( name = "msys2_base", - sha256 = "7a3e5c0a728ebefd24dca6da179fe569dc98d6c7d20ce80981d8dc33fb15f7fc", - url = "https://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20260322.tar.zst", - version = "20260322", # Curated overlay of pacman packages from https://repo.msys2.org/msys/x86_64/. # Together they satisfy rules_foreign_cc's preinstalled_{make,m4,autoconf, # automake,pkgconfig}_toolchain lookups (see deps/repos.MODULE.bazel) plus @@ -382,6 +379,9 @@ msys2_base_repository( "e63db0aa2b708359e25f61f506e2d2c92c315febf5f88cc9f386e4ec0bdf4fa5", ], }, + sha256 = "7a3e5c0a728ebefd24dca6da179fe569dc98d6c7d20ce80981d8dc33fb15f7fc", + url = "https://repo.msys2.org/distrib/x86_64/msys2-base-x86_64-20260322.tar.zst", + version = "20260322", ) # TODO{agent-build}: Find a way to register platform-specific toolchains dynamically @@ -437,21 +437,10 @@ rust = use_extension("@rules_rust//rust:extensions.bzl", "rust") # Register a GNU-ABI Rust toolchain for Windows so that rustc generates # GNU-style linker flags (-lkernel32, -o, etc.) compatible with the MinGW -# cc_toolchain used on the Windows CI runner. Processed before the default -# toolchains, so Bazel's toolchain resolution picks it over the auto-registered -# x86_64-pc-windows-msvc toolchain. rust.repository_set( name = "rust_windows_gnu_x86_64", edition = "2024", exec_triple = "x86_64-pc-windows-gnu", - extra_exec_rustc_flags = [ - "-Clinker=C:/tools/msys64/mingw64/bin/gcc.exe", - "-Cdlltool=C:/tools/msys64/mingw64/bin/dlltool.exe", - ], - extra_rustc_flags = [ - "-Clinker=C:/tools/msys64/mingw64/bin/gcc.exe", - "-Cdlltool=C:/tools/msys64/mingw64/bin/dlltool.exe", - ], target_compatible_with = [ "@platforms//os:windows", "@platforms//cpu:x86_64", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index e8638ca81d53..90af3a3a16d9 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -319,7 +319,7 @@ "moduleExtensions": { "//bazel/toolchains/linux_headers:linux_headers.bzl%linux_headers_extension": { "general": { - "bzlTransitiveDigest": "st+R/ulVqPkT0R/t3yLLAqOk8EAK4MzET2sMpfW8YB4=", + "bzlTransitiveDigest": "CeK/BA0wTzNFQJ9vNuYKCg8wXpdL8fZWAPwRNbkfc9g=", "usagesDigest": "Gkt9dniyJltedRsP9VwZgpDnbzWmq0SeOXle8DjQ96s=", "recordedInputs": [], "generatedRepoSpecs": { @@ -344,7 +344,7 @@ }, "//bazel/toolchains/llvm_bpf:llvm_bpf_configure.bzl%llvm_bpf_extension": { "general": { - "bzlTransitiveDigest": "eOYXODssdQNOf4X8CCImpZYvyFCF/VqCz/43DBjJieA=", + "bzlTransitiveDigest": "L67A58ViDXnouey/grwOU76W/Dr+mOtr9FuG9fDhK0Q=", "usagesDigest": "lBRxCKYy9X03c4jyF/C6hWEAf+24zs/m6M/bS7BVXQU=", "recordedInputs": [], "generatedRepoSpecs": { @@ -370,7 +370,7 @@ }, "//bazel/toolchains/otool:otool_configure.bzl%find_system_otool": { "general": { - "bzlTransitiveDigest": "68iERr/kCkKOV3vSc2q4LzpEgt7vSpOMHMX9EL+4l7g=", + "bzlTransitiveDigest": "1EqlvLMsXc9SmdJwuH1LmOHrKN/1VU/SzoT5jzL/dmQ=", "usagesDigest": "fSN6/kn8e/g7DLnxiuMteG88rd6OVfCnXR7SSOcGUdw=", "recordedInputs": [], "generatedRepoSpecs": { @@ -383,7 +383,7 @@ }, "//bazel/toolchains/patchelf:patchelf_configure.bzl%find_system_patchelf": { "general": { - "bzlTransitiveDigest": "LycsDwhNSFStb20EW25ZzhzQ5zgmwMvzS7gxKN0tTo0=", + "bzlTransitiveDigest": "h/34iJRsppuwRgke9QA2KoAGS7yIA0wXxQxbSaS7UKU=", "usagesDigest": "sbh/OV5qDj0UnO59z1JQIBVtZdGEcUj1f7xDqQlvL+Y=", "recordedInputs": [], "generatedRepoSpecs": { @@ -396,13 +396,13 @@ }, "//bazel/tools:bazelisk_check.bzl%bazelisk_check": { "general": { - "bzlTransitiveDigest": "vIQYQXzy/Q+owB/yz0XcUOwOyn9G0aQIpTxYlBfiCcQ=", + "bzlTransitiveDigest": "wvNx8+i7P/XkPcVAF75VMwpjWhlTDPm78mV4GGE//UM=", "usagesDigest": "CKsn1dtme4oztWA0td45Tt6oCZK6MZnpIocVVZK14sU=", "recordedInputs": [ - "FILE:@@//.bazelversion 077ee5b3a3a7fc2622ceff6466fea0c28f3fb08b24653dcc7f4baab29b9aef36", - "FILE:@@//tools/bazel 8cd8e05bc6563f0ec312c10447f63c0d984f3bee34418ee07490e007f3ebfc3f", + "FILE:@@//.bazelversion a057c631bcb95ae0ff23fec4538e6366e34e028545911a188bdbd93e6f810fbd", + "FILE:@@//tools/bazel f61f1a2c68823c1823c70f0c0588bf346df9f864529161b3944687bed823a8fc", "FILE:@@//tools/bazel.bat 24c80eecf763cf0f70ef5919413247b8e3dbc9eecd41dd63bc21f7c461b8a010", - "FILE:@@//tools/bazelisk.md 184864dc41f9cd398f786ba7c7cd858942d19daa792203e0b18a1a3bfaf372f0" + "FILE:@@//tools/bazelisk.md e125fa021d1d97f3e14d2641b9e2fcac85deeb90783e2480598ed5e321b552bc" ], "generatedRepoSpecs": {} } @@ -774,7 +774,7 @@ }, "@@rules_pkg+//toolchains/rpm:rpmbuild_configure.bzl%find_system_rpmbuild_bzlmod": { "general": { - "bzlTransitiveDigest": "9HVW2/lqcIJQiRN0QXRBEEEu4X5577GLCxN3ifPnx1I=", + "bzlTransitiveDigest": "RXRGyXaIh7OaREqpAZu8mNyIfJgp7mzjnti6rMs+EMg=", "usagesDigest": "/KxCvOG4ymMxLBcldn54pcMSEjOygkYUMlHj/fabSNE=", "recordedInputs": [], "generatedRepoSpecs": { diff --git a/bazel/patches/rules_rust-crateinfo-env-leak.patch b/bazel/patches/rules_rust-crateinfo-env-leak.patch deleted file mode 100644 index 1e8129375763..000000000000 --- a/bazel/patches/rules_rust-crateinfo-env-leak.patch +++ /dev/null @@ -1,34 +0,0 @@ -Do not leak ctx.configuration.default_shell_env into CrateInfo.rustc_env. - -At the tail of rustc_compile_action, CrateInfo is re-created with -`rustc_env = env`. But `env` was built as `dict(default_shell_env) + -env_from_args`, so it carries the host's default_shell_env (PATH, LIB, -INCLUDE, ...) on top of the rustc-specific env. Every downstream consumer -that inherits crate_info.rustc_env (notably rust_test(crate = ...)) then -picks up the host env, and at its own compile time the inherited values -clobber cc_toolchain's link_env -- because construct_arguments applies -link_env first (line ~1183) and crate_info.rustc_env after. - -Persisting only env_from_args keeps CrateInfo honest: Bazel re-injects -default_shell_env at action execution time anyway, so there is no reason -to bake it into provider state. - -Upstream issue: https://github.com/bazelbuild/rules_rust/issues/TBD - -diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl -index f43ddaba67abb9b27f840b7d984c4cafd3147c29..dc30e62545a6778315c30237a3895a63911babb9 100644 ---- a/rust/private/rustc.bzl -+++ b/rust/private/rustc.bzl -@@ -1797,8 +1797,11 @@ def rustc_compile_action( - ) - - if crate_info_dict != None: -+ # Persist only rustc-specific env; `env` above also carries default_shell_env -+ # which must not leak through CrateInfo (it would clobber cc_toolchain -+ # link_env in downstream rust_test(crate = ...)). - crate_info_dict.update({ -- "rustc_env": env, -+ "rustc_env": env_from_args, - }) - crate_info = rust_common.create_crate_info( - deps = depset(deps), diff --git a/bazel/patches/rules_rust-derive-dlltool-from-linker.patch b/bazel/patches/rules_rust-derive-dlltool-from-linker.patch new file mode 100644 index 000000000000..ee04cfec0238 --- /dev/null +++ b/bazel/patches/rules_rust-derive-dlltool-from-linker.patch @@ -0,0 +1,35 @@ +diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl +index c8f6ecc9..c7319735 100644 +--- a/rust/private/rustc.bzl ++++ b/rust/private/rustc.bzl +@@ -1158,6 +1158,30 @@ def construct_arguments( + # If linker_type is not explicitly set, infer from which linker is actually being used + ld_is_direct_driver = False + ++ # rustc on Windows-GNU shells out to dlltool to synthesize import ++ # libraries -- both for #[link(... kind = "raw-dylib")] declarations ++ # in *any* crate (rlib included; windows-sys 0.61+ is the common case) ++ # and for the *.dll.a companion that cdylib crates emit alongside ++ # their .dll. cc_toolchain has no action / tool slot for dlltool ++ # because gcc-as-linker invokes it internally -- but rustc drives the ++ # linker directly, so it needs an explicit pointer. ++ # ++ # Inject the derived path here, *outside* the link-emit gate below, so ++ # rlib compiles also see it. Every MinGW distribution (winlibs, MSYS2 ++ # mingw64, llvm-mingw) ships dlltool.exe next to the linker, so reuse ++ # the cc_toolchain's CPP_LINK_EXECUTABLE_ACTION_NAME tool path and ++ # swap the basename. User-supplied `-Cdlltool=` via extra_rustc_flags ++ # is appended later and still wins because rustc honors the last ++ # `-C=` for a given key. ++ if cc_toolchain and toolchain.target_os == "windows" and toolchain.target_abi != "msvc": ++ dlltool_linker = cc_common.get_tool_for_action( ++ feature_configuration = feature_configuration, ++ action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, ++ ) ++ dlltool_sep = max(dlltool_linker.rfind("/"), dlltool_linker.rfind("\\")) ++ if dlltool_sep >= 0: ++ rustc_flags.add(dlltool_linker[:dlltool_sep] + "/dlltool.exe", format = "--codegen=dlltool=%s") ++ + # Link! + if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or add_flags_for_binary: + # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc diff --git a/bazel/toolchains/msys2/msys2.BUILD.bazel b/bazel/toolchains/msys2/msys2.BUILD.bazel index 7216745da93a..bbd10898cbce 100644 --- a/bazel/toolchains/msys2/msys2.BUILD.bazel +++ b/bazel/toolchains/msys2/msys2.BUILD.bazel @@ -40,6 +40,8 @@ filegroup( sh_toolchain( name = "msys2_bash", path = "%BASH_ABSOLUTE_PATH%", + launcher = "@bazel_tools//tools/launcher", + launcher_maker = "@bazel_tools//tools/launcher:launcher_maker", ) toolchain( From f48112f1d1b332952c1d3013f8cb2d77a569fef6 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 12:38:46 +0200 Subject: [PATCH 07/12] Update MODULE.bazel.lock --- MODULE.bazel.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 90af3a3a16d9..cdb0de7ffe10 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -606,7 +606,7 @@ }, "@@gcc_toolchain+//:internal.bzl%non_bazel_dependencies": { "general": { - "bzlTransitiveDigest": "1Tl8gNDYKNLwM1L435EfTXDlrC2wLwGKvKHwUAGvOnI=", + "bzlTransitiveDigest": "eoU8kd2a+xVyiuTr1TTJARgRCyksUTJkkzOl+CcW6sE=", "usagesDigest": "sWe6gvmFpdWalNUJUWNZF23cgclfU+ijKXCf9JEbTjs=", "recordedInputs": [ "REPO_MAPPING:gcc_toolchain+,bazel_tools bazel_tools", @@ -616,7 +616,7 @@ "openssl": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file_content": "filegroup(\n name = \"srcs\",\n srcs = glob(\n include = [\"**\"],\n exclude = [\"**/* *\"],\n ),\n visibility = [\"//visibility:public\"],\n)\n", + "build_file_content": "filegroup(\r\n name = \"srcs\",\r\n srcs = glob(\r\n include = [\"**\"],\r\n exclude = [\"**/* *\"],\r\n ),\r\n visibility = [\"//visibility:public\"],\r\n)\r\n", "sha256": "40dceb51a4f6a5275bde0e6bf20ef4b91bfc32ed57c0552e2e8e15463372b17a", "strip_prefix": "openssl-1.1.1n", "url": "https://www.openssl.org/source/openssl-1.1.1n.tar.gz" @@ -625,9 +625,9 @@ "lapack": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file_content": "filegroup(\n name = \"srcs\",\n srcs = glob(\n include = [\"**\"],\n exclude = [\"**/* *\"],\n ),\n visibility = [\"//visibility:public\"],\n)\n", + "build_file_content": "filegroup(\r\n name = \"srcs\",\r\n srcs = glob(\r\n include = [\"**\"],\r\n exclude = [\"**/* *\"],\r\n ),\r\n visibility = [\"//visibility:public\"],\r\n)\r\n", "patch_cmds": [ - "cat > make.inc < make.inc < Date: Thu, 21 May 2026 13:15:41 +0200 Subject: [PATCH 08/12] Revert MODULE.bazel.lock --- MODULE.bazel.lock | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index d366c07ecbc6..b3a3ed9613d3 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -320,7 +320,7 @@ "moduleExtensions": { "//bazel/toolchains/linux_headers:linux_headers.bzl%linux_headers_extension": { "general": { - "bzlTransitiveDigest": "CeK/BA0wTzNFQJ9vNuYKCg8wXpdL8fZWAPwRNbkfc9g=", + "bzlTransitiveDigest": "st+R/ulVqPkT0R/t3yLLAqOk8EAK4MzET2sMpfW8YB4=", "usagesDigest": "Gkt9dniyJltedRsP9VwZgpDnbzWmq0SeOXle8DjQ96s=", "recordedInputs": [], "generatedRepoSpecs": { @@ -345,7 +345,7 @@ }, "//bazel/toolchains/llvm_bpf:llvm_bpf_configure.bzl%llvm_bpf_extension": { "general": { - "bzlTransitiveDigest": "L67A58ViDXnouey/grwOU76W/Dr+mOtr9FuG9fDhK0Q=", + "bzlTransitiveDigest": "eOYXODssdQNOf4X8CCImpZYvyFCF/VqCz/43DBjJieA=", "usagesDigest": "lBRxCKYy9X03c4jyF/C6hWEAf+24zs/m6M/bS7BVXQU=", "recordedInputs": [], "generatedRepoSpecs": { @@ -371,7 +371,7 @@ }, "//bazel/toolchains/otool:otool_configure.bzl%find_system_otool": { "general": { - "bzlTransitiveDigest": "1EqlvLMsXc9SmdJwuH1LmOHrKN/1VU/SzoT5jzL/dmQ=", + "bzlTransitiveDigest": "68iERr/kCkKOV3vSc2q4LzpEgt7vSpOMHMX9EL+4l7g=", "usagesDigest": "fSN6/kn8e/g7DLnxiuMteG88rd6OVfCnXR7SSOcGUdw=", "recordedInputs": [], "generatedRepoSpecs": { @@ -384,7 +384,7 @@ }, "//bazel/toolchains/patchelf:patchelf_configure.bzl%find_system_patchelf": { "general": { - "bzlTransitiveDigest": "h/34iJRsppuwRgke9QA2KoAGS7yIA0wXxQxbSaS7UKU=", + "bzlTransitiveDigest": "LycsDwhNSFStb20EW25ZzhzQ5zgmwMvzS7gxKN0tTo0=", "usagesDigest": "sbh/OV5qDj0UnO59z1JQIBVtZdGEcUj1f7xDqQlvL+Y=", "recordedInputs": [], "generatedRepoSpecs": { @@ -397,13 +397,13 @@ }, "//bazel/tools:bazelisk_check.bzl%bazelisk_check": { "general": { - "bzlTransitiveDigest": "wvNx8+i7P/XkPcVAF75VMwpjWhlTDPm78mV4GGE//UM=", + "bzlTransitiveDigest": "vIQYQXzy/Q+owB/yz0XcUOwOyn9G0aQIpTxYlBfiCcQ=", "usagesDigest": "CKsn1dtme4oztWA0td45Tt6oCZK6MZnpIocVVZK14sU=", "recordedInputs": [ - "FILE:@@//.bazelversion a057c631bcb95ae0ff23fec4538e6366e34e028545911a188bdbd93e6f810fbd", - "FILE:@@//tools/bazel f61f1a2c68823c1823c70f0c0588bf346df9f864529161b3944687bed823a8fc", + "FILE:@@//.bazelversion 077ee5b3a3a7fc2622ceff6466fea0c28f3fb08b24653dcc7f4baab29b9aef36", + "FILE:@@//tools/bazel 8cd8e05bc6563f0ec312c10447f63c0d984f3bee34418ee07490e007f3ebfc3f", "FILE:@@//tools/bazel.bat 24c80eecf763cf0f70ef5919413247b8e3dbc9eecd41dd63bc21f7c461b8a010", - "FILE:@@//tools/bazelisk.md e125fa021d1d97f3e14d2641b9e2fcac85deeb90783e2480598ed5e321b552bc" + "FILE:@@//tools/bazelisk.md 184864dc41f9cd398f786ba7c7cd858942d19daa792203e0b18a1a3bfaf372f0" ], "generatedRepoSpecs": {} } @@ -607,7 +607,7 @@ }, "@@gcc_toolchain+//:internal.bzl%non_bazel_dependencies": { "general": { - "bzlTransitiveDigest": "eoU8kd2a+xVyiuTr1TTJARgRCyksUTJkkzOl+CcW6sE=", + "bzlTransitiveDigest": "1Tl8gNDYKNLwM1L435EfTXDlrC2wLwGKvKHwUAGvOnI=", "usagesDigest": "sWe6gvmFpdWalNUJUWNZF23cgclfU+ijKXCf9JEbTjs=", "recordedInputs": [ "REPO_MAPPING:gcc_toolchain+,bazel_tools bazel_tools", @@ -617,7 +617,7 @@ "openssl": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file_content": "filegroup(\r\n name = \"srcs\",\r\n srcs = glob(\r\n include = [\"**\"],\r\n exclude = [\"**/* *\"],\r\n ),\r\n visibility = [\"//visibility:public\"],\r\n)\r\n", + "build_file_content": "filegroup(\n name = \"srcs\",\n srcs = glob(\n include = [\"**\"],\n exclude = [\"**/* *\"],\n ),\n visibility = [\"//visibility:public\"],\n)\n", "sha256": "40dceb51a4f6a5275bde0e6bf20ef4b91bfc32ed57c0552e2e8e15463372b17a", "strip_prefix": "openssl-1.1.1n", "url": "https://www.openssl.org/source/openssl-1.1.1n.tar.gz" @@ -626,9 +626,9 @@ "lapack": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file_content": "filegroup(\r\n name = \"srcs\",\r\n srcs = glob(\r\n include = [\"**\"],\r\n exclude = [\"**/* *\"],\r\n ),\r\n visibility = [\"//visibility:public\"],\r\n)\r\n", + "build_file_content": "filegroup(\n name = \"srcs\",\n srcs = glob(\n include = [\"**\"],\n exclude = [\"**/* *\"],\n ),\n visibility = [\"//visibility:public\"],\n)\n", "patch_cmds": [ - "cat > make.inc < make.inc < Date: Thu, 21 May 2026 13:38:17 +0200 Subject: [PATCH 09/12] Use where instead of dir on Windows --- pkg/util/executable/executable_test.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/pkg/util/executable/executable_test.go b/pkg/util/executable/executable_test.go index 312d40c2b48a..73adeabf9e91 100644 --- a/pkg/util/executable/executable_test.go +++ b/pkg/util/executable/executable_test.go @@ -17,13 +17,15 @@ import ( "github.com/stretchr/testify/require" ) -func TestResolvePath(t *testing.T) { - testProgram := "ls" +func probeProgram() string { if runtime.GOOS == "windows" { - testProgram = "dir" + return "where" } + return "ls" +} - actualPath, err := ResolvePath(testProgram) +func TestResolvePath(t *testing.T) { + actualPath, err := ResolvePath(probeProgram()) require.NoError(t, err) require.NotEmpty(t, actualPath) @@ -34,12 +36,7 @@ func TestResolvePath(t *testing.T) { } func TestResolvePathIsAbsolute(t *testing.T) { - testProgram := "ls" - if runtime.GOOS == "windows" { - testProgram = "dir" - } - - actualPath, err := ResolvePath(testProgram) + actualPath, err := ResolvePath(probeProgram()) require.NoError(t, err) absPath, err := filepath.Abs(actualPath) From 0a4869485d11b9b4d2116b0762c4f105eb1b97b1 Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 14:04:48 +0200 Subject: [PATCH 10/12] Add bash_shim.exe --- .bazelrc | 212 +++++++++++++-------------- .gitattributes | 131 +++++++++-------- bazel/toolchains/msys2/README.md | 29 ++++ bazel/toolchains/msys2/bash_shim.bat | 43 ------ bazel/toolchains/msys2/bash_shim.c | 109 ++++++++++++++ 5 files changed, 310 insertions(+), 214 deletions(-) create mode 100644 bazel/toolchains/msys2/README.md delete mode 100644 bazel/toolchains/msys2/bash_shim.bat create mode 100644 bazel/toolchains/msys2/bash_shim.c diff --git a/.bazelrc b/.bazelrc index 664e4a96a6f2..0b6715680339 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,106 +1,106 @@ -# Do not edit this file without a review from @DataDog/agent-build - -# ADMS config ---------------------------------------------------------------------------------------------------------- -# Ensure access to DataDog internal artifact repositories in CI -import %workspace%/.adms/adms.bazelrc - -# Use --config=adms to use adms for upstream dependency caching. -# TOOD: rename dd-internal in the .adms tree to adms-internal. That -# will be easier to understand. -common:adms --config=dd-internal - -# Startup options ------------------------------------------------------------------------------------------------------ -startup --max_idle_secs=28800 # Keep the server alive for at most 8 hours of inactivity - -# Common options ------------------------------------------------------------------------------------------------------- -common --@rules_python//python/config_settings:bootstrap_impl=script # https://github.com/bazel-contrib/rules_python/blob/main/BZLMOD_SUPPORT.md -common --check_direct_dependencies=error # Escalate any bypassed `bazel_dep` to a resolution failure -common --enable_platform_specific_config # Supported OS identifiers are linux, macos, windows, freebsd, and openbsd -common --experimental_disk_cache_gc_max_size=30G # Cap applied whenever --disk_cache is also set, no-op otherwise. Override in user.bazelrc (50G good, 100G ideal) -common --experimental_proto_descriptor_sets_include_source_info # Preserve comments in generated pb.go -common --experimental_strict_repo_env # Do not leak uncontrolled environment variables into repository rules -common --experimental_ui_max_stdouterr_bytes=1073741819 # why? -common --http_timeout_scaling=3.0 # At least one attempt reaches 30s (3,6,12,24,30,30,30,30) instead of only 10s (1,2,4,8,10,10,10,10) -common --repo_env=DEPLOY_AGENT # Keep in sync with env_vars in MODULE.bazel -common --repo_env=FORCED_PACKAGE_COMPRESSION_LEVEL # Keep in sync with env_vars in MODULE.bazel -common --repo_env=GOCACHE # https://pkg.go.dev/cmd/go#hdr-Build_and_test_caching -common --repo_env=GOMODCACHE # https://wiki.archlinux.org/title/XDG_Base_Directory#Partial -common --repo_env=PACKAGE_VERSION # Keep in sync with env_vars in MODULE.bazel -common --repo_env=SIGN_MAC # Keep in sync with env_vars in MODULE.bazel -common --repo_env=XDG_CACHE_HOME # https://wiki.archlinux.org/title/XDG_Base_Directory -common --skip_incompatible_explicit_targets # Let target_compatible_with skip rather than fail -common --test_output=errors # Print test errors to console output instead of only capturing them in buried test.log -common --verbose_failures - -# Lint config (static code analyzers) ---------------------------------------------------------------------------------- -# Go -TODO(agent-build) -# Python -TODO(agent-build) -# Rust -common:lint --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks -common:lint --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect --output_groups=+rustfmt_checks - -# Linux config --------------------------------------------------------------------------------------------------------- -common:linux --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper -common:linux --strategy=sandboxed - -# macOS config --------------------------------------------------------------------------------------------------------- -common:macos --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper -common:macos --features=-macos_default_link_flags # https://github.com/bazelbuild/bazel/issues/23312 -common:macos --macos_minimum_os=12.0 # Keep in sync with https://docs.datadoghq.com/agent/supported_platforms/?tab=macos -common:macos --strategy=sandboxed - -# Windows config ------------------------------------------------------------------------------------------------------- -common:windows --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper.bat -common:windows --strategy=standalone # Valid values are: [dynamic_worker, standalone, dynamic, remote, worker, local] -# rules_python 1.9.0 transitions enable_runfiles to `true` for every py_ target on Windows. Pre-setting it here makes -# that transition a no-op, so Bazel deduplicates python_win and avoids 2 concurrent MSBuild racing on shared resources. -common:windows --enable_runfiles -common:windows --repo_env=PIP_CACHE_DIR # https://pip.pypa.io/en/stable/topics/caching/#default-paths -common:windows --repo_env=SYSTEMDRIVE # needed by vswhere to locate the VS installer instance database -common:windows --repo_env=SYSTEMROOT # used by COM to load system DLLs, needed by vswhere -common:windows --repo_env=USERPROFILE # used by MSYS2 bash to emulate HOME, needed by git to fetch repositories -common:windows --repo_env=VSTUDIO_ROOT # visual_studio(path_variable) in MODULE.bazel -# Hermetic bash discovery for ctx.actions.run_shell / genrule / rules_foreign_cc. -# Bazel resolves --shell_executable relative to the workspace root at startup, -# and the .bat shim then chains to the bash.exe extracted by @msys2_base -# (see //bazel/toolchains/msys2:msys2.bzl). Needed until bazelbuild/bazel#21089 -# is fixed; sh_toolchain alone covers sh_binary / sh_test but not run_shell. -common:windows --shell_executable=bazel/toolchains/msys2/bash_shim.bat - -# Force the x86_64-pc-windows-gnu Rust toolchain (compact name for rust_windows_gnu_x86_64) -# to take priority over the default MSVC toolchain, since the CI cc_toolchain is MinGW/GCC. -common:windows --extra_toolchains=@rust_toolchains//:rw-2070622084 - -# Remote cache config -------------------------------------------------------------------------------------------------- -# datadog-agent virtually isolates caching instance from its parent (which is remote-caching). -# If entry isn't found in datadog-agent, it will be searched in remote-caching. -common:cache --remote_cache=grpcs://buildbarn-frontend-datadog-agent.us1.ddbuild.io:443 -common:cache --remote_instance_name=ci/datadog-agent -common:cache --remote_local_fallback # best-effort on transient connection errors (no such host) -common:cache --incompatible_remote_local_fallback_for_remote_cache # works only if --remote_local_fallback is also set -common:cache --remote_retries=1 -common:cache --remote_timeout=60 - -# CI config ------------------------------------------------------------------------------------------------------------ -common:ci --config=adms -common:ci --config=cache -common:ci --config=lint -# Opt-in override: only Linux CI runs in k8s and can reach the in-cluster edge cache. -# tools/bazel adds `--config=ci-edge-cache` when `uname -s = Linux`. -common:ci-edge-cache --remote_cache=grpc://buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog:443 -common:ci --noexperimental_convenience_symlinks # not CI-suitable: "These symlinks are only for the user's convenience" - -# Project/Language configs -------------------------------------------------------------------------------------- -import %workspace%/bazel/configs/rust.bazelrc - -# Global release config ------------------------------------------------------------------------------------------------ -# This should aggregate all the release configs for all the languages with enabled optimizations, -# stripping, etc. It does not strictly mean that this build is the one to be released as a product. -# It just selects all the flags we should use on product builds. For example, we need to build with -# optimization and compress packages in CI so that we can run performance and size gates, even -# though we will never release that instance of the package to customers. -common:release --config=rust-release -common:release --//:release - -# Local development options -------------------------------------------------------------------------------------------- -try-import %workspace%/user.bazelrc +# Do not edit this file without a review from @DataDog/agent-build + +# ADMS config ---------------------------------------------------------------------------------------------------------- +# Ensure access to DataDog internal artifact repositories in CI +import %workspace%/.adms/adms.bazelrc + +# Use --config=adms to use adms for upstream dependency caching. +# TOOD: rename dd-internal in the .adms tree to adms-internal. That +# will be easier to understand. +common:adms --config=dd-internal + +# Startup options ------------------------------------------------------------------------------------------------------ +startup --max_idle_secs=28800 # Keep the server alive for at most 8 hours of inactivity + +# Common options ------------------------------------------------------------------------------------------------------- +common --@rules_python//python/config_settings:bootstrap_impl=script # https://github.com/bazel-contrib/rules_python/blob/main/BZLMOD_SUPPORT.md +common --check_direct_dependencies=error # Escalate any bypassed `bazel_dep` to a resolution failure +common --enable_platform_specific_config # Supported OS identifiers are linux, macos, windows, freebsd, and openbsd +common --experimental_disk_cache_gc_max_size=30G # Cap applied whenever --disk_cache is also set, no-op otherwise. Override in user.bazelrc (50G good, 100G ideal) +common --experimental_proto_descriptor_sets_include_source_info # Preserve comments in generated pb.go +common --experimental_strict_repo_env # Do not leak uncontrolled environment variables into repository rules +common --experimental_ui_max_stdouterr_bytes=1073741819 # why? +common --http_timeout_scaling=3.0 # At least one attempt reaches 30s (3,6,12,24,30,30,30,30) instead of only 10s (1,2,4,8,10,10,10,10) +common --repo_env=DEPLOY_AGENT # Keep in sync with env_vars in MODULE.bazel +common --repo_env=FORCED_PACKAGE_COMPRESSION_LEVEL # Keep in sync with env_vars in MODULE.bazel +common --repo_env=GOCACHE # https://pkg.go.dev/cmd/go#hdr-Build_and_test_caching +common --repo_env=GOMODCACHE # https://wiki.archlinux.org/title/XDG_Base_Directory#Partial +common --repo_env=PACKAGE_VERSION # Keep in sync with env_vars in MODULE.bazel +common --repo_env=SIGN_MAC # Keep in sync with env_vars in MODULE.bazel +common --repo_env=XDG_CACHE_HOME # https://wiki.archlinux.org/title/XDG_Base_Directory +common --skip_incompatible_explicit_targets # Let target_compatible_with skip rather than fail +common --test_output=errors # Print test errors to console output instead of only capturing them in buried test.log +common --verbose_failures + +# Lint config (static code analyzers) ---------------------------------------------------------------------------------- +# Go -TODO(agent-build) +# Python -TODO(agent-build) +# Rust +common:lint --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect --output_groups=+clippy_checks +common:lint --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect --output_groups=+rustfmt_checks + +# Linux config --------------------------------------------------------------------------------------------------------- +common:linux --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper +common:linux --strategy=sandboxed + +# macOS config --------------------------------------------------------------------------------------------------------- +common:macos --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper +common:macos --features=-macos_default_link_flags # https://github.com/bazelbuild/bazel/issues/23312 +common:macos --macos_minimum_os=12.0 # Keep in sync with https://docs.datadoghq.com/agent/supported_platforms/?tab=macos +common:macos --strategy=sandboxed + +# Windows config ------------------------------------------------------------------------------------------------------- +common:windows --credential_helper=buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog=%workspace%/bazel/tools/credential-helper.bat +common:windows --strategy=standalone # Valid values are: [dynamic_worker, standalone, dynamic, remote, worker, local] +# rules_python 1.9.0 transitions enable_runfiles to `true` for every py_ target on Windows. Pre-setting it here makes +# that transition a no-op, so Bazel deduplicates python_win and avoids 2 concurrent MSBuild racing on shared resources. +common:windows --enable_runfiles +common:windows --repo_env=PIP_CACHE_DIR # https://pip.pypa.io/en/stable/topics/caching/#default-paths +common:windows --repo_env=SYSTEMDRIVE # needed by vswhere to locate the VS installer instance database +common:windows --repo_env=SYSTEMROOT # used by COM to load system DLLs, needed by vswhere +common:windows --repo_env=USERPROFILE # used by MSYS2 bash to emulate HOME, needed by git to fetch repositories +common:windows --repo_env=VSTUDIO_ROOT # visual_studio(path_variable) in MODULE.bazel +# Hermetic bash discovery for ctx.actions.run_shell / genrule / rules_foreign_cc. +# Routes through a tiny prebuilt .exe (not a .bat) because cmd.exe truncates +# multi-line `-c "..."` arguments at the first newline; the .exe forwards +# GetCommandLineW() verbatim to the bash.exe extracted by @msys2_base. +# Needed until bazelbuild/bazel#21089 wires sh_toolchain into run_shell. +common:windows --shell_executable=bazel/toolchains/msys2/bash_shim.exe + +# Force the x86_64-pc-windows-gnu Rust toolchain (compact name for rust_windows_gnu_x86_64) +# to take priority over the default MSVC toolchain, since the CI cc_toolchain is MinGW/GCC. +common:windows --extra_toolchains=@rust_toolchains//:rw-2070622084 + +# Remote cache config -------------------------------------------------------------------------------------------------- +# datadog-agent virtually isolates caching instance from its parent (which is remote-caching). +# If entry isn't found in datadog-agent, it will be searched in remote-caching. +common:cache --remote_cache=grpcs://buildbarn-frontend-datadog-agent.us1.ddbuild.io:443 +common:cache --remote_instance_name=ci/datadog-agent +common:cache --remote_local_fallback # best-effort on transient connection errors (no such host) +common:cache --incompatible_remote_local_fallback_for_remote_cache # works only if --remote_local_fallback is also set +common:cache --remote_retries=1 +common:cache --remote_timeout=60 + +# CI config ------------------------------------------------------------------------------------------------------------ +common:ci --config=adms +common:ci --config=cache +common:ci --config=lint +# Opt-in override: only Linux CI runs in k8s and can reach the in-cluster edge cache. +# tools/bazel adds `--config=ci-edge-cache` when `uname -s = Linux`. +common:ci-edge-cache --remote_cache=grpc://buildbarn-edge-cache.buildbarn.local-cluster.local-dc.fabric.dog:443 +common:ci --noexperimental_convenience_symlinks # not CI-suitable: "These symlinks are only for the user's convenience" + +# Project/Language configs -------------------------------------------------------------------------------------- +import %workspace%/bazel/configs/rust.bazelrc + +# Global release config ------------------------------------------------------------------------------------------------ +# This should aggregate all the release configs for all the languages with enabled optimizations, +# stripping, etc. It does not strictly mean that this build is the one to be released as a product. +# It just selects all the flags we should use on product builds. For example, we need to build with +# optimization and compress packages in CI so that we can run performance and size gates, even +# though we will never release that instance of the package to customers. +common:release --config=rust-release +common:release --//:release + +# Local development options -------------------------------------------------------------------------------------------- +try-import %workspace%/user.bazelrc diff --git a/.gitattributes b/.gitattributes index d49c2be22c42..e0cf8a0c8728 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,65 +1,66 @@ -# Set batch file line endings to CRLF so that they can be executed on Windows -*.bat text eol=crlf -*.cmd text eol=crlf -*.bin binary - -# Set go source line endings to LF on all platforms so gofmt can be used -*.go text=auto eol=lf -# Same for gopatch (does not handle CRLF line endings) -*.gopatch text eol=lf -# Same for go workspace files (root and testdata) -*go.work text eol=lf -go.sum -diff -merge linguist-generated=true -*.pb.go -diff -merge -*.pb.go linguist-generated=true -*.pb.gw.go -diff -merge -*.pb.gw.go linguist-generated=true -*_easyjson.go -diff -merge -*_easyjson.go linguist-generated=true -pkg/config/schema/*.yaml text eol=lf -pkg/config/schema/compressed/*.zstd binary -diff -merge linguist-generated=true -pkg/security/probe/constantfetch/btfhub/constants.json -diff -merge linguist-generated=true -pkg/security/seclwin/** -diff -merge linguist-generated=true -# CWS doc JSON is generated as LF on Linux; force LF on Windows too -# so Bazel's byte-exact diff_test doesn't trip on core.autocrlf-rewritten CRLF. -docs/cloud-workload-security/** text eol=lf -# Fixtures should have LF line endings because they are checked against OCI packages built on Linux -pkg/fleet/installer/fixtures/** text=auto eol=lf - -# Fix `git diff` when running on the below file formats. -# Our windows build image uses MinGit which tries to use the astextplain diff algorithm (https://git-scm.com/docs/gitattributes#_setting_the_internal_diff_algorithm). -# The astextplain binary is not embedded in the docker image making the git diff command fail when one of the below file formats is in the diff. -# The error is: -# ``` -# error: cannot spawn astexplain: No such files or directory -# fatal: unable to read files diff -# ``` -# We're overriding the MinGit default gitattributes config to avoid using astextplain on the file formats below. -# The MinGit's gitconfig file still have the problematic config though it should not use it anymore: -# ``` -# [diff "astextplain"] -# textconv = astextplain -# ``` - -*.doc diff -*.DOC diff -*.docx diff -*.DOCX diff -*.docm diff -*.DOCM diff -*.dot diff -*.DOT diff -*.dotx diff -*.DOTX diff -*.dotm diff -*.DOTM diff -*.pdf diff -*.PDF diff -*.rtf diff -*.RTF diff -*.ods diff -*.ODS diff -*.odf diff -*.ODF diff -*.odt diff -*.ODT diff +# Set batch file line endings to CRLF so that they can be executed on Windows +*.bat text eol=crlf +*.cmd text eol=crlf +*.bin binary +*.exe binary + +# Set go source line endings to LF on all platforms so gofmt can be used +*.go text=auto eol=lf +# Same for gopatch (does not handle CRLF line endings) +*.gopatch text eol=lf +# Same for go workspace files (root and testdata) +*go.work text eol=lf +go.sum -diff -merge linguist-generated=true +*.pb.go -diff -merge +*.pb.go linguist-generated=true +*.pb.gw.go -diff -merge +*.pb.gw.go linguist-generated=true +*_easyjson.go -diff -merge +*_easyjson.go linguist-generated=true +pkg/config/schema/*.yaml text eol=lf +pkg/config/schema/compressed/*.zstd binary -diff -merge linguist-generated=true +pkg/security/probe/constantfetch/btfhub/constants.json -diff -merge linguist-generated=true +pkg/security/seclwin/** -diff -merge linguist-generated=true +# CWS doc JSON is generated as LF on Linux; force LF on Windows too +# so Bazel's byte-exact diff_test doesn't trip on core.autocrlf-rewritten CRLF. +docs/cloud-workload-security/** text eol=lf +# Fixtures should have LF line endings because they are checked against OCI packages built on Linux +pkg/fleet/installer/fixtures/** text=auto eol=lf + +# Fix `git diff` when running on the below file formats. +# Our windows build image uses MinGit which tries to use the astextplain diff algorithm (https://git-scm.com/docs/gitattributes#_setting_the_internal_diff_algorithm). +# The astextplain binary is not embedded in the docker image making the git diff command fail when one of the below file formats is in the diff. +# The error is: +# ``` +# error: cannot spawn astexplain: No such files or directory +# fatal: unable to read files diff +# ``` +# We're overriding the MinGit default gitattributes config to avoid using astextplain on the file formats below. +# The MinGit's gitconfig file still have the problematic config though it should not use it anymore: +# ``` +# [diff "astextplain"] +# textconv = astextplain +# ``` + +*.doc diff +*.DOC diff +*.docx diff +*.DOCX diff +*.docm diff +*.DOCM diff +*.dot diff +*.DOT diff +*.dotx diff +*.DOTX diff +*.dotm diff +*.DOTM diff +*.pdf diff +*.PDF diff +*.rtf diff +*.RTF diff +*.ods diff +*.ODS diff +*.odf diff +*.ODF diff +*.odt diff +*.ODT diff diff --git a/bazel/toolchains/msys2/README.md b/bazel/toolchains/msys2/README.md new file mode 100644 index 000000000000..3f0c96f0b19a --- /dev/null +++ b/bazel/toolchains/msys2/README.md @@ -0,0 +1,29 @@ +# bash_shim + +`bash_shim.exe` is Bazel's `--shell_executable` on Windows. It exists because +`cmd.exe` re-tokenises arguments when running `.bat` files and truncates +`bash -c "..."` payloads at the first newline, which silently breaks +multi-line `ctx.actions.run_shell` actions (e.g. `GoMockSourceGen`). + +The shim: +1. Reads its raw command line via `GetCommandLineW`, so embedded newlines + survive intact. +2. Prepends the hermetic `@msys2_base` and `@winlibs_mingw64` bin directories + to `PATH`. +3. Spawns `bash.exe` from `@msys2_base` with the original arguments via + `CreateProcessW` and forwards its exit code. + +## Rebuilding + +The `.exe` is committed so first-time builds work without bootstrapping a +toolchain. Rebuild only when `bash_shim.c` changes: + +```powershell +$gcc = "external/+winlibs_mingw_repository+winlibs_mingw64/bin/gcc.exe" +& $gcc -O2 -s -static -municode -o bazel/toolchains/msys2/bash_shim.exe ` + bazel/toolchains/msys2/bash_shim.c +git add bazel/toolchains/msys2/bash_shim.exe +``` + +Resolve `$gcc` relative to your Bazel output base if `external/` is not +symlinked into the workspace; `bazel info output_base` prints the location. diff --git a/bazel/toolchains/msys2/bash_shim.bat b/bazel/toolchains/msys2/bash_shim.bat deleted file mode 100644 index bbce5691838e..000000000000 --- a/bazel/toolchains/msys2/bash_shim.bat +++ /dev/null @@ -1,43 +0,0 @@ -@echo off -:: Shell shim invoked via --shell_executable on Windows so that ctx.actions.run_shell -:: and friends (genrule, rules_foreign_cc, ...) reach the hermetic MSYS2 bash -:: provided by //bazel/toolchains/msys2:msys2.bzl instead of whatever bash.exe -:: happens to be on the action's PATH (typically C:\Windows\System32\bash.exe, -:: which is the WSL stub). -:: -:: Bazel resolves --shell_executable relative to the workspace root at startup, -:: then invokes us with the action's execroot (output_base\execroot\_main) as -:: CWD. External repos are only staged under execroot when declared as action -:: inputs; the make_tool, configure_make and similar rules_foreign_cc actions -:: don't declare @msys2_base, so we reach the canonical extraction path under -:: output_base by going two levels up from CWD. -:: -:: Reaching outside execroot is safe under --strategy=standalone (which the -:: workspace .bazelrc pins on Windows) but would fail under sandbox / RBE. -:: -:: Required because bazelbuild/bazel#21089 — ctx.actions.run_shell does not -:: consult sh_toolchain — is still open upstream. Once that lands the shim and -:: this .bazelrc flag can be dropped in favour of the toolchain registration -:: alone. -setlocal -set "MSYS2_ROOT=%cd%\..\..\external\+msys2_base_repository+msys2_base" -set "MINGW_ROOT=%cd%\..\..\external\+winlibs_mingw_repository+winlibs_mingw64" -set "BASH=%MSYS2_ROOT%\usr\bin\bash.exe" -if not exist "%BASH%" ( - echo bash_shim: hermetic bash not found at %BASH% 1>&2 - echo bash_shim: run 'bazelisk fetch @msys2_base//...' to materialise it 1>&2 - exit /b 1 -) -if not exist "%MINGW_ROOT%\bin\gcc.exe" ( - echo bash_shim: hermetic gcc not found at %MINGW_ROOT%\bin\gcc.exe 1>&2 - echo bash_shim: run 'bazelisk fetch @winlibs_mingw64//...' to materialise it 1>&2 - exit /b 1 -) -:: Prepend MSYS2's usr/bin (sed, gawk, grep, tar, ...) and WinLibs' bin/ (gcc, -:: g++, ld, ...) so bash and any tools it execs resolve to the hermetic copies -:: rather than whatever leaked in via the action's PATH. MSYS2 first so its -:: msys-2.0.dll-linked coreutils win over any same-name native Windows tools -:: that may ship in WinLibs. -set "PATH=%MSYS2_ROOT%\usr\bin;%MINGW_ROOT%\bin;%PATH%" -"%BASH%" %* -exit /b %ERRORLEVEL% diff --git a/bazel/toolchains/msys2/bash_shim.c b/bazel/toolchains/msys2/bash_shim.c new file mode 100644 index 000000000000..9ea2c6f87289 --- /dev/null +++ b/bazel/toolchains/msys2/bash_shim.c @@ -0,0 +1,109 @@ +// bash_shim: thin C launcher routed to hermetic MSYS2 bash, used as Bazel's +// --shell_executable on Windows. A .bat wrapper here truncates multi-line +// -c arguments at the first newline because cmd.exe re-tokenises argv when +// running batch files; this .exe is invoked directly via CreateProcessW and +// keeps the raw command line intact. +// +// Build (run once after editing this file, from the workspace root): +// external/+winlibs_mingw_repository+winlibs_mingw64/bin/gcc.exe \ +// -O2 -s -static -municode \ +// -o bazel/toolchains/msys2/bash_shim.exe \ +// bazel/toolchains/msys2/bash_shim.c +// then `git add bazel/toolchains/msys2/bash_shim.exe`. + +#include +#include +#include +#include +#include + +#define MSYS2_REL L"..\\..\\external\\+msys2_base_repository+msys2_base" +#define MINGW_REL L"..\\..\\external\\+winlibs_mingw_repository+winlibs_mingw64" + +static int file_exists(const wchar_t *p) { + DWORD attr = GetFileAttributesW(p); + return attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY); +} + +static void die_missing(const wchar_t *what, const wchar_t *where, const wchar_t *fetch_hint) { + fwprintf(stderr, L"bash_shim: hermetic %ls not found at %ls\n", what, where); + fwprintf(stderr, L"bash_shim: run 'bazelisk fetch %ls' to materialise it\n", fetch_hint); +} + +// Skip past argv[0] in a raw command line as Windows would tokenise it. +static const wchar_t *skip_argv0(const wchar_t *cmd) { + const wchar_t *p = cmd; + if (*p == L'"') { + for (++p; *p && *p != L'"'; ++p) {} + if (*p == L'"') ++p; + } else { + while (*p && *p != L' ' && *p != L'\t') ++p; + } + while (*p == L' ' || *p == L'\t') ++p; + return p; +} + +int wmain(int argc, wchar_t **argv) { + (void)argc; (void)argv; + + wchar_t cwd[MAX_PATH]; + if (!GetCurrentDirectoryW(MAX_PATH, cwd)) { + fwprintf(stderr, L"bash_shim: GetCurrentDirectory failed: %lu\n", GetLastError()); + return 1; + } + + wchar_t bash_path[MAX_PATH], gcc_path[MAX_PATH], msys_bin[MAX_PATH], mingw_bin[MAX_PATH]; + _snwprintf(bash_path, MAX_PATH, L"%ls\\%ls\\usr\\bin\\bash.exe", cwd, MSYS2_REL); + _snwprintf(gcc_path, MAX_PATH, L"%ls\\%ls\\bin\\gcc.exe", cwd, MINGW_REL); + _snwprintf(msys_bin, MAX_PATH, L"%ls\\%ls\\usr\\bin", cwd, MSYS2_REL); + _snwprintf(mingw_bin, MAX_PATH, L"%ls\\%ls\\bin", cwd, MINGW_REL); + + if (!file_exists(bash_path)) { + die_missing(L"bash", bash_path, L"@msys2_base//..."); + return 1; + } + if (!file_exists(gcc_path)) { + die_missing(L"gcc", gcc_path, L"@winlibs_mingw64//..."); + return 1; + } + + // Prepend hermetic tool dirs to PATH so bash and the commands it execs + // resolve to the pinned binaries rather than whatever leaked in. + DWORD path_len = GetEnvironmentVariableW(L"PATH", NULL, 0); + wchar_t *old_path = (wchar_t *)calloc(path_len + 1, sizeof(wchar_t)); + if (!old_path) { fwprintf(stderr, L"bash_shim: oom\n"); return 1; } + if (path_len) GetEnvironmentVariableW(L"PATH", old_path, path_len); + size_t new_path_len = wcslen(msys_bin) + wcslen(mingw_bin) + wcslen(old_path) + 3; + wchar_t *new_path = (wchar_t *)calloc(new_path_len, sizeof(wchar_t)); + if (!new_path) { fwprintf(stderr, L"bash_shim: oom\n"); return 1; } + _snwprintf(new_path, new_path_len, L"%ls;%ls;%ls", msys_bin, mingw_bin, old_path); + SetEnvironmentVariableW(L"PATH", new_path); + free(old_path); + free(new_path); + + // Forward the raw command line so embedded newlines in -c "..." survive + // intact instead of going through argv[] reparsing. + const wchar_t *rest = skip_argv0(GetCommandLineW()); + size_t bash_len = wcslen(bash_path); + size_t rest_len = wcslen(rest); + size_t full_len = bash_len + rest_len + 4; // quotes + space + NUL + wchar_t *full_cmd = (wchar_t *)calloc(full_len, sizeof(wchar_t)); + if (!full_cmd) { fwprintf(stderr, L"bash_shim: oom\n"); return 1; } + _snwprintf(full_cmd, full_len, L"\"%ls\" %ls", bash_path, rest); + + STARTUPINFOW si = { .cb = sizeof(si) }; + PROCESS_INFORMATION pi = { 0 }; + if (!CreateProcessW(NULL, full_cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { + fwprintf(stderr, L"bash_shim: CreateProcess failed: %lu\n", GetLastError()); + free(full_cmd); + return 1; + } + free(full_cmd); + + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exit_code = 1; + GetExitCodeProcess(pi.hProcess, &exit_code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return (int)exit_code; +} From a1f92fd5ffc9f9251c1073e4ef5150fdc9bf8dfa Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 14:58:57 +0200 Subject: [PATCH 11/12] Add bash_shim.exe --- .bazelrc | 4 - .gitignore | 514 ++++++++++++++------------- bazel/toolchains/msys2/bash_shim.exe | Bin 0 -> 50688 bytes 3 files changed, 258 insertions(+), 260 deletions(-) create mode 100644 bazel/toolchains/msys2/bash_shim.exe diff --git a/.bazelrc b/.bazelrc index 0b6715680339..e007449c2190 100644 --- a/.bazelrc +++ b/.bazelrc @@ -61,10 +61,6 @@ common:windows --repo_env=SYSTEMROOT # used by COM to load system DLLs, needed b common:windows --repo_env=USERPROFILE # used by MSYS2 bash to emulate HOME, needed by git to fetch repositories common:windows --repo_env=VSTUDIO_ROOT # visual_studio(path_variable) in MODULE.bazel # Hermetic bash discovery for ctx.actions.run_shell / genrule / rules_foreign_cc. -# Routes through a tiny prebuilt .exe (not a .bat) because cmd.exe truncates -# multi-line `-c "..."` arguments at the first newline; the .exe forwards -# GetCommandLineW() verbatim to the bash.exe extracted by @msys2_base. -# Needed until bazelbuild/bazel#21089 wires sh_toolchain into run_shell. common:windows --shell_executable=bazel/toolchains/msys2/bash_shim.exe # Force the x86_64-pc-windows-gnu Rust toolchain (compact name for rust_windows_gnu_x86_64) diff --git a/.gitignore b/.gitignore index dce69cf88371..4070904101b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,256 +1,258 @@ -# dogstatsd image temporary binaries -Dockerfiles/dogstatsd/alpine/static/ - -# folders -vendor/ -.vendor-new/ -bin/ -/dev/ -/site/ -__pycache__ -.pytest_cache -venv*/ -target - -**/*.tmp -**/debug.test -# files -.DS_Store -# *.cov could be removed in OCT 2023, replaced by **/coverage.out -*.cov -**/coverage.out -# Python coverage -.coverage -*.pyc -*.swp -*.exe -*.syso -*.log -/main - -# Build System Protocol (Bazel <-> JetBrains IDEs) -/.bazelbsp/ -# In-workpace cache -/.cache/ -# Bazel convenience symlinks to output folders -/bazel-* -# User specific bazelrc file -/user.bazelrc - -# file generated by Cluster Agent image build -Dockerfiles/cluster-agent/nosys-seccomp - -# local development environment control files -.env -.envrc -# I explicitely include this file because it could be ignored globally in custom configurations -!.python-version - -# specific pre-commit hooks -.pre-commit-config-*.yaml - -# Utility tools -devagent -deva - -# go-generated files -datadog.yaml -system-probe.yaml -security-agent.yaml -dogstatsd.yaml -cloudfoundry.yaml -apm-inject.yaml -Dockerfiles/cluster-agent/datadog-cluster.yaml -Dockerfiles/cluster-agent/dist -Dockerfiles/cluster-agent/security-agent-policies -pkg/status/template.go - -# JetBrains IDE project files: GoLand, IntelliJ IDEA, PyCharm, etc. -/*.iml -/.idea/ -/.ijwb/ - -# Ignore debs from the root of the project. -datadog-agent*_amd64.deb - -# Ignore pem created during the tests -*.pem - -pkg/process/config/testdata/secret - -auth_token -/test/e2e/scripts/setup-instance/instance-id.json -/test/e2e/scripts/setup-instance/specification.json -/test/e2e/scripts/setup-instance/spot-instance-request.json -/test/e2e/scripts/setup-instance/spot-request-id.json -/test/e2e/scripts/setup-instance/id_rsa -/test/e2e/scripts/setup-instance/id_rsa.pub -/test/e2e/scripts/setup-instance/ignition.json -/test/e2e/containers/fake_datadog/venv/ -/e2e-output -/comp/logs-library/pipeline/registry.json - -# local copy of device signing cert -platform.pk8 - -# trace agent windows artifacts -pkg/trace/info/git_version.go - -# process agent test artifacts -pkg/process/config/logs - -# sd-agent -/pkg/discovery/module/rust/sd-agent - -# sysprobe artifacts -**/.ninja_log -**/.ninja_deps -*.ninja -compile_commands.json -pkg/ebpf/bytecode/build/**/*.d -pkg/ebpf/kernelbugs/c/*.d -pkg/ebpf/kernelbugs/c/*.o -pkg/ebpf/kernelbugs/c/detect-seccomp-bug -pkg/security/tests/syscall_tester/**/*.d - -# dsd artifacts -cmd/dogstatsd/windows_resources/dogstatsd-msg.rc -cmd/dogstatsd/windows_resources/*.bin -dogstatsd-msg.h - -# omnibus files -omnibus/.tarball-version -omnibus/files/sources -omnibus/resources/agent/msi/cal/packages/ - -# serverless artifact -cmd/serverless/serverless - -#visual studio files -*.aps -tools/windows/install-help/cal/packages/ -./[Dd]ebug/ -[Rr]elease/ - -# ebpf object files -pkg/ebpf/bytecode/build/ -*.bc - -# CGo generated object files -**/_obj/*.o -**/_cgo_*.o - -# windows container build output -build.out/ - -# windows resource files -pkg/util/winutil/messagestrings/*.h -pkg/util/winutil/messagestrings/*.rc -pkg/util/winutil/messagestrings/*.bin - -# dev VM -.vagrant -Vagrantfile -packer.json -*.box -devenv/iso -devenv/output-virtualbox-iso/ -devenv/packer_cache -test_output*.json -module_test_output.json -e2e_test_output.json -e2e_test_output*.json.*.part -junit-out-AgentFlavor.base-*.xml - -# doxygen doc & error log -rtloader/doc -rtloader/doxygen/errors.log - -# integrations-core when checked out for unit tests -integrations-core/ - -# netlink message dump test files -pkg/network/netlink/testdata/message_dump* - -# serverless -.layers -.extension - -# Emacs -*~ -.dir-locals.el - -# Vim -.vimrc - -# etags -/TAGS - -# cscope -cscope.out - -tools/windows/DatadogAgentInstaller/.vs/ -tools/windows/DatadogAgentInstaller/obj/ -tools/windows/DatadogAgentInstaller/packages/ -tools/windows/DatadogAgentInstaller/WixSetup/cabcache/ - -*.wixobj -*.obj -*.g.wxs -*.DotSettings.user - -# e2e test intake generated binary -test/fakeintake/build/* - - -# Allow Single Machine Performance material to ignore excludes -!test/regression/** -!test/workload-checks/** - -kmt-deps/ -test/new-e2e/system-probe/test-json-review/test-json-review -test/new-e2e/system-probe/test-runner/test-runner -test/new-e2e/start-microvms - -# trace-agent generates install.json files during tests -pkg/serverless/trace/**/install.json - -# Files used for eBPF complexity analysis -ebpf-calculator - -# File generated by job creating flake finder pipeline -flake-finder-gitlab-ci.yml - - -# go.work.sum is always changing, we should not need it since we tidy each module individually -go.work.sum - - -#### Cursor related files - -# Personal rules -**/.cursor/rules/personal/ - -# CLAUDE override file for personal use -CLAUDE_PERSONAL.md - -# Claude local settings and overrides -**/.claude/settings.local.json -**/CLAUDE.local.md - -# Claude Code auto-jira work tracking (local only, never commit) -AUTO_JIRA.md -.claude/skills/auto-jira/SKILL.notes.md - -# Claude Code worktrees (local development, not for repo) -.claude/worktrees/ - -# Zed config -.zed - -# Devcontainer -.devcontainer/ -!.devcontainer/datadog/default -.python-nix-home -.venv-nix -flake.lock +# dogstatsd image temporary binaries +Dockerfiles/dogstatsd/alpine/static/ + +# folders +vendor/ +.vendor-new/ +bin/ +/dev/ +/site/ +__pycache__ +.pytest_cache +venv*/ +target + +**/*.tmp +**/debug.test +# files +.DS_Store +# *.cov could be removed in OCT 2023, replaced by **/coverage.out +*.cov +**/coverage.out +# Python coverage +.coverage +*.pyc +*.swp +*.exe +# Checked-in MSYS2 bash shim used by the Windows Bazel toolchain +!bazel/toolchains/msys2/bash_shim.exe +*.syso +*.log +/main + +# Build System Protocol (Bazel <-> JetBrains IDEs) +/.bazelbsp/ +# In-workpace cache +/.cache/ +# Bazel convenience symlinks to output folders +/bazel-* +# User specific bazelrc file +/user.bazelrc + +# file generated by Cluster Agent image build +Dockerfiles/cluster-agent/nosys-seccomp + +# local development environment control files +.env +.envrc +# I explicitely include this file because it could be ignored globally in custom configurations +!.python-version + +# specific pre-commit hooks +.pre-commit-config-*.yaml + +# Utility tools +devagent +deva + +# go-generated files +datadog.yaml +system-probe.yaml +security-agent.yaml +dogstatsd.yaml +cloudfoundry.yaml +apm-inject.yaml +Dockerfiles/cluster-agent/datadog-cluster.yaml +Dockerfiles/cluster-agent/dist +Dockerfiles/cluster-agent/security-agent-policies +pkg/status/template.go + +# JetBrains IDE project files: GoLand, IntelliJ IDEA, PyCharm, etc. +/*.iml +/.idea/ +/.ijwb/ + +# Ignore debs from the root of the project. +datadog-agent*_amd64.deb + +# Ignore pem created during the tests +*.pem + +pkg/process/config/testdata/secret + +auth_token +/test/e2e/scripts/setup-instance/instance-id.json +/test/e2e/scripts/setup-instance/specification.json +/test/e2e/scripts/setup-instance/spot-instance-request.json +/test/e2e/scripts/setup-instance/spot-request-id.json +/test/e2e/scripts/setup-instance/id_rsa +/test/e2e/scripts/setup-instance/id_rsa.pub +/test/e2e/scripts/setup-instance/ignition.json +/test/e2e/containers/fake_datadog/venv/ +/e2e-output +/comp/logs-library/pipeline/registry.json + +# local copy of device signing cert +platform.pk8 + +# trace agent windows artifacts +pkg/trace/info/git_version.go + +# process agent test artifacts +pkg/process/config/logs + +# sd-agent +/pkg/discovery/module/rust/sd-agent + +# sysprobe artifacts +**/.ninja_log +**/.ninja_deps +*.ninja +compile_commands.json +pkg/ebpf/bytecode/build/**/*.d +pkg/ebpf/kernelbugs/c/*.d +pkg/ebpf/kernelbugs/c/*.o +pkg/ebpf/kernelbugs/c/detect-seccomp-bug +pkg/security/tests/syscall_tester/**/*.d + +# dsd artifacts +cmd/dogstatsd/windows_resources/dogstatsd-msg.rc +cmd/dogstatsd/windows_resources/*.bin +dogstatsd-msg.h + +# omnibus files +omnibus/.tarball-version +omnibus/files/sources +omnibus/resources/agent/msi/cal/packages/ + +# serverless artifact +cmd/serverless/serverless + +#visual studio files +*.aps +tools/windows/install-help/cal/packages/ +./[Dd]ebug/ +[Rr]elease/ + +# ebpf object files +pkg/ebpf/bytecode/build/ +*.bc + +# CGo generated object files +**/_obj/*.o +**/_cgo_*.o + +# windows container build output +build.out/ + +# windows resource files +pkg/util/winutil/messagestrings/*.h +pkg/util/winutil/messagestrings/*.rc +pkg/util/winutil/messagestrings/*.bin + +# dev VM +.vagrant +Vagrantfile +packer.json +*.box +devenv/iso +devenv/output-virtualbox-iso/ +devenv/packer_cache +test_output*.json +module_test_output.json +e2e_test_output.json +e2e_test_output*.json.*.part +junit-out-AgentFlavor.base-*.xml + +# doxygen doc & error log +rtloader/doc +rtloader/doxygen/errors.log + +# integrations-core when checked out for unit tests +integrations-core/ + +# netlink message dump test files +pkg/network/netlink/testdata/message_dump* + +# serverless +.layers +.extension + +# Emacs +*~ +.dir-locals.el + +# Vim +.vimrc + +# etags +/TAGS + +# cscope +cscope.out + +tools/windows/DatadogAgentInstaller/.vs/ +tools/windows/DatadogAgentInstaller/obj/ +tools/windows/DatadogAgentInstaller/packages/ +tools/windows/DatadogAgentInstaller/WixSetup/cabcache/ + +*.wixobj +*.obj +*.g.wxs +*.DotSettings.user + +# e2e test intake generated binary +test/fakeintake/build/* + + +# Allow Single Machine Performance material to ignore excludes +!test/regression/** +!test/workload-checks/** + +kmt-deps/ +test/new-e2e/system-probe/test-json-review/test-json-review +test/new-e2e/system-probe/test-runner/test-runner +test/new-e2e/start-microvms + +# trace-agent generates install.json files during tests +pkg/serverless/trace/**/install.json + +# Files used for eBPF complexity analysis +ebpf-calculator + +# File generated by job creating flake finder pipeline +flake-finder-gitlab-ci.yml + + +# go.work.sum is always changing, we should not need it since we tidy each module individually +go.work.sum + + +#### Cursor related files + +# Personal rules +**/.cursor/rules/personal/ + +# CLAUDE override file for personal use +CLAUDE_PERSONAL.md + +# Claude local settings and overrides +**/.claude/settings.local.json +**/CLAUDE.local.md + +# Claude Code auto-jira work tracking (local only, never commit) +AUTO_JIRA.md +.claude/skills/auto-jira/SKILL.notes.md + +# Claude Code worktrees (local development, not for repo) +.claude/worktrees/ + +# Zed config +.zed + +# Devcontainer +.devcontainer/ +!.devcontainer/datadog/default +.python-nix-home +.venv-nix +flake.lock diff --git a/bazel/toolchains/msys2/bash_shim.exe b/bazel/toolchains/msys2/bash_shim.exe new file mode 100644 index 0000000000000000000000000000000000000000..d4b55e90ffc011dd0272ada1753edabf63750f88 GIT binary patch literal 50688 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 Z0)nk~@4kEJ?%3V&yAk>D@BgX<{tuPL(Oduk literal 0 HcmV?d00001 From aa1220547128ea8ab9600a543e66e86b9a49c56f Mon Sep 17 00:00:00 2001 From: Joseph Gette Date: Thu, 21 May 2026 16:04:33 +0200 Subject: [PATCH 12/12] Add diff test for bash_shim.exe --- bazel/toolchains/msys2/BUILD.bazel | 49 +++++++++++++++++++++++++-- bazel/toolchains/msys2/README.md | 21 ++++++++---- bazel/toolchains/msys2/bash_shim.c | 7 +--- bazel/toolchains/msys2/bash_shim.exe | Bin 50688 -> 85297 bytes 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/bazel/toolchains/msys2/BUILD.bazel b/bazel/toolchains/msys2/BUILD.bazel index 2d8e40f58ce2..d577736862e1 100644 --- a/bazel/toolchains/msys2/BUILD.bazel +++ b/bazel/toolchains/msys2/BUILD.bazel @@ -1,3 +1,46 @@ -# Hermetic MSYS2 sh_toolchain lives in @msys2_base//: (see msys2.BUILD.bazel). -# This file exists only to mark the package; the BUILD template is loaded from -# //bazel/toolchains/msys2:msys2.BUILD.bazel by the MSYS2 repository rule. +load("@bazel_lib//lib:write_source_files.bzl", "write_source_file") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + +# Hermetic MSYS2 sh_toolchain lives in @msys2_base//: (see msys2.BUILD.bazel, +# loaded by the MSYS2 repository rule). This package additionally owns the +# bash_shim wired into .bazelrc as --shell_executable on Windows: the +# checked-in bash_shim.exe is the runtime artifact (Bazel reads it at startup); +# the cc_binary below is the source of truth for how to rebuild it, and the +# write_source_file target keeps the two in sync. + +exports_files(["bash_shim.exe"]) + +cc_binary( + name = "bash_shim_bin", + srcs = ["bash_shim.c"], + copts = [ + "-O2", + "-municode", + ], + linkopts = [ + "-municode", + "-static", + "-s", + # Zero out the PE timestamp so two builds of the same source produce + # byte-identical output and the diff_test below stays stable. + "-Wl,--no-insert-timestamp", + ], + target_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], +) + +# `bazel run //bazel/toolchains/msys2:bash_shim` rebuilds bash_shim.exe in +# place. `bazel test //bazel/toolchains/msys2:bash_shim_test` (auto-generated) +# fails CI if the in-tree binary drifts from what bash_shim.c would produce. +write_source_file( + name = "bash_shim", + in_file = ":bash_shim_bin", + out_file = "bash_shim.exe", + target_compatible_with = [ + "@platforms//os:windows", + "@platforms//cpu:x86_64", + ], + visibility = ["//visibility:public"], +) diff --git a/bazel/toolchains/msys2/README.md b/bazel/toolchains/msys2/README.md index 3f0c96f0b19a..4d529a35fa78 100644 --- a/bazel/toolchains/msys2/README.md +++ b/bazel/toolchains/msys2/README.md @@ -15,15 +15,22 @@ The shim: ## Rebuilding -The `.exe` is committed so first-time builds work without bootstrapping a -toolchain. Rebuild only when `bash_shim.c` changes: +`bash_shim.exe` is checked in so Bazel can find it on startup (`.bazelrc` +wires `--shell_executable=bazel/toolchains/msys2/bash_shim.exe` +unconditionally on Windows). The canonical recipe for producing it is the +`cc_binary` in this directory, compiled with the hermetic +`@winlibs_mingw64` toolchain. + +Rebuild after editing `bash_shim.c`: ```powershell -$gcc = "external/+winlibs_mingw_repository+winlibs_mingw64/bin/gcc.exe" -& $gcc -O2 -s -static -municode -o bazel/toolchains/msys2/bash_shim.exe ` - bazel/toolchains/msys2/bash_shim.c +bazel run //bazel/toolchains/msys2:bash_shim git add bazel/toolchains/msys2/bash_shim.exe ``` -Resolve `$gcc` relative to your Bazel output base if `external/` is not -symlinked into the workspace; `bazel info output_base` prints the location. +`bazel test //bazel/toolchains/msys2:bash_shim_test` (auto-generated by +`write_source_file`) verifies the committed `.exe` still matches what +`bash_shim.c` would produce. Wire it into the CI test suite to catch drift. + +Reproducibility relies on `-Wl,--no-insert-timestamp` to zero out the PE +header timestamp; if the diff_test flaps, that's the first thing to check. diff --git a/bazel/toolchains/msys2/bash_shim.c b/bazel/toolchains/msys2/bash_shim.c index 9ea2c6f87289..2ec10e07f52c 100644 --- a/bazel/toolchains/msys2/bash_shim.c +++ b/bazel/toolchains/msys2/bash_shim.c @@ -4,12 +4,7 @@ // running batch files; this .exe is invoked directly via CreateProcessW and // keeps the raw command line intact. // -// Build (run once after editing this file, from the workspace root): -// external/+winlibs_mingw_repository+winlibs_mingw64/bin/gcc.exe \ -// -O2 -s -static -municode \ -// -o bazel/toolchains/msys2/bash_shim.exe \ -// bazel/toolchains/msys2/bash_shim.c -// then `git add bazel/toolchains/msys2/bash_shim.exe`. +// See README.md in this directory for the build/rebuild workflow. #include #include diff --git a/bazel/toolchains/msys2/bash_shim.exe b/bazel/toolchains/msys2/bash_shim.exe index d4b55e90ffc011dd0272ada1753edabf63750f88..0d8bbbb437ccea811807a2694cd327aaac54b1d6 100644 GIT binary patch literal 85297 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 delta 34 lcmdlui?yMLc|r&O<