Revised linux packaging with diags #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 }} |