Skip to content

chore(deps)(deps): bump lru from 0.17.0 to 0.18.0 in /src-tauri #561

chore(deps)(deps): bump lru from 0.17.0 to 0.18.0 in /src-tauri

chore(deps)(deps): bump lru from 0.17.0 to 0.18.0 in /src-tauri #561

Workflow file for this run

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