chore(deps)(deps): bump lru from 0.17.0 to 0.18.0 in /src-tauri #561
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*' | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: 'Release tag (for example v1.2.3)' | |
| required: true | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| sync-version: | |
| name: Sync Version Metadata | |
| if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event.inputs.tag && startsWith(github.event.inputs.tag, 'v')) }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| release_tag: ${{ steps.context.outputs.release_tag }} | |
| release_ref: ${{ steps.context.outputs.release_ref }} | |
| release_version: ${{ steps.context.outputs.release_version }} | |
| default_branch: ${{ steps.context.outputs.default_branch }} | |
| changes_detected: ${{ steps.diff.outputs.changed }} | |
| steps: | |
| - name: Generate GitHub App token | |
| id: smg-actions | |
| uses: getsentry/action-github-app-token@v3 | |
| with: | |
| app_id: ${{ secrets.APP_ID }} | |
| private_key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - name: Resolve release context | |
| id: context | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF_TYPE: ${{ github.ref_type }} | |
| REF_NAME: ${{ github.ref_name }} | |
| INPUT_TAG: ${{ github.event.inputs.tag || '' }} | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'master' }} | |
| run: | | |
| tag="" | |
| if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
| tag="$INPUT_TAG" | |
| elif [[ "$REF_TYPE" == "tag" ]]; then | |
| tag="$REF_NAME" | |
| fi | |
| if [[ -z "$tag" ]]; then | |
| echo "Release tag is required (tag push or workflow input)." >&2 | |
| exit 1 | |
| fi | |
| version="${tag#v}" | |
| if [[ "$version" == "$tag" ]]; then | |
| version="$tag" | |
| fi | |
| echo "release_tag=$tag" >> "$GITHUB_OUTPUT" | |
| echo "release_ref=refs/tags/$tag" >> "$GITHUB_OUTPUT" | |
| echo "release_version=$version" >> "$GITHUB_OUTPUT" | |
| echo "default_branch=$DEFAULT_BRANCH" >> "$GITHUB_OUTPUT" | |
| echo "RELEASE_TAG=$tag" >> "$GITHUB_ENV" | |
| echo "RELEASE_REF=refs/tags/$tag" >> "$GITHUB_ENV" | |
| echo "RELEASE_VERSION=$version" >> "$GITHUB_ENV" | |
| echo "TARGET_BRANCH=$DEFAULT_BRANCH" >> "$GITHUB_ENV" | |
| - name: Checkout target branch | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ steps.context.outputs.default_branch }} | |
| token: ${{ steps.smg-actions.outputs.token }} | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: 20 | |
| - name: Update npm metadata | |
| shell: bash | |
| run: | | |
| current=$(node -p "require('./package.json').version") | |
| if [[ "$current" == "$RELEASE_VERSION" ]]; then | |
| echo "package.json already version $current; skipping." | |
| else | |
| npm version "$RELEASE_VERSION" --no-git-tag-version | |
| fi | |
| - name: Update Tauri config | |
| run: | | |
| node <<'NODE' | |
| const fs = require('fs'); | |
| const version = process.env.RELEASE_VERSION; | |
| const file = 'src-tauri/tauri.conf.json'; | |
| const content = fs.readFileSync(file, 'utf8'); | |
| const json = JSON.parse(content); | |
| json.version = version; | |
| fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n'); | |
| NODE | |
| - name: Format version metadata | |
| run: npx --yes prettier@3.6.2 --write package.json package-lock.json src-tauri/tauri.conf.json | |
| - name: Detect changes | |
| id: diff | |
| run: | | |
| if git diff --quiet; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Commit version bump | |
| if: steps.diff.outputs.changed == 'true' | |
| uses: stefanzweifel/git-auto-commit-action@v7 | |
| with: | |
| commit_message: 'chore: sync version to ${{ env.RELEASE_TAG }}' | |
| branch: ${{ env.TARGET_BRANCH }} | |
| commit_user_name: marlin-release-bot | |
| commit_user_email: releases@marlin.app | |
| - name: Ensure release tag exists | |
| if: steps.diff.outputs.changed == 'true' | |
| run: | | |
| git fetch --tags --force | |
| git tag -f "$RELEASE_TAG" | |
| git push origin "refs/tags/$RELEASE_TAG" --force | |
| - name: Verify tag matches synced version | |
| if: steps.diff.outputs.changed == 'true' | |
| run: | | |
| if ! git diff --quiet; then | |
| echo "Version sync failed; release tag $RELEASE_TAG still differs." >&2 | |
| exit 1 | |
| fi | |
| release: | |
| name: Release (${{ matrix.arch }}-${{ matrix.target }}) | |
| needs: sync-version | |
| if: ${{ github.event_name != 'pull_request' && needs.sync-version.result == 'success' }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS Apple Silicon (M1/M2/M3) - GitHub-hosted | |
| - os: macos-15 | |
| target: aarch64-apple-darwin | |
| arch: arm64 | |
| rust-targets: aarch64-apple-darwin | |
| smb: true | |
| self_hosted: false | |
| # macOS Intel - GitHub-hosted | |
| - os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| arch: x86_64 | |
| rust-targets: x86_64-apple-darwin | |
| smb: false | |
| self_hosted: false | |
| # Linux x86_64 | |
| - os: [self-hosted, Linux, X64, medium] | |
| target: x86_64-unknown-linux-gnu | |
| arch: x86_64 | |
| rust-targets: x86_64-unknown-linux-gnu | |
| smb: true | |
| self_hosted: true | |
| # Linux arm64 - GitHub-hosted (no self-hosted arm64 runner yet) | |
| - os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| arch: arm64 | |
| rust-targets: aarch64-unknown-linux-gnu | |
| smb: true | |
| self_hosted: false | |
| permissions: | |
| contents: write | |
| env: | |
| RELEASE_TAG: ${{ needs.sync-version.outputs.release_tag || github.ref_name }} | |
| RELEASE_REF: ${{ needs.sync-version.outputs.release_ref || github.ref }} | |
| steps: | |
| - name: Setup persistent cargo target dir (self-hosted) | |
| if: matrix.self_hosted | |
| run: | | |
| # Use persistent target dir outside workspace so it survives between different repo checkouts | |
| echo "CARGO_TARGET_DIR=$HOME/.cache/cargo-target/marlin-${{ matrix.target }}" >> $GITHUB_ENV | |
| mkdir -p "$HOME/.cache/cargo-target/marlin-${{ matrix.target }}" | |
| - name: Validate release tag | |
| shell: bash | |
| run: | | |
| if [ -z "$RELEASE_TAG" ]; then | |
| echo "RELEASE_TAG is not available." >&2 | |
| exit 1 | |
| fi | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ env.RELEASE_REF }} | |
| - name: Install dependencies (Ubuntu) | |
| if: runner.os == 'Linux' | |
| run: | | |
| apt_retry() { | |
| for attempt in 1 2 3 4 5 6 7 8 9 10 11 12; do | |
| if sudo DEBIAN_FRONTEND=noninteractive apt-get "$@"; then | |
| return 0 | |
| fi | |
| echo "apt-get $* failed on attempt $attempt; retrying in 10s..." >&2 | |
| sleep 10 | |
| done | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get "$@" | |
| } | |
| apt_retry update | |
| apt_retry install -y \ | |
| build-essential \ | |
| clang \ | |
| curl \ | |
| wget \ | |
| file \ | |
| xdg-utils \ | |
| libssl-dev \ | |
| libclang-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| pkg-config \ | |
| patchelf \ | |
| libglib2.0-bin \ | |
| libgdk-pixbuf2.0-bin \ | |
| desktop-file-utils \ | |
| shared-mime-info \ | |
| gperf | |
| apt_retry install -y libwebkit2gtk-4.1-dev || apt_retry install -y libwebkit2gtk-4.0-dev | |
| apt_retry install -y libfuse2 || apt_retry install -y libfuse2t64 | |
| # Install SMB dependencies if enabled for this target | |
| if [ "${{ matrix.smb }}" = "true" ]; then | |
| apt_retry install -y libsmbclient-dev | |
| fi | |
| - name: Setup Homebrew PATH (macOS self-hosted) | |
| if: runner.os == 'macOS' && matrix.self_hosted | |
| run: | | |
| # Add Homebrew to PATH for Apple Silicon Macs | |
| echo "/opt/homebrew/bin" >> $GITHUB_PATH | |
| echo "/opt/homebrew/sbin" >> $GITHUB_PATH | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' && matrix.smb && !matrix.self_hosted | |
| run: | | |
| brew install samba | |
| # Only needed for GitHub-hosted runners (npm cache) | |
| - name: Normalize npm lockfile for caching | |
| if: ${{ !matrix.self_hosted }} | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| src = Path('package-lock.json') | |
| data = json.loads(src.read_text()) | |
| data['version'] = '' | |
| root_pkg = data.get('packages', {}).get('') | |
| if isinstance(root_pkg, dict): | |
| root_pkg['version'] = '' | |
| Path('package-lock.ci.json').write_text( | |
| json.dumps(data, sort_keys=True, separators=(',', ':')) | |
| ) | |
| PY | |
| - uses: actions/setup-node@v6 | |
| if: ${{ !matrix.self_hosted }} | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.ci.json | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: stable | |
| targets: ${{ matrix.rust-targets }} | |
| - name: Cache Rust build artifacts | |
| if: ${{ !matrix.self_hosted }} | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: | | |
| src-tauri -> target | |
| shared-key: release-${{ matrix.target }} | |
| cache-all-crates: true | |
| cache-on-failure: true | |
| # Tauri caching only needed for GitHub-hosted runners | |
| - name: Prepare Tauri cache directory (Linux) | |
| if: runner.os == 'Linux' && !matrix.self_hosted | |
| run: mkdir -p ~/.cache/tauri | |
| - name: Cache Tauri downloads (Linux) | |
| if: runner.os == 'Linux' && !matrix.self_hosted | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/tauri | |
| key: tauri-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/tauri.conf.json') }} | |
| restore-keys: | | |
| tauri-${{ runner.os }}-${{ matrix.target }}- | |
| tauri-${{ runner.os }}- | |
| - name: Prepare Tauri cache directory (macOS) | |
| if: runner.os == 'macOS' && !matrix.self_hosted | |
| run: mkdir -p ~/Library/Caches/tauri | |
| - name: Cache Tauri downloads (macOS) | |
| if: runner.os == 'macOS' && !matrix.self_hosted | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/Library/Caches/tauri | |
| key: tauri-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/tauri.conf.json') }} | |
| restore-keys: | | |
| tauri-${{ runner.os }}-${{ matrix.target }}- | |
| tauri-${{ runner.os }}- | |
| - name: Install frontend dependencies | |
| run: npm ci | |
| - name: Import Apple Certificate | |
| if: runner.os == 'macOS' && env.APPLE_CERTIFICATE != '' | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| # Create temporary keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| KEYCHAIN_PASSWORD=$(openssl rand -base64 32) | |
| # Create keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| # Download Apple Developer ID intermediate certificates for chain verification | |
| curl -sL "https://www.apple.com/certificateauthority/DeveloperIDCA.cer" -o $RUNNER_TEMP/DeveloperIDCA.cer | |
| curl -sL "https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer" -o $RUNNER_TEMP/DeveloperIDG2CA.cer | |
| security import $RUNNER_TEMP/DeveloperIDCA.cer -k "$KEYCHAIN_PATH" -T /usr/bin/codesign || true | |
| security import $RUNNER_TEMP/DeveloperIDG2CA.cer -k "$KEYCHAIN_PATH" -T /usr/bin/codesign || true | |
| rm -f $RUNNER_TEMP/DeveloperIDCA.cer $RUNNER_TEMP/DeveloperIDG2CA.cer | |
| # Import signing certificate | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > $RUNNER_TEMP/certificate.p12 | |
| security import $RUNNER_TEMP/certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" | |
| # Add temp keychain to search list, keeping existing valid keychains (other runners may be active on this machine) | |
| EXISTING_KEYCHAINS=$(security list-keychains -d user | tr -d '"' | tr '\n' ' ') | |
| security list-keychain -d user -s "$KEYCHAIN_PATH" $EXISTING_KEYCHAINS | |
| # Allow codesign to access keychain | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" | |
| # Clean up certificate file | |
| rm -f $RUNNER_TEMP/certificate.p12 | |
| - name: Clean up stale build artifacts (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # Remove stale bundle artifacts from previous builds to avoid "File exists" errors | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/dmg" || true | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/macos" || true | |
| - name: Clean up stale build artifacts (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| # Remove stale AppImage bundle to avoid linuxdeploy "Cannot deploy non-existing library" errors | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/appimage" || true | |
| - name: Create SMB sidecar placeholders | |
| run: | | |
| # Create placeholder files for all platforms to satisfy tauri-build | |
| mkdir -p src-tauri/binaries | |
| touch src-tauri/binaries/marlin-smb-x86_64-unknown-linux-gnu | |
| touch src-tauri/binaries/marlin-smb-aarch64-unknown-linux-gnu | |
| touch src-tauri/binaries/marlin-smb-x86_64-apple-darwin | |
| touch src-tauri/binaries/marlin-smb-aarch64-apple-darwin | |
| - name: Build SMB sidecar | |
| if: matrix.smb | |
| run: | | |
| cd src-tauri | |
| cargo build --release --bin marlin-smb --features smb-sidecar --target ${{ matrix.target }} | |
| # Copy sidecar to binaries directory with target triple suffix (Tauri convention) | |
| # This overwrites the placeholder created above | |
| # Use CARGO_TARGET_DIR if set (self-hosted), otherwise default 'target' dir | |
| TARGET_DIR="${CARGO_TARGET_DIR:-target}" | |
| cp "$TARGET_DIR/${{ matrix.target }}/release/marlin-smb" binaries/marlin-smb-${{ matrix.target }} | |
| chmod +x binaries/marlin-smb-${{ matrix.target }} | |
| - name: Codesign SMB sidecar (macOS) | |
| if: matrix.smb && runner.os == 'macOS' && env.APPLE_SIGNING_IDENTITY != '-' | |
| env: | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY || '-' }} | |
| run: | | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" src-tauri/binaries/marlin-smb-${{ matrix.target }} | |
| - name: Build and publish installers | |
| uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} | |
| TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} | |
| APPIMAGE_EXTRACT_AND_RUN: ${{ runner.os == 'Linux' && '1' || '' }} | |
| TAURI_BUNDLE_DEBUG: '1' | |
| TAURI_LOG: trace | |
| RUST_LOG: tauri_bundler=debug | |
| TAURI_BUNDLE_LINUX_APPIMAGE_ARGS: ${{ runner.os == 'Linux' && '--verbosity=2' || '' }} | |
| # linuxdeploy needs to find libzpl.so when bundling the AppImage | |
| LD_LIBRARY_PATH: ${{ runner.os == 'Linux' && format('{0}/{1}/release', env.CARGO_TARGET_DIR || 'src-tauri/target', matrix.target) || '' }} | |
| # Apple code signing (use ad-hoc signing with '-' if secrets not configured) | |
| # Only pass APPLE_SIGNING_IDENTITY here — omit APPLE_ID/PASSWORD/TEAM_ID | |
| # so Tauri signs without trying to notarize (our DMG step handles notarization) | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY || '-' }} | |
| with: | |
| tagName: ${{ env.RELEASE_TAG }} | |
| releaseName: Marlin ${{ env.RELEASE_TAG }} | |
| releaseBody: | | |
| Automated binaries for Marlin. Download the installer matching your platform. | |
| **macOS users**: Choose `arm64` for Apple Silicon (M1/M2/M3) or `x86_64` for Intel Macs. | |
| releaseDraft: false | |
| prerelease: ${{ startsWith(env.RELEASE_TAG, 'v0.') }} | |
| # macOS: skip DMG (create-dmg uses AppleScript/Finder which requires TCC approval) | |
| # We create a notarized DMG manually in the next step using hdiutil instead | |
| # arm64 Linux (GitHub-hosted): deb only (no AppImage on arm64) | |
| args: >- | |
| --target ${{ matrix.target }} | |
| --verbose | |
| ${{ runner.os == 'macOS' && '--bundles app updater' || '' }} | |
| ${{ matrix.target == 'aarch64-unknown-linux-gnu' && '--bundles deb' || '' }} | |
| - name: Create DMG (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| set -euo pipefail | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| APP_PATH="$TARGET_DIR/${{ matrix.target }}/release/bundle/macos/Marlin.app" | |
| ARCH="${{ matrix.arch }}" | |
| VERSION="${RELEASE_TAG#v}" | |
| DMG_NAME="Marlin_${VERSION}_${ARCH}.dmg" | |
| DMG_DIR="$TARGET_DIR/${{ matrix.target }}/release/bundle/dmg" | |
| DMG_PATH="$DMG_DIR/$DMG_NAME" | |
| mkdir -p "$DMG_DIR" | |
| # Create a temporary directory with the app and Applications symlink | |
| STAGING=$(mktemp -d) | |
| trap 'rm -rf "$STAGING"' EXIT | |
| cp -R "$APP_PATH" "$STAGING/" | |
| ln -s /Applications "$STAGING/Applications" | |
| # Create DMG using hdiutil (no AppleScript/Finder needed) | |
| # Use arch-specific volume name to avoid "Resource busy" when two | |
| # macOS runners create DMGs on the same machine simultaneously. | |
| created=false | |
| for attempt in 1 2 3; do | |
| rm -f "$DMG_PATH" | |
| hdiutil detach "/Volumes/Marlin-${ARCH}" -force || true | |
| if hdiutil create -volname "Marlin-${ARCH}" -srcfolder "$STAGING" -ov -format UDZO "$DMG_PATH"; then | |
| created=true | |
| break | |
| fi | |
| echo "hdiutil attempt $attempt failed." | |
| if [ "$attempt" -lt 3 ]; then | |
| echo "Retrying in 10s..." | |
| sleep 10 | |
| fi | |
| done | |
| if [ "$created" != "true" ]; then | |
| echo "Failed to create $DMG_PATH after 3 attempts." >&2 | |
| exit 1 | |
| fi | |
| echo "DMG created: $DMG_PATH" | |
| - name: Notarize DMG (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| run: | | |
| set -euo pipefail | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| ARCH="${{ matrix.arch }}" | |
| VERSION="${RELEASE_TAG#v}" | |
| DMG_NAME="Marlin_${VERSION}_${ARCH}.dmg" | |
| DMG_PATH="$TARGET_DIR/${{ matrix.target }}/release/bundle/dmg/$DMG_NAME" | |
| if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then | |
| echo "Apple notarization credentials are not configured." >&2 | |
| exit 1 | |
| fi | |
| echo "Notarizing $DMG_NAME..." | |
| xcrun notarytool submit "$DMG_PATH" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait | |
| xcrun stapler staple "$DMG_PATH" | |
| echo "DMG notarized and stapled: $DMG_PATH" | |
| - name: Upload DMG to release (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| ARCH="${{ matrix.arch }}" | |
| VERSION="${RELEASE_TAG#v}" | |
| DMG_NAME="Marlin_${VERSION}_${ARCH}.dmg" | |
| DMG_PATH="$TARGET_DIR/${{ matrix.target }}/release/bundle/dmg/$DMG_NAME" | |
| gh release upload "${{ env.RELEASE_TAG }}" "$DMG_PATH" --clobber | |
| release-dry-run: | |
| name: Dry Run (${{ matrix.arch }}-${{ matrix.target }}) | |
| if: ${{ github.event_name == 'pull_request' }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # macOS Apple Silicon (M1/M2/M3) - GitHub-hosted | |
| - os: macos-15 | |
| target: aarch64-apple-darwin | |
| arch: arm64 | |
| rust-targets: aarch64-apple-darwin | |
| smb: true | |
| self_hosted: false | |
| # macOS Intel - GitHub-hosted | |
| - os: macos-15-intel | |
| target: x86_64-apple-darwin | |
| arch: x86_64 | |
| rust-targets: x86_64-apple-darwin | |
| smb: false | |
| self_hosted: false | |
| # Linux x86_64 | |
| - os: [self-hosted, Linux, X64, medium] | |
| target: x86_64-unknown-linux-gnu | |
| arch: x86_64 | |
| rust-targets: x86_64-unknown-linux-gnu | |
| smb: true | |
| self_hosted: true | |
| # Linux arm64 - GitHub-hosted (no self-hosted arm64 runner yet) | |
| - os: ubuntu-24.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| arch: arm64 | |
| rust-targets: aarch64-unknown-linux-gnu | |
| smb: true | |
| self_hosted: false | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - name: Setup persistent cargo target dir (self-hosted) | |
| if: matrix.self_hosted | |
| run: | | |
| # Use persistent target dir outside workspace so it survives between different repo checkouts | |
| echo "CARGO_TARGET_DIR=$HOME/.cache/cargo-target/marlin-${{ matrix.target }}" >> $GITHUB_ENV | |
| mkdir -p "$HOME/.cache/cargo-target/marlin-${{ matrix.target }}" | |
| - name: Install dependencies (Ubuntu) | |
| if: runner.os == 'Linux' | |
| run: | | |
| apt_retry() { | |
| for attempt in 1 2 3 4 5 6 7 8 9 10 11 12; do | |
| if sudo DEBIAN_FRONTEND=noninteractive apt-get "$@"; then | |
| return 0 | |
| fi | |
| echo "apt-get $* failed on attempt $attempt; retrying in 10s..." >&2 | |
| sleep 10 | |
| done | |
| sudo DEBIAN_FRONTEND=noninteractive apt-get "$@" | |
| } | |
| apt_retry update | |
| apt_retry install -y \ | |
| build-essential \ | |
| clang \ | |
| curl \ | |
| wget \ | |
| file \ | |
| xdg-utils \ | |
| libssl-dev \ | |
| libclang-dev \ | |
| libgtk-3-dev \ | |
| libayatana-appindicator3-dev \ | |
| librsvg2-dev \ | |
| pkg-config \ | |
| patchelf \ | |
| libglib2.0-bin \ | |
| libgdk-pixbuf2.0-bin \ | |
| desktop-file-utils \ | |
| shared-mime-info \ | |
| gperf | |
| apt_retry install -y libwebkit2gtk-4.1-dev || apt_retry install -y libwebkit2gtk-4.0-dev | |
| apt_retry install -y libfuse2 || apt_retry install -y libfuse2t64 | |
| # Install SMB dependencies if enabled for this target | |
| if [ "${{ matrix.smb }}" = "true" ]; then | |
| apt_retry install -y libsmbclient-dev | |
| fi | |
| - name: Setup Homebrew PATH (macOS self-hosted) | |
| if: runner.os == 'macOS' && matrix.self_hosted | |
| run: | | |
| # Add Homebrew to PATH for Apple Silicon Macs | |
| echo "/opt/homebrew/bin" >> $GITHUB_PATH | |
| echo "/opt/homebrew/sbin" >> $GITHUB_PATH | |
| - name: Install dependencies (macOS) | |
| if: runner.os == 'macOS' && matrix.smb && !matrix.self_hosted | |
| run: | | |
| brew install samba | |
| # Only needed for GitHub-hosted runners (npm cache) | |
| - name: Normalize npm lockfile for caching | |
| if: ${{ !matrix.self_hosted }} | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| from pathlib import Path | |
| src = Path('package-lock.json') | |
| data = json.loads(src.read_text()) | |
| data['version'] = '' | |
| root_pkg = data.get('packages', {}).get('') | |
| if isinstance(root_pkg, dict): | |
| root_pkg['version'] = '' | |
| Path('package-lock.ci.json').write_text( | |
| json.dumps(data, sort_keys=True, separators=(',', ':')) | |
| ) | |
| PY | |
| - uses: actions/setup-node@v6 | |
| if: ${{ !matrix.self_hosted }} | |
| with: | |
| node-version: 20 | |
| cache: 'npm' | |
| cache-dependency-path: package-lock.ci.json | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: stable | |
| targets: ${{ matrix.rust-targets }} | |
| - name: Cache Rust build artifacts | |
| if: ${{ !matrix.self_hosted }} | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: | | |
| src-tauri -> target | |
| shared-key: release-${{ matrix.target }} | |
| cache-all-crates: true | |
| cache-on-failure: true | |
| # Tauri caching only needed for GitHub-hosted runners | |
| - name: Prepare Tauri cache directory (Linux) | |
| if: runner.os == 'Linux' && !matrix.self_hosted | |
| run: mkdir -p ~/.cache/tauri | |
| - name: Cache Tauri downloads (Linux) | |
| if: runner.os == 'Linux' && !matrix.self_hosted | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/.cache/tauri | |
| key: tauri-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/tauri.conf.json') }} | |
| restore-keys: | | |
| tauri-${{ runner.os }}-${{ matrix.target }}- | |
| tauri-${{ runner.os }}- | |
| - name: Prepare Tauri cache directory (macOS) | |
| if: runner.os == 'macOS' && !matrix.self_hosted | |
| run: mkdir -p ~/Library/Caches/tauri | |
| - name: Cache Tauri downloads (macOS) | |
| if: runner.os == 'macOS' && !matrix.self_hosted | |
| uses: actions/cache@v5 | |
| with: | |
| path: ~/Library/Caches/tauri | |
| key: tauri-${{ runner.os }}-${{ matrix.target }}-${{ hashFiles('src-tauri/tauri.conf.json') }} | |
| restore-keys: | | |
| tauri-${{ runner.os }}-${{ matrix.target }}- | |
| tauri-${{ runner.os }}- | |
| - name: Install frontend dependencies | |
| run: npm ci | |
| - name: Clean up stale build artifacts (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # Remove stale bundle artifacts from previous builds to avoid "File exists" errors | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/dmg" || true | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/macos" || true | |
| - name: Clean up stale build artifacts (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| # Remove stale AppImage bundle to avoid linuxdeploy "Cannot deploy non-existing library" errors | |
| TARGET_DIR="${CARGO_TARGET_DIR:-src-tauri/target}" | |
| rm -rf "$TARGET_DIR/${{ matrix.target }}/release/bundle/appimage" || true | |
| - name: Create SMB sidecar placeholders | |
| run: | | |
| # Create placeholder files for all platforms to satisfy tauri-build | |
| mkdir -p src-tauri/binaries | |
| touch src-tauri/binaries/marlin-smb-x86_64-unknown-linux-gnu | |
| touch src-tauri/binaries/marlin-smb-aarch64-unknown-linux-gnu | |
| touch src-tauri/binaries/marlin-smb-x86_64-apple-darwin | |
| touch src-tauri/binaries/marlin-smb-aarch64-apple-darwin | |
| - name: Build SMB sidecar | |
| if: matrix.smb | |
| run: | | |
| cd src-tauri | |
| cargo build --release --bin marlin-smb --features smb-sidecar --target ${{ matrix.target }} | |
| # Copy sidecar to binaries directory with target triple suffix (Tauri convention) | |
| # This overwrites the placeholder created above | |
| # Use CARGO_TARGET_DIR if set (self-hosted), otherwise default 'target' dir | |
| TARGET_DIR="${CARGO_TARGET_DIR:-target}" | |
| cp "$TARGET_DIR/${{ matrix.target }}/release/marlin-smb" binaries/marlin-smb-${{ matrix.target }} | |
| chmod +x binaries/marlin-smb-${{ matrix.target }} | |
| - name: Build Tauri bundle | |
| run: | | |
| set -euxo pipefail | |
| npm run tauri build -- --ci --target ${{ matrix.target }} --verbose ${{ runner.os == 'macOS' && '--bundles app updater' || '' }} ${{ matrix.target == 'aarch64-unknown-linux-gnu' && '--bundles deb' || '' }} | |
| env: | |
| APPIMAGE_EXTRACT_AND_RUN: ${{ runner.os == 'Linux' && '1' || '0' }} | |
| TAURI_BUNDLE_DEBUG: '1' | |
| TAURI_LOG: trace | |
| RUST_LOG: tauri_bundler=debug | |
| TAURI_BUNDLE_LINUX_APPIMAGE_ARGS: ${{ runner.os == 'Linux' && '--verbosity=2' || '' }} | |
| # linuxdeploy needs to find libzpl.so when bundling the AppImage | |
| LD_LIBRARY_PATH: ${{ runner.os == 'Linux' && format('{0}/{1}/release', env.CARGO_TARGET_DIR || 'src-tauri/target', matrix.target) || '' }} | |
| - name: Dump linuxdeploy diagnostics | |
| if: failure() && runner.os == 'Linux' | |
| run: | | |
| set -euxo pipefail | |
| echo "Listing tauri cache directory" >&2 | |
| ls -R ~/.cache/tauri | head -n 200 || true | |
| echo "Inspecting extracted linuxdeploy payloads" >&2 | |
| find ~/.cache/tauri -maxdepth 2 -type d -name 'squashfs-root' -print || true | |
| echo "Searching for linuxdeploy logs" >&2 | |
| find ~/.cache/tauri -type f -name '*.log' -print -exec tail -n +1 {} + || true | |
| target_dir="src-tauri/target/${{ matrix.target }}/release" | |
| appdir="$target_dir/bundle/appimage/Marlin.AppDir" | |
| case "${{ matrix.target }}" in | |
| *x86_64*) linuxdeploy_pattern="linuxdeploy-x86_64.AppImage" ;; | |
| *aarch64*|*arm64*) linuxdeploy_pattern="linuxdeploy-aarch64.AppImage" ;; | |
| *) linuxdeploy_pattern="linuxdeploy-*.AppImage" ;; | |
| esac | |
| shopt -s nullglob | |
| linuxdeploy_candidates=(~/.cache/tauri/${linuxdeploy_pattern}) | |
| shopt -u nullglob | |
| if [ ${#linuxdeploy_candidates[@]} -gt 0 ] && [ -d "$appdir" ]; then | |
| linuxdeploy_path="${linuxdeploy_candidates[0]}" | |
| echo "Re-running linuxdeploy with DEBUG=1 for additional context" >&2 | |
| set +e | |
| DEBUG=1 "$linuxdeploy_path" --appimage-extract-and-run --verbosity 3 --appdir "$GITHUB_WORKSPACE/$appdir" --plugin gtk --output appimage | |
| replay_status=$? | |
| echo "linuxdeploy replay exited with status $replay_status" >&2 | |
| set -e | |
| fi |