Skip to content

windows_sys: gate #[link] behind cfg(windows) so non-Windows cargo test binaries link - #35084

Merged
Jarred-Sumner merged 5 commits into
mainfrom
farm/575e90a1/windows-sys-link-cfg
Jul 22, 2026
Merged

Jarred-Sumner merged 5 commits into
mainfrom
farm/575e90a1/windows-sys-link-cfg

Conversation

@robobun

@robobun robobun commented Jul 22, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

bun_sys, bun_errno and bun_spawn_sys depend on bun_windows_sys unconditionally so that Win32 POD types (IO_COUNTERS, Win32Error, NTSTATUS) resolve on every target. The #[link(name = "ntdll"/"kernel32"/"ws2_32"/"shell32"/"advapi32")] attributes on the extern blocks were therefore being baked into the rlib on non-Windows hosts too, and any standalone test/bench binary that transitively pulled in bun_windows_sys failed to link:

$ cargo test -p bun_windows_sys --no-run
...
ld.lld: error: unable to find library -lntdll
ld.lld: error: unable to find library -lkernel32
ld.lld: error: unable to find library -lws2_32
ld.lld: error: unable to find library -lshell32
ld.lld: error: unable to find library -ladvapi32

Same for cargo test -p bun_spawn_sys, and (further downstream) cargo test -p bun_resolver / bun_router per the note in #35082. bun bd was unaffected because it produces a staticlib and never asks cargo to drive a final link.

Fix: wrap every #[link(name = "...")] in #[cfg_attr(windows, ...)]. On Windows this expands to the identical #[link(name = "...")], so the import-library set for bun.exe and bun_shim_impl.exe is unchanged. On every other target the -l flags stop being emitted. Typedefs, consts and the extern "system" declarations themselves stay available on all targets, so the unconditional dependents keep resolving their type re-exports without any downstream #[cfg] churn.

Guard tests:

  • test/internal/source-lints/windows-sys-link-cfg.test.ts: greps src/windows_sys/externs.rs for any bare #[link(name = ...)] not wrapped in cfg_attr(windows, ...). Runs on every PR that touches src/**/*.rs via the source-lints workflow, with no cargo/workspace prerequisite.
  • test/internal/rust-windows-sys-link.test.ts: end-to-end check that spawns cargo test -p bun_windows_sys --no-run on non-Windows hosts and asserts it links. cargo check --tests only type-checks, so this is the one that actually drives the linker. Skips on test-only CI lanes where the cargo workspace is not resolvable (same workspaceResolvable gate as linear-fifo.test.ts).

cargo test -p bun_resolver still fails to link on Linux after this change because bun_core/bun_simdutf_sys reference native C symbols (highway_index_of_char, simdutf__*) that only the full CMake build provides. That is a separate pre-existing issue and out of scope here; the -lntdll/-lkernel32/... flags are gone from its link line.

How did you verify your code works?

$ cargo test -p bun_windows_sys --no-run                       # ld.lld -lntdll error before, links after
$ cargo test -p bun_spawn_sys   --no-run                       # same
$ cargo check -p bun_windows_sys --target x86_64-pc-windows-msvc   # unchanged
$ cargo check -p bun_windows_sys --target aarch64-pc-windows-msvc  # unchanged
$ cargo check -p bun_sys -p bun_errno -p bun_spawn_sys --target x86_64-pc-windows-msvc  # unchanged
$ bun bd test test/internal/rust-windows-sys-link.test.ts test/internal/source-lints/windows-sys-link-cfg.test.ts   # pass

Fail-before / pass-after verified with git checkout origin/main -- src/windows_sys/externs.rs around both tests.


no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/internal/rust-windows-sys-link.test.ts

bun_errno and bun_spawn_sys depend on bun_windows_sys unconditionally so
that Win32 POD types (IO_COUNTERS, Win32Error, NTSTATUS) resolve on every
target. The unconditional #[link(name = "ntdll"/"kernel32"/...)]
attributes on the extern blocks were therefore baked into the rlib on
non-Windows hosts too, and any standalone test/bench binary that
transitively pulled in bun_windows_sys failed to link:

    ld.lld: error: unable to find library -lntdll
    ld.lld: error: unable to find library -lkernel32
    ...

bun bd was unaffected because it builds a staticlib (no final link step
driven by cargo).

Wrap every #[link(name = ...)] in #[cfg_attr(windows, ...)] so the -l
flags are only emitted for Windows targets. Typedefs, consts and the
extern fn declarations themselves remain available on all targets.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 16 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 191dc9e7-93f2-4b95-ab03-2b9e8ae8bc94

📥 Commits

