ADR 0030: cargo-dist Evaluation for Release Packaging

Status

Rejected (2026-02-18)

Context

Problem

cargo-dist is not viable for beamtalk's release packaging needs at this time.

Beamtalk has a hand-written 566-line release.yml GitHub Actions workflow that builds platform-specific distribution tarballs/zips for 4 platforms (Linux x86_64, macOS x86_64, macOS ARM64, Windows x86_64) and bundles Erlang BEAM runtime files. This works but is custom CI to maintain.

cargo-dist (v0.30.4, now branded as "dist") automates release packaging for Rust projects — generating CI workflows, building platform binaries, creating installers (shell scripts, Homebrew taps), checksums, and uploading to GitHub Releases.

Current Distribution Layout

Beamtalk's distribution requires a specific layout that bundles 4 Rust binaries alongside 8 Erlang OTP applications:

  bin/
    beamtalk
    beamtalk-compiler-port
    beamtalk-lsp
    beamtalk-mcp
  lib/beamtalk/lib/
    beamtalk_runtime/ebin/*.beam, *.app
    beamtalk_workspace/ebin/*.beam, *.app
    beamtalk_compiler/ebin/*.beam, *.app
    beamtalk_stdlib/ebin/*.beam, *.app
    jsx/ebin/*.beam, *.app
    cowboy/ebin/*.beam, *.app
    cowlib/ebin/*.beam, *.app
    ranch/ebin/*.beam, *.app

The lib/ directory is required at runtime — the CLI locates it relative to the binary location to load BEAM code paths.

Current Workflow Structure

The existing release.yml has 7 jobs:

Each platform job installs Erlang/OTP 27.0 via erlef/setup-beam@v1, runs rebar3 compile, builds stdlib, and creates the full distribution.

Evaluation

What cargo-dist Handles Well

CapabilityAssessment
Multi-platform Rust binary builds✅ Excellent — native support for all 4 target platforms
GitHub Release creation✅ Automatic with changelog integration
Checksums (SHA-256)✅ Built-in, which our current workflow lacks
Shell/PowerShell install scriptscurl | sh style installers
Homebrew tap generation✅ Built-in, which we don't have today
CI workflow generation✅ Generated 296-line workflow vs our 566-line hand-written one
Tag-based release trigger✅ Standard semver tag detection

What cargo-dist Cannot Handle

1. Custom Directory Layout (Blocker) 🔴

cargo-dist archives contain only binaries in a flat structure. There is no supported mechanism to include a lib/ directory tree alongside binaries in the release archive.

Beamtalk requires lib/beamtalk/lib/*/ebin/*.beam to be co-located with binaries in a specific directory hierarchy. Without this, the CLI cannot find BEAM code paths at runtime.

2. Erlang/OTP Build Dependency (Significant) 🟡

cargo-dist's dependencies system supports Apt, Chocolatey, and Homebrew for installing system packages. However:

Workaround exists via github-build-setup to inject erlef/setup-beam before the build, but this is fragile and underdocumented for complex build chains.

3. Multi-Step Build Process (Significant) 🟡

Beamtalk's build requires a specific sequence:

  1. cargo build --release (Rust binaries)
  2. rebar3 compile (Erlang runtime apps)
  3. beamtalk build-stdlib (uses the just-built Rust binary to compile stdlib .bt → .beam)

cargo-dist's build-command replaces the standard Cargo build entirely (for non-Rust projects). For Rust projects with additional build steps, extra-artifacts provides a hook, but it runs after the Cargo build and its artifacts are uploaded separately — not merged into the platform archive.

There is no way to say "after Cargo builds, also run rebar3 and bundle the results into the same archive."

4. VS Code Extension Pipeline (Not Supported) 🟡

The existing workflow includes 3 jobs for cross-compiling the LSP server, packaging platform-specific VS Code extensions (.vsix), and publishing to the marketplace. cargo-dist has no concept of VS Code extensions. These jobs would need to remain as custom CI regardless.

5. Windows Build Complexity (Minor) 🟢

The existing Windows build has special handling (explicit bash shell, manual artifact collection, 7z packaging). cargo-dist handles Windows builds natively, but the Erlang bundling issues above apply equally on Windows.

Generated vs Existing Workflow Comparison

Aspectcargo-dist GeneratedExisting release.yml
Lines296566
Jobs4 (plan, build-local, build-global, host)7 (4 platform + 3 VS Code)
TriggerTag push (*.*.*)Release published / workflow_dispatch
Erlang support❌ None✅ OTP 27.0 via setup-beam
BEAM bundling❌ No mechanism✅ Full lib/ tree
Install scripts✅ shell + powershell❌ None
Checksums✅ SHA-256❌ None
Homebrew✅ Available❌ None
VS Code ext❌ Not supported✅ Full pipeline
Archive formattar.xz (unix), zip (win)tar.gz (unix), zip (win)

Gains from Adopting cargo-dist

  1. Checksums — SHA-256 for all archives (we lack this today)
  2. Install scriptscurl | sh installer (we lack this today)
  3. Homebrew tap — automated formula generation
  4. Less CI maintenance — workflow regenerated on dist init
  5. Standardized release flow — tag-based, with prerelease support

Losses from Adopting cargo-dist

  1. Cannot bundle BEAM files in archives — fundamental blocker
  2. VS Code extension pipeline — must remain custom
  3. Erlang build steps — must be injected via workarounds
  4. Release trigger flexibility — loses workflow_dispatch trigger
  5. Archive format control — defaults to tar.xz (minor)

Decision

Do not adopt cargo-dist for beamtalk's release packaging at this time.

The fundamental blocker is that cargo-dist cannot include arbitrary directory trees (our lib/beamtalk/lib/*/ebin/ structure) inside release archives. This is a core requirement for beamtalk — without bundled BEAM files, the distributed binary cannot function.

