name: Release CLI
on:
workflow_dispatch: inputs:
version:
description: "Version to release (without v prefix). Leave empty to use Cargo.toml version."
required: false
type: string
workflow_call: inputs:
version:
description: "Version to release (without v prefix)"
required: false
type: string
env:
CARGO_TERM_COLOR: always
CARGO_BUILD_JOBS: 2
permissions:
contents: write
jobs:
version-check:
name: Version Check
uses: ./.github/workflows/version-check.yml
build-cli:
needs: version-check
name: Build CLI
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
artifact_name: odm-linux-x86_64
binary_ext: ""
- os: macos-latest
target: x86_64-apple-darwin
artifact_name: odm-macos-x86_64
binary_ext: ""
- os: macos-latest
target: aarch64-apple-darwin
artifact_name: odm-macos-arm64
binary_ext: ""
- os: windows-latest
target: x86_64-pc-windows-msvc
artifact_name: odm-windows-x86_64
binary_ext: ".exe"
steps:
- uses: actions/checkout@v4
- name: Free disk space (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
echo "Disk space before cleanup:"
df -h /
# Remove large unnecessary tools
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/local/lib/android
sudo rm -rf /opt/ghc
sudo rm -rf /opt/hostedtoolcache/CodeQL
sudo rm -rf /usr/local/share/boost
sudo rm -rf /usr/share/swift
# Clean apt cache
sudo apt-get clean
echo "Disk space after cleanup:"
df -h /
- name: Add swap space (Linux)
if: matrix.os == 'ubuntu-latest'
run: |
sudo swapoff -a || true
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo "Swap space configured:"
free -h
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Add target for cross-compilation (if needed)
if: matrix.os != 'windows-latest' && matrix.target != 'x86_64-unknown-linux-gnu'
run: rustup target add ${{ matrix.target }}
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-cargo-
- name: Build CLI binary (odm)
shell: bash
run: |
cargo build --release --package odm --features cli-full,openapi,odps-validation --target ${{ matrix.target }}
- name: Import Code Signing Certificate (macOS)
if: startsWith(matrix.os, 'macos')
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
run: |
if [ -n "$MACOS_CERTIFICATE" ]; then
# Create a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/signing.keychain-db
security create-keychain -p "$MACOS_KEYCHAIN_PWD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$MACOS_KEYCHAIN_PWD" $KEYCHAIN_PATH
# Import certificate
echo "$MACOS_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12
security import $RUNNER_TEMP/certificate.p12 -P "$MACOS_CERTIFICATE_PWD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Allow codesign to access the key
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_KEYCHAIN_PWD" $KEYCHAIN_PATH
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
echo "SIGNING_ENABLED=true" >> $GITHUB_ENV
else
echo "No signing certificate provided, skipping code signing"
echo "SIGNING_ENABLED=false" >> $GITHUB_ENV
fi
- name: Sign macOS binary
if: startsWith(matrix.os, 'macos') && env.SIGNING_ENABLED == 'true'
env:
MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
run: |
BINARY_PATH="target/${{ matrix.target }}/release/odm"
echo "Signing binary: $BINARY_PATH"
codesign --force --options runtime --sign "$MACOS_SIGNING_IDENTITY" "$BINARY_PATH"
echo "Verifying signature..."
codesign --verify --verbose "$BINARY_PATH"
- name: Notarize macOS binary
if: startsWith(matrix.os, 'macos') && env.SIGNING_ENABLED == 'true'
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
if [ -n "$APPLE_ID" ] && [ -n "$APPLE_ID_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
BINARY_PATH="target/${{ matrix.target }}/release/odm"
# Create a zip for notarization
NOTARIZE_ZIP="$RUNNER_TEMP/odm-notarize.zip"
ditto -c -k --keepParent "$BINARY_PATH" "$NOTARIZE_ZIP"
# Submit for notarization
echo "Submitting for notarization..."
xcrun notarytool submit "$NOTARIZE_ZIP" \
--apple-id "$APPLE_ID" \
--password "$APPLE_ID_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
echo "Notarization complete"
else
echo "Apple notarization credentials not provided, skipping notarization"
fi
- name: Get version
id: version
shell: bash
env:
INPUT_VERSION: ${{ inputs.version }}
VERIFIED_VERSION: ${{ needs.version-check.outputs.version }}
run: |
if [ -n "$INPUT_VERSION" ]; then
# Version passed from workflow_call
VERSION="$INPUT_VERSION"
elif [ -n "$VERIFIED_VERSION" ]; then
# Use version from version-check job
VERSION="$VERIFIED_VERSION"
elif [[ "${{ github.ref }}" == refs/tags/* ]]; then
# Tag release: use tag version
VERSION=${GITHUB_REF#refs/tags/v}
elif [[ "${{ github.ref }}" == refs/heads/release/* ]]; then
# Release branch: use Cargo.toml version with dev postfix
BASE_VERSION=$(grep -A 10 '^\[package\]' Cargo.toml | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/')
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
VERSION="${BASE_VERSION}-dev.${SHORT_SHA}"
else
# Manual run: use Cargo.toml version with dev postfix
BASE_VERSION=$(grep -A 10 '^\[package\]' Cargo.toml | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/')
VERSION="${BASE_VERSION}-dev"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Building CLI version: $VERSION"
- name: Prepare binary for upload (Unix)
if: matrix.os != 'windows-latest'
id: prepare-unix
shell: bash
run: |
BINARY_NAME="odm${{ matrix.binary_ext }}"
BINARY_PATH="target/${{ matrix.target }}/release/$BINARY_NAME"
ARCHIVE_NAME="${{ matrix.artifact_name }}-v${{ steps.version.outputs.version }}.tar.gz"
tar -czf $ARCHIVE_NAME -C target/${{ matrix.target }}/release $BINARY_NAME
echo "archive_name=$ARCHIVE_NAME" >> $GITHUB_OUTPUT
echo "binary_path=$BINARY_PATH" >> $GITHUB_OUTPUT
- name: Prepare binary for upload (Windows)
if: matrix.os == 'windows-latest'
id: prepare-windows
shell: pwsh
run: |
$BINARY_NAME = "odm${{ matrix.binary_ext }}"
$BINARY_PATH = "target/${{ matrix.target }}/release/$BINARY_NAME"
$ARCHIVE_NAME = "${{ matrix.artifact_name }}-v${{ steps.version.outputs.version }}.zip"
$SOURCE_DIR = "target/${{ matrix.target }}/release"
Compress-Archive -Path "$SOURCE_DIR\$BINARY_NAME" -DestinationPath $ARCHIVE_NAME -Force
echo "archive_name=$ARCHIVE_NAME" >> $env:GITHUB_OUTPUT
echo "binary_path=$BINARY_PATH" >> $env:GITHUB_OUTPUT
- name: Set prepare outputs
id: prepare
shell: bash
run: |
if [ "${{ matrix.os }}" = "windows-latest" ]; then
echo "archive_name=${{ steps.prepare-windows.outputs.archive_name }}" >> $GITHUB_OUTPUT
echo "binary_path=${{ steps.prepare-windows.outputs.binary_path }}" >> $GITHUB_OUTPUT
else
echo "archive_name=${{ steps.prepare-unix.outputs.archive_name }}" >> $GITHUB_OUTPUT
echo "binary_path=${{ steps.prepare-unix.outputs.binary_path }}" >> $GITHUB_OUTPUT
fi
- name: Output version info
shell: bash
run: |
echo "Version: ${{ steps.version.outputs.version }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ steps.prepare.outputs.archive_name }}
retention-days: 30
update-release:
name: Update GitHub Release
needs: [version-check, build-cli]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get version
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
VERIFIED_VERSION: ${{ needs.version-check.outputs.version }}
run: |
if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
elif [ -n "$VERIFIED_VERSION" ]; then
VERSION="$VERIFIED_VERSION"
else
echo "No version available"
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Check if this is a dev release
if [[ "$VERSION" == *"-dev"* ]]; then
echo "is_prerelease=true" >> $GITHUB_OUTPUT
else
echo "is_prerelease=false" >> $GITHUB_OUTPUT
fi
- name: Download all CLI artifacts
uses: actions/download-artifact@v4
with:
path: artifacts/
pattern: odm-*
- name: Create checksums
run: |
cd artifacts
find . -type f \( -name "*.tar.gz" -o -name "*.zip" \) | while read file; do
sha256sum "$file" > "${file}.sha256"
done
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: v${{ steps.version.outputs.version }}
name: v${{ steps.version.outputs.version }}
body: |
## CLI Release v${{ steps.version.outputs.version }}
**Linux (x86_64):** odm-linux-x86_64-v${{ steps.version.outputs.version }}.tar.gz
**macOS (Intel):** odm-macos-x86_64-v${{ steps.version.outputs.version }}.tar.gz
**macOS (Apple Silicon):** odm-macos-arm64-v${{ steps.version.outputs.version }}.tar.gz
**Windows (x86_64):** odm-windows-x86_64-v${{ steps.version.outputs.version }}.zip
files: |
artifacts/**/*.tar.gz
artifacts/**/*.zip
artifacts/**/*.sha256
fail_on_unmatched_files: false
prerelease: ${{ steps.version.outputs.is_prerelease }}