Reviewing files that changed from the base of the PR and between 12b30c3 and 06d79c3.

📒 Files selected for processing (3)
  • src/windows_sys/externs.rs
  • test/internal/rust-windows-sys-link.test.ts
  • test/internal/source-lints/windows-sys-link-cfg.test.ts

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

@robobun

robobun commented Jul 22, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:12 AM PT - Jul 22nd, 2026

✅ @robobun, your commit 06d79c3d9848c1a355e8e6ce4dff84976281b854 passed in Build #77572! 🎉


🧪   To try this PR locally:

bunx bun-pr 35084

That installs a local version of the PR into your bun-35084 executable, so you can run:

bun-35084 --bun

Comment thread test/internal/rust-windows-sys-link.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the workspaceResolvable guard from my earlier comment is now in place (bdd901b) and matches linear-fifo.test.ts exactly.

What was reviewed:

  • All 15 #[link(name = ...)] sites in src/windows_sys/externs.rs are wrapped; grep confirms none remain unwrapped. On Windows cfg_attr(windows, link(...)) expands to the identical attribute, so the import-library set is unchanged.
  • The extern blocks with no #[link] (GetUserNameW, SetStdHandle/GetConsoleCP/GetConsoleOutputCP, windows_enable_stdio_inheritance) were correctly left alone — they never emitted -l flags.
  • Guard test drains stdout/stderr concurrently, asserts the combined {stdout, stderr, exitCode} object, and skips on Windows / missing cargo / unresolvable workspace.
Extended reasoning...

Overview

Mechanical build-tooling fix in src/windows_sys/externs.rs: each #[link(name = "ntdll"/"kernel32"/"ws2_32"/"shell32"/"advapi32")] attribute (15 sites) is wrapped in #[cfg_attr(windows, ...)]. A new guard test in test/internal/rust-windows-sys-link.test.ts spawns cargo test -p bun_windows_sys --no-run on non-Windows hosts to prove the crate now links standalone. A doc comment at the top of externs.rs explains why the gating exists.

Security risks

None. This touches linker attribute metadata only — no user-facing surface, no input handling, no runtime code paths change on any platform.

Level of scrutiny

Low. On Windows, #[cfg_attr(windows, link(name = "X"))] expands to the byte-identical #[link(name = "X")], so bun.exe/bun_shim_impl.exe see no change. On non-Windows, the extern fn declarations remain (so type re-exports for bun_errno/bun_spawn_sys still resolve) but the -l flags stop being baked into the rlib — which only ever mattered when cargo drove a final link for a standalone test binary; bun bd produces a staticlib and was never affected. This is a standard Rust idiom for cross-platform FFI crates.

Other factors

  • I verified via grep that no un-gated #[link(name = ...)] remains anywhere under src/windows_sys/.
  • My earlier inline finding (missing workspaceResolvable skip guard for test-only CI lanes) was addressed in bdd901b; the guard now checks both vendor/lolhtml/Cargo.toml and build/debug/codegen/build_options.rs, identical to the sibling test/internal/linear-fifo.test.ts. That thread is resolved.
  • The test follows harness conventions: await using on the spawned process, concurrent pipe draining via Promise.all, and the combined {stdout, stderr, exitCode} assertion pattern so failure output includes cargo's stderr.
  • PR description documents fail-before/pass-after verification and cross-checks against both Windows MSVC targets.

The cargo-link test skips on test-only lanes (no vendor/lolhtml), so add a
grep-based lint that runs unconditionally via the source-lints workflow. Also
reword the externs.rs doc comment to state the invariant rather than an
incomplete list of dependents.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the earlier workspaceResolvable guard concern is addressed in bdd901b, and the remaining --locked nit below is non-blocking (CI checkouts always have a synced lockfile).

Extended reasoning...

Overview

This PR wraps every #[link(name = "...")] attribute in src/windows_sys/externs.rs with #[cfg_attr(windows, ...)] (15 sites: kernel32 ×8, ntdll ×2, ws2_32 ×2, shell32, advapi32), so that non-Windows builds of the bun_windows_sys rlib stop emitting -lntdll/-lkernel32/... to the linker. Two guard tests are added: a source-lint that greps externs.rs for any bare #[link(name = ...)], and an end-to-end cargo test -p bun_windows_sys --no-run link check that skips on Windows / no cargo / unresolvable workspace.

Security risks

None. This is a build-attribute change with no runtime code path affected. On Windows, #[cfg_attr(windows, link(name = "X"))] expands to the identical #[link(name = "X")], so the import-library set for bun.exe is byte-for-byte unchanged. On non-Windows, the extern declarations remain for type-checking; only the -l linker flags are dropped, which were unresolvable there anyway.