The gains (checksums, install scripts, Homebrew) are valuable but can be added to the existing workflow incrementally without cargo-dist.

Prior Art

User Impact

Rejecting cargo-dist has no immediate impact on end users: release artifacts, supported platforms, and archive contents remain the same. Improvements (checksums, install scripts) are added incrementally on top of the existing release process.

Steelman Analysis

The strongest case for cargo-dist is standardized release packaging, automatic checksums and installers, and reduced CI maintenance via workflow regeneration. If cargo-dist could package arbitrary directory trees alongside binaries, beamtalk would benefit from a simpler release pipeline and alignment with Rust ecosystem practices. The tool is actively developed and may add this capability in the future.

Alternatives Evaluated

1. GoReleaser (Most Promising) ⭐

GoReleaser added Rust support in v2.5 via cargo-zigbuild. It's the most feature-rich option and can handle beamtalk's requirements.

Why it could work:

Example .goreleaser.yaml sketch:

before:
  hooks:
    - rebar3 compile
    - ./target/release/beamtalk build-stdlib

builds:
  - builder: rust
    binary: beamtalk
    targets:
      - x86_64-unknown-linux-gnu
      - x86_64-apple-darwin
      - aarch64-apple-darwin
      - x86_64-pc-windows-msvc

archives:
  - format: tar.gz
    files:
      - src: runtime/_build/default/lib/beamtalk_runtime/ebin
        dst: lib/beamtalk/lib/beamtalk_runtime/ebin
      - src: runtime/_build/default/lib/beamtalk_workspace/ebin
        dst: lib/beamtalk/lib/beamtalk_workspace/ebin
      # ... remaining apps
      - src: runtime/apps/beamtalk_stdlib/ebin
        dst: lib/beamtalk/lib/beamtalk_stdlib/ebin

Concerns:

Verdict: Worth a deeper spike. If src/dst mapping works in the free version for directory trees, this could replace the existing workflow. Create a follow-up issue for a hands-on evaluation.

2. nFPM (Linux Package Formats Only)

nFPM (from GoReleaser team) creates .deb, .rpm, .apk, and tarballs with full control over directory layout via contents entries with src/dst and type: tree.

Why it's interesting:

Why it's insufficient alone:

Verdict: Useful as a complement for Linux package distribution, not a standalone replacement.

3. Earthly CI (Build Orchestration)

Earthly provides Dockerfile-meets-Makefile build definitions with cross-platform support. Good for multi-language projects (Rust + Erlang).

Why it's interesting:

Why it doesn't fit:

Verdict: Not the right tool for this problem. Solves build reproducibility, not release packaging.

4. Nix Flakes (Reproducible Builds)

Nix can build both Rust (buildRustPackage) and Erlang (beamPackages) with full reproducibility, including ERTS bundling.

Why it's interesting:

Why it doesn't fit:

Verdict: Would be great for internal reproducibility but wrong abstraction for end-user distribution.

5. Incremental Improvements to Existing Workflow (Recommended for Now) ⭐

Rather than replacing the workflow, add the missing features identified in the cargo-dist evaluation:

FeatureApproachEffort
SHA-256 checksumsAdd sha256sum step after packaging, upload .sha256 filesSmall (~10 lines)
Install scriptCreate scripts/install.sh that detects OS/arch, downloads correct archive from GitHub ReleasesMedium (~100 lines)
Homebrew tapCreate jamesc/homebrew-beamtalk repo, auto-update formula on releaseMedium (new repo + ~30 lines of workflow)

This approach:

Recommended Follow-Up Actions

  1. Add checksums to existing release.yml — generate SHA-256 for each archive and upload alongside (small change)
  2. Add install script — create a standalone install.sh that downloads the correct platform archive from GitHub Releases
  3. Evaluate GoReleaser — the most promising full replacement; create a spike issue to test src/dst archive mapping with beamtalk's lib/ tree using the free version
  4. Consider Homebrew formula — can be maintained independently as a tap repository
  5. Monitor cargo-distIssue #934 tracks lib/ directory support; re-evaluate when this lands

Consequences

Implementation

N/A for rejected ADR — no implementation work will be done to adopt cargo-dist.

Migration Path

N/A for rejected ADR — the current release workflow is retained.

References