Skip to content

Revised linux packaging with diags #6

Revised linux packaging with diags

Revised linux packaging with diags #6

Workflow file for this run

name: Build precompiled NIFs
on:
push:
branches:
- main
paths:
# Only run on main when the native crate or this workflow itself
# changes. Tag pushes always run (via the tags trigger below).
- "native/**"
- ".github/workflows/release.yml"
tags:
- "v*"
pull_request:
paths:
- "native/**"
- ".github/workflows/release.yml"
jobs:
build_release:
name: NIF ${{ matrix.nif }} - ${{ matrix.job.target }} (${{ matrix.job.os }})
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: false
matrix:
# NIF version 2.16 covers OTP 24-26; 2.17 covers OTP 27-28.
# Both contemporary versions are built so consumers across the
# range get a precompiled artifact.
nif: ["2.16", "2.17"]
job:
# Apple Silicon. macos-14 runners are arm64 natively.
- { target: aarch64-apple-darwin, os: macos-14 }
# Intel Mac. macos-13 was retired by GitHub on Dec 4 2025;
# macos-15-intel is the replacement and is the last available
# Intel-architecture macOS runner (supported through Aug 2027).
# See https://github.com/actions/runner-images/issues/13045.
- { target: x86_64-apple-darwin, os: macos-15-intel }
# Linux x86_64. Built on ubuntu-22.04 — produces a binary
# that requires glibc >= 2.35. See the comment below about
# tightening this with a manylinux container if older distro
# support becomes a goal.
- { target: x86_64-unknown-linux-gnu, os: ubuntu-22.04 }
# Linux ARM64 via cross-rs (cross-compiled from x86_64 runner).
- { target: aarch64-unknown-linux-gnu, os: ubuntu-22.04, use-cross: true }
# Windows x86_64 (MSVC ABI). Covers >95% of Windows users.
- { target: x86_64-pc-windows-msvc, os: windows-2022 }
steps:
- name: Checkout source
uses: actions/checkout@v6
- name: Extract project version from mix.exs
id: version
shell: bash
run: |
version=$(sed -n 's/^ @version "\(.*\)"$/\1/p' mix.exs | head -1)
if [ -z "$version" ]; then
echo "Failed to extract @version from mix.exs" >&2
exit 1
fi
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "Project version: ${version}"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.job.target }}
- name: Install cross (Linux ARM64)
if: matrix.job.use-cross
shell: bash
run: cargo install --git https://github.com/cross-rs/cross --tag v0.2.5 cross
# Build the NIF directly with cargo. The `ort` crate downloads
# onnxruntime during this step. On macOS the runtime is linked
# statically into the NIF (no sidecar needed). On Linux and
# Windows it remains a separate shared library and must travel
# in the tarball alongside the NIF — that's the packaging step
# below.
- name: Build NIF
shell: bash
working-directory: native/ortex
env:
RUSTLER_NIF_VERSION: ${{ matrix.nif }}
run: |
set -euo pipefail
if [ "${{ matrix.job.use-cross }}" = "true" ]; then
# Cross-compilation: pass --target explicitly. Output goes
# to target/<triple>/release/. Note: cross runs cargo inside
# a Docker container, so libonnxruntime artifacts may not
# surface on the host filesystem — see comment in the
# packaging step below.
cross build --release --target "${{ matrix.job.target }}"
else
# Native build: omit --target. The runner's host arch
# matches matrix.job.target by design, so the default cargo
# target produces the right binary. Crucially, omitting
# --target also makes the `ort` crate's build script stage
# libonnxruntime symlinks in target/release/ — those
# symlinks are skipped when --target is explicit, leaving
# the runtime libraries only in `~/.cache/ort/`. The
# packaging step needs the symlinks to bundle the runtime
# alongside the NIF.
cargo build --release
fi
# Diagnostic: print every libonnxruntime / onnxruntime file in
# the workspace and the user-level ort cache so we can verify
# where cargo + the `ort` crate placed them. Remove this step
# once the layout is well-understood and bundling is verified.
- name: Locate onnxruntime artifacts (diagnostic)
shell: bash
run: |
set +e
echo "=== onnxruntime files in workspace ==="
if find --version >/dev/null 2>&1; then
find "$GITHUB_WORKSPACE" \( -name 'libonnxruntime*' -o -name 'onnxruntime*' \) \
-printf '%p (%s bytes, type=%y)\n' 2>/dev/null
else
find "$GITHUB_WORKSPACE" \( -name 'libonnxruntime*' -o -name 'onnxruntime*' \) \
-exec ls -la {} \;
fi
echo
echo "=== onnxruntime files in ort user cache ==="
# The `ort` crate's default download strategy stages prebuilt
# onnxruntime under the user cache. Locations vary by OS.
for d in "$HOME/.cache/ort" "$HOME/Library/Caches/ort" "$LOCALAPPDATA/ort" "$USERPROFILE/AppData/Local/ort"; do
[ -d "$d" ] || continue
echo "-- $d --"
find "$d" \( -name 'libonnxruntime*' -o -name 'onnxruntime*' \) 2>/dev/null
done
echo
echo "=== native build dir listing (target/release/) ==="
ls -la native/ortex/target/release/ 2>&1 || echo "(does not exist — cross build)"
echo
echo "=== cross build dir listing (target/<triple>/release/) ==="
ls -la "native/ortex/target/${{ matrix.job.target }}/release/" 2>&1 || \
echo "(does not exist — native build)"
# Stage the right files into a tarball matching RustlerPrecompiled's
# expected layout. Naming and contents follow the conventions in
# rustler_precompiled.ex (lib_name_with_ext/2 + lib_prefix/1):
#
# * Tarball name: lib<crate>-v<ver>-nif-<nif>-<target>.so.tar.gz
# on Linux/macOS, <crate>-v<ver>-nif-<nif>-<target>.dll.tar.gz
# on Windows. macOS keeps the .so suffix even though the NIF
# is a Mach-O dylib — RustlerPrecompiled normalises on .so.
#
# * Top-level files in the tarball: the renamed NIF, plus any
# libonnxruntime sidecars cargo produced. We deliberately
# EXCLUDE libonnxruntime_providers_cuda and ..._tensorrt —
# those are >450 MB and only useful with the matching cargo
# features enabled. CPU-only is the default precompile target;
# GPU users opt into source build via ORTEX_BUILD=true.
#
# On macOS the libonnxruntime files don't exist (statically
# linked), so the loop adds nothing and the tarball ends up with
# a single file. That's the correct shape for static builds.
- name: Package artifact
id: pkg
shell: bash
env:
VERSION: ${{ steps.version.outputs.version }}
TARGET: ${{ matrix.job.target }}
NIF: ${{ matrix.nif }}
run: |
set -euo pipefail
case "$TARGET" in
*-pc-windows-msvc|*-pc-windows-gnu)
prefix=""
nif_src_ext="dll"
tarball_ext="dll"
runtime_ext="dll"
;;
*-apple-darwin)
prefix="lib"
nif_src_ext="dylib"
tarball_ext="so"
runtime_ext="dylib"
;;
*-unknown-linux-*)
prefix="lib"
nif_src_ext="so"
tarball_ext="so"
runtime_ext="so"
;;
*)
echo "Unsupported target: $TARGET" >&2
exit 1
;;
esac
NAME="${prefix}ortex-v${VERSION}-nif-${NIF}-${TARGET}"
TARBALL="${NAME}.${tarball_ext}.tar.gz"
# Build output location depends on whether --target was used.
# Native builds (cargo build --release) land in target/release/.
# Cross builds (cross build --release --target X) land in
# target/<X>/release/. The "Build NIF" step controls which.
if [ "${{ matrix.job.use-cross }}" = "true" ]; then
BUILD_DIR="native/ortex/target/${TARGET}/release"
else
BUILD_DIR="native/ortex/target/release"
fi
mkdir -p pkg
cd pkg
rm -f -- *
# Copy the NIF, renaming to the long versioned filename
# RustlerPrecompiled expects. -L follows symlinks (cargo
# sometimes leaves the .so as a symlink into deps/).
cp -L "../${BUILD_DIR}/${prefix}ortex.${nif_src_ext}" "${NAME}.${tarball_ext}"
# Bundle libonnxruntime sidecars if present. Exclude GPU
# provider libraries (huge; not used by CPU-only NIFs).
shopt -s nullglob
for src in "../${BUILD_DIR}/${prefix}onnxruntime"*."${runtime_ext}"*; do
base="$(basename "$src")"
case "$base" in
*providers_cuda*|*providers_tensorrt*)
echo "Excluding GPU provider library: $base"
;;
*)
echo "Bundling: $base"
cp -L "$src" "$base"
;;
esac
done
tar -czf "$TARBALL" -- *
echo "=== Final tarball contents ==="
tar -tzvf "$TARBALL"
echo "=== Tarball size ==="
ls -lh "$TARBALL"
echo "tarball=pkg/${TARBALL}" >> "$GITHUB_OUTPUT"
echo "tarball-name=${TARBALL}" >> "$GITHUB_OUTPUT"
- name: Upload artifact (workflow run)
uses: actions/upload-artifact@v7
with:
name: ${{ steps.pkg.outputs.tarball-name }}
path: ${{ steps.pkg.outputs.tarball }}
- name: Attach to GitHub release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v3
with:
draft: true
files: ${{ steps.pkg.outputs.tarball }}