Skip to content

fix(beam): write package modules to the package's own src/#4817

Merged
dbrattli merged 1 commit into
mainfrom
fix/beam-package-output-layout
Jul 18, 2026
Merged

fix(beam): write package modules to the package's own src/#4817
dbrattli merged 1 commit into
mainfrom
fix/beam-package-output-layout

Conversation

@dbrattli

@dbrattli dbrattli commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Problem

A NuGet package source in a subdirectory was written to fable_modules/<pkg>/<subdir>/src/ instead of the package's own src/. No OTP app covers that path — {project_app_dirs, [".", "fable_modules/*"]} globs one level, and <subdir>/ has no .app.src of its own — so rebar3 never compiled those files and reported nothing. dotnet fable succeeded, rebar3 compile succeeded, and the first symptom was undef at runtime on a module sitting right there in the output directory.

Project sources were already flat; only package sources mirrored their subdirectory. In one build of Fable.Giraffe's Beam suite, 8 modules were stranded — including all seven Fable.Beam.Otp.* ones, so the bindings library ships a subdirectory of modules unreachable at runtime.

Fix

Write package modules to <pkg>/src/<module>.erl regardless of source subdirectory. The module name already encodes the path (Sinks/Universal.fsscriptorium_parchment_sinks_universal), so an app is flat by construction and there is no collision risk.

If the output path has no fable_modules/<package> segment to anchor on, this now fails with a FableError instead of falling back to the previous layout — that fallback would silently reinstate the exact bug being fixed, with undef at runtime as the only symptom.

Two alternatives from the report were considered and dropped: populating {modules, [...]} in the generated .app.src validates nothing, since rebar3 fills the module list in the generated .app from the compiled beams and ignores .app.src; and a post-emit assertion would guard an invariant that getOutPath — the only writer — now establishes five lines away.

Verification

In-tree BEAM suite: 2633 passed, 0 failed, plus both entry-point program tests.

Against ../Fable.Giraffe's Beam suite, with its find-based flatten workaround removed:

  • zero nested */src/*.erl under fable_modules (was 8 stranded modules)
  • all seven fable_beam_otp_* plus scriptorium_parchment_sinks_universal compile into their app's ebin, and code:ensure_loaded/1 resolves them
  • suite: 37 passed / 8 skipped
  • project layout unchanged: test/shared/RoutingTests.fssrc/shared_routing_tests.erl

No regression test in-tree: reproducing this needs a package with subdirectory sources in fable_modules, which the Beam test projects do not have.

Note

This PR previously also carried an unrelated async-stacktrace fix. That has been split out into its own PR so this one can land on its own evidence.

🤖 Generated with Claude Code

A NuGet package source in a subdirectory was written to
`fable_modules/<pkg>/<subdir>/src/` instead of the package's own `src/`. No OTP
app covers that path — `{project_app_dirs, [".", "fable_modules/*"]}` globs one
level, and `<subdir>/` has no `.app.src` of its own — so rebar3 never compiled
those files and reported nothing. `dotnet fable` succeeded, `rebar3 compile`
succeeded, and the first symptom was `undef` at runtime on a module sitting
right there in the output directory.

Project sources were already flat; only package sources mirrored their
subdirectory. In one build of `Fable.Giraffe`'s Beam suite, 8 modules were
stranded — including all seven `Fable.Beam.Otp.*` ones.

Write package modules to `<pkg>/src/<module>.erl` regardless of source
subdirectory. The module name already encodes the path (`Sinks/Universal.fs` ->
`scriptorium_parchment_sinks_universal`), so an app is flat by construction and
there is no collision risk.

If the output path turns out to have no `fable_modules/<package>` segment to
anchor on, fail with a `FableError` rather than falling back to the old layout:
that fallback would silently reinstate the exact bug this fixes, and the only
symptom would again be `undef` at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dbrattli
dbrattli force-pushed the fix/beam-package-output-layout branch from 8b5b1a1 to 025234b Compare July 18, 2026 14:33
@dbrattli
dbrattli merged commit 06bf863 into main Jul 18, 2026
31 checks passed
@dbrattli
dbrattli deleted the fix/beam-package-output-layout branch July 18, 2026 14:44
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.

1 participant