Level of scrutiny

Low-to-medium. The Rust change is purely mechanical and uses the canonical idiom for this problem. bun bd was already unaffected (staticlib, no cargo-driven final link), so this only widens what cargo test -p ... can link on developer machines. The PR description shows fail-before/pass-after and cross-checks on both Windows MSVC targets.

Other factors

  • My previous review flagged the missing workspaceResolvable skip guard; the author addressed it in bdd901b by mirroring linear-fifo.test.ts exactly (vendor/lolhtml + build/debug/codegen/build_options.rs), and the thread is resolved.
  • The source-lint regex correctly distinguishes #[link(name = ...)] from #[link_name = ...] (symbol rename), strips line comments, and is scoped to externs.rs only (bare #[link] is fine in modules already gated by #[cfg(windows)]).
  • The cargo test spreads process.env rather than bunEnv, matching linear-fifo.test.ts (it spawns cargo, not Bun); pipes are drained concurrently; await using handles cleanup.
  • The one remaining finding is a nit: --locked should be added to match the sibling test's invocation for hermeticity on developer machines with a drifted Cargo.lock. It cannot flake CI and is a one-token fix, so it does not block approval.

Comment thread test/internal/rust-windows-sys-link.test.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — both earlier findings (the workspaceResolvable skip guard and --locked) are now in place.

What was reviewed:

  • externs.rs: confirmed all 15 #[link(name = ...)] sites are now #[cfg_attr(windows, ...)]-wrapped and none remain bare; #[link_name = ...] (symbol rename) is correctly left alone. On Windows this expands to the identical attribute, so the import-library set is unchanged.
  • rust-windows-sys-link.test.ts: matches linear-fifo.test.ts conventions (workspaceResolvable gate on vendor/lolhtml + build/debug/codegen, --locked, await using, concurrent stdout/stderr/exited drain).
  • windows-sys-link-cfg.test.ts: same shape as the other regex-based source-lints (e.g. shim-stdint-includes.test.ts); the regex correctly excludes #[link_name = ...] and comment-stripped lines.
Extended reasoning...

Overview

This PR wraps every #[link(name = "...")] attribute in src/windows_sys/externs.rs with #[cfg_attr(windows, ...)] (15 sites across ntdll/kernel32/ws2_32/shell32/advapi32 extern blocks), adds a module-level doc comment explaining why, and ships two guard tests: an end-to-end cargo test -p bun_windows_sys --no-run link check and a source-lint that greps for any future bare #[link(name = ...)] in this file.

Security risks

None. This is purely build-time link-attribute metadata; no runtime code paths, inputs, or trust boundaries are touched.

Level of scrutiny

Low. The Rust change is a mechanical attribute rewrite: on Windows #[cfg_attr(windows, link(name = X))] expands to exactly #[link(name = X)], so bun.exe's import-library set is unchanged; on non-Windows the attribute is simply dropped, which removes the spurious -lntdll/-lkernel32/... flags that were breaking standalone cargo test/bench binaries. bun bd was already unaffected (staticlib, no cargo-driven final link), so there is no shipped-binary behavior change on any platform. I verified against the full file that no #[link(name = ...)] remains unwrapped and that the unrelated #[link_name = "WaitForSingleObject"] is untouched.

Other factors

Two prior review rounds raised findings that have both been addressed and resolved: (1) the workspaceResolvable guard on vendor/lolhtml/Cargo.toml + build/debug/codegen/build_options.rs so test-only CI lanes skip cleanly, and (2) --locked on the cargo invocation for hermeticity. The resulting test now mirrors test/internal/linear-fifo.test.ts line-for-line on those conventions (same guard expression, same spawn shape, concurrent pipe drain, await using). The source-lint test follows the established pattern in test/internal/source-lints/ (e.g. shim-stdint-includes.test.ts): read source, regex-scan, expect(violations).toEqual([]). Its regex (#\[\s*link\s*\(\s*name\s*=) correctly distinguishes the library-link attribute from the #[link_name = ...] symbol-rename attribute and strips line comments first.

@Jarred-Sumner
Jarred-Sumner merged commit 7d913b4 into main Jul 22, 2026
52 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the farm/575e90a1/windows-sys-link-cfg branch July 22, 2026 21:48
robobun added a commit that referenced this pull request Jul 23, 2026
…n block

The windows-sys-link-cfg source lint from #35084 requires every #[link]
in bun_windows_sys to use the cfg_attr form. The block is already
#[cfg(windows)] (CONTEXT only exists there), so the cfg_attr is redundant
but matches the enforced pattern.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants