name: Build Tauri Applications on: workflow_dispatch: inputs: platform: description: "Platform to build (windows, macos, linux, or all)" required: true default: "all" type: choice options: - all - windows - macos - linux pull_request: branches: [main, V2] paths: - 'frontend/src-tauri/**' - 'frontend/src/**' - 'frontend/package.json' - 'frontend/package-lock.json' - '.github/workflows/tauri-build.yml' push: branches: [main, V2] paths: - 'frontend/src-tauri/**' - 'frontend/src/**' - 'frontend/package.json' - 'frontend/package-lock.json' - '.github/workflows/tauri-build.yml' permissions: contents: read jobs: determine-matrix: runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - name: Determine build matrix id: set-matrix run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then case "${{ github.event.inputs.platform }}" in "windows") echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"}]}' >> $GITHUB_OUTPUT ;; "macos") echo 'matrix={"include":[{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"}]}' >> $GITHUB_OUTPUT ;; "linux") echo 'matrix={"include":[{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; *) echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT ;; esac else # For PR/push events, build all platforms echo 'matrix={"include":[{"platform":"windows-latest","args":"--target x86_64-pc-windows-msvc","name":"windows-x86_64"},{"platform":"macos-latest","args":"--target aarch64-apple-darwin","name":"macos-aarch64"},{"platform":"macos-13","args":"--target x86_64-apple-darwin","name":"macos-x86_64"},{"platform":"ubuntu-22.04","args":"","name":"linux-x86_64"}]}' >> $GITHUB_OUTPUT fi build: needs: determine-matrix strategy: fail-fast: false matrix: ${{ fromJson(needs.determine-matrix.outputs.matrix) }} runs-on: ${{ matrix.platform }} steps: - name: Harden Runner uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: egress-policy: audit - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libjavascriptcoregtk-4.0-dev libsoup2.4-dev libjavascriptcoregtk-4.1-dev libsoup-3.0-dev - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' cache-dependency-path: frontend/package-lock.json - name: Setup Rust uses: dtolnay/rust-toolchain@stable with: toolchain: stable targets: ${{ (matrix.platform == 'macos-latest' || matrix.platform == 'macos-13') && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: Set up JDK 21 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: java-version: "21" distribution: "temurin" - name: Build Java backend with JLink working-directory: ./ shell: bash run: | chmod +x ./gradlew echo "🔧 Building Stirling-PDF JAR..." # STIRLING_PDF_DESKTOP_UI=false ./gradlew clean bootJar --no-daemon ./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube # Find the built JAR STIRLING_JAR=$(ls app/core/build/libs/stirling-pdf-*.jar | head -n 1) echo "✅ Built JAR: $STIRLING_JAR" # Create Tauri directories mkdir -p ./frontend/src-tauri/libs mkdir -p ./frontend/src-tauri/runtime # Copy JAR to Tauri libs cp "$STIRLING_JAR" ./frontend/src-tauri/libs/ echo "✅ JAR copied to Tauri libs" # Analyze JAR dependencies for jlink modules echo "🔍 Analyzing JAR dependencies..." if command -v jdeps &> /dev/null; then DETECTED_MODULES=$(jdeps --print-module-deps --ignore-missing-deps "$STIRLING_JAR" 2>/dev/null || echo "") if [ -n "$DETECTED_MODULES" ]; then echo "📋 jdeps detected modules: $DETECTED_MODULES" MODULES="$DETECTED_MODULES,java.compiler,java.instrument,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" else echo "⚠️ jdeps analysis failed, using predefined modules" MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" fi else echo "⚠️ jdeps not available, using predefined modules" MODULES="java.base,java.compiler,java.desktop,java.instrument,java.logging,java.management,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,java.xml.crypto,jdk.crypto.ec,jdk.crypto.cryptoki,jdk.unsupported" fi # Create custom JRE with jlink (always rebuild) echo "🔧 Creating custom JRE with jlink..." echo "📋 Using modules: $MODULES" # Remove any existing JRE rm -rf ./frontend/src-tauri/runtime/jre # Create the custom JRE jlink \ --add-modules "$MODULES" \ --strip-debug \ --compress=2 \ --no-header-files \ --no-man-pages \ --output ./frontend/src-tauri/runtime/jre if [ ! -d "./frontend/src-tauri/runtime/jre" ]; then echo "❌ Failed to create JLink runtime" exit 1 fi # Test the bundled runtime if [ -f "./frontend/src-tauri/runtime/jre/bin/java" ]; then RUNTIME_VERSION=$(./frontend/src-tauri/runtime/jre/bin/java --version 2>&1 | head -n 1) echo "✅ Custom JRE created successfully: $RUNTIME_VERSION" else echo "❌ Custom JRE executable not found" exit 1 fi # Calculate runtime size RUNTIME_SIZE=$(du -sh ./frontend/src-tauri/runtime/jre | cut -f1) echo "📊 Custom JRE size: $RUNTIME_SIZE" env: DISABLE_ADDITIONAL_FEATURES: true - name: Install frontend dependencies working-directory: ./frontend run: npm install - name: Import Apple Developer Certificate if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} run: | echo "Importing Apple Developer Certificate..." echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12 security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security default-keychain -s build.keychain security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain security set-keychain-settings -t 3600 -u build.keychain security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain security find-identity -v -p codesigning build.keychain - name: Verify Certificate if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' run: | echo "Verifying Apple Developer Certificate..." CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application") echo "Certificate Info: $CERT_INFO" CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}') echo "Certificate ID: $CERT_ID" echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV echo "Certificate imported." - name: Check DMG creation dependencies (macOS only) if: matrix.platform == 'macos-latest' || matrix.platform == 'macos-13' run: | echo "🔍 Checking DMG creation dependencies on ${{ matrix.platform }}..." echo "hdiutil version: $(hdiutil --version || echo 'NOT FOUND')" echo "create-dmg availability: $(which create-dmg || echo 'NOT FOUND')" echo "Available disk space: $(df -h /tmp | tail -1)" echo "macOS version: $(sw_vers -productVersion)" echo "Available tools:" ls -la /usr/bin/hd* || echo "No hd* tools found" - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APPIMAGETOOL_SIGN_PASSPHRASE }} SIGN: 1 CI: true with: projectPath: ./frontend tauriScript: npx tauri args: ${{ matrix.args }} - name: Rename artifacts shell: bash run: | mkdir -p ./dist cd ./frontend/src-tauri/target # Find and rename artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then find . -name "*.exe" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.exe" \; find . -name "*.msi" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.msi" \; elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then find . -name "*.dmg" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.dmg" \; find . -name "*.app" -exec cp -r {} "../../../dist/Stirling-PDF-${{ matrix.name }}.app" \; else find . -name "*.deb" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.deb" \; find . -name "*.AppImage" -exec cp {} "../../../dist/Stirling-PDF-${{ matrix.name }}.AppImage" \; fi - name: Upload artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: Stirling-PDF-${{ matrix.name }} path: ./dist/* retention-days: 7 - name: Verify build artifacts shell: bash run: | cd ./frontend/src-tauri/target # Check for expected artifacts based on platform if [ "${{ matrix.platform }}" = "windows-latest" ]; then echo "Checking for Windows artifacts..." find . -name "*.exe" -o -name "*.msi" | head -5 if [ $(find . -name "*.exe" | wc -l) -eq 0 ]; then echo "❌ No Windows executable found" exit 1 fi elif [ "${{ matrix.platform }}" = "macos-latest" ] || [ "${{ matrix.platform }}" = "macos-13" ]; then echo "Checking for macOS artifacts..." find . -name "*.dmg" -o -name "*.app" | head -5 if [ $(find . -name "*.dmg" -o -name "*.app" | wc -l) -eq 0 ]; then echo "❌ No macOS artifacts found" exit 1 fi else echo "Checking for Linux artifacts..." find . -name "*.deb" -o -name "*.AppImage" | head -5 if [ $(find . -name "*.deb" -o -name "*.AppImage" | wc -l) -eq 0 ]; then echo "❌ No Linux artifacts found" exit 1 fi fi echo "✅ Build artifacts found for ${{ matrix.name }}" - name: Test artifact sizes shell: bash run: | cd ./frontend/src-tauri/target echo "Artifact sizes for ${{ matrix.name }}:" find . -name "*.exe" -o -name "*.dmg" -o -name "*.deb" -o -name "*.AppImage" -o -name "*.msi" | while read file; do if [ -f "$file" ]; then size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "unknown") echo "$file: $size bytes" # Check if file is suspiciously small (less than 1MB) if [ "$size" != "unknown" ] && [ "$size" -lt 1048576 ]; then echo "⚠️ Warning: $file is smaller than 1MB" fi fi done report: needs: build runs-on: ubuntu-latest if: always() steps: - name: Report build results run: | if [ "${{ needs.build.result }}" = "success" ]; then echo "✅ All Tauri builds completed successfully!" echo "Artifacts are ready for distribution." else echo "❌ Some Tauri builds failed." echo "Please check the logs and fix any issues." exit 1 fi