LuckyWorld/.gitea/workflows/macos-build.yml

506 lines
22 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: macOS Build, Sign and Notarize
on:
workflow_dispatch: # Manuel tetikleme
push:
branches: [ozgur/build]
jobs:
build-sign-notarize:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Setup environment
run: |
# Çalışma dizini yolunu al
WORKSPACE_DIR="$(pwd)"
echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV"
echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV"
# CI ortam değişkenini true olarak ayarla
echo "CI=true" >> "$GITHUB_ENV"
# Build için gerekli dizinleri oluştur
mkdir -p Builds/Mac
mkdir -p PackagedReleases
mkdir -p ArchivedApps
echo "Environment setup complete"
shell: bash
- name: Setup Certificate and Keychain
id: setup-cert
shell: bash
env:
CERTIFICATE_BASE64: ${{ secrets.MACOS_CERTIFICATE }}
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
echo "🔐 Setting up certificate..."
# Create a temporary directory for certificates
CERT_DIR="$HOME/certificates"
mkdir -p "$CERT_DIR"
# Decode the certificate to a p12 file
echo "$CERTIFICATE_BASE64" | base64 --decode > "$CERT_DIR/certificate.p12"
# Create keychain
KEYCHAIN_PATH="$CERT_DIR/app-signing.keychain-db"
KEYCHAIN_PASSWORD="temppassword123"
# Delete existing keychain if it exists
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
# Create new keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -t 3600 -u -l "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Add to search list and make default
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
security default-keychain -s "$KEYCHAIN_PATH"
# Import certificate
echo "🔑 Importing developer certificate..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
# Try with additional parameters if needed
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12 || true
# Set partition list for codesign to access keychain
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Verify certificate
echo "🔍 Verifying code signing identities..."
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
# Try to use the System keychain as a fallback
echo "🔍 Checking system keychain for code signing identities..."
SYSTEM_IDENTITIES=$(security find-identity -v -p codesigning)
echo "$SYSTEM_IDENTITIES"
if echo "$SYSTEM_IDENTITIES" | grep -q "Developer ID Application"; then
echo "✅ Found Developer ID Application certificate in system keychain"
echo "::set-output name=use_system_cert::true"
else
echo "::set-output name=use_system_cert::false"
fi
# Store keychain variables for later steps
echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV
echo "KEYCHAIN_PASSWORD=$KEYCHAIN_PASSWORD" >> $GITHUB_ENV
echo "APPLE_TEAM_ID=$APPLE_TEAM_ID" >> $GITHUB_ENV
# Clean up
rm -f "$CERT_DIR/certificate.p12"
- name: Build for macOS
id: build
run: |
if [ -f "./scripts/mac_build.sh" ]; then
chmod +x ./scripts/mac_build.sh
# Set CI environment variable explicitly before running
export CI=true
./scripts/mac_build.sh
# Check if the build succeeded by looking for the app
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
if [ -z "$APP_PATHS" ]; then
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
fi
if [ -z "$APP_PATHS" ]; then
echo "❌ ERROR: Build command appeared to succeed but no app bundle was found!"
echo "This usually means the build failed but didn't properly return an error code."
exit 1
fi
else
echo "ERROR: Build script not found at ./scripts/mac_build.sh"
exit 1
fi
shell: bash
- name: Find app bundle
id: find-app
run: |
# Add error handling
set +e # Don't exit immediately on error for this block
echo "Build status check..."
if [ ! -d "./Builds" ] && [ ! -d "./Saved/StagedBuilds" ]; then
echo "❌ ERROR: Build directories do not exist. Build likely failed."
exit 1
fi
# First check Saved/StagedBuilds directory - where Unreal often places built apps
echo "Checking Saved/StagedBuilds directory..."
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null || echo "")
# If not found, check Builds directory
if [ -z "$APP_PATHS" ]; then
echo "No app found in Saved/StagedBuilds, checking Builds directory..."
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null || echo "")
fi
# If still not found, check the whole workspace
if [ -z "$APP_PATHS" ]; then
echo "No app found in Builds, checking entire workspace..."
APP_PATHS=$(find . -type d -name "*.app" -not -path "*/\.*" 2>/dev/null || echo "")
fi
if [ -z "$APP_PATHS" ]; then
echo "❌ ERROR: Could not find any app bundles!"
echo "Listing all directories to help debug:"
find . -type d -maxdepth 3 | sort
exit 1
fi
echo "Found potential app bundles:"
echo "$APP_PATHS"
# Use the first app path found (preferably the main app, not a child app)
MAIN_APP_PATH=$(echo "$APP_PATHS" | grep -v "CrashReportClient" | head -1 || echo "$APP_PATHS" | head -1)
echo "Using app bundle: $MAIN_APP_PATH"
# Make sure app exists - using local variable
if [ ! -d "$MAIN_APP_PATH" ]; then
echo "❌ ERROR: App bundle not found at $MAIN_APP_PATH!"
exit 1
fi
# Export APP_PATH for next steps to use
echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV"
# Get bundle ID from Info.plist for reference (not modifying)
if [ -f "$MAIN_APP_PATH/Contents/Info.plist" ]; then
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist")
echo "Detected bundle ID: $BUNDLE_ID"
echo "BUNDLE_ID=$BUNDLE_ID" >> "$GITHUB_ENV"
fi
shell: bash
- name: Sign App
id: sign
shell: bash
run: |
echo "🔏 Signing app with Developer ID certificate..."
# Check if app path exists
if [ ! -d "${{ env.APP_PATH }}" ]; then
echo "❌ App bundle not found at ${{ env.APP_PATH }}"
echo "::set-output name=signed::none"
exit 1
fi
# Check if entitlements file exists
if [ ! -f "${{ env.ENTITLEMENTS_FILE }}" ]; then
echo "❌ Entitlements file not found at ${{ env.ENTITLEMENTS_FILE }}"
echo "::set-output name=signed::none"
exit 1
fi
# Decide which keychain to use
if [ "${{ steps.setup-cert.outputs.use_system_cert }}" = "true" ]; then
echo "Using system keychain identity"
# Get certificate hash instead of name to avoid ambiguity
IDENTITY_HASH=$(security find-identity -v -p codesigning | grep "Developer ID Application" | head -1 | awk '{print $2}')
echo "Using certificate hash: $IDENTITY_HASH"
else
# Make sure keychain is unlocked
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
echo "Using custom keychain identity"
# Get certificate hash instead of name to avoid ambiguity
IDENTITY_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}')
echo "Using certificate hash: $IDENTITY_HASH"
fi
if [ -z "$IDENTITY_HASH" ]; then
echo "❌ No valid Developer ID Application certificate found"
echo "Falling back to ad-hoc signing for testing..."
# Use ad-hoc identity as fallback
codesign --force --deep --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign - --timestamp "${{ env.APP_PATH }}"
echo "::set-output name=signed::adhoc"
else
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
# Enhanced deep recursive signing for all binaries
echo "🔍 Performing deep recursive signing of all components..."
# First, find all .dylib files and sign them individually
echo "Signing all dynamic libraries (.dylib files)..."
find "${{ env.APP_PATH }}" -name "*.dylib" | while read -r dylib; do
echo "Signing: $dylib"
codesign --force --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$dylib" || echo "⚠️ Failed to sign: $dylib"
done
# Sign all .so files
echo "Signing all shared objects (.so files)..."
find "${{ env.APP_PATH }}" -name "*.so" | while read -r so; do
echo "Signing: $so"
codesign --force --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$so" || echo "⚠️ Failed to sign: $so"
done
# Sign all executable files (files with execute permission)
echo "Signing all executable files..."
find "${{ env.APP_PATH }}" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | while read -r exe; do
echo "Signing executable: $exe"
codesign --force --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "⚠️ Failed to sign: $exe"
done
# Sign all frameworks
echo "Signing frameworks..."
find "${{ env.APP_PATH }}" -path "*.framework" -type d | while read -r framework; do
echo "Signing framework: $framework"
codesign --force --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "⚠️ Failed to sign: $framework"
done
# Final signing of the main bundle
echo "🔐 Performing final signing of the main app bundle..."
codesign --force --deep --verbose --options runtime --entitlements "${{ env.ENTITLEMENTS_FILE }}" --sign "$IDENTITY_HASH" --timestamp "${{ env.APP_PATH }}"
echo "::set-output name=signed::identity"
fi
# Verify signing
echo "🔍 Verifying signature..."
codesign -vvv --deep --strict "${{ env.APP_PATH }}"
# Check entitlements
echo "🔍 Checking entitlements..."
codesign -d --entitlements - "${{ env.APP_PATH }}"
- name: Notarize App
id: notarize
if: steps.sign.outputs.signed != 'none'
shell: bash
env:
API_KEY_ID: ${{ secrets.NOTARY_API_KEY_ID }}
API_ISSUER_ID: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
API_KEY_PATH: ${{ secrets.NOTARY_API_KEY_PATH }}
run: |
echo "📤 Notarizing app..."
# Set default output
echo "::set-output name=notarized::false"
# Get app name for zip file naming
APP_NAME=$(basename "${{ env.APP_PATH }}" .app)
BUNDLE_ID="${{ env.BUNDLE_ID }}"
# If bundle ID is not provided, try to extract from Info.plist
if [ -z "$BUNDLE_ID" ]; then
if [ -f "${{ env.APP_PATH }}/Contents/Info.plist" ]; then
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ env.APP_PATH }}/Contents/Info.plist")
echo "Extracted bundle ID: $BUNDLE_ID"
else
BUNDLE_ID="com.luckyrobots.app"
echo "Using default bundle ID: $BUNDLE_ID"
fi
fi
# Check if we're using API key notarization method
if [ -n "$API_KEY_ID" ] && [ -n "$API_ISSUER_ID" ] && [ -n "$API_KEY_PATH" ]; then
echo "Using App Store Connect API key for notarization..."
# Create directory for API key if API_KEY_PATH contains content
mkdir -p ~/private_keys
# Check if API_KEY_PATH is a path or content
if [[ "$API_KEY_PATH" == /* ]] && [ -f "$API_KEY_PATH" ]; then
# It's a path to a file
echo "Using API key from path: $API_KEY_PATH"
cp "$API_KEY_PATH" ~/private_keys/AuthKey_${API_KEY_ID}.p8
else
# It contains the key content
echo "Using API key from content"
echo "$API_KEY_PATH" > ~/private_keys/AuthKey_${API_KEY_ID}.p8
fi
# Create zip for notarization
ZIP_PATH="${APP_NAME}-notarize.zip"
ditto -c -k --keepParent "${{ env.APP_PATH }}" "$ZIP_PATH"
echo "Submitting for notarization with API key..."
# First, submit without waiting for completion
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" 2>&1)
SUBMIT_STATUS=$?
# Display output for debugging
echo "Notarization submission output:"
echo "$SUBMIT_OUTPUT"
echo "Submission exit status: $SUBMIT_STATUS"
# Check if submission was successful
if [ $SUBMIT_STATUS -ne 0 ]; then
echo "❌ Failed to submit for notarization. Exit code: $SUBMIT_STATUS"
exit 1
fi
# Extract submission ID for log retrieval
SUBMISSION_ID=$(echo "$SUBMIT_OUTPUT" | grep -o "id: [a-f0-9\-]*" | head -1 | cut -d ' ' -f 2)
if [ -z "$SUBMISSION_ID" ]; then
echo "❌ Could not extract submission ID from output. Notarization failed."
exit 1
fi
echo "Submission ID: $SUBMISSION_ID"
echo "Waiting for notarization to complete..."
# Now wait for the processing to complete
COMPLETE=false
MAX_ATTEMPTS=60 # Maximum number of attempts (60 * 30 seconds = 30 minutes max)
ATTEMPT=1
while [ "$COMPLETE" = "false" ] && [ $ATTEMPT -le $MAX_ATTEMPTS ]; do
echo "Checking notarization status (attempt $ATTEMPT of $MAX_ATTEMPTS)..."
INFO_OUTPUT=$(xcrun notarytool info "$SUBMISSION_ID" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" 2>&1)
INFO_STATUS=$?
echo "Status check output:"
echo "$INFO_OUTPUT"
# Check if the notarization is complete
if echo "$INFO_OUTPUT" | grep -q "status: Accepted"; then
echo "✅ Notarization completed successfully!"
COMPLETE=true
FINAL_STATUS="Accepted"
elif echo "$INFO_OUTPUT" | grep -q "status: Invalid"; then
echo "❌ Notarization failed with status: Invalid"
COMPLETE=true
FINAL_STATUS="Invalid"
elif echo "$INFO_OUTPUT" | grep -q "status: Rejected"; then
echo "❌ Notarization failed with status: Rejected"
COMPLETE=true
FINAL_STATUS="Rejected"
else
echo "Notarization still in progress. Waiting 30 seconds before checking again..."
sleep 30
ATTEMPT=$((ATTEMPT + 1))
fi
done
# Handle timeout
if [ "$COMPLETE" = "false" ]; then
echo "❌ Notarization timed out after $MAX_ATTEMPTS attempts."
exit 1
fi
# Handle completed notarization
if [ "$FINAL_STATUS" = "Accepted" ]; then
# Get logs for information (even though successful)
echo "📋 Getting notarization logs for information..."
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" 2>&1)
echo "==== NOTARIZATION LOG SUMMARY ===="
echo "$LOGS_OUTPUT" | head -20
echo "=================================="
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple -v "${{ env.APP_PATH }}"
STAPLE_STATUS=$?
if [ $STAPLE_STATUS -eq 0 ]; then
echo "✅ Stapling completed successfully!"
# Verify the stapling worked properly
echo "Verifying stapled ticket is properly attached..."
xcrun stapler validate -v "${{ env.APP_PATH }}"
echo "::set-output name=notarized::true"
else
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
fi
else
# Get detailed logs for failed notarization
echo "📋 Fetching detailed logs for submission ID: $SUBMISSION_ID"
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" 2>&1)
echo "==== DETAILED NOTARIZATION LOGS ===="
echo "$LOGS_OUTPUT"
echo "=================================="
echo "❌ Notarization failed with status: $FINAL_STATUS"
exit 1
fi
# Clean up
rm -rf ~/private_keys
else
echo "⚠️ Missing notarization credentials. Skipping notarization."
echo "Set these secrets for notarization:"
echo " - NOTARY_API_KEY_ID: Your API key ID"
echo " - NOTARY_API_KEY_ISSUER_ID: Your API issuer ID"
echo " - NOTARY_API_KEY_PATH: Your p8 file content"
fi
- name: Create DMG Package
id: package
if: steps.notarize.outputs.notarized == 'true'
shell: bash
run: |
echo "📦 Creating DMG package..."
# Get app name for DMG file naming
APP_NAME=$(basename "${{ env.APP_PATH }}" .app)
# Create a DMG package
DMG_FILE="./PackagedReleases/${APP_NAME}-Signed-Notarized.dmg"
rm -f "$DMG_FILE" 2>/dev/null || true
# Create temporary folder for DMG contents
DMG_TMP_DIR=$(mktemp -d)
cp -R "${{ env.APP_PATH }}" "$DMG_TMP_DIR/"
# Create DMG file with the app
hdiutil create -volname "${APP_NAME}" -srcfolder "$DMG_TMP_DIR" -ov -format UDZO "$DMG_FILE"
if [ -f "$DMG_FILE" ]; then
echo "✅ Created DMG package: $DMG_FILE"
echo "Size: $(du -h "$DMG_FILE" | cut -f1)"
echo "DMG_PATH=$DMG_FILE" >> $GITHUB_ENV
else
echo "❌ Failed to create DMG package"
exit 1
fi
# Clean up temporary directory
rm -rf "$DMG_TMP_DIR"
- name: Upload DMG Package
uses: actions/upload-artifact@v3
if: steps.package.outputs.DMG_PATH != ''
with:
name: LuckyWorld-macOS-Signed-Notarized
path: ${{ env.DMG_PATH }}
retention-days: 30
- name: Cleanup
if: always()
shell: bash
run: |
echo "🧹 Cleaning up..."
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
rm -f *-notarize.zip || true
echo "✅ Cleanup complete"