Skip to content

Sync platform npm binaries via dune promotion - #8560

Merged
cristianoc merged 1 commit into
masterfrom
dune-promote-platform-binaries
Aug 18, 2026
Merged

Sync platform npm binaries via dune promotion#8560
cristianoc merged 1 commit into
masterfrom
dune-promote-platform-binaries

Conversation

@cristianoc

Copy link
Copy Markdown
Collaborator

Rationale

The compiler binaries in packages/@rescript/<platform>/bin are what actually runs in most workflows: cli/bsc.js and friends resolve them via the platform npm package, and so do the mocha/build tests (scripts/test.js), the tools tests, the rewatch integration tests, and the runtime build. But those binaries were only refreshed by copy steps — mtime-based Makefile rules locally, and a separate scripts/copyExes.js --compiler step in CI — which run only when make (or that CI step) runs.

The command sequence that gets into trouble

# edit a compiler source
$ dune build                # _build is now current
$ ./cli/bsc.js test.res     # ...runs the OLD compiler

Nothing updates the packages copy between those two commands. Worse, the tree becomes split-brained: scripts/test_syntax.sh and the analysis tests read _build/install/default/bin directly (new compiler), while everything routed through the cli reads the packages copy (old compiler) — in the same shell session, with no error anywhere. In practice this showed up as confusingly inconsistent results: syntax fixtures regenerated by the new compiler while mocha snapshots were regenerated by the old one, costing real debugging time before anyone suspects the binary.

A programmatic illustration (probe string compiled into the binaries, then one bare dune build):

binary                                               contains
_build/install/default/bin/bsc                       NEW
packages/@rescript/darwin-arm64/bin/bsc.exe          old   <- what cli/bsc.js runs

entry point                                          runs
cli/bsc.js, scripts/test.js, tools tests, rewatch    old
test_syntax.sh, analysis tests (read _build)         NEW

Why this resolves it

Dune promotion makes the packages binaries an output of the build itself: rules in compiler/sync/dune (one per platform, covering bsc, rescript-editor-analysis, rescript-tools) copy-and-strip each binary into the platform package on every dune build. There is no separate step to forget. Key properties:

  • Content-compared: promotion only rewrites a file when its bytes changed, so no-op builds cause no timestamp churn and make's downstream stamps (runtime build, etc.) don't re-trigger spuriously.
  • Sole producer: the Makefile copy rules, both mtime-compensation touch loops, and copyExes.js's compiler mode are deleted. If a platform's enabled_if predicate is wrong, the binary is missing on a fresh checkout and fails loudly and proximately — the "present but stale" failure mode no longer has a producer. (copyExes.js keeps its rewatch mode: that binary is cargo-built and stays make's responsibility.)
  • Platform matrix: one rule per platform (%{system}/%{architecture} from ocamlc -config); Windows copies without strip, matching the historical packaging step.
  • Playground-safe: all rules carry (<> %{profile} browser), so a make playground build (which compiles a playground-flavoured bsc) can never overwrite the native binaries.

Verification layers

  • make compiler now verifies instead of assumes: each packages binary is cmp-checked against the dune build output (_build/default/compiler/sync/), with one forced re-promotion retry that also self-heals a damaged or deleted copy, and a lost executable bit is restored. If promotion genuinely didn't produce the binary (e.g. an uncovered platform), it fails with a message pointing at compiler/sync/dune instead of an ENOENT deep inside some downstream harness.
  • CI's former copy step is replaced by scripts/checkCompilerExes.js, which asserts on every platform in the matrix that the promoted binaries byte-match the build (and are executable on Unix) — so the per-platform enabled_if predicates are actually tested rather than masked by a re-copy.

Verifications done (macOS/arm64)

  • The illustration above re-run after the change: every entry point converges on the new compiler after a bare dune build.
  • Fresh production: binaries deleted, bare dune build re-creates all three; fresh worktree (fresh-clone equivalent) produces them from nothing.
  • No-op build leaves promoted files' mtimes untouched (no downstream churn).
  • Failure injection: a deliberately wrong enabled_if makes both make compiler and checkCompilerExes.js fail with the intended messages; a corrupted or chmod 644 binary is detected and self-healed.
  • Browser profile: dune rules --profile browser confirms the promotion targets don't exist there.
  • make compiler, make lib, make test, Biome, and formatting all green.

Linux and Windows validation is exactly what this PR's CI run provides: with no fallback producer left, a wrong platform predicate means a missing binary and a loud, attributable failure in that job.

🤖 Generated with Claude Code

The compiler binaries in packages/@rescript/<platform>/bin - what
cli/*.js, the test harnesses, and the runtime build actually run - were
copied there by mtime-based Makefile rules that only fired when make
itself ran, plus a separate scripts/copyExes.js step in CI. A bare
`dune build` left them stale, and because some test suites read _build
directly while others resolve through the npm package, the tree could
silently disagree with itself about which compiler was under test.

Replace both copy mechanisms with dune promotion rules
(compiler/sync/dune): one rule per platform, covering bsc,
rescript-editor-analysis, and rescript-tools. Promotion runs on every
`dune build`, compares content so unchanged binaries are not rewritten
(no timestamp churn for make's downstream stamps), strips on Unix like
the packaging step always did, and copies unstripped on Windows. The
browser profile is excluded so a playground build can never overwrite
the native binaries.

The promotion rules are now the only producer of these files:
- The Makefile copy rules and both mtime-compensation touch loops are
  deleted. `make compiler` verifies each binary against the dune build
  output (cmp, with one forced re-promotion retry that also self-heals
  a damaged copy), restores a lost executable bit, and fails with a
  pointer to compiler/sync/dune when promotion did not produce it -
  e.g. when a platform predicate is wrong - rather than silently
  accepting a missing or stale binary.
- scripts/copyExes.js loses its compiler mode (the rewatch mode
  remains), and the CI step that used it is replaced by
  scripts/checkCompilerExes.js, which asserts on every platform in the
  matrix that the promoted binaries match the build instead of quietly
  re-creating them.

The coverage flow still deliberately swaps in unstripped instrumented
binaries after its build; the next regular `dune build` restores the
clean ones automatically.

Signed-Off-By: Cristiano Calcagno <cristianoc@users.noreply.github.com>

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cristianoc
cristianoc force-pushed the dune-promote-platform-binaries branch from 8f3692f to 3d23f5a Compare August 18, 2026 11:33
@cristianoc
cristianoc requested a review from cknitt August 18, 2026 11:44
@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@8560

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@8560

@rescript/darwin-x64

npm i https://pkg.pr.new/@rescript/darwin-x64@8560

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@8560

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@8560

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@8560

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@8560

commit: 3d23f5a

@github-actions

Copy link
Copy Markdown

@cknitt cknitt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice improvement!

@cristianoc
cristianoc merged commit 27c5bd3 into master Aug 18, 2026
29 checks passed
@cristianoc
cristianoc deleted the dune-promote-platform-binaries branch August 18, 2026 17:06
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.

2 participants