From 57e04a174ca8c93238b0c64fafad43c9bee9c34f Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Thu, 8 Oct 2026 00:59:05 +0400 Subject: [PATCH 1/3] Allow oxc 0.19 oxc 0.19.0 adds OXC.isolated_declarations/3 and changes nothing QuickBEAM calls. --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 05e85e32..933679d8 100644 --- a/mix.exs +++ b/mix.exs @@ -73,7 +73,7 @@ defmodule QuickBEAM.MixProject do {:json_codec, "~> 0.3", optional: true}, {:yaml_elixir, "~> 2.12", only: :test}, {:varint, "~> 1.6"}, - {:oxc, "~> 0.17.8 or ~> 0.18.0"}, + {:oxc, "~> 0.17.8 or ~> 0.18.0 or ~> 0.19.0"}, {:npm, "~> 0.7.6", optional: true}, {:mint, "~> 1.10"}, {:mint_web_socket, "~> 1.0"}, diff --git a/mix.lock b/mix.lock index 33119cf7..a0b7eb83 100644 --- a/mix.lock +++ b/mix.lock @@ -29,7 +29,7 @@ "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, "npm": {:hex, :npm, "0.7.6", "de8ca3dd26a40fac7ab8a17680088b8d55bab0e0294be9770b029c276361482f", [:mix], [{:hex_solver, "~> 0.2", [hex: :hex_solver, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:npm_semver, "~> 0.1.0", [hex: :npm_semver, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "e0a1ce77e3e49ec108e095aba133bd315e86da298dba26b142a105972141d9ef"}, "npm_semver": {:hex, :npm_semver, "0.1.0", "3ab2c2a151d8c87c364209b2ca1a4fd2ab98507ed61afbd1ea12c1826e67200a", [:mix], [{:hex_solver, "~> 0.2", [hex: :hex_solver, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "77afbc4c523c19a572325190bc4c968ec027e1c6ef8538bcddacf835966072fa"}, - "oxc": {:hex, :oxc, "0.18.0", "0b22ae1ac62f6e985458aae53bb0020756bd790ed07bcda1a89168b266025415", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:json_codec, "~> 0.3", [hex: :json_codec, repo: "hexpm", optional: false]}, {:rustler, "~> 0.36", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "121495bbbaba0807433653d4446cb9ed68bc7db35e6f11a9c82da56f89bcaa6c"}, + "oxc": {:hex, :oxc, "0.19.0", "9786fc88ec3da84da1fec5e08bb94b108e3917288cdac07d62842edfcd2500ac", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:json_codec, "~> 0.3", [hex: :json_codec, repo: "hexpm", optional: false]}, {:rustler, "~> 0.36", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.8", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "ffeec25c488d87144fc12e14d30bca92997576303be2458e80e6e84dd756a296"}, "pegasus": {:hex, :pegasus, "0.2.6", "b4af6522326fbb2ffd1bb706e78ec05854fbcb4b03a7fe08f8db2e9de1d0be67", [:mix], [{:nimble_parsec, "~> 1.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0ac159f0ccab7967cf90208327cc8a35788874814c8d78e19d47104d3fc049b9"}, "plug": {:hex, :plug, "1.20.3", "56c480c633ec2ce10140e236e15233bf576e1d323887d7c96711bd02ab5160db", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "be266aee1b8536ef6409d58cf39a3121319f0ec47cfa1b24024485aa0e76ad76"}, "plug_crypto": {:hex, :plug_crypto, "2.2.0", "144014737daaf485407f5ed77daeaad74d651b216a28c87543f8cc7043f8efc8", [:mix], [], "hexpm", "83a95744ab1c75876542b6fab135fcc176280e0f301a111c1f757fddcec95d2c"}, From a43aa852f71f2b6f04da2d9521d37b20512a39b2 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Thu, 8 Oct 2026 00:59:40 +0400 Subject: [PATCH 2/3] Note the oxc 0.19 requirement --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c5d289..d3ca33fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Changed + +- Allow `oxc` 0.19. + ## 0.11.2 - 2026-09-30 ### Changed From 89bec0c5ee9f0cfa8fc970950ed9bbf2056fb44e Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Thu, 8 Oct 2026 01:01:55 +0400 Subject: [PATCH 3/3] Precompile NIFs for Alpine (musl) Adds x86_64-linux-musl and aarch64-linux-musl to the precompiled targets, loads and runs each musl artifact in an Alpine container before release, and uploads artifacts to the release only after every target's smoke test passed. Taken from mindreframer's fork, without its fork-specific release URL. Closes #24 --- .github/workflows/precompile.yml | 96 ++++++++++++++++++++++++++-- CHANGELOG.md | 4 ++ README.md | 3 +- lib/quickbeam/native.ex | 9 ++- scripts/smoke_precompiled_native.exs | 17 +++++ 5 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 scripts/smoke_precompiled_native.exs diff --git a/.github/workflows/precompile.yml b/.github/workflows/precompile.yml index ff73bd63..43185121 100644 --- a/.github/workflows/precompile.yml +++ b/.github/workflows/precompile.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: permissions: - contents: write + contents: read jobs: build: @@ -16,7 +16,9 @@ jobs: matrix: include: - { target: x86_64-linux-gnu, os: ubuntu-latest } + - { target: x86_64-linux-musl, os: ubuntu-latest } - { target: aarch64-linux-gnu, os: ubuntu-latest } + - { target: aarch64-linux-musl, os: ubuntu-latest } - { target: aarch64-macos-none, os: macos-15 } - { target: x86_64-windows-gnu, os: windows-2022 } @@ -69,12 +71,6 @@ jobs: path: artifacts/*.tar.gz if-no-files-found: error - - name: Upload to release - if: startsWith(github.ref, 'refs/tags/') - uses: softprops/action-gh-release@v1 - with: - files: artifacts/*.tar.gz - smoke: needs: build runs-on: ${{ matrix.os }} @@ -112,3 +108,89 @@ jobs: rm checksum-QuickBEAM.Native.exs mix compile mix run scripts/smoke_precompiled.exs + + # Alpine has no runner, so the artifact is loaded in a musl container. The + # project is reduced to the native module: a full checkout would compile + # the Hex dependencies for musl, which is the problem being tested around. + smoke-musl: + name: Smoke ${{ matrix.target }} on Alpine + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { target: x86_64-linux-musl, os: ubuntu-latest, platform: linux/amd64 } + - { target: aarch64-linux-musl, os: ubuntu-24.04-arm, platform: linux/arm64 } + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + name: ${{ matrix.target }} + path: artifacts + + - name: Load and exercise the precompiled NIF on Alpine + shell: bash + env: + ALPINE_IMAGE: hexpm/elixir:1.18.3-erlang-27.3-alpine-3.21.7 + run: | + smoke_dir="${{ runner.temp }}/quickbeam-musl-smoke" + mkdir -p "$smoke_dir/lib/quickbeam/native" "$smoke_dir/artifacts" "$smoke_dir/scripts" + cp lib/quickbeam/native.ex "$smoke_dir/lib/quickbeam/native.ex" + cp lib/quickbeam/native/platform.ex "$smoke_dir/lib/quickbeam/native/platform.ex" + cp scripts/smoke_precompiled_native.exs "$smoke_dir/scripts/smoke.exs" + cp artifacts/*.tar.gz "$smoke_dir/artifacts/" + + cat > "$smoke_dir/mix.exs" <<'ELIXIR' + defmodule QuickBEAMMuslSmoke.MixProject do + use Mix.Project + + def project do + [ + app: :quickbeam, + version: System.fetch_env!("QUICKBEAM_VERSION"), + elixir: "~> 1.15", + deps: [{:zigler_precompiled, "~> 0.1.6"}] + ] + end + + def application, do: [extra_applications: [:logger, :inets, :ssl, :public_key]] + end + ELIXIR + + quickbeam_version=$(sed -n 's/^ @version "\(.*\)"/\1/p' mix.exs | head -n1) + + docker run --rm --platform "${{ matrix.platform }}" \ + -v "$smoke_dir:/workspace" \ + -w /workspace \ + -e QUICKBEAM_VERSION="$quickbeam_version" \ + -e ZIGLER_PRECOMPILED_GLOBAL_CACHE_PATH=/tmp/quickbeam-precompiled \ + "$ALPINE_IMAGE" /bin/sh -lc ' + set -eu + mix local.hex --force + mix local.rebar --force + mix deps.get + mkdir -p "$ZIGLER_PRECOMPILED_GLOBAL_CACHE_PATH" + cp artifacts/*.tar.gz "$ZIGLER_PRECOMPILED_GLOBAL_CACHE_PATH/" + mix compile + mix run scripts/smoke.exs + ' + + # Artifacts reach the release only after every target loaded and ran. + release: + if: startsWith(github.ref, 'refs/tags/') + needs: [build, smoke, smoke-musl] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: Upload artifacts to release + uses: softprops/action-gh-release@v2 + with: + files: artifacts/*.tar.gz diff --git a/CHANGELOG.md b/CHANGELOG.md index d3ca33fd..816ff863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- Precompiled NIFs for `x86_64-linux-musl` and `aarch64-linux-musl`, so Alpine images need no Zig. Each release's artifacts are loaded and run on Alpine before they are published ([#24](https://github.com/elixir-volt/quickbeam/issues/24)). + ### Changed - Allow `oxc` 0.19. diff --git a/README.md b/README.md index b0ecb9f5..67e1e64f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ def deps do end ``` -Precompiled NIFs target Zig's baseline CPU for each supported architecture. +Precompiled NIFs target Zig's baseline CPU and cover x86-64 and ARM64 Linux +with glibc or musl (Alpine included), ARM64 macOS, and x86-64 Windows. Source builds require Zig 0.16 and default to the build machine's CPU; set `QUICKBEAM_CPU=baseline` alongside `QUICKBEAM_BUILD=1` for a portable build. When changing CPU or optimization settings, force recompilation with `mix compile --force`. diff --git a/lib/quickbeam/native.ex b/lib/quickbeam/native.ex index 1392691d..4bd52b62 100644 --- a/lib/quickbeam/native.ex +++ b/lib/quickbeam/native.ex @@ -138,7 +138,14 @@ defmodule QuickBEAM.Native do base_url: "https://github.com/elixir-volt/quickbeam/releases/download/v#{@version}", version: @version, force_build: System.get_env("QUICKBEAM_BUILD") in ["1", "true"], - targets: ~w(x86_64-linux-gnu aarch64-linux-gnu aarch64-macos-none x86_64-windows-gnu), + targets: ~w( + x86_64-linux-gnu + x86_64-linux-musl + aarch64-linux-gnu + aarch64-linux-musl + aarch64-macos-none + x86_64-windows-gnu + ), zig_code_path: "quickbeam.zig", optimize: :env, build_flags: @build_flags, diff --git a/scripts/smoke_precompiled_native.exs b/scripts/smoke_precompiled_native.exs new file mode 100644 index 00000000..5692632b --- /dev/null +++ b/scripts/smoke_precompiled_native.exs @@ -0,0 +1,17 @@ +{:module, QuickBEAM.Native} = Code.ensure_loaded(QuickBEAM.Native) + +resource = QuickBEAM.Native.start_runtime(self(), %{}) +ref = QuickBEAM.Native.eval(resource, "40 + 2", 0, "") + +receive do + {^ref, {:ok, 42}} -> + :ok + + {^ref, other} -> + raise "unexpected eval response: #{inspect(other)}" +after + 30_000 -> raise "NIF eval timed out" +end + +QuickBEAM.Native.stop_runtime(resource) +IO.puts("Precompiled NIF: load, startup, and eval passed")