Skip to content

fix(link): preserve native members in Windows UI dedup - #7920

Merged
proggeramlug merged 2 commits into
mainfrom
audit/6023-windows-textfield
Aug 12, 2026
Merged

fix(link): preserve native members in Windows UI dedup#7920
proggeramlug merged 2 commits into
mainfrom
audit/6023-windows-textfield

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve path-qualified native COFF members when rebuilding the deduplicated Windows UI archive (notably WebView2LoaderStatic's obj/.../*.obj members)
  • normalize extracted members to unique flat names so equal basenames cannot overwrite one another
  • treat .drv members as Windows import-library members alongside .dll, and supply uxtheme.lib, winspool.lib, and rpcrt4.lib from the canonical system link line

Why

#6051 fixed #6023's original LNK1158: cannot run mt.exe, but the TextField half was never verified. During a current-main Windows validation, perry/ui linking failed earlier because UI archive dedup silently omitted WebView2's path-qualified objects and retained an incomplete flattened subset of winspool.drv members. This left CreateCoreWebView2EnvironmentWithOptions, SetWindowTheme, UuidCreate, and winspool descriptor/thunk symbols unresolved.

With the archive fix applied, the current Windows UI fixture links and runs. Sending EN_CHANGE through the TextField's actual immediate PerryVStack parent reaches the callback and prints the updated value; Toggle callback dispatch also succeeds as a control. The historical mt.exe discovery/fallback tests remain green.

Validation

  • cargo test -p perry --no-default-features --features dev-cli --bin perry windows_link -- --nocapture — 22 passed
  • cargo test -p perry --no-default-features --features dev-cli --bin perry commands::compile::strip_dedup::strip_dedup_tests -- --nocapture — 9 passed
  • cargo build --release -p perry-runtime-static -p perry-ui-windows
  • patched compiler: test-files/test_ui_controls.ts linked successfully on Windows with the coherent current-main runtime/UI archives
  • Win32 event probe: TextField EN_CHANGE through PerryVStack emitted Name: parent-route; direct app-route control emitted Name: top-route; Toggle emitted Notifications: 1
  • cargo fmt --check -p perry
  • no version bump

Closes #6023

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows builds by preserving required UI and runtime system library imports.
    • Fixed handling of path-qualified archive members and Windows DLL/driver import entries.
    • Prevented filename collisions when extracting bundled object files, improving build reliability.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7410c4da-f29f-40c8-82a0-9430a9492d89

📥 Commits

Reviewing files that changed from the base of the PR and between dbf5826 and 68a138d.

📒 Files selected for processing (1)
  • changelog.d/7920-windows-ui-archive-dedup.md

📝 Walkthrough

Walkthrough

The Windows linker now includes three additional system libraries. Archive extraction resolves path-qualified members by basename, excludes Windows import members, and assigns unique normalized filenames. Tests cover library emission, member resolution, filename handling, and import-member classification.

Changes

Windows linking and archive handling

Layer / File(s) Summary
Archive member resolution and extraction
crates/perry/src/commands/compile/strip_dedup.rs
Archive extraction supports basename fallback and unique normalized output names. Windows .dll and .drv import members are excluded from UI dependency extraction. Tests cover these cases.
Windows system library linking
crates/perry/src/commands/compile/link/windows_link.rs, changelog.d/7920-windows-ui-archive-dedup.md
The linker adds uxtheme.lib, winspool.lib, and rpcrt4.lib. The Windows UI system import test verifies their emission. The changelog records the archive and linker changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • PerryTS/perry#7083: Both changes add Windows system libraries required across static library boundaries.
  • PerryTS/perry#7085: Both changes modify Windows archive deduplication and import-member extraction in strip_dedup.rs.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preserving native members during Windows UI archive deduplication.
Description check ✅ Passed The description explains the changes, motivation, linked issue, validation results, and absence of a version bump, despite using custom headings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/6023-windows-textfield

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
crates/perry/src/commands/compile/strip_dedup.rs (1)

1892-1914: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Cover extraction and basename collisions end to end.

The new path test creates loader_impl.obj manually and calls extracted_archive_member. It does not exercise llvm-ar x, std::fs::rename, or rebuild_archive. It cannot detect two path-qualified members with the same basename overwriting each other.

Extend the existing archive fixture at Lines 1818-1891 with two members such as a/loader_impl.obj and b/loader_impl.obj, then assert that both symbol sets remain in the rebuilt archive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/strip_dedup.rs` around lines 1892 - 1914,
Extend the existing archive fixture and end-to-end rebuild test around the
archive construction and rebuild flow, adding path-qualified members such as
a/loader_impl.obj and b/loader_impl.obj with distinct symbols. Run the actual
extraction, rename, and rebuild_archive path, then inspect the rebuilt archive
to assert both members’ symbol sets are preserved and neither basename collision
overwrites the other.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry/src/commands/compile/strip_dedup.rs`:
- Line 736: Update the archive member filtering condition around
is_windows_import_archive_member(m) to also require is_win_lib, so import-member
removal only occurs for Windows archives while ordinary foo.dll or foo.drv
members in other archives remain intact.
- Around line 817-824: Update the rlib extraction loop around
extracted_archive_member and the corresponding staticlib loop to count every
member for which extraction returns None, while preserving the existing
successful-object handling. Before calling rebuild_archive, return an error when
either loop recorded any extraction failures, so incomplete archives are never
rebuilt or returned as successful.

---

Nitpick comments:
In `@crates/perry/src/commands/compile/strip_dedup.rs`:
- Around line 1892-1914: Extend the existing archive fixture and end-to-end
rebuild test around the archive construction and rebuild flow, adding
path-qualified members such as a/loader_impl.obj and b/loader_impl.obj with
distinct symbols. Run the actual extraction, rename, and rebuild_archive path,
then inspect the rebuilt archive to assert both members’ symbol sets are
preserved and neither basename collision overwrites the other.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 39f32916-38b6-4a18-b14c-7463b1b6264d

📥 Commits

Reviewing files that changed from the base of the PR and between c109b08 and dbf5826.

📒 Files selected for processing (2)
  • crates/perry/src/commands/compile/link/windows_link.rs
  • crates/perry/src/commands/compile/strip_dedup.rs

.iter()
.filter(|m| {
if m.ends_with(".dll") {
if is_windows_import_archive_member(m) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Restrict import-member filtering to Windows archives.

This check runs for every archive. If a non-Windows archive contains an ordinary member named foo.dll or foo.drv, the code drops it as an import member. Gate the predicate with is_win_lib.

Suggested fix
-            if is_windows_import_archive_member(m) {
+            if is_win_lib && is_windows_import_archive_member(m) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if is_windows_import_archive_member(m) {
if is_win_lib && is_windows_import_archive_member(m) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/strip_dedup.rs` at line 736, Update the
archive member filtering condition around is_windows_import_archive_member(m) to
also require is_win_lib, so import-member removal only occurs for Windows
archives while ordinary foo.dll or foo.drv members in other archives remain
intact.

Comment on lines +817 to +824
if let Some(extracted) = extracted_archive_member(&extract_dir, member) {
// Move every extracted object to a unique flat name before
// extracting the next member. Two path-qualified members
// may share a basename, and llvm-ar would otherwise
// overwrite the earlier one.
let normalized = extract_dir.join(format!("rlib_{member_index}.obj"));
std::fs::rename(extracted, &normalized)?;
all_objects.push(normalized);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Return an error when any member extraction fails.

When llvm-ar succeeds but extracted_archive_member returns None, the rlib loop silently omits the member. The staticlib loop records the failure but continues to rebuild_archive. The downstream code in crates/perry/src/commands/compile/link/build_and_run.rs at Lines 985-1010 uses the rebuilt archive for Ok and falls back to the original archive only for Err. This can produce an incomplete UI archive and remove required symbols.

Count failures in both loops. Return Err before rebuilding when any extraction fails.

Suggested failure handling
+    let mut extract_fail = 0usize;
+
     // Extract rlib members.
...
             if let Some(extracted) = extracted_archive_member(&extract_dir, member) {
                 ...
                 all_objects.push(normalized);
                 rlib_extracted += 1;
+            } else {
+                extract_fail += 1;
             }
+        } else {
+            extract_fail += 1;
         }
...
     if extract_fail > 0 {
         eprintln!("[strip-dedup] WARNING: {extract_fail} members failed to extract from staticlib");
+        return Err(anyhow::anyhow!(
+            "failed to extract {extract_fail} archive members"
+        ));
     }

Also applies to: 838-859

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry/src/commands/compile/strip_dedup.rs` around lines 817 - 824,
Update the rlib extraction loop around extracted_archive_member and the
corresponding staticlib loop to count every member for which extraction returns
None, while preserving the existing successful-object handling. Before calling
rebuild_archive, return an error when either loop recorded any extraction
failures, so incomplete archives are never rebuilt or returned as successful.

@proggeramlug
proggeramlug merged commit b3f1bf8 into main Aug 12, 2026
11 of 19 checks passed
@proggeramlug
proggeramlug deleted the audit/6023-windows-textfield branch August 12, 2026 06:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: TextField onChange broken and LNK1158 build error in v0.5.1220 (follow-up to #2169)

1 participant