data-modelling-sdk 2.4.0

Shared SDK for model operations across platforms (API, WASM, Native)
Documentation
name: Release CLI

on:
  workflow_dispatch: # Allow manual triggering
    inputs:
      version:
        description: "Version to release (without v prefix). Leave empty to use Cargo.toml version."
        required: false
        type: string
  workflow_call: # Allow being called from publish.yml
    inputs:
      version:
        description: "Version to release (without v prefix)"
        required: false
        type: string

env:
  CARGO_TERM_COLOR: always
  # Reduce parallelism to lower memory usage during DuckDB compilation
  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

      # Free up disk space on Linux runners (removes ~30GB of unused tools)
      - 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 /

      # Add swap space for memory-intensive DuckDB linking (Linux)
      - 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 }}