chore(release): v0.2.7 #39
Workflow file for this run
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: 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 |