name: Test macOS Build Action on: workflow_dispatch: # Manual trigger only for testing push: branches: [ozgur/build] jobs: test-macos-build: runs-on: macos steps: - name: Checkout repository uses: actions/checkout@v3 with: lfs: true fetch-depth: 0 # Setup environment for build - name: Setup environment run: | # Get the working directory path for absolute paths WORKSPACE_DIR="$(pwd)" echo "WORKSPACE_DIR=$WORKSPACE_DIR" >> "$GITHUB_ENV" echo "ENTITLEMENTS_FILE=LuckyWorld.entitlements" >> "$GITHUB_ENV" # Set CI environment variable to true for build script echo "CI=true" >> "$GITHUB_ENV" # Create directories for builds mkdir -p Builds/Mac mkdir -p PackagedReleases echo "Environment setup complete" shell: bash # Verify bundle identifier is correctly set - name: Verify bundle identifier run: | # Set the constant bundle ID for the workflow echo "BUNDLE_ID=com.luckyrobots.luckyworld" >> "$GITHUB_ENV" # Verify the bundle ID is correctly set in DefaultGame.ini CONFIG_FILE="Config/DefaultGame.ini" if [ -f "$CONFIG_FILE" ]; then if grep -q "\[/Script/MacTargetPlatform\.MacTargetSettings\]" "$CONFIG_FILE" && grep -q "BundleIdentifier=com.luckyrobots.luckyworld" "$CONFIG_FILE"; then echo "✅ Bundle ID correctly set in DefaultGame.ini" else echo "⚠️ Warning: Bundle ID may not be correctly set in DefaultGame.ini" echo "Please make sure the following section exists:" echo "[/Script/MacTargetPlatform.MacTargetSettings]" echo "BundleIdentifier=com.luckyrobots.luckyworld" fi else echo "⚠️ DefaultGame.ini not found!" fi shell: bash # Check Unreal Engine Project settings - name: Inspect Unreal Settings run: | # Check for any potential issues in UE project settings if [ -f "Config/DefaultEngine.ini" ]; then echo "Checking DefaultEngine.ini for settings that might affect bundle ID..." grep -i "bundle\|identifier\|package" Config/DefaultEngine.ini || echo "No relevant settings found" fi if [ -f "Config/DefaultGame.ini" ]; then echo "Checking DefaultGame.ini for settings that might affect bundle ID..." grep -i "bundle\|identifier\|package" Config/DefaultGame.ini || echo "No relevant settings found" fi shell: bash # Build for macOS - use your own build script or create a test app if needed - name: Build for macOS 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 # Find the app bundle - name: Find app bundle 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." echo "Checking build logs for common issues..." # Check for the specific GlobalDefinitions error if grep -q "GlobalDefinitions.*This is not allowed" "$GITHUB_WORKSPACE"/*.log 2>/dev/null || grep -q "BuildEnvironment\|bOverrideBuildEnvironment" "$GITHUB_WORKSPACE"/*.log 2>/dev/null; then echo "🔍 Found issue with GlobalDefinitions in build target." echo "⚠️ Fix required in LuckyWorld.Target.cs - need to add bOverrideBuildEnvironment = true;" echo "Fix suggestion for LuckyWorld.Target.cs:" echo "Add this line in the constructor:" echo " bOverrideBuildEnvironment = true;" echo "" echo "Example:" echo " public LuckyWorldTarget(TargetInfo Target) : base(Target)" echo " {" echo " Type = TargetType.Game;" echo " DefaultBuildSettings = BuildSettingsVersion.V5;" echo " bOverrideBuildEnvironment = true; // Add this line" echo " " echo " // Rest of your constructor..." echo " }" fi 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" echo "APP_PATH=$MAIN_APP_PATH" >> "$GITHUB_ENV" # 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" # Note: While bundle ID should be set by Unreal Engine build system based on DefaultGame.ini # and LuckyWorld.Build.cs settings, we still check and fix if needed as a safety measure, # since UE builds can sometimes have issues with bundle ID propagation echo "Checking bundle IDs (fallback fix in case UE didn't properly apply settings)..." # Fix bundle ID in Info.plist before signing if [ -f "$MAIN_APP_PATH/Contents/Info.plist" ]; then echo "Checking current bundle identifier..." CURRENT_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist") echo "Current bundle ID: $CURRENT_BUNDLE_ID" if [ "$CURRENT_BUNDLE_ID" != "$BUNDLE_ID" ]; then echo "Bundle ID mismatch - fixing it!" echo "Setting bundle identifier to $BUNDLE_ID" /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $BUNDLE_ID" "$MAIN_APP_PATH/Contents/Info.plist" echo "Updated bundle ID in Info.plist: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$MAIN_APP_PATH/Contents/Info.plist")" else echo "✅ Bundle ID is already correct: $BUNDLE_ID" fi else echo "WARNING: Could not find Info.plist in app bundle." fi # Find and repair nested app bundles as well (like CrashReportClient.app) NESTED_APPS=$(find "$MAIN_APP_PATH" -name "*.app" -type d | grep -v "^$MAIN_APP_PATH$") if [ -n "$NESTED_APPS" ]; then echo "Found nested app bundles, checking their bundle IDs:" echo "$NESTED_APPS" | while read -r NESTED_APP; do if [ -f "$NESTED_APP/Contents/Info.plist" ]; then NESTED_NAME=$(basename "$NESTED_APP" .app) NESTED_BUNDLE_ID="$BUNDLE_ID.$NESTED_NAME" CURRENT_NESTED_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$NESTED_APP/Contents/Info.plist") if [ "$CURRENT_NESTED_ID" != "$NESTED_BUNDLE_ID" ]; then echo "Setting nested bundle ID to $NESTED_BUNDLE_ID for $NESTED_APP" /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $NESTED_BUNDLE_ID" "$NESTED_APP/Contents/Info.plist" # Verify the change UPDATED_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$NESTED_APP/Contents/Info.plist") echo "Updated nested app bundle ID: $UPDATED_ID" else echo "✅ Nested app $NESTED_NAME already has correct bundle ID: $CURRENT_NESTED_ID" fi fi done fi shell: bash # Fix common issues that may cause notarization failure - name: Fix common issues for notarization run: | echo "🛠️ Fixing common issues that may cause notarization failure..." APP_PATH="${{ env.APP_PATH }}" # Remove get-task-allow entitlement from Info.plist files echo "Checking for get-task-allow entitlement..." find "$APP_PATH" -name "*.plist" -exec plutil -convert xml1 {} \; -exec grep -l "get-task-allow" {} \; | while read -r plist_file; do echo "Removing get-task-allow from $plist_file" /usr/libexec/PlistBuddy -c "Delete :com.apple.security.get-task-allow" "$plist_file" 2>/dev/null || true done # Check for problematic libraries that cause issues echo "Looking for problematic files..." PROBLEM_FILES=$(find "$APP_PATH" -type f -name "*.dylib" | grep -i "boost\|tbb\|ogg\|vorbis\|onnx") if [ -n "$PROBLEM_FILES" ]; then echo "Found potentially problematic libraries. These will be carefully handled during signing:" echo "$PROBLEM_FILES" | head -10 if [ $(echo "$PROBLEM_FILES" | wc -l) -gt 10 ]; then echo "... and $(echo "$PROBLEM_FILES" | wc -l) more" fi fi # Verify CrashReportClient specifically CRASH_REPORTER=$(find "$APP_PATH" -path "*CrashReportClient.app*" -type d | head -1) if [ -n "$CRASH_REPORTER" ]; then echo "Found CrashReportClient at $CRASH_REPORTER" if [ -f "$CRASH_REPORTER/Contents/Info.plist" ]; then # Ensure it has the correct bundle ID format CRASH_BUNDLE_ID="$BUNDLE_ID.CrashReportClient" CURRENT_CRASH_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$CRASH_REPORTER/Contents/Info.plist") if [ "$CURRENT_CRASH_ID" != "$CRASH_BUNDLE_ID" ]; then echo "Setting CrashReportClient bundle ID to $CRASH_BUNDLE_ID" /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $CRASH_BUNDLE_ID" "$CRASH_REPORTER/Contents/Info.plist" echo "Updated CrashReportClient bundle ID: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$CRASH_REPORTER/Contents/Info.plist")" else echo "✅ CrashReportClient already has correct bundle ID: $CURRENT_CRASH_ID" fi fi fi shell: bash # Use the macos-notarize action to sign and notarize the app - name: Sign and Notarize macOS App uses: ./.gitea/actions/macos-notarize id: sign-and-notarize with: app-path: ${{ env.APP_PATH }} entitlements-file: ${{ env.ENTITLEMENTS_FILE }} team-id: ${{ secrets.APPLE_TEAM_ID }} certificate-base64: ${{ secrets.MACOS_CERTIFICATE }} certificate-password: ${{ secrets.MACOS_CERTIFICATE_PWD }} notarization-method: 'api-key' notary-api-key-id: ${{ secrets.NOTARY_API_KEY_ID }} notary-api-key-issuer-id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }} notary-api-key-path: ${{ secrets.NOTARY_API_KEY_PATH }} bundle-id: ${{ env.BUNDLE_ID }} fallback-to-adhoc: 'false' # Additional verification and stapling to ensure the app opens without warning - name: Verify and Staple App if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' run: | echo "🔒 Performing additional verification and stapling..." APP_PATH="${{ env.APP_PATH }}" # Make sure the app is properly stapled echo "Stapling notarization ticket to the app..." xcrun stapler staple "$APP_PATH" # Verify the stapling echo "Verifying stapling..." xcrun stapler validate "$APP_PATH" # Perform deep verification of code signing echo "Verifying code signature (deep)..." codesign -vvv --deep "$APP_PATH" # Additional check for quarantine attributes echo "Checking for quarantine attributes..." if [ -n "$(xattr -l "$APP_PATH" | grep quarantine)" ]; then echo "Removing quarantine attribute..." xattr -d com.apple.quarantine "$APP_PATH" else echo "No quarantine attribute found, good!" fi echo "✅ Verification and stapling completed!" # Export STAPLED_APP_PATH for later use echo "STAPLED_APP_PATH=$APP_PATH" >> "$GITHUB_ENV" shell: bash # Create a properly archived ZIP of the stapled app (preserves stapling) - name: Create Stapled App Archive (ZIP) if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' run: | STAPLED_APP_PATH="${{ env.STAPLED_APP_PATH }}" APP_NAME=$(basename "$STAPLED_APP_PATH" .app) ARCHIVE_DIR="./ArchivedApps" mkdir -p "$ARCHIVE_DIR" # Create the ZIP archive (preserving all metadata) echo "Creating ZIP archive of stapled app..." cd "$(dirname "$STAPLED_APP_PATH")" # Use ditto to preserve all metadata and permissions ditto -c -k --keepParent "$(basename "$STAPLED_APP_PATH")" "$GITHUB_WORKSPACE/$ARCHIVE_DIR/$APP_NAME.zip" cd "$GITHUB_WORKSPACE" echo "ZIP archive created at: $ARCHIVE_DIR/$APP_NAME.zip" echo "STAPLED_APP_ZIP=$ARCHIVE_DIR/$APP_NAME.zip" >> "$GITHUB_ENV" shell: bash # Create a DMG file (macOS disk image) for easy distribution - name: Create DMG for Distribution if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' run: | STAPLED_APP_PATH="${{ env.STAPLED_APP_PATH }}" APP_NAME=$(basename "$STAPLED_APP_PATH" .app) ARCHIVE_DIR="./ArchivedApps" DMG_FILE="$ARCHIVE_DIR/$APP_NAME.dmg" # Create a DMG file echo "Creating DMG file..." hdiutil create -volname "$APP_NAME" -srcfolder "$STAPLED_APP_PATH" -ov -format UDZO "$DMG_FILE" echo "DMG file created at: $DMG_FILE" echo "STAPLED_APP_DMG=$DMG_FILE" >> "$GITHUB_ENV" shell: bash # Upload stapled app directly (this is the most reliable approach) - name: Upload Stapled App Bundle uses: actions/upload-artifact@v3 if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' with: name: LuckyWorld-macOS-Stapled-App-Bundle path: ${{ env.STAPLED_APP_PATH }} retention-days: 30 # Upload the ZIP archive (proper archiving that preserves stapling) - name: Upload Stapled App ZIP Archive uses: actions/upload-artifact@v3 if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' with: name: LuckyWorld-macOS-Stapled-ZIP path: ${{ env.STAPLED_APP_ZIP }} retention-days: 30 # Upload the DMG file - name: Upload Stapled App DMG uses: actions/upload-artifact@v3 if: steps.sign-and-notarize.outputs.notarized == 'true' && steps.sign-and-notarize.outputs.signed != 'none' with: name: LuckyWorld-macOS-Stapled-DMG path: ${{ env.STAPLED_APP_DMG }} retention-days: 30 # Upload signed app (might be DMG or other package format) - name: Upload Signed App Package uses: actions/upload-artifact@v3 if: steps.sign-and-notarize.outputs.signed != 'none' with: name: ${{ steps.sign-and-notarize.outputs.notarized == 'true' && 'LuckyWorld-macOS-Signed-Notarized-Package' || 'LuckyWorld-macOS-Signed-Package' }} path: ${{ steps.sign-and-notarize.outputs.package-path }} retention-days: 30 # Upload ZIP package if DMG was created (as a backup) - name: Upload ZIP Package uses: actions/upload-artifact@v3 if: steps.sign-and-notarize.outputs.signed != 'none' && steps.sign-and-notarize.outputs.zip-package-path != '' with: name: ${{ steps.sign-and-notarize.outputs.notarized == 'true' && 'LuckyWorld-macOS-Signed-Notarized-ZIP' || 'LuckyWorld-macOS-Signed-ZIP' }} path: ${{ steps.sign-and-notarize.outputs.zip-package-path }} retention-days: 30 # Report results - name: Report Results run: | echo "🔐 App signing: ${{ steps.sign-and-notarize.outputs.signed }}" echo "🔏 App notarization: ${{ steps.sign-and-notarize.outputs.notarized }}" if [ "${{ steps.sign-and-notarize.outputs.signed }}" != "none" ]; then echo "✅ Packaging completed successfully!" echo "Final package: ${{ steps.sign-and-notarize.outputs.package-path }}" else echo "⚠️ App was not signed - check the logs for details" fi shell: bash