From dbf582669dae667a6f62ec92b23b3e5abb5c9ef2 Mon Sep 17 00:00:00 2001 From: Ralph Kuepper Date: Wed, 12 Aug 2026 08:32:30 +0200 Subject: [PATCH 1/2] fix(link): preserve native members in Windows UI dedup --- .../src/commands/compile/link/windows_link.rs | 13 ++- .../perry/src/commands/compile/strip_dedup.rs | 81 ++++++++++++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/crates/perry/src/commands/compile/link/windows_link.rs b/crates/perry/src/commands/compile/link/windows_link.rs index f8249ed9d2..9739234346 100644 --- a/crates/perry/src/commands/compile/link/windows_link.rs +++ b/crates/perry/src/commands/compile/link/windows_link.rs @@ -44,6 +44,14 @@ pub(super) fn add_system_libs(cmd: &mut Command) { .arg("shlwapi.lib") .arg("ole32.lib") .arg("comctl32.lib") + // Perry strips bundled `.dll` import-library members from the UI + // staticlib during runtime deduplication. Keep every UI import explicit + // here so the trimmed archive remains self-contained at final link: + // uxtheme provides SetWindowTheme, winspool supplies the print-dialog + // imports, and rpcrt4 provides UuidCreate (windows-core GUID::new). + .arg("uxtheme.lib") + .arg("winspool.lib") + .arg("rpcrt4.lib") .arg("advapi32.lib") .arg("comdlg32.lib") .arg("ws2_32.lib") @@ -80,7 +88,7 @@ mod tests { use super::*; #[test] - fn image_widget_system_imports_survive_the_staticlib_boundary() { + fn windows_ui_system_imports_survive_the_staticlib_boundary() { let mut command = Command::new("link.exe"); add_system_libs(&mut command); let args: Vec<_> = command @@ -89,6 +97,9 @@ mod tests { .collect(); assert!(args.iter().any(|arg| arg == "shlwapi.lib")); assert!(args.iter().any(|arg| arg == "winhttp.lib")); + assert!(args.iter().any(|arg| arg == "uxtheme.lib")); + assert!(args.iter().any(|arg| arg == "winspool.lib")); + assert!(args.iter().any(|arg| arg == "rpcrt4.lib")); } } diff --git a/crates/perry/src/commands/compile/strip_dedup.rs b/crates/perry/src/commands/compile/strip_dedup.rs index 96d2aa822e..73db55400d 100644 --- a/crates/perry/src/commands/compile/strip_dedup.rs +++ b/crates/perry/src/commands/compile/strip_dedup.rs @@ -412,6 +412,38 @@ fn collect_archive_undefined_by_member( ))) } +/// Locate a member after `llvm-ar x` extracted it into `extract_dir`. +/// +/// COFF archives can preserve path-qualified member names (WebView2's loader +/// uses names such as `obj/.../loader_impl.obj`), but `llvm-ar x` writes those +/// members as their basename. Looking only at `extract_dir.join(member)` made +/// the extraction appear successful while silently omitting the object from +/// the rebuilt UI archive. +fn extracted_archive_member(extract_dir: &Path, member: &str) -> Option { + let exact = extract_dir.join(member); + if exact.exists() { + return Some(exact); + } + Path::new(member) + .file_name() + .map(|name| extract_dir.join(name)) + .filter(|path| path.exists()) +} + +/// Rust staticlibs can bundle Windows SDK import-library members named after +/// either a `.dll` or a `.drv` (notably the five same-named `winspool.drv` +/// members). These must come from Perry's canonical system-library link line: +/// extracting same-named import members one by one flattens/overwrites them and +/// leaves an incomplete descriptor/thunk set in the rebuilt archive. +fn is_windows_import_archive_member(member: &str) -> bool { + Path::new(member) + .extension() + .and_then(|extension| extension.to_str()) + .is_some_and(|extension| { + extension.eq_ignore_ascii_case("dll") || extension.eq_ignore_ascii_case("drv") + }) +} + /// On Windows, build a trimmed UI lib using the rlib (not staticlib). /// /// perry-ui-windows builds as both rlib and staticlib. The staticlib bundles @@ -701,7 +733,7 @@ pub(super) fn strip_duplicate_objects_from_lib(lib_path: &PathBuf) -> Result = staticlib_members .iter() .filter(|m| { - if m.ends_with(".dll") { + if is_windows_import_archive_member(m) { return false; } if m.contains("compiler_builtins") { @@ -769,7 +801,7 @@ pub(super) fn strip_duplicate_objects_from_lib(lib_path: &PathBuf) -> Result Result Result Result Date: Wed, 12 Aug 2026 08:48:20 +0200 Subject: [PATCH 2/2] docs: changelog fragment for #7920 Claude-Session: https://claude.ai/code/session_01Y1QZ5wUP9gRSwpiweT4Wix --- changelog.d/7920-windows-ui-archive-dedup.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 changelog.d/7920-windows-ui-archive-dedup.md diff --git a/changelog.d/7920-windows-ui-archive-dedup.md b/changelog.d/7920-windows-ui-archive-dedup.md new file mode 100644 index 0000000000..c4eacb810f --- /dev/null +++ b/changelog.d/7920-windows-ui-archive-dedup.md @@ -0,0 +1,9 @@ +**Windows UI archive dedup no longer drops path-qualified native members.** +Rebuilding the deduplicated Windows UI archive flattened member names, so +equal basenames overwrote one another and WebView2LoaderStatic's +`obj/.../*.obj` members (plus part of `winspool.drv`) were silently omitted — +leaving `CreateCoreWebView2EnvironmentWithOptions` and friends undefined at +link. Members are now normalized to unique flat names, `.drv` members are +treated as import-library members alongside `.dll`, and `uxtheme.lib` / +`winspool.lib` / `rpcrt4.lib` come from the canonical system link line. +(Fragment added at merge; see the PR body for the full analysis.)