From baf0369b90d6cd171d1948a747211f38d3974bea Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Thu, 22 Oct 2026 10:53:32 -0700 Subject: [PATCH 1/3] perf(windows): delay-load system DLLs the proxies don't use Add user32, shell32, ole32, oleaut32, crypt32, secur32 and ws2_32 to the existing delay-load list for rustup-init, so proxied cargo/rustc calls do not map them (and their dependencies) at start-up. They still load from System32 on first use. --- build.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build.rs b/build.rs index e6e6a69197..07bca81f20 100644 --- a/build.rs +++ b/build.rs @@ -70,6 +70,16 @@ fn main() { for dll in delay_load_dlls { println!("cargo::rustc-link-arg-bin=rustup-init=/delayload:{dll}.dll"); } + // Also delay load system DLLs that the proxies never call. Every `cargo`, + // `rustc` etc. started through rustup is a separate process on Windows, and + // each of these DLLs (with what it pulls in, such as gdi32 for user32) costs + // start-up time when it is loaded eagerly. + let delay_load_for_startup = [ + "user32", "shell32", "ole32", "oleaut32", "crypt32", "secur32", "ws2_32", + ]; + for dll in delay_load_for_startup { + println!("cargo::rustc-link-arg-bin=rustup-init=/delayload:{dll}.dll"); + } // When using delayload, it's necessary to also link delayimp.lib // https://learn.microsoft.com/en-us/cpp/build/reference/dependentloadflag?view=msvc-170 println!("cargo::rustc-link-arg-bin=rustup-init=delayimp.lib"); From 958542d47bb7d66569542cb23f382418885123a5 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Thu, 22 Oct 2026 11:20:04 -0700 Subject: [PATCH 2/3] build(windows): don't build test and bench harnesses for rustup-init rustup-init has no unit tests or benchmarks. Building its harnesses with --all-targets applies the binary's link arguments, so the delay-load list is linked without the code that imports user32, and /WX turns LNK4199 into a link error. --- Cargo.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 23807c7ad4..3e8a74df81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,6 +163,16 @@ wildcard_imports = "warn" name = "rustup" path = "src/lib.rs" +[[bin]] +name = "rustup-init" +path = "src/bin/rustup-init.rs" +# The binary has no unit tests or benchmarks. Its empty test and bench harnesses +# would otherwise be linked with the binary's delay-load list (see `build.rs`) but +# without the code that imports those DLLs, which `/WX` turns into a link error +# (LNK4199). +test = false +bench = false + [profile.release] codegen-units = 1 lto = true From 68cc7a152fb155f37c1a43910480702382ad4e00 Mon Sep 17 00:00:00 2001 From: Alexandre Zollinger Chohfi Date: Mon, 26 Oct 2026 18:15:31 -0700 Subject: [PATCH 3/3] build(windows): skip the startup delay-load list in test builds Builds with the `test` feature also link rustup-init's empty test and bench harnesses, which don't import the DLLs in the startup delay-load list, so `/WX` turns the unused `/delayload` (LNK4199) into a link error. Only add the list when the `test` feature is off, and revert the `test = false` / `bench = false` workaround from 958542d. --- Cargo.toml | 10 ---------- build.rs | 16 +++++++++++----- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3e8a74df81..23807c7ad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -163,16 +163,6 @@ wildcard_imports = "warn" name = "rustup" path = "src/lib.rs" -[[bin]] -name = "rustup-init" -path = "src/bin/rustup-init.rs" -# The binary has no unit tests or benchmarks. Its empty test and bench harnesses -# would otherwise be linked with the binary's delay-load list (see `build.rs`) but -# without the code that imports those DLLs, which `/WX` turns into a link error -# (LNK4199). -test = false -bench = false - [profile.release] codegen-units = 1 lto = true diff --git a/build.rs b/build.rs index 07bca81f20..7bb2f550f8 100644 --- a/build.rs +++ b/build.rs @@ -74,11 +74,17 @@ fn main() { // `rustc` etc. started through rustup is a separate process on Windows, and // each of these DLLs (with what it pulls in, such as gdi32 for user32) costs // start-up time when it is loaded eagerly. - let delay_load_for_startup = [ - "user32", "shell32", "ole32", "oleaut32", "crypt32", "secur32", "ws2_32", - ]; - for dll in delay_load_for_startup { - println!("cargo::rustc-link-arg-bin=rustup-init=/delayload:{dll}.dll"); + // + // Builds with the `test` feature (CI's test runs) skip this: they also link + // the binary's empty test and bench harnesses, which don't import these + // DLLs, so `/WX` would turn the unused `/delayload` into a link error. + if env::var_os("CARGO_FEATURE_TEST").is_none() { + let delay_load_for_startup = [ + "user32", "shell32", "ole32", "oleaut32", "crypt32", "secur32", "ws2_32", + ]; + for dll in delay_load_for_startup { + println!("cargo::rustc-link-arg-bin=rustup-init=/delayload:{dll}.dll"); + } } // When using delayload, it's necessary to also link delayimp.lib // https://learn.microsoft.com/en-us/cpp/build/reference/dependentloadflag?view=msvc-170