Fix broken mix_release build script #1104

Open
opened 2026-03-28 04:31:43 +00:00 by mfreeman451 · 1 comment
Owner

Imported from GitHub.

Original GitHub issue: #3028
Original author: @marvin-hansen
Original URL: https://github.com/carverauto/serviceradar/issues/3028
Original created: 2026-03-11T04:23:28Z


Currently all Elixir targets are built with a completely broken mix release custom build target that bypassed Bazels dependency management, cache management, and basically works like a giant bash script that blocks parallel build execution. A hot fix can be applied but that would violate Bazels hermetic philosophy.

Instead, a complete refactoring of all Elixir targets would be necessary to resolve this issue and eliminate the broken rule. below a high-level AI generated outline of the refactoring task.

And notice there are some caveats here:

  • Configuration details for specific programming language rules have been omitted.
  • Cross-platform and multi-arch support has also been omitted.
  • Target parallelization was not prioritized and it is assumed that some of the build targets would need to be separated into sub-targets for better build performance.

Therefore, the outline servers more as a road map and needs refinement for each stage to add the missing details.

How to Build a Proper Bazel-native Elixir Setup

If we were to eliminate the monolithic mix_release script and convert the Elixir (web-ng) project into a proper, idiomatic Bazel build structure, we would need to decompose the pipeline into isolated, granular Bazel targets.

Here is exactly what needs to be done and why:

1. Manage Dependencies via Bazel (Workspace/Bzlmod)
Currently, mix deps.get dynamically resolves and downloads dependencies during the build action. This violates hermeticity.

The Solution: We must translate the mix.lock file into Bazel repository rules. Tools like rules_beam or custom macros would read mix.lock during the workspace evaluation phase and fetch every Hex package and Git repository as an external Bazel workspace (e.g., @hex_jason//:pkg).
Why: This ensures Bazel knows about every downloaded file before the action phase begins, allowing it to accurately track input hashes and cache the downloaded archives globally.

2. Granular BEAM Compilation (rules_elixir / rules_beam)
Currently, mix deps.compile compiles the entire universe sequentially inside one action.

The Solution: We must use (or write) Bazel rules that compile Elixir code package-by-package. Each dependency becomes a discrete elixir_library Bazel target. The core web-ng app would also be an elixir_library that explicitly depends on the compiled .beam outputs of its dependencies.
Why: This leverages Bazel's core strength: incremental caching and parallelism. If you change one file in web-ng, Bazel immediately knows that the 100 dependencies haven't changed. It instantly reuses the cached .beam files for the dependencies and only recompiles web-ng. Because the actions are small and isolated, they are perfectly hermetic and can be distributed across remote executors.

3. Decouple Rustler NIF Compilation
Currently, Elixir dependencies that use Rust (via rustler) spawn Cargo commands internally during mix deps.compile.

The Solution: We extract the compilation of Rust code into native rust_library and rust_shared_library targets using rules_rust. The resulting .so or .dylib files are then passed as data attributes to the elixir_library that needs them.
Why: rules_rust already handles Cargo dependency resolution, rustc invocation, and Bazel caching perfectly. By separating this from Mix, we avoid the massive Cargo bootstrapping overhead we saw earlier, and we let Bazel orchestrate the C toolchains and cross-compilation hermetically.

4. Decouple Frontend Asset Compilation (rules_js)
Currently, mix release pauses to run bun install, tailwind, and esbuild.

The Solution: We define the JS/CSS build using standard Bazel rules (like aspect_rules_js / aspect_rules_ts). We create targets for npm_install, tailwind bundling, and esbuild bundling.
Why: Frontend bundling is notorious for having massive dependency trees (node_modules). By isolating this to rules_js, Bazel can aggressively cache the Webpack/Vite/Esbuild outputs. Changes to an .ex file won't invalidate the CSS compile step, and changes to a .css file won't trigger Elixir recompilation.

5. The Final Assembly (Release Generation)
The Solution: Once all the discrete pieces are built by Bazel (Elixir .beam files, Rust NIF .so files, and static CSS/JS assets), a final elixir_release rule simply stitches them together. It takes all those pre-compiled artifacts and invokes standard Erlang/Mix release scripts purely to generate the release directory layout and boot scripts.
Why: At this stage, absolutely no compilation happens. It is a purely I/O bound operation that runs in milliseconds. It reads the inputs Bazel provides and spits out the serviceradar-web-ng-release.tar.gz.

Imported from GitHub. Original GitHub issue: #3028 Original author: @marvin-hansen Original URL: https://github.com/carverauto/serviceradar/issues/3028 Original created: 2026-03-11T04:23:28Z --- Currently all Elixir targets are built with a completely broken mix release custom build target that bypassed Bazels dependency management, cache management, and basically works like a giant bash script that blocks parallel build execution. A hot fix can be applied but that would violate Bazels hermetic philosophy. Instead, a complete refactoring of all Elixir targets would be necessary to resolve this issue and eliminate the broken rule. below a high-level AI generated outline of the refactoring task. And notice there are some caveats here: * Configuration details for specific programming language rules have been omitted. * Cross-platform and multi-arch support has also been omitted. * Target parallelization was not prioritized and it is assumed that some of the build targets would need to be separated into sub-targets for better build performance. Therefore, the outline servers more as a road map and needs refinement for each stage to add the missing details. **How to Build a Proper Bazel-native Elixir Setup** If we were to eliminate the monolithic mix_release script and convert the Elixir (web-ng) project into a proper, idiomatic Bazel build structure, we would need to decompose the pipeline into isolated, granular Bazel targets. **Here is exactly what needs to be done and why:** **1. Manage Dependencies via Bazel (Workspace/Bzlmod)** Currently, mix deps.get dynamically resolves and downloads dependencies during the build action. This violates hermeticity. The Solution: We must translate the mix.lock file into Bazel repository rules. Tools like rules_beam or custom macros would read mix.lock during the workspace evaluation phase and fetch every Hex package and Git repository as an external Bazel workspace (e.g., @hex_jason//:pkg). Why: This ensures Bazel knows about every downloaded file before the action phase begins, allowing it to accurately track input hashes and cache the downloaded archives globally. **2. Granular BEAM Compilation (rules_elixir / rules_beam)** Currently, mix deps.compile compiles the entire universe sequentially inside one action. The Solution: We must use (or write) Bazel rules that compile Elixir code package-by-package. Each dependency becomes a discrete elixir_library Bazel target. The core web-ng app would also be an elixir_library that explicitly depends on the compiled .beam outputs of its dependencies. Why: This leverages Bazel's core strength: incremental caching and parallelism. If you change one file in web-ng, Bazel immediately knows that the 100 dependencies haven't changed. It instantly reuses the cached .beam files for the dependencies and only recompiles web-ng. Because the actions are small and isolated, they are perfectly hermetic and can be distributed across remote executors. **3. Decouple Rustler NIF Compilation** Currently, Elixir dependencies that use Rust (via rustler) spawn Cargo commands internally during mix deps.compile. The Solution: We extract the compilation of Rust code into native rust_library and rust_shared_library targets using rules_rust. The resulting .so or .dylib files are then passed as data attributes to the elixir_library that needs them. Why: rules_rust already handles Cargo dependency resolution, rustc invocation, and Bazel caching perfectly. By separating this from Mix, we avoid the massive Cargo bootstrapping overhead we saw earlier, and we let Bazel orchestrate the C toolchains and cross-compilation hermetically. **4. Decouple Frontend Asset Compilation (rules_js)** Currently, mix release pauses to run bun install, tailwind, and esbuild. The Solution: We define the JS/CSS build using standard Bazel rules (like aspect_rules_js / aspect_rules_ts). We create targets for npm_install, tailwind bundling, and esbuild bundling. Why: Frontend bundling is notorious for having massive dependency trees (node_modules). By isolating this to rules_js, Bazel can aggressively cache the Webpack/Vite/Esbuild outputs. Changes to an .ex file won't invalidate the CSS compile step, and changes to a .css file won't trigger Elixir recompilation. **5. The Final Assembly (Release Generation)** The Solution: Once all the discrete pieces are built by Bazel (Elixir .beam files, Rust NIF .so files, and static CSS/JS assets), a final elixir_release rule simply stitches them together. It takes all those pre-compiled artifacts and invokes standard Erlang/Mix release scripts purely to generate the release directory layout and boot scripts. Why: At this stage, absolutely no compilation happens. It is a purely I/O bound operation that runs in milliseconds. It reads the inputs Bazel provides and spits out the serviceradar-web-ng-release.tar.gz.
Author
Owner

Imported GitHub comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/3028#issuecomment-4040225808
Original created: 2026-03-11T15:54:00Z


fixed in PR #3027

Imported GitHub comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/3028#issuecomment-4040225808 Original created: 2026-03-11T15:54:00Z --- fixed in PR #3027
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar#1104
No description provided.