Skip to content

build: order smoke_test and dsymutil after strip - #36139

Merged
Jarred-Sumner merged 2 commits into
mainfrom
claude/farm/3c644de7/smoke-test-strip-race
Jul 28, 2026
Merged

build: order smoke_test and dsymutil after strip#36139
Jarred-Sumner merged 2 commits into
mainfrom
claude/farm/3c644de7/smoke-test-strip-race

Conversation

@robobun

@robobun robobun commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

When bun on PATH resolves into the build directory (e.g. build/release is first on PATH, so process.execPath == /path/to/build/release/bun), cfg.jsRuntime gets baked into the smoke_test and dsymutil ninja rule commands as the stream.ts wrapper. But those build edges only declare bun-profile as an input, not the stripped bun, so ninja is free to run strip and the wrapper concurrently (both depend only on the link output). The wrapper then execs build/release/bun while strip is writing it:

/bin/sh: 1: /workspace/bun/build/release/bun: Permission denied

Generated ninja before this change:

rule smoke_test
  command = /workspace/bun/build/release/bun .../stream.ts check --console sh -c '...'
build bun-profile.smoke-test-passed: smoke_test bun-profile
build bun: strip bun-profile
default bun check

Fix

Extract an emitPostLink() helper that owns the strip / dsymutil / bun phony / smoke-test sequence for every linking mode (full, link-only, rust-and-link), and have it add the stripped exe as an order-only input to the smoke_test and dsymutil edges. After:

build bun-profile.smoke-test-passed: smoke_test bun-profile || bun

Ninja now serializes strip before those edges; the wrapper never sees a half-written file. Order-only (not implicit) because the edges don't read the stripped binary's content, they only need it to not be mid-write. Debug builds (no strip) are unchanged.

The sequence was previously open-coded in all three modes; #30539's partial fix missed emitRustAndLink. Folding it into one helper means any future mode gets the ordering for free. The refactor produces byte-identical build.ninja output for every profile.

Why this is the right fix

Refusing a jsRuntime inside buildDir at configure time would break any environment where the only bun is the build output (no ~/.bun/bin/bun, build/release on PATH). Dropping the stream.ts wrapper would lose the Windows console buffering compensation. Declaring the actual dependency is the ninja-correct model and costs ~1s of serialization on release builds.

Verification

$ ninja -C build/release -t query bun
bun:
  input: strip
    bun-profile
  outputs:
    bun-profile.smoke-test-passed

New test/internal/build-post-link-ordering.test.ts asserts the edge shape through emitPostLink for release (order-only dep present), debug (absent, bun phony instead), and darwin-target configs (dsymutil ordered too).

Note: the fix is in scripts/, not src/, so the automated fail-before check (which stashes src/ packages/ only) cannot revert it; the before/after ninja graph above is the proof.

Related

#30539 carries a partial version of this fix (smoke_test only, two of three call sites) bundled with an unrelated bundler change; this standalone PR supersedes that portion so it can land independently.


no test proof · iteration 0 · Platform-specific test-only change; deferring to CI.

The smoke_test and dsymutil rule commands wrap through cfg.jsRuntime
(process.execPath). When bun on PATH resolves into the build directory,
that path is the strip output itself (build/release/bun). Both edges
depended only on the linked exe, so ninja could schedule them
concurrently with strip, and the wrapper exec would fail with
'Permission denied' on the half-written file.

Adds the stripped exe as an order-only input to both edges whenever
strip runs. Covers all three build modes (full, link-only,
rust-and-link).
@coderabbitai

coderabbitai Bot commented Jul 27, 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: 5 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: 67d624fc-25e1-4fbf-bc10-9e1da94d0483

📥 Commits

Reviewing files that changed from the base of the PR and between 4eb6f99 and bd2e594.

📒 Files selected for processing (2)
  • scripts/build/bun.ts
  • test/internal/build-post-link-ordering.test.ts

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

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. bundler: chain inline input sourcemaps through to output #30539 - Carries the same strip/smoke_test ordering fix (2 of 3 call sites) bundled with an unrelated sourcemap feature
  2. bundler: chain external input sourcemaps and thread chains through the dev server #32473 - Extends bundler: chain inline input sourcemaps through to output #30539 and inherits the same partial strip/smoke_test ordering fix

🤖 Generated with Claude Code

@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 — focused build-graph ordering fix; the two inline notes are non-blocking nits.

Extended reasoning...

Overview

Adds the stripped executable as an order-only ninja input to the smoke_test and dsymutil build edges so they can't run concurrently with strip. Threads a new strippedExe parameter through emitSmokeTest / emitDsymutil at all three linking call sites (full, link-only, rust-and-link) in scripts/build/bun.ts, exports both helpers for test access, and adds test/internal/build-post-link-ordering.test.ts asserting the emitted edge shape for release, debug, and darwin-cross configs.

Security risks

None. This only changes ninja dependency-graph metadata in the build scripts; no runtime code, no user input, no auth/crypto surface.

Level of scrutiny

Low-to-moderate. It's build tooling, not shipped runtime — a mistake would surface as a build failure rather than a user-facing bug. The mechanism (order-only || inputs) is the ninja-correct model for "must exist first but content isn't read", and I confirmed BuildNode.orderOnlyInputs is already supported in scripts/build/ninja.ts and rendered as || <path> in the format the tests assert. emitDsymutil taking strippedExe: string (non-optional) is safe because every call site is inside if (shouldStrip(cfg)) where strippedExe is always defined; emitSmokeTest correctly keeps it optional and only spreads orderOnlyInputs when present, so debug builds emit an unchanged edge.

Other factors

The PR description gives a clear root cause (jsRuntime = process.execPath resolving to the strip output) with before/after ninja -t query output, and explicitly considered/rejected two alternatives. All sibling call sites are covered — I checked there are no other emitSmokeTest/emitDsymutil callers. The two inline findings are both nits: the test helper's continuation-unwrap regex is latent (the asserted edges are well under the 120-char wrap threshold) and the mockToolchain duplication follows an established pattern in test/internal/. Neither affects correctness of the fix.

Comment thread test/internal/build-post-link-ordering.test.ts
Comment thread test/internal/build-post-link-ordering.test.ts
@robobun

robobun commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 1:52 PM PT - Jul 27th, 2026

@robobun, your commit bd2e594baedf1ff1e4f385947e6f36ad28462330 passed in Build #83558! 🎉


🧪   To try this PR locally:

bunx bun-pr 36139

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

bun-36139 --bun

The strip/dsymutil/phony/smoke-test sequence was open-coded in all three
linking modes (full, link-only, rust-and-link); the partial fix in #30539
missed one of them. Fold it into a single emitPostLink() so any future
mode gets the ordering for free, and retarget the test at the helper all
call sites use instead of the leaf functions.

Also fix the ninja-continuation unwrap regex in the test helper to match
what wrapLongLine actually emits (space, $, newline, 4+ spaces).

@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 order-only dep is the right ninja model, and folding the three call sites into emitPostLink removes the duplication that let #30539 miss one.

What was reviewed:

  • Refactor is behavior-preserving: strip/dsymutil/phony/smoke-test emit in the same order and under the same shouldStrip/cfg.darwin guards as before, at all three call sites.
  • orderOnlyInputs is a real BuildNode field and emitDsymutil's new strippedExe: string param is always defined at its only call site (inside if (shouldStrip)).
  • Both prior nits are resolved — the unwrap regex now matches wrapLongLine()'s output, and the mockToolchain dedup is reasonably deferred.
  • Test's buildEdge(out, "phony") first-match reliance checked: the bun phony is emitted before check in the debug case, and only check exists in the darwin-cross case.
Extended reasoning...

Overview

Two files: scripts/build/bun.ts extracts the post-link sequence (strip → dsymutil → bun phony → smoke test) from three linking modes (emitBun, emitLinkOnly, emitRustAndLink) into a single exported emitPostLink(), and threads the strip output through as an order-only input on the smoke_test and dsymutil build edges. test/internal/build-post-link-ordering.test.ts is new and asserts the exact ninja edge text for release (order-only || bun present), debug (absent, plus the bun phony), and darwin-cross (dsymutil ordered, smoke test short-circuits to a check phony).

Security risks

None. This is configure-time ninja graph emission in scripts/; no runtime code, no user input, no network, no crypto.

Level of scrutiny

Low-to-moderate. Build infrastructure only — a mistake here fails the build loudly rather than shipping a broken binary. The refactor is mechanical (three copies → one helper) and I diffed each old block against emitPostLink's body: same guards, same call order, same phony logic. The only semantic addition is the orderOnlyInputs: [strippedExe] on two n.build() calls, which is the textbook ninja idiom for "must exist first, don't rebuild me on change" and matches the PR's stated intent.

Other factors

  • The PR description explains both the mechanism (cfg.jsRuntime can be the strip output) and why alternatives (rejecting the config, dropping the wrapper) are worse.
  • Test coverage hits the variant matrix REVIEW.md asks for: strip-on / strip-off, and the darwin-only dsymutil path via a cross-config. The .skipIf(isMacOS) on the darwin test is justified in-comment (native macOS resolveConfig probes xcode-select).
  • I confirmed orderOnlyInputs exists on BuildNode (scripts/build/ninja.ts:66) and that phony() emits build <name>: phony <deps> matching the test's .toBe() strings.
  • Both of my earlier inline nits are resolved: the continuation-unwrap regex is now / \$\n +/g" " (matches wrapLongLine()), and the mockToolchain() duplication is acknowledged as a follow-up — reasonable scope call for a focused fix.
  • No CODEOWNERS entry covers scripts/.

@Jarred-Sumner
Jarred-Sumner merged commit b650f55 into main Jul 28, 2026
54 checks passed
@Jarred-Sumner
Jarred-Sumner deleted the claude/farm/3c644de7/smoke-test-strip-race branch July 28, 2026 00:08
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