From 65038b9c8601a2bf2effac20ac5d8da4e056a1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Desgroppes?= Date: Wed, 5 Aug 2026 09:02:34 +0200 Subject: [PATCH] Derive `dlltool` path from the linker for Windows-GNU builds `rustc` on Windows-GNU shells out to `dlltool` to synthesize import libraries: for `#[link(... kind = "raw-dylib")]` declarations in any crate (`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`, since `gcc` used as a linker runs it internally, but `rustc` drives the linker directly and so needs an explicit `--codegen dlltool=` pointer, which previously had to be supplied by hand via `extra_rustc_flags` on every target. Every MinGW toolchain, whether a native Windows distribution (winlibs, MSYS2 mingw64, llvm-mingw) or a Unix cross toolchain, ships `dlltool` right next to the linker with the same naming convention (`.exe`/`.EXE` suffix or none), so derive its path from the `cc_toolchain`'s own linker tool path instead. A user-supplied `-Cdlltool=` via `extra_rustc_flags` still wins, since `rustc` honors the last `-C=` for a given key. This adds no new `constraint_value` nor `config_setting` and does not affect toolchain auto-registration or resolution: `target_abi` is already parsed from the target triple on the resolved `rust_toolchain`, the same field `portable_link_flags` and `symlink_for_ambiguous_lib` key off of a few lines below. Assisted-by: Claude --- rust/private/rustc.bzl | 27 +++++++++++++++ .../windows_lib_name_test.bzl | 34 +++++++++++++++++-- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 1e7616cd5e..ae45d5f22f 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -925,6 +925,22 @@ def _args_map_bin_dir(file): """ return "/".join(file.path.split("/", 3)[:3]) +def dlltool_path_from_linker_path(linker_path): + """Derives the path to `dlltool`, which MinGW ships next to the linker. + + Args: + linker_path (str): Path to the cc_toolchain's linker executable. + + Returns: + str: The derived path, or None if `linker_path` has no directory component. + """ + sep = max(linker_path.rfind("/"), linker_path.rfind("\\")) + if sep < 0: + return None + + suffix = linker_path[-4:].lower() + return "{}dlltool{}".format(linker_path[:sep + 1], suffix if suffix == ".exe" else "") + def construct_arguments( *, ctx, @@ -1291,6 +1307,17 @@ def construct_arguments( # If linker_type is not explicitly set, infer from which linker is actually being used ld_is_direct_driver = False + # gcc runs dlltool internally when acting as the linker, but rustc drives the linker directly so + # it needs dlltool's path: apply outside the link-emit gate below since rlib compiles need it too. + if cc_toolchain and toolchain.target_os == "windows" and toolchain.target_abi != "msvc": + linker_path = cc_common.get_tool_for_action( + feature_configuration = feature_configuration, + action_name = CPP_LINK_EXECUTABLE_ACTION_NAME, + ) + dlltool_path = dlltool_path_from_linker_path(linker_path) + if dlltool_path: + rustc_flags.add(dlltool_path, 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/test/unit/windows_lib_name/windows_lib_name_test.bzl b/test/unit/windows_lib_name/windows_lib_name_test.bzl index 83d272649a..c65c55f9e4 100644 --- a/test/unit/windows_lib_name/windows_lib_name_test.bzl +++ b/test/unit/windows_lib_name/windows_lib_name_test.bzl @@ -1,9 +1,9 @@ -"""Analysistests for Windows-specific library naming and link flags.""" +"""Tests for Windows-specific library naming, link flags, and dlltool derivation.""" -load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts") +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") # buildifier: disable=bzl-visibility -load("//rust/private:rustc.bzl", "portable_link_flags", "symlink_for_ambiguous_lib") +load("//rust/private:rustc.bzl", "dlltool_path_from_linker_path", "portable_link_flags", "symlink_for_ambiguous_lib") # buildifier: disable=bzl-visibility load("//rust/private:utils.bzl", "get_lib_name_default", "get_lib_name_for_windows") @@ -125,6 +125,30 @@ def _symlink_name_windows_msvc_test_impl(ctx): symlink_name_windows_msvc_test = analysistest.make(_symlink_name_windows_msvc_test_impl) +def _dlltool_path_from_linker_path_test_impl(ctx): + env = unittest.begin(ctx) + + # Cross-compiling from a Unix host (e.g. llvm-mingw or a Debian mingw-w64 package): + asserts.equals( + env, + "/usr/x86_64-w64-mingw32/bin/dlltool", + dlltool_path_from_linker_path("/usr/x86_64-w64-mingw32/bin/x86_64-w64-mingw32-gcc"), + ) + + # Windows-style linker path (native MinGW toolchain): + asserts.equals( + env, + "C:\\mingw64\\bin\\dlltool.exe", + dlltool_path_from_linker_path("C:\\mingw64\\bin\\GCC.EXE"), + ) + + # No directory component: nothing to derive from: + asserts.equals(env, None, dlltool_path_from_linker_path("gcc")) + + return unittest.end(env) + +dlltool_path_from_linker_path_test = unittest.make(_dlltool_path_from_linker_path_test_impl) + def _define_targets(): portable_link_flags_probe( name = "portable_link_flags_windows_gnu_probe", @@ -172,6 +196,9 @@ def windows_lib_name_test_suite(name): name = "symlink_name_windows_msvc_test", target_under_test = ":symlink_windows_msvc_probe", ) + dlltool_path_from_linker_path_test( + name = "dlltool_path_from_linker_path_test", + ) native.test_suite( name = name, @@ -180,5 +207,6 @@ def windows_lib_name_test_suite(name): ":portable_link_flags_windows_msvc_test", ":symlink_name_windows_gnu_test", ":symlink_name_windows_msvc_test", + ":dlltool_path_from_linker_path_test", ], )