Skip to content

chore(release): v0.2.7 #39

chore(release): v0.2.7

chore(release): v0.2.7 #39

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*'
permissions:
contents: write
id-token: write
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
VERSION: ${{ github.ref_name }}
jobs:
build-release:
environment: Production
name: Build ${{ matrix.platform.release_for }}
env:
RUNMAT_TELEMETRY_KEY: ${{ secrets.TELEMETRY_INGESTION_KEY }}
strategy:
max-parallel: 8
matrix:
platform:
- release_for: windows-x86_64
os: windows-latest-l
target: x86_64-pc-windows-msvc
bin: runmat.exe
name: runmat-windows-x86_64.zip
command: build
- release_for: macos-x86_64
os: macOS-latest
target: x86_64-apple-darwin
bin: runmat
name: runmat-darwin-x86_64.tar.gz
command: build
- release_for: macos-aarch64
os: macOS-latest
target: aarch64-apple-darwin
bin: runmat
name: runmat-darwin-aarch64.tar.gz
command: build
- release_for: linux-x86_64
os: ubuntu-latest-gpu
target: x86_64-unknown-linux-gnu
bin: runmat
name: runmat-linux-x86_64.tar.gz
command: build
runs-on: ${{ matrix.platform.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Install BLAS/LAPACK dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libopenblas-dev liblapack-dev libzmq3-dev pkg-config libssl-dev
- name: Install macOS dependencies (ZMQ)
if: runner.os == 'macOS'
run: |
brew update
brew install zeromq
- name: Install Windows MSVC dependencies (ZMQ, vcpkg + OpenBLAS/LAPACK)
if: runner.os == 'Windows' && matrix.platform.target != 'aarch64-pc-windows-gnu'
shell: pwsh
run: |
choco install -y pkgconfiglite
Write-Host "Fetching prebuilt ZeroMQ for Windows (MSVC)"
curl -L -o libzmq.zip https://github.com/zeromq/libzmq/releases/download/v4.3.5/zeromq-4.3.5.zip
Expand-Archive -Path libzmq.zip -DestinationPath zmq
# Set env vars for ZMQ include/lib
$zmq = Join-Path $pwd "zmq"
Add-Content -Path $env:GITHUB_ENV -Value "INCLUDE=$($zmq)\include;$env:INCLUDE"
Add-Content -Path $env:GITHUB_ENV -Value "LIB=$($zmq)\lib;$env:LIB"
Add-Content -Path $env:GITHUB_ENV -Value "ZMQ_PATH=$($zmq)"
Add-Content -Path $env:GITHUB_ENV -Value "PKG_CONFIG_PATH=$($zmq)\lib\pkgconfig"
Write-Host "Cloning vcpkg and installing OpenBLAS/LAPACK"
$vcpkgRoot = Join-Path $pwd "vcpkg"
git clone https://github.com/microsoft/vcpkg.git $vcpkgRoot
& "$vcpkgRoot\bootstrap-vcpkg.bat"
# Compute vcpkg triplet based on target
$target = "${{ matrix.platform.target }}"
if ($target -eq "aarch64-pc-windows-msvc") {
$triplet = "arm64-windows"
} else {
$triplet = "x64-windows"
}
# Ensure triplet in env
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_ROOT=$vcpkgRoot"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKG_DEFAULT_TRIPLET=$triplet"
Add-Content -Path $env:GITHUB_ENV -Value "VCPKGRS_TRIPLET=$triplet"
# Install OpenBLAS and LAPACK provider; on arm64-windows allow unsupported gfortran
& "$vcpkgRoot\vcpkg.exe" install "openblas:$triplet"
if ($triplet -eq "arm64-windows") {
& "$vcpkgRoot\vcpkg.exe" install "lapack-reference:$triplet" --allow-unsupported
} else {
& "$vcpkgRoot\vcpkg.exe" install "lapack-reference:$triplet"
}
Add-Content -Path $env:GITHUB_ENV -Value "VCPKGRS_DYNAMIC=1"
Add-Content -Path $env:GITHUB_PATH -Value "$vcpkgRoot\installed\$triplet\bin"
# Hint BLAS/LAPACK discovery for blas-sys/lapack-sys
Add-Content -Path $env:GITHUB_ENV -Value "BLAS_LIB_DIR=$vcpkgRoot\installed\$triplet\lib"
Add-Content -Path $env:GITHUB_ENV -Value "BLAS_LIBS=openblas"
Add-Content -Path $env:GITHUB_ENV -Value "LAPACK_LIB_DIR=$vcpkgRoot\installed\$triplet\lib"
Add-Content -Path $env:GITHUB_ENV -Value "LAPACK_LIBS=lapack;openblas"
Add-Content -Path $env:GITHUB_ENV -Value "OPENBLAS_DIR=$vcpkgRoot\installed\$triplet"
- name: Cross build (linux-x86_64)
if: matrix.platform.target == 'x86_64-unknown-linux-gnu'
uses: houseabsolute/actions-rust-cross@v0
with:
command: ${{ matrix.platform.command }}
target: ${{ matrix.platform.target }}
# Use system OpenSSL via pkg-config; libssl-dev installed above
args: "--locked --release --bin runmat --features blas-lapack"
strip: true
- name: Cross build (other targets)
if: matrix.platform.target != 'x86_64-unknown-linux-gnu'
uses: houseabsolute/actions-rust-cross@v0
env:
OPENSSL_NO_PKG_CONFIG: '1'
OPENSSL_STATIC: '1'
with:
command: ${{ matrix.platform.command }}
target: ${{ matrix.platform.target }}
# Build all targets with vendored OpenSSL and full BLAS+LAPACK
args: "--locked --release --bin runmat --features blas-lapack,vendored-openssl"
strip: true
- name: Import signing cert and codesign (macOS)
if: runner.os == 'macOS'
shell: bash
env:
MACOS_CERT_P12: ${{ secrets.MACOS_CERT_P12 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
MACOS_CERT_IDENTITY: ${{ secrets.MACOS_CERT_IDENTITY }}
run: |
set -euxo pipefail
# Prepare keychain with Developer ID Application certificate
KEYCHAIN=build.keychain
KEYCHAIN_PWD=$(uuidgen)
echo "$MACOS_CERT_P12" | base64 --decode > cert.p12
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security import cert.p12 -k "$KEYCHAIN" -P "$MACOS_CERT_PASSWORD" -T /usr/bin/codesign
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
security default-keychain -s "$KEYCHAIN"
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PWD" "$KEYCHAIN"
BIN="target/${{ matrix.platform.target }}/release/runmat"
# Minimal entitlements to support JIT and dynamic loading under Hardened Runtime
cat > entitlements.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
<key>com.apple.security.cs.disable-library-validation</key><true/>
</dict>
</plist>
EOF
codesign --force --timestamp --options runtime --entitlements entitlements.plist \
--sign "$MACOS_CERT_IDENTITY" "$BIN"
codesign -dv --verbose=4 "$BIN" || true
- name: Notarize (macOS)
if: runner.os == 'macOS'
shell: bash
env:
APPLE_NOTARIZE_KEY_ID: ${{ secrets.APPLE_NOTARIZE_KEY_ID }}
APPLE_NOTARIZE_ISSUER_ID: ${{ secrets.APPLE_NOTARIZE_ISSUER_ID }}
APPLE_NOTARIZE_PRIVATE_KEY: ${{ secrets.APPLE_NOTARIZE_PRIVATE_KEY }}
run: |
set -euxo pipefail
BIN="target/${{ matrix.platform.target }}/release/runmat"
# Create a temporary zip only for notarization submission (do not ship this)
ZIP_NAME="notarize-submit.zip"
/usr/bin/zip -j "$ZIP_NAME" "$BIN"
echo "$APPLE_NOTARIZE_PRIVATE_KEY" | base64 --decode > notary_key.p8
# Submit and wait for notarization result
xcrun notarytool submit "$ZIP_NAME" --key notary_key.p8 \
--key-id "$APPLE_NOTARIZE_KEY_ID" --issuer "$APPLE_NOTARIZE_ISSUER_ID" --wait
# Staple ticket to binary if possible (no-op for some formats)
xcrun stapler staple "$BIN" || true
rm -f "$ZIP_NAME" notary_key.p8
- name: Package as archive (Windows)
if: runner.os == 'Windows' && matrix.platform.target != 'aarch64-pc-windows-gnu'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$target = "${{ matrix.platform.target }}"
$outDir = Join-Path "target" (Join-Path $target "release")
$artifactName = "runmat-${{ env.VERSION }}-${{ matrix.platform.release_for }}"
Push-Location $outDir
# Copy required runtime DLLs into output dir
$triplet = $env:VCPKG_DEFAULT_TRIPLET
$vcpkgBin = Join-Path $env:VCPKG_ROOT ("installed\" + $triplet + "\bin")
if (Test-Path $vcpkgBin) { Copy-Item -Path (Join-Path $vcpkgBin "*.dll") -Destination . -Force -ErrorAction SilentlyContinue }
$zmqBin = Join-Path $env:ZMQ_PATH "bin"
if (Test-Path $zmqBin) { Copy-Item -Path (Join-Path $zmqBin "*.dll") -Destination . -Force -ErrorAction SilentlyContinue }
else {
$zmqLib = Join-Path $env:ZMQ_PATH "lib"
if (Test-Path $zmqLib) { Copy-Item -Path (Join-Path $zmqLib "*.dll") -Destination . -Force -ErrorAction SilentlyContinue }
}
# Create zip with binary and DLLs
7z a (Join-Path "..\..\.." ("$artifactName.zip")) ${{ matrix.platform.bin }} *.dll
Pop-Location
- name: Package as archive (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
cd target/${{ matrix.platform.target }}/release
ARTIFACT_NAME="runmat-${{ env.VERSION }}-${{ matrix.platform.release_for }}"
tar czvf ../../../${ARTIFACT_NAME}.tar.gz ${{ matrix.platform.bin }}
cd -
- name: Publish release artifacts
uses: actions/upload-artifact@v4
with:
name: runmat-${{ env.VERSION }}-${{ matrix.platform.release_for }}
path: |
runmat-*.tar.gz
runmat-*.zip
create-release:
name: Create Release
needs: [build-release, publish-crates]
environment: Production
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
- name: Create release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ github.ref_name }}
name: RunMat ${{ github.ref_name }}
body: |
## RunMat ${{ github.ref_name }}
High-performance MATLAB/Octave runtime with Jupyter kernel support.
### Installation
#### Quick Install (Recommended)
```bash
# Linux/macOS
curl -fsSL https://runmat.org/install.sh | sh
# Windows (PowerShell)
iwr https://runmat.org/install.ps1 | iex
```
#### Manual Installation
1. Download the appropriate binary for your platform below
2. Extract the archive
3. Add the binary to your PATH
### Documentation
- [Getting Started](https://runmat.org/docs/getting-started)
files: |
artifacts/**/*
draft: false
prerelease: false
publish-crates:
name: Publish crates to crates.io
needs: [build-release]
environment: Production
runs-on: ["self-hosted", "linux", "gpu"]
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.ref_name }}
- name: Install BLAS/LAPACK/ZMQ dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libopenblas-dev liblapack-dev libzmq3-dev pkg-config libssl-dev
- name: Install Rust toolchain
uses: dtolnay/[email protected]
with:
components: clippy,rustfmt
- name: Install cargo-workspaces
run: cargo install cargo-workspaces --locked
- name: Derive release version (strip leading 'v')
shell: bash
run: |
RAW_VERSION="${{ env.VERSION }}"
CLEAN_VERSION=${RAW_VERSION#v}
echo "RELEASE_VERSION=$CLEAN_VERSION" >> $GITHUB_ENV
echo "Using release version: $CLEAN_VERSION"
- name: Ensure LICENSE is included in each crate
shell: bash
run: |
set -euxo pipefail
for d in crates/* runmat; do
if [ -d "$d" ]; then
# Copy root LICENSE to each crate as LICENSE (crates.io requires in-package)
if [ -f LICENSE ] || [ -f LICENSE.md ]; then
cp -f LICENSE* "$d/" || true
# Normalize to LICENSE for crates.io scanners
if [ -f "$d/LICENSE.md" ]; then mv -f "$d/LICENSE.md" "$d/LICENSE"; fi
fi
fi
done
- name: Normalize Cargo.toml license metadata
shell: bash
run: |
set -euxo pipefail
for toml in crates/*/Cargo.toml runmat/Cargo.toml; do
# If license-file points outside the crate, retarget it to in-crate LICENSE
if grep -q '^license-file\s*=\s*"\..*/LICENSE' "$toml"; then
sed -i.bak 's#^license-file\s*=\s*"\..*/LICENSE.*#license-file = "LICENSE"#' "$toml"
rm -f "$toml.bak"
fi
# If license not set at all, but LICENSE exists, add MIT license fallback
if ! grep -q '^license\s*=' "$toml" && [ -f "${toml%/*}/LICENSE" ]; then
# Append license = "MIT" under [package] if not present
awk 'BEGIN{printed=0} {print} /^\[package\]/{if(!printed){print "license = \"MIT\""; printed=1}}' "$toml" > "$toml.tmp" && mv "$toml.tmp" "$toml"
fi
done
- name: Prepare branch context for publish (main)
shell: bash
run: |
set -euxo pipefail
# Ensure releases are cut from main: verify tag commit is on origin/main
git fetch origin +refs/heads/main:refs/remotes/origin/main || true
if ! git show-ref --verify --quiet refs/remotes/origin/main; then
echo "origin/main not found" >&2
exit 1
fi
if ! git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main; then
echo "Tag commit $GITHUB_SHA is not on origin/main. Aborting publish." >&2
exit 1
fi
# Create local main at the tag commit to satisfy tools that require a branch
git checkout -B main "$GITHUB_SHA"
# Set env for later steps
echo "CARGO_PUBLISH_BRANCH=main" >> "$GITHUB_ENV"
- name: Verify crate versions match tag (no bump in CI)
shell: bash
run: |
set -euo pipefail
mismatches=()
check_file() {
local file="$1"
# Extract version within [package] table
local ver_line
ver_line=$(awk 'BEGIN{inpkg=0}
/^\[package\]/{inpkg=1; next}
/^\[/{if(inpkg){exit}; inpkg=0}
{ if(inpkg && $0 ~ /^version[[:space:]]*=/){ print; exit } }' "$file" || true)
if [ -z "${ver_line:-}" ]; then return; fi
local ver
ver=$(printf '%s' "$ver_line" | sed -E 's/.*version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/')
if [ -n "${ver:-}" ] && [ "$ver" != "$RELEASE_VERSION" ]; then
mismatches+=("$file:$ver != $RELEASE_VERSION")
fi
}
for toml in runmat/Cargo.toml crates/*/Cargo.toml; do
[ -f "$toml" ] || continue
check_file "$toml"
done
if [ ${#mismatches[@]} -gt 0 ]; then
echo "Version mismatch between tag and Cargo.toml files:" >&2
printf ' - %s\n' "${mismatches[@]}" >&2
echo "Please merge a version bump PR on main before tagging." >&2
exit 1
fi
- name: Preflight crates publish (dry-run)
shell: bash
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
GIT_SSL_CAINFO: /etc/ssl/certs/ca-certificates.crt
run: |
set -euo pipefail
# Let dry-run fail loudly if compilation or verification fails
DRY_RUN_OUTPUT="$(cargo workspaces publish --all --yes --allow-dirty --skip-published --dry-run --from-git --no-verify --token "$CARGO_REGISTRY_TOKEN" 2>&1)"
echo "$DRY_RUN_OUTPUT"
if ! echo "$DRY_RUN_OUTPUT" | grep -Eqi 'publish(ing)?[[:space:]]+[a-z0-9_-]'; then
echo "No crates selected for publish (dry run). Failing to avoid false green release."
exit 1
fi
- name: Publish all crates in dependency order
shell: bash
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
GIT_SSL_CAINFO: /etc/ssl/certs/ca-certificates.crt
run: |
set -euxo pipefail
# Ensure git identity exists for any local tagging that tools may perform
git config user.email "${GITHUB_ACTOR:-github-actions}@users.noreply.github.com" || true
git config user.name "${GITHUB_ACTOR:-github-actions}" || true
# Silence git CA warning in some envs
if [ -f /etc/ssl/certs/ca-certificates.crt ]; then
git config --global http.cainfo /etc/ssl/certs/ca-certificates.crt || true
git config --global http.sslcainfo /etc/ssl/certs/ca-certificates.crt || true
fi
# Publish from local main with branch guard, retrying on crates.io rate limits (HTTP 429)
MAX_RETRIES=4
ATTEMPT=1
while [ $ATTEMPT -le $MAX_RETRIES ]; do
set +e
OUTPUT=$(cargo workspaces publish --all --yes --allow-dirty --skip-published --from-git \
--allow-branch "$CARGO_PUBLISH_BRANCH" \
--token "$CARGO_REGISTRY_TOKEN" 2>&1)
STATUS=$?
set -e
echo "$OUTPUT"
if [ $STATUS -eq 0 ]; then
echo "Publish succeeded."
break
fi
if echo "$OUTPUT" | grep -qi 'Too Many Requests'; then
echo "Hit crates.io rate limit (HTTP 429). Sleeping 900 seconds before retry ($ATTEMPT/$MAX_RETRIES)..."
sleep 900
ATTEMPT=$((ATTEMPT + 1))
continue
fi
echo "Publish failed with non-rate-limit error. Aborting."
exit $STATUS
done
if [ $ATTEMPT -gt $MAX_RETRIES ]; then
echo "Exceeded maximum retries due to crates.io rate limiting. Please retry later or contact [email protected]."
exit 1
fi