WIP: feat(workflows): add new build workflows for Windows, Linux, and macOS, and remove obsolete build scripts #17

Draft
m wants to merge 93 commits from ozgur/build into main
21 changed files with 2370 additions and 506 deletions

View File

@ -0,0 +1,46 @@
name: 'Linux Build Steps'
description: 'Build Linux application'
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Set environment variables for Linux toolchain (needed for cross-compilation)
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
# Create directories for builds
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
shell: pwsh
- name: Build for Linux
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/linux_build.sh"
shell: pwsh
- name: Package Linux build
run: |
echo "Packaging Linux build..."
if [ -d "Builds/Linux" ]; then
cd Builds/Linux
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
cd ../..
fi
echo "=== Packaged Linux release ==="
ls -la PackagedReleases/
shell: bash
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
with:
name: LuckyRobots-Linux
path: PackagedReleases/LuckyRobots-Linux.zip
retention-days: 365

View File

@ -0,0 +1,132 @@
name: 'macOS Build Steps'
description: 'Build, sign and notarize macOS application'
inputs:
apple_team_id:
description: 'Apple Team ID for signing'
required: true
apple_certificate_base64:
description: 'Base64-encoded certificate file'
required: true
apple_certificate_password:
description: 'Password for certificate file'
required: true
api_key_path:
description: 'Base64-encoded API key file'
required: true
api_key_id:
description: 'API Key ID'
required: true
api_key_issuer_id:
description: 'API Key Issuer ID'
required: true
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Use the correct path where Unreal Engine is installed
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
if [ ! -d "$UE_PATH" ]; then
echo "Error: Unreal Engine is not installed in the expected location"
echo "Please ensure Unreal Engine is installed at $UE_PATH"
exit 1
fi
# Create directories for builds
mkdir -p Builds/Mac
mkdir -p PackagedReleases
echo "Using Unreal Engine 5.5"
shell: bash
- name: Build for macOS
run: |
chmod +x ./scripts/mac_build.sh
./scripts/mac_build.sh
shell: bash
- name: Setup for Signing
id: setup-signing
if: ${{ success() }}
env:
API_KEY_PATH: ${{ inputs.api_key_path }}
run: |
# Create output directory
mkdir -p PackagedReleases
# Decode the API key from Base64 secret
echo "$API_KEY_PATH" | base64 --decode > api_key.p8
echo "api_key_file=$(pwd)/api_key.p8" >> $GITHUB_OUTPUT
# Find app bundle
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
if [ -z "$APP_PATH" ]; then
# Look for a directory that might be a bundle but not named .app
APP_PATH=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
if [ -z "$APP_PATH" ]; then
echo "No build directory found, cannot continue"
exit 1
fi
fi
echo "Found app path: $APP_PATH"
echo "app_path=$APP_PATH" >> $GITHUB_OUTPUT
shell: bash
- name: Sign macOS App
uses: lando/code-sign-action@v3
id: sign-app
with:
file: ${{ steps.setup-signing.outputs.app_path }}
certificate-data: ${{ inputs.apple_certificate_base64 }}
certificate-password: ${{ inputs.apple_certificate_password }}
certificate-id: ${{ inputs.apple_team_id }}
options: --force --options runtime --deep --timestamp --entitlements ./LuckyRobots.entitlements
- name: Notarize macOS App
run: |
# Create a temporary file for notarization
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
NOTARIZE_APP_PATH="./LuckyRobots-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$NOTARIZE_APP_PATH"
API_KEY_FILE="${{ steps.setup-signing.outputs.api_key_file }}"
# Submit for notarization using API key
echo "Submitting for notarization with API key..."
xcrun notarytool submit "$NOTARIZE_APP_PATH" --key "$API_KEY_FILE" --key-id "${{ inputs.api_key_id }}" --issuer "${{ inputs.api_key_issuer_id }}" --wait
# Staple the ticket to the application
xcrun stapler staple "$APP_PATH"
# Clean up the API key file
rm -f "$API_KEY_FILE"
rm -f "$NOTARIZE_APP_PATH"
shell: bash
- name: Package macOS App
run: |
# Package the signed and notarized app
APP_PATH="${{ steps.setup-signing.outputs.app_path }}"
APP_NAME=$(basename "$APP_PATH")
DIR_PATH=$(dirname "$APP_PATH")
echo "Creating final package..."
(cd "$DIR_PATH" && zip -r "../../PackagedReleases/LuckyRobots-macOS.zip" "$APP_NAME")
echo "Created packaged release: PackagedReleases/LuckyRobots-macOS.zip"
echo "Packaged releases:"
ls -la PackagedReleases/
shell: bash
- name: Upload macOS Build Artifact
uses: actions/upload-artifact@v3
if: success()
with:
name: LuckyRobots-macOS
path: PackagedReleases/LuckyRobots-macOS.zip
retention-days: 365

View File

@ -0,0 +1,717 @@
name: "macOS Sign and Notarize"
description: "Signs and notarizes macOS applications with Developer ID certificate"
author: moersoy"
inputs:
app-path:
description: "Path to the app bundle (.app)"
required: true
entitlements-file:
description: "Path to the entitlements file (.entitlements)"
required: true
team-id:
description: "Apple Developer Team ID"
required: true
certificate-base64:
description: "Base64-encoded Developer ID Application certificate (.p12)"
required: true
certificate-password:
description: "Password for the Developer ID Application certificate"
required: true
notarization-method:
description: "Method to use for notarization: 'api-key' or 'app-password'"
required: false
default: "api-key"
notary-user:
description: "Apple ID for notarization (for app-password method)"
required: false
notary-password:
description: "App-specific password for notarization (for app-password method)"
required: false
notary-api-key-id:
description: "API Key ID for notarization (for api-key method)"
required: false
notary-api-key-issuer-id:
description: "API Issuer ID for notarization (for api-key method)"
required: false
notary-api-key-path:
description: "Path to or content of the API Key .p8 file (for api-key method)"
required: false
bundle-id:
description: "App bundle identifier (com.example.app)"
required: false
fallback-to-adhoc:
description: "Whether to fall back to ad-hoc signing if certificate is invalid"
required: false
default: "true"
outputs:
signed:
description: "Whether the app was signed (identity, adhoc, or none)"
value: ${{ steps.sign.outputs.signed }}
notarized:
description: "Whether the app was notarized (true or false)"
value: ${{ steps.notarize.outputs.notarized }}
package-path:
description: "Path to the final package"
value: ${{ steps.package.outputs.package-path }}
runs:
using: "composite"
steps:
- name: Setup Certificate
id: setup-cert
shell: bash
env:
CERTIFICATE_BASE64: ${{ inputs.certificate-base64 }}
CERTIFICATE_PASSWORD: ${{ inputs.certificate-password }}
APPLE_TEAM_ID: ${{ inputs.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: Sign App
id: sign
shell: bash
run: |
echo "🔏 Signing app with Developer ID certificate..."
# Check if app path exists
if [ ! -d "${{ inputs.app-path }}" ]; then
echo "❌ App bundle not found at ${{ inputs.app-path }}"
echo "::set-output name=signed::none"
exit 1
fi
# Check if entitlements file exists
if [ ! -f "${{ inputs.entitlements-file }}" ]; then
echo "❌ Entitlements file not found at ${{ inputs.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"
if [ "${{ inputs.fallback-to-adhoc }}" = "true" ]; then
echo "Falling back to ad-hoc signing for testing..."
# Use ad-hoc identity as fallback
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign - --timestamp "${{ inputs.app-path }}"
echo "::set-output name=signed::adhoc"
else
echo "Skipping signing. Set fallback-to-adhoc=true to use ad-hoc signing instead."
echo "::set-output name=signed::none"
exit 1
fi
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 "${{ inputs.app-path }}" -name "*.dylib" | while read -r dylib; do
echo "Signing: $dylib"
codesign --force --verbose --options runtime --entitlements "${{ inputs.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 "${{ inputs.app-path }}" -name "*.so" | while read -r so; do
echo "Signing: $so"
codesign --force --verbose --options runtime --entitlements "${{ inputs.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 "${{ inputs.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 "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$exe" || echo "⚠️ Failed to sign: $exe"
done
# Sign all frameworks
echo "Signing frameworks..."
find "${{ inputs.app-path }}" -path "*.framework" -type d | while read -r framework; do
echo "Signing framework: $framework"
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$framework" || echo "⚠️ Failed to sign: $framework"
done
# Special handling for CrashReportClient.app
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
if [ -n "$CRASH_REPORTER" ]; then
echo "🔍 Special handling for CrashReportClient.app: $CRASH_REPORTER"
# Sign CrashReportClient.app specifically with focus on hardened runtime
find "$CRASH_REPORTER" -type f -perm +111 | while read -r crash_bin; do
echo "Signing CrashReportClient binary: $crash_bin"
codesign --force --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$crash_bin" || echo "⚠️ Failed to sign: $crash_bin"
done
echo "Signing the CrashReportClient.app bundle itself..."
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$CRASH_REPORTER" || echo "⚠️ Failed to sign CrashReportClient.app"
fi
# Sign any other nested app bundles
find "${{ inputs.app-path }}" -path "*.app" -type d | grep -v CrashReportClient | while read -r nested_app; do
if [ "$nested_app" != "${{ inputs.app-path }}" ]; then
echo "Signing nested app: $nested_app"
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "$nested_app" || echo "⚠️ Failed to sign: $nested_app"
fi
done
# Final signing of the main bundle
echo "🔐 Performing final signing of the main app bundle..."
codesign --force --deep --verbose --options runtime --entitlements "${{ inputs.entitlements-file }}" --sign "$IDENTITY_HASH" --timestamp "${{ inputs.app-path }}"
echo "::set-output name=signed::identity"
fi
# Verify signing
echo "🔍 Verifying signature..."
codesign -vvv --deep --strict "${{ inputs.app-path }}"
# Check entitlements
echo "🔍 Checking entitlements..."
codesign -d --entitlements - "${{ inputs.app-path }}"
# Verify CrashReportClient
CRASH_REPORTER=$(find "${{ inputs.app-path }}" -path "*CrashReportClient.app" -type d | head -1)
if [ -n "$CRASH_REPORTER" ]; then
echo "🔍 Verifying CrashReportClient signature..."
codesign -vvv --deep --strict "$CRASH_REPORTER" || echo "⚠️ CrashReportClient may have verification issues"
echo "CrashReportClient entitlements:"
codesign -d --entitlements - "$CRASH_REPORTER" || echo "⚠️ Could not display CrashReportClient entitlements"
fi
- name: Notarize App
id: notarize
if: steps.sign.outputs.signed != 'none'
shell: bash
env:
APPLE_ID: ${{ inputs.notary-user }}
APP_PASSWORD: ${{ inputs.notary-password }}
API_KEY_ID: ${{ inputs.notary-api-key-id }}
API_ISSUER_ID: ${{ inputs.notary-api-key-issuer-id }}
API_KEY_PATH: ${{ inputs.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 "${{ inputs.app-path }}" .app)
BUNDLE_ID="${{ inputs.bundle-id }}"
# If bundle ID is not provided, try to extract from Info.plist
if [ -z "$BUNDLE_ID" ]; then
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.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 [ "${{ inputs.notarization-method }}" = "api-key" ] && [ -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 "${{ inputs.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 "${{ inputs.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 "${{ inputs.app-path }}"
# Check if stapling metadata is correctly stored in xattr
echo "Checking app extended attributes..."
if command -v xattr &> /dev/null; then
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
fi
# Add special instructions for distribution
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
echo "For proper distribution, consider the following options:"
echo "1. Use a DMG installer with a signed, notarized app inside"
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
else
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
fi
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
echo "::set-output name=notarized::true"
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 "=================================="
# Extract specific issues for easier debugging
echo "🔍 Extracting specific issues from logs..."
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
# Show current bundle ID in Info.plist
echo "📋 Current bundle ID information:"
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
echo "Info.plist content for bundle ID:"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
echo "Full Info.plist excerpt:"
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
else
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
fi
# Check for mismatched bundle ID
if [ "$BUNDLE_ID" != "$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null)" ]; then
echo "⚠️ WARNING: Bundle ID mismatch detected between workflow and app!"
echo " - Workflow/input bundle ID: $BUNDLE_ID"
echo " - Actual app bundle ID: $(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" 2>/dev/null || echo "Could not read")"
echo "This mismatch could cause notarization problems."
fi
# Check for code signature issues in internal components
echo "🔍 Checking for code signature issues in app components..."
find "${{ inputs.app-path }}" -type f -name "*.dylib" -o -name "*.so" | head -5 | while read -r lib; do
echo "Checking signature on: $lib"
codesign -vvv "$lib" || echo "⚠️ Signature issue with: $lib"
done
echo "❌ Notarization failed with status: $FINAL_STATUS"
exit 1
fi
# Clean up
rm -rf ~/private_keys
# Fall back to App-specific password if requested
elif [ "${{ inputs.notarization-method }}" = "app-password" ] && [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
echo "Using App-specific password for notarization..."
# Create zip for notarization
ZIP_PATH="${APP_NAME}-notarize.zip"
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_PATH"
echo "Submitting for notarization..."
# First, submit without waiting for completion
SUBMIT_OUTPUT=$(xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_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 "${{ inputs.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 "${{ inputs.app-path }}"
# Check if stapling metadata is correctly stored in xattr
echo "Checking app extended attributes..."
if command -v xattr &> /dev/null; then
xattr "${{ inputs.app-path }}" | grep -q "com.apple.provenance" || echo "⚠️ Warning: com.apple.provenance attribute not found"
fi
# Add special instructions for distribution
echo "📋 IMPORTANT DISTRIBUTION NOTE: When users download this app, they may still see Gatekeeper warnings."
echo "This happens because of the 'quarantine' extended attribute that browsers add to downloaded files."
echo "For proper distribution, consider the following options:"
echo "1. Use a DMG installer with a signed, notarized app inside"
echo "2. Add instructions for users on how to open a quarantined app (right-click > Open)"
echo "3. If distributing directly, use a distribution platform that preserves notarization tickets"
else
echo "⚠️ Stapling completed with status $STAPLE_STATUS (may still be valid)"
fi
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "${{ inputs.app-path }}"
echo "::set-output name=notarized::true"
else
# Get detailed logs for failed notarization
echo "📋 Fetching detailed logs for submission ID: $SUBMISSION_ID"
LOGS_OUTPUT=$(xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" 2>&1)
echo "==== DETAILED NOTARIZATION LOGS ===="
echo "$LOGS_OUTPUT"
echo "=================================="
# Extract specific issues for easier debugging
echo "🔍 Extracting specific issues from logs..."
echo "$LOGS_OUTPUT" | grep -A 3 "issues"
# Show current bundle ID in Info.plist
echo "📋 Current bundle ID information:"
if [ -f "${{ inputs.app-path }}/Contents/Info.plist" ]; then
echo "Info.plist content for bundle ID:"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "${{ inputs.app-path }}/Contents/Info.plist" || echo "Could not read bundle ID from Info.plist"
echo "Full Info.plist excerpt:"
plutil -p "${{ inputs.app-path }}/Contents/Info.plist" | grep -i bundle
else
echo "Info.plist not found at expected location: ${{ inputs.app-path }}/Contents/Info.plist"
fi
echo "❌ Notarization failed with status: $FINAL_STATUS"
exit 1
fi
else
echo "⚠️ Missing notarization credentials. Skipping notarization."
echo "For App Store Connect API key method, set these inputs:"
echo " - notarization-method: api-key"
echo " - notary-api-key-id: Your API key ID"
echo " - notary-api-key-issuer-id: Your API issuer ID"
echo " - notary-api-key-path: Path to or content of your p8 file"
echo ""
echo "For App-specific password method, set these inputs:"
echo " - notarization-method: app-password"
echo " - notary-user: Your Apple ID (email)"
echo " - notary-password: Your app-specific password"
echo " - team-id: Your Apple Developer team ID"
fi
- name: Package App
id: package
if: steps.sign.outputs.signed != 'none'
shell: bash
run: |
echo "📦 Packaging signed app..."
# Get app name for zip file naming
APP_NAME=$(basename "${{ inputs.app-path }}" .app)
if [ "${{ steps.notarize.outputs.notarized }}" = "true" ]; then
PACKAGE_SUFFIX="Signed-Notarized"
echo "Creating distribution package with notarized app..."
else
PACKAGE_SUFFIX="Signed"
echo "Creating distribution package with signed app..."
fi
# Create zip package
ZIP_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.zip"
ditto -c -k --keepParent "${{ inputs.app-path }}" "$ZIP_FILE"
echo "✅ Created ZIP package: $ZIP_FILE"
# Check if we can create DMG (hdiutil is available)
if command -v hdiutil &> /dev/null; then
# Create DMG package (much better for distribution)
DMG_FILE="${APP_NAME}-${PACKAGE_SUFFIX}.dmg"
echo "Creating DMG distribution package..."
# Create temporary folder for DMG contents
DMG_TMP_DIR=$(mktemp -d)
cp -R "${{ inputs.app-path }}" "$DMG_TMP_DIR/"
# Optional: Add README or instructions
echo "# Installation Instructions\n\nDrag the application to your Applications folder to install." > "$DMG_TMP_DIR/README.txt"
# 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"
# Use DMG as the primary package if available
echo "::set-output name=package-path::$DMG_FILE"
echo "::set-output name=zip-package-path::$ZIP_FILE"
else
echo "⚠️ Failed to create DMG, falling back to ZIP package"
echo "::set-output name=package-path::$ZIP_FILE"
fi
# Clean up temp directory
rm -rf "$DMG_TMP_DIR"
else
echo "hdiutil not available, skipping DMG creation"
echo "::set-output name=package-path::$ZIP_FILE"
fi
- 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"

View File

@ -0,0 +1,42 @@
name: 'Windows Build Steps'
description: 'Build Windows application'
runs:
using: "composite"
steps:
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Create directories for builds
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
shell: pwsh
- name: Build for Windows
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./scripts/win_build.sh"
shell: pwsh
- name: Package Windows build
run: |
echo "Packaging Windows build..."
if [ -d "Builds/Windows" ]; then
cd Builds/Windows
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
cd ../..
fi
echo "=== Packaged Windows release ==="
ls -la PackagedReleases/
shell: bash
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
with:
name: LuckyRobots-Windows
path: PackagedReleases/LuckyRobots-Windows.zip
retention-days: 365

209
.gitea/workflows/build.yml Normal file
View File

@ -0,0 +1,209 @@
name: Unreal Engine Build
on:
workflow_dispatch:
# push:
# branches: [ozgur/build]
jobs:
windows-build:
runs-on: windows
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build Windows
uses: ./.gitea/actions/windows-build
linux-build:
runs-on: windows
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build Linux
uses: ./.gitea/actions/linux-build
macos-build:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Build macOS
uses: ./.gitea/actions/macos-build
with:
apple_team_id: ${{ secrets.APPLE_TEAM_ID }}
apple_certificate_base64: ${{ secrets.MACOS_CERTIFICATE }}
apple_certificate_password: ${{ secrets.MACOS_CERTIFICATE_PWD }}
api_key_path: ${{ secrets.NOTARY_API_KEY_PATH }}
api_key_id: ${{ secrets.NOTARY_API_KEY_ID }}
api_key_issuer_id: ${{ secrets.NOTARY_API_KEY_ISSUER_ID }}
create-release:
needs: [windows-build, linux-build, macos-build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Create Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag
git tag -a "$TAG" -m "Release $TAG"
# Push tag
git push origin "$TAG"
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: releases
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
}
}' > build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<div class="platform">
<h2>macOS</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
build-info.json
downloads.html
token: '${{ secrets.GITEATOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -1,103 +0,0 @@
name: Unreal Release
on:
workflow_dispatch:
inputs:
windows_build_path:
description: 'Absolute path to the Windows build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Windows\LuckyRobots-Windows.zip'
linux_build_path:
description: 'Absolute path to the Linux build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Linux\LuckyRobots-Linux.zip'
mac_build_path:
description: 'Absolute path to the Mac build zip file'
required: true
default: 'E:\LuckyWorld\LuckyRobots\UNREAL_PROJECTS\Luckyrobots\Builds\Mac\LuckyRobots-Mac.zip'
jobs:
build:
runs-on: windows
steps:
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Linux
path: ${{ github.event.inputs.linux_build_path }}
retention-days: 365
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Windows
path: ${{ github.event.inputs.windows_build_path }}
retention-days: 365
- name: Upload Mac Build Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyRobots-Mac
path: ${{ github.event.inputs.mac_build_path }}
retention-days: 365
- name: Get Release Tag
shell: pwsh
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
# Uses Sort-Object with a version comparison scriptblock
$latestTag = git tag -l "v[0-9]*.[0-9]*.[0-9]*" | Sort-Object -Property @{Expression={[version]($_ -replace 'v')}} | Select-Object -Last 1
$newVersion = "1.0.0" # Default start version
if ($null -ne $latestTag -and $latestTag -ne '') {
Write-Host "Latest tag found: $latestTag"
# Strip 'v' prefix
$versionString = $latestTag -replace '^v'
# Split version into parts
$versionParts = $versionString.Split('.')
if ($versionParts.Length -eq 3) {
$major = [int]$versionParts[0]
$minor = [int]$versionParts[1]
$patch = [int]$versionParts[2]
# Auto-increment patch version
$patch++
$newVersion = "$major.$minor.$patch"
Write-Host "Auto-incremented patch version from $versionString to $newVersion"
} else {
Write-Host "Could not parse version from tag: $latestTag. Defaulting to 1.0.0"
}
} else {
Write-Host "No previous version tags found, starting with 1.0.0"
}
# Final tag with v prefix
$tag = "v$newVersion"
# Set environment variable for subsequent steps
echo "RELEASE_TAG=$tag" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Write-Host "Using release tag: $tag"
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
token: '${{ secrets.GITEA_TOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
Windows, Linux and Mac builds are attached below.
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -0,0 +1,163 @@
name: Create Release
on:
workflow_dispatch:
workflow_call:
jobs:
create-release:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Create Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach
git remote set-url origin "https://runner:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag
git tag -a "$TAG" -m "Release $TAG"
# Push tag
git push origin "$TAG"
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Download all artifacts
uses: actions/download-artifact@v3
with:
path: releases
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux",
"macos": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS"
}
}' > build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<div class="platform">
<h2>macOS</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS" class="download-btn">Download macOS Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
build-info.json
downloads.html
token: '${{ secrets.GITEATOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
- [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'

View File

@ -0,0 +1,388 @@
name: Test Local Signing
on:
workflow_dispatch: # Manual trigger
# push:
# branches: [ozgur/build]
jobs:
test-local-signing:
runs-on: macos
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Create Test Entitlements
run: |
echo "📝 Creating entitlements file..."
cat > LuckyWorld.entitlements << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>
EOF
echo "✅ Created entitlements file"
cat LuckyWorld.entitlements
shell: bash
- name: Create Test App Bundle
run: |
echo "📦 Creating test app bundle..."
# Create test app bundle structure
TEST_APP_DIR="TestApp.app"
mkdir -p "$TEST_APP_DIR/Contents/MacOS"
# Create a simple test executable
echo '#!/bin/bash
echo "Hello from TestApp!"' > "$TEST_APP_DIR/Contents/MacOS/TestApp"
chmod +x "$TEST_APP_DIR/Contents/MacOS/TestApp"
# Create Info.plist
cat > "$TEST_APP_DIR/Contents/Info.plist" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>TestApp</string>
<key>CFBundleIdentifier</key>
<string>com.luckyrobots.luckyworld.testapp</string>
<key>CFBundleName</key>
<string>TestApp</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
</dict>
</plist>
EOF
echo "✅ Created test app bundle"
# Verify app bundle exists
if [ ! -d "$TEST_APP_DIR" ]; then
echo "❌ Error: App bundle not found at $TEST_APP_DIR"
exit 1
fi
echo "🔍 App bundle contents:"
ls -la "$TEST_APP_DIR"
# Store app path as environment variable
echo "APP_PATH=$(pwd)/TestApp.app" >> "$GITHUB_ENV"
shell: bash
- name: Setup Certificate
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"
# Check certificate format and details
echo "📑 Certificate format check:"
file "$CERT_DIR/certificate.p12"
# Try to get certificate info with openssl
echo "📑 Certificate info with OpenSSL:"
openssl pkcs12 -info -in "$CERT_DIR/certificate.p12" -nokeys -passin pass:"$CERTIFICATE_PASSWORD" || echo "Failed to read certificate with OpenSSL"
# 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"
# Try multiple import approaches
echo "🔑 Importing developer certificate - attempt 1 (standard)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign
echo "🔑 Importing developer certificate - attempt 2 (with flags)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -x -A
echo "🔑 Importing developer certificate - attempt 3 (with format)..."
security import "$CERT_DIR/certificate.p12" -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -T /usr/bin/codesign -f pkcs12
# Set partition list for codesign to access keychain
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
# Check all certificates in keychain
echo "🔍 Listing all certificates in keychain..."
security find-certificate -a "$KEYCHAIN_PATH"
# Verify certificate
echo "🔍 Verifying code signing identities..."
security find-identity -v -p codesigning "$KEYCHAIN_PATH"
# Alternative check for identities
echo "🔍 Listing identities with code signing usage..."
security find-certificate -a -c "Developer ID Application" -p "$KEYCHAIN_PATH" | grep -q "Code Signing" && echo "✅ Certificate has code signing usage" || echo "❌ Certificate does NOT have code signing usage"
# 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 "USE_SYSTEM_CERT=true" >> "$GITHUB_ENV"
else
echo "❌ No Developer ID Application certificate found in system keychain"
echo "USE_SYSTEM_CERT=false" >> "$GITHUB_ENV"
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"
# Debug: keep p12 file for inspection
echo "💾 Keeping certificate.p12 for debugging"
shell: bash
- name: Debug Certificate Content
if: always()
env:
CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
run: |
echo "🔎 Debugging certificate content..."
CERT_DIR="$HOME/certificates"
# Check if p12 file exists
if [ ! -f "$CERT_DIR/certificate.p12" ]; then
echo "❌ Certificate file not found"
exit 0
fi
# Try with OpenSSL to extract certificate info
echo "Attempting to extract certificate info..."
openssl pkcs12 -in "$CERT_DIR/certificate.p12" -info -nokeys -passin pass:"$CERTIFICATE_PASSWORD" > cert_info.txt || echo "Failed to extract info"
# Check certificate contents
echo "Certificate subject information:"
grep "subject" cert_info.txt || echo "No subject information found"
echo "Certificate issuer information:"
grep "issuer" cert_info.txt || echo "No issuer information found"
# Check if it's a Developer ID certificate
if grep -q "Developer ID" cert_info.txt; then
echo "✅ This appears to be a Developer ID certificate"
else
echo "❌ This does NOT appear to be a Developer ID certificate"
fi
# Check if it has a private key
echo "Checking for private key..."
if openssl pkcs12 -in "$CERT_DIR/certificate.p12" -nocerts -passin pass:"$CERTIFICATE_PASSWORD" -passout pass:temp 2>/dev/null; then
echo "✅ Certificate contains a private key"
else
echo "❌ Certificate does NOT contain a private key or wrong password"
fi
shell: bash
- name: Sign with Developer ID
run: |
echo "🔏 Signing app with Developer ID certificate..."
# Decide which keychain to use
if [ "${USE_SYSTEM_CERT:-false}" = "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 LuckyWorld.entitlements --sign - --timestamp "$APP_PATH"
echo "SIGNED=adhoc" >> "$GITHUB_ENV"
else
echo "Signing app bundle with Developer ID hash: $IDENTITY_HASH"
# Sign the app bundle using the hash
codesign --force --deep --verbose --options runtime --entitlements LuckyWorld.entitlements --sign "$IDENTITY_HASH" --timestamp "$APP_PATH"
echo "SIGNED=identity" >> "$GITHUB_ENV"
fi
# Verify signing
echo "🔍 Verifying signature..."
codesign -vvv --deep --strict "$APP_PATH"
# Check entitlements
echo "🔍 Checking entitlements..."
codesign -d --entitlements - "$APP_PATH"
shell: bash
- name: Notarize App
if: success()
env:
APPLE_ID: ${{ secrets.NOTARY_USER }}
APP_PASSWORD: ${{ secrets.NOTARY_PASSWORD }}
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..."
# Check if we have API key credentials
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="TestApp-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
echo "Submitting for notarization with API key..."
xcrun notarytool submit "$ZIP_PATH" \
--key ~/private_keys/AuthKey_${API_KEY_ID}.p8 \
--key-id "$API_KEY_ID" \
--issuer "$API_ISSUER_ID" \
--wait
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "$APP_PATH"
echo "NOTARIZED=true" >> "$GITHUB_ENV"
# Clean up
rm -rf ~/private_keys
# Fall back to App-specific password if API key not available
elif [ -n "$APPLE_ID" ] && [ -n "$APP_PASSWORD" ] && [ -n "$APPLE_TEAM_ID" ]; then
echo "Using App-specific password for notarization..."
# Create zip for notarization
ZIP_PATH="TestApp-notarize.zip"
ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH"
echo "Submitting for notarization..."
xcrun notarytool submit "$ZIP_PATH" \
--apple-id "$APPLE_ID" \
--password "$APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait
# Staple the notarization ticket
echo "Stapling notarization ticket..."
xcrun stapler staple "$APP_PATH"
# Verify notarization
echo "🔍 Verifying notarization..."
spctl --assess --verbose --type exec "$APP_PATH"
echo "NOTARIZED=true" >> "$GITHUB_ENV"
else
echo "⚠️ Missing notarization credentials. Skipping notarization."
echo "For App Store Connect API key method, set these secrets:"
echo " - NOTARY_API_KEY_ID: Your API key ID"
echo " - NOTARY_API_KEY_ISSUER_ID: Your API issuer ID"
echo " - NOTARY_API_KEY_PATH: Path to or content of your p8 file"
echo ""
echo "For App-specific password method, set these secrets:"
echo " - NOTARY_USER: Your Apple ID (email)"
echo " - NOTARY_PASSWORD: Your app-specific password"
echo " - APPLE_TEAM_ID: Your Apple Developer team ID"
echo "NOTARIZED=false" >> "$GITHUB_ENV"
exit 0
fi
shell: bash
- name: Package Signed App
run: |
echo "📦 Packaging signed app..."
if [ "${NOTARIZED:-false}" == "true" ]; then
ZIP_FILE="TestApp-Signed-Notarized.zip"
echo "Creating distribution package with notarized app..."
else
ZIP_FILE="TestApp-Signed.zip"
echo "Creating distribution package with signed app..."
fi
# Create zip package
ditto -c -k --keepParent "$APP_PATH" "$ZIP_FILE"
echo "✅ Created package: $ZIP_FILE"
shell: bash
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: LuckyWorld-Signed-App
path: TestApp-*.zip
retention-days: 7
- name: Cleanup
if: always()
run: |
echo "🧹 Cleaning up..."
rm -rf TestApp.app TestApp-*.zip || true
security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true
echo "✅ Cleanup complete"
shell: bash

View File

@ -0,0 +1,365 @@
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
# Cache Unreal Engine build artifacts to speed up builds
- name: Cache UE build artifacts
uses: actions/cache@v3
with:
path: |
./Intermediate
./DerivedDataCache
./Saved/StagedBuilds
key: ${{ runner.os }}-ue-build-${{ hashFiles('LuckyWorld.uproject') }}-${{ hashFiles('Config/**/*.ini') }}
restore-keys: |
${{ runner.os }}-ue-build-${{ hashFiles('LuckyWorld.uproject') }}-
${{ runner.os }}-ue-build-
# 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
# Verify Build.cs and Target.cs files
BUILD_CS="Source/LuckyWorld/LuckyWorld.Build.cs"
TARGET_CS="Source/LuckyWorld.Target.cs"
if [ -f "$BUILD_CS" ]; then
if grep -q "APP_BUNDLE_IDENTIFIER=com.luckyrobots.luckyworld" "$BUILD_CS"; then
echo "✅ Bundle ID correctly set in LuckyWorld.Build.cs"
else
echo "⚠️ Warning: Bundle ID may not be correctly set in LuckyWorld.Build.cs"
fi
else
echo "⚠️ LuckyWorld.Build.cs not found!"
fi
if [ -f "$TARGET_CS" ]; then
if grep -q "APP_BUNDLE_IDENTIFIER=com.luckyrobots.luckyworld" "$TARGET_CS"; then
echo "✅ Bundle ID correctly set in LuckyWorld.Target.cs"
else
echo "⚠️ Warning: Bundle ID may not be correctly set in LuckyWorld.Target.cs"
fi
else
echo "⚠️ LuckyWorld.Target.cs 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
id: build-app
run: |
if [ -f "./scripts/mac_build.sh" ]; then
echo "🔨 Running mac_build.sh and capturing output to build_log.txt..."
chmod +x ./scripts/mac_build.sh
# Set CI environment variable explicitly before running
export CI=true
# Run build script and capture all output to a log file
./scripts/mac_build.sh 2>&1 | tee build_log.txt
# Check if build succeeded based on exit code
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo "❌ Build script failed with exit code ${PIPESTATUS[0]}"
echo "See build_log.txt for details"
# Set output to indicate failure
echo "::set-output name=build_success::false"
else
echo "✅ Build script completed successfully"
echo "::set-output name=build_success::true"
fi
else
echo "❌ ERROR: Build script not found at ./scripts/mac_build.sh"
echo "::set-output name=build_success::false"
exit 1
fi
shell: bash
# Upload build logs regardless of success
- name: Upload Build Logs
uses: actions/upload-artifact@v3
with:
name: build-logs
path: |
build_log.txt
./Saved/Logs/*.log
retention-days: 7
if-no-files-found: warn
# Find the app bundle - this may fail if build failed
- name: Find app bundle
id: find-app
continue-on-error: true # Allow this step to fail but continue the workflow
run: |
# Debug: Show directory structure to help diagnose issues
echo "📂 Current directory structure:"
ls -la ./
# Debug: Show if the build directory exists
echo "📂 Checking if Builds directory exists:"
ls -la ./ | grep Builds || echo "Builds directory not found!"
# Debug: Show if Saved directory exists
echo "📂 Checking if Saved directory exists:"
ls -la ./ | grep Saved || echo "Saved directory not found!"
# First check Saved/StagedBuilds directory - where Unreal often places built apps
echo "📂 Checking Saved/StagedBuilds directory..."
if [ -d "./Saved/StagedBuilds" ]; then
echo "Saved/StagedBuilds exists, checking content:"
ls -la ./Saved/StagedBuilds
APP_PATHS=$(find ./Saved/StagedBuilds -type d -name "*.app" 2>/dev/null)
else
echo "⚠️ Saved/StagedBuilds directory doesn't exist!"
APP_PATHS=""
fi
# If not found, check Saved directory
if [ -z "$APP_PATHS" ]; then
echo "📂 Checking Saved directory..."
if [ -d "./Saved" ]; then
echo "Saved exists, looking for app bundles:"
ls -la ./Saved
APP_PATHS=$(find ./Saved -type d -name "*.app" 2>/dev/null)
else
echo "⚠️ Saved directory doesn't exist!"
fi
fi
# If not found, check Builds directory
if [ -z "$APP_PATHS" ]; then
echo "📂 Checking Builds directory..."
if [ -d "./Builds" ]; then
echo "Builds exists, looking for app bundles:"
ls -la ./Builds
ls -la ./Builds/Mac 2>/dev/null || echo "No Builds/Mac directory found"
APP_PATHS=$(find ./Builds -type d -name "*.app" 2>/dev/null)
else
echo "⚠️ Builds directory doesn't exist!"
fi
fi
# If still not found, check the whole workspace
if [ -z "$APP_PATHS" ]; then
echo "📂 Checking entire workspace for .app bundles..."
APP_PATHS=$(find . -type d -name "*.app" -not -path "*/\.*" 2>/dev/null)
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
echo "Build process likely failed. Checking mac_build.sh log:"
cat build_log.txt 2>/dev/null || echo "No build log found"
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"
# 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
# Set the application name to "LuckyWorld" instead of "LuckyWorld-Mac-Shipping"
echo "Setting application display name to LuckyWorld..."
/usr/libexec/PlistBuddy -c "Set :CFBundleName LuckyWorld" "$MAIN_APP_PATH/Contents/Info.plist"
echo "Updated app name: $(/usr/libexec/PlistBuddy -c "Print :CFBundleName" "$MAIN_APP_PATH/Contents/Info.plist")"
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)
if [ -n "$NESTED_APPS" ]; then
echo "Found nested app bundles, fixing 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"
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"
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"
echo "Setting CrashReportClient bundle ID to $CRASH_BUNDLE_ID"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier $CRASH_BUNDLE_ID" "$CRASH_REPORTER/Contents/Info.plist"
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'
# Upload signed app if available
- name: Upload Signed App
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' || 'LuckyWorld-macOS-Signed' }}
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

View File

@ -1,347 +0,0 @@
name: Unreal Engine Build
on:
workflow_dispatch:
jobs:
build-and-release:
runs-on: windows
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Setup environment
run: |
# Set environment variables for Unreal Engine
echo "UE_ROOT=E:/Games/UE_5.5" >> $GITHUB_ENV
# Set environment variables for Linux toolchain
$env:LINUX_MULTIARCH_ROOT="C:/UnrealToolchains/v23_clang-18.1.0-rockylinux8"
echo "LINUX_MULTIARCH_ROOT=${LINUX_MULTIARCH_ROOT}" >> $GITHUB_ENV
# Create directories for builds (with error handling)
if (!(Test-Path "Builds/Windows")) { New-Item -ItemType Directory -Path "Builds/Windows" -Force }
if (!(Test-Path "Builds/Linux")) { New-Item -ItemType Directory -Path "Builds/Linux" -Force }
if (!(Test-Path "PackagedReleases")) { New-Item -ItemType Directory -Path "PackagedReleases" -Force }
- name: Build for Windows
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./win_build.sh"
- name: Build for Linux
run: |
# Chmod command doesn't exist in Windows, use PowerShell to run the bash script
& 'C:\Program Files\Git\bin\bash.exe' -c "./linux_build.sh"
- name: Package builds
run: |
echo "Packaging Windows build..."
if [ -d "Builds/Windows" ]; then
cd Builds/Windows
zip -r ../../PackagedReleases/LuckyRobots-Windows.zip .
cd ../..
fi
echo "Packaging Linux build..."
if [ -d "Builds/Linux" ]; then
cd Builds/Linux
zip -r ../../PackagedReleases/LuckyRobots-Linux.zip .
cd ../..
fi
echo "=== Packaged releases ==="
ls -la PackagedReleases/
- name: Upload Windows Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Windows.zip') != ''
with:
name: LuckyRobots-Windows
path: PackagedReleases/LuckyRobots-Windows.zip
retention-days: 365
- name: Upload Linux Build Artifact
uses: actions/upload-artifact@v3
if: success() && hashFiles('PackagedReleases/LuckyRobots-Linux.zip') != ''
with:
name: LuckyRobots-Linux
path: PackagedReleases/LuckyRobots-Linux.zip
retention-days: 365
- name: Create Tag
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag, if any
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
# No previous version tag, start with 1.0.0
NEW_VERSION="1.0.0"
echo "No previous version tags found, starting with 1.0.0"
else
# Strip 'v' prefix if it exists
VERSION=${LATEST_TAG#v}
# Split version into parts
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
# Auto-increment patch version
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
echo "Auto-incremented patch version from ${VERSION} to ${NEW_VERSION}"
fi
# Final tag with v prefix
TAG="v${NEW_VERSION}"
echo "Creating git tag: $TAG"
# Configure git with token authentication
git config --global user.email "actions@gitea.com"
git config --global user.name "Gitea Actions"
# Direct token approach - simplest method
git remote set-url origin "https://goran:${{ secrets.GITEATOKEN }}@luckyrobots.com/luckyrobots/luckyworld.git"
# Set git to not prompt for input
$env:GIT_TERMINAL_PROMPT=0
# Check if tag exists
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
# Create tag without opening editor (-m flag)
git tag -a "$TAG" -m "Release $TAG"
# Push with timeout and debug
echo "Pushing tag $TAG to origin..."
git push --verbose origin "$TAG" || {
echo "Error: Failed to push tag. Check your token permissions."
exit 1
}
echo "Successfully created and pushed tag: $TAG"
else
echo "Tag $TAG already exists, skipping tag creation"
fi
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
- name: Create Build Info
run: |
# Create a build info JSON file
echo '{
"version": "${{ env.RELEASE_TAG }}",
"buildNumber": "${{ github.run_number }}",
"commit": "${{ github.sha }}",
"branch": "${{ github.ref_name }}",
"buildDate": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'",
"artifacts": {
"windows": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows",
"linux": "https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux"
}
}' > PackagedReleases/build-info.json
# Create a simple HTML download page
echo '<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LuckyRobots ${{ env.RELEASE_TAG }} Downloads</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
.download-btn {
display: inline-block;
background-color: #4CAF50;
color: white;
padding: 10px 20px;
text-decoration: none;
border-radius: 4px;
margin: 10px 5px;
}
.download-btn:hover { background-color: #45a049; }
.platform { margin-bottom: 30px; }
</style>
</head>
<body>
<h1>LuckyRobots Game - ${{ env.RELEASE_TAG }}</h1>
<p>Build #${{ github.run_number }} - Built from commit: ${{ github.sha }}</p>
<div class="platform">
<h2>Windows</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows" class="download-btn">Download Windows Build</a></p>
</div>
<div class="platform">
<h2>Linux</h2>
<p><a href="https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux" class="download-btn">Download Linux Build</a></p>
</div>
<footer>
<p>Generated on '$(date -u +"%Y-%m-%d %H:%M:%S UTC")'</p>
</footer>
</body>
</html>' > PackagedReleases/downloads.html
- name: Create Release
uses: https://gitea.com/actions/gitea-release-action@main
with:
files: |-
PackagedReleases/build-info.json
PackagedReleases/downloads.html
token: '${{ secrets.GITEA_TOKEN }}'
title: 'Release ${{ env.RELEASE_TAG }}'
body: |
## LuckyRobots Game Release ${{ env.RELEASE_TAG }}
### Download Links
Download builds from our CI artifacts:
- [Windows Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Windows)
- [Linux Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-Linux)
### Build Information
- Build Number: #${{ github.run_number }}
- Commit: ${{ github.sha }}
- Branch: ${{ github.ref_name }}
- Build Date: $(date -u +"%Y-%m-%d %H:%M:%S UTC")
prerelease: ${{ github.ref != 'refs/heads/main' }}
tag_name: '${{ env.RELEASE_TAG }}'
macos-build:
runs-on: macos
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
lfs: true
fetch-depth: 0
- name: Get Release Tag
run: |
# Fetch all tags
git fetch --tags
# Get the latest version tag
LATEST_TAG=$(git tag -l "v[0-9]*.[0-9]*.[0-9]*" | sort -V | tail -n1)
if [ -z "$LATEST_TAG" ]; then
NEW_VERSION="1.0.0"
else
VERSION=${LATEST_TAG#v}
MAJOR=$(echo $VERSION | cut -d. -f1)
MINOR=$(echo $VERSION | cut -d. -f2)
PATCH=$(echo $VERSION | cut -d. -f3)
PATCH=$((PATCH + 1))
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
fi
TAG="v${NEW_VERSION}"
echo "RELEASE_TAG=$TAG" >> $GITHUB_ENV
echo "Using release tag: $TAG"
- name: Setup Unreal Engine
run: |
# Use the correct path where Unreal Engine is installed
UE_PATH="/Users/Shared/Epic Games/UE_5.5"
if [ ! -d "$UE_PATH" ]; then
echo "Error: Unreal Engine is not installed in the expected location"
echo "Please ensure Unreal Engine is installed at $UE_PATH"
exit 1
fi
# Set environment variable with the correct Engine path
echo "UE_ROOT=$UE_PATH/Engine" >> $GITHUB_ENV
echo "Using Unreal Engine 5.5"
- name: Build Unreal Project
run: |
chmod +x ./mac_build.sh
./mac_build.sh
- name: Prepare Mac release
run: |
echo "Preparing packaged files for release..."
# Create a directory for release files
mkdir -p PackagedReleases
# Debug: Show what we're packaging
echo "=== Packaging for Release ==="
echo "Build directory contents:"
ls -la Builds/
# Find the app bundle in the Builds directory
APP_PATH=$(find Builds -type d -name "*.app" | head -1)
if [ -n "$APP_PATH" ]; then
echo "Found app bundle: $APP_PATH"
# Get the app name
APP_NAME=$(basename "$APP_PATH")
# Create zip file of the app bundle
(cd $(dirname "$APP_PATH") && zip -r "../../PackagedReleases/${APP_NAME%.app}-macOS.zip" "$APP_NAME")
echo "Created packaged release: PackagedReleases/${APP_NAME%.app}-macOS.zip"
else
echo "No .app bundle found in Builds directory"
# Look for a directory that might be a bundle but not named .app
MAIN_BUILD_DIR=$(find Builds -mindepth 1 -maxdepth 1 -type d | head -1)
if [ -n "$MAIN_BUILD_DIR" ]; then
echo "Found main build directory: $MAIN_BUILD_DIR"
DIR_NAME=$(basename "$MAIN_BUILD_DIR")
# Package this directory as if it were the app
(cd $(dirname "$MAIN_BUILD_DIR") && zip -r "../../PackagedReleases/${DIR_NAME}-macOS.zip" "$DIR_NAME")
echo "Created packaged release from main directory: PackagedReleases/${DIR_NAME}-macOS.zip"
else
# Package the entire Builds directory as a fallback
echo "No main directory found, packaging everything"
zip -r "PackagedReleases/LuckyRobots-macOS.zip" Builds
echo "Created fallback package: PackagedReleases/LuckyRobots-macOS.zip"
fi
fi
echo "Packaged releases:"
ls -la PackagedReleases/
- name: Upload macOS Build Artifact
uses: actions/upload-artifact@v3
if: success()
with:
name: LuckyRobots-macOS
path: PackagedReleases/*-macOS.zip
retention-days: 365
- name: Create Release Note
run: |
echo "## macOS Build Completed" > release-note.md
echo "" >> release-note.md
echo "macOS build is available as an artifact." >> release-note.md
echo "" >> release-note.md
echo "Download from: [macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)" >> release-note.md
- name: Create Gitea Release
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
uses: https://gitea.com/actions/gitea-release-action@main
with:
token: ${{ secrets.GITEATOKEN }}
tag_name: ${{ env.RELEASE_TAG }}
title: "Release ${{ env.RELEASE_TAG }} - macOS"
body: |
## macOS Build Available as Artifact
The macOS build is available as an artifact due to its large file size.
[Download macOS Build](https://luckyrobots.com/luckyrobots/luckyworld/actions/runs/${{ github.run_id }}/artifacts/LuckyRobots-macOS)
Built from commit: ${{ github.sha }}
files: release-note.md

1
.gitignore vendored
View File

@ -41,3 +41,4 @@ DerivedDataCache/*
#this only is the Binaries folder on the root, not the Binaries folder in the plugin folders
Binaries/**
*.app/
.cursorrules

View File

@ -338,3 +338,27 @@ NearClipPlane=0.100000
bFinalUsesRDO=True
FinalRDOLambda=100
[/Script/MacTargetPlatform.MacTargetSettings]
TargetedRHIs=SF_METAL_SM5
MetalLanguageVersion=5
MaxShaderLanguageVersion=4
MinimumOSVersion=11
BundleName=LuckyWorld
BundleDisplayName=LuckyWorld
bEnableMathOptimizations=True
UseFastIntrinsics=True
EnableMipGenOption=Default
FrameRateLock=PUFRL_None
AudioSampleRate=48000
AudioMaxChannels=32
bUseCustomIcon=False
bUseMiniUPnP=False
MetalDynamicLibraries=()
MetalRuntimeLibrary=1
OutputRealFPS=False
bBuildEmbeddedFrameworksForGame=False
EnableCodeCoverage=False
EnableCodeCoveragePath=(Path="")
ForwardShading=False
UseFastCopyToResolve=True

View File

@ -7,6 +7,9 @@ ProjectVersion=0.1
;bAddPacks=True
;InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
[/Script/MacTargetPlatform.MacTargetSettings]
BundleIdentifier=com.luckyrobots.luckyworld
[/Script/UnrealEd.ProjectPackagingSettings]
Build=IfProjectHasCode
BuildConfiguration=PPBC_Shipping

26
LuckyWorld.entitlements Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -196,4 +196,4 @@ git lfs pull
## License
Lucky World is licensed under the Polyform License - see the [LICENSE](LICENSE) file for details.
Lucky World is licensed under the Polyform License - see the [LICENSE](LICENSE) file for details.

View File

@ -18,5 +18,14 @@ public class LuckyWorldTarget : TargetRules
{
this.bUseLoggingInShipping = true;
}
// macOS specific settings
if (Target.Platform == UnrealTargetPlatform.Mac)
{
// Force use the bundle ID from DefaultGame.ini
GlobalDefinitions.Add("APP_BUNDLE_IDENTIFIER=com.luckyrobots.luckyworld");
GlobalDefinitions.Add("APP_BUNDLE_NAME=LuckyWorld");
GlobalDefinitions.Add("APP_BUNDLE_DISPLAY_NAME=LuckyWorld");
}
}
}

View File

@ -19,5 +19,13 @@ public class LuckyWorld : ModuleRules
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
// Set the bundle identifier for macOS builds
if (Target.Platform == UnrealTargetPlatform.Mac)
{
PublicDefinitions.Add("APP_BUNDLE_IDENTIFIER=com.luckyrobots.luckyworld");
PublicDefinitions.Add("APP_BUNDLE_NAME=LuckyWorld");
PublicDefinitions.Add("APP_BUNDLE_DISPLAY_NAME=LuckyWorld");
}
}
}

View File

@ -1,55 +0,0 @@
#!/bin/bash
# Get the user's home directory
USER_HOME="$HOME"
# Set up Unreal Engine paths
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
# Set up project paths
PROJECT_ROOT="$(pwd)"
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
rm -rf DerivedDataCache Intermediate Binaries Saved
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
# Run the build command
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
-command=VerifySdk \
-platform=Mac \
-UpdateIfNeeded \
-EditorIO \
-EditorIOPort=59484 \
-project="$PROJECT_FILE" \
BuildCookRun \
-nop4 \
-utf8output \
-cook \
-project="$PROJECT_FILE" \
-target=LuckyWorld \
-unrealexe="$UE_EDITOR" \
-platform=Mac \
-installed \
-stage \
-archive \
-package \
-build \
-iterativecooking \
-pak \
-iostore \
-compressed \
-prereqs \
-archivedirectory="$ARCHIVE_DIR" \
-CrashReporter \
-clientconfig=Shipping \
# -nocompile \
# -nocompileuat \
# -nocompileeditor \
# -skipbuildeditor \
# enable these if you want to test build without pak and iostore (you're just testing the build)
# -skipiostore \
# -skippak \ (disable -pak and -iostore)

236
scripts/mac_build.sh Executable file
View File

@ -0,0 +1,236 @@
#!/bin/bash
# Get the user's home directory
USER_HOME="$HOME"
# Set up Unreal Engine paths
UE_ROOT="/Users/Shared/Epic Games/UE_5.5"
UE_EDITOR="$UE_ROOT/Engine/Binaries/Mac/UnrealEditor.app/Contents/MacOS/UnrealEditor"
UE_UAT="$UE_ROOT/Engine/Build/BatchFiles/RunUAT.command"
# Set up project paths
PROJECT_ROOT="$(pwd)"
PROJECT_FILE="$PROJECT_ROOT/LuckyWorld.uproject"
ARCHIVE_DIR="$PROJECT_ROOT/Builds"
# Check for entitlements file
if [ -f "$PROJECT_ROOT/LuckyWorld.entitlements" ]; then
ENTITLEMENTS_FILE="$PROJECT_ROOT/LuckyWorld.entitlements"
else
echo "Warning: No entitlements file found. This might affect notarization."
ENTITLEMENTS_FILE=""
fi
# For debugging: print paths and config
echo "Project root: $PROJECT_ROOT"
echo "Project file: $PROJECT_FILE"
echo "Archive directory: $ARCHIVE_DIR"
echo "Entitlements file: $ENTITLEMENTS_FILE"
# Clean up previous build artifacts
rm -rf DerivedDataCache Intermediate Binaries Saved
# Generate project files
"$UE_ROOT/Engine/Build/BatchFiles/Mac/GenerateProjectFiles.sh" -project="$PROJECT_FILE" -game -engine
# Run the build command
"$UE_UAT" -ScriptsForProject="$PROJECT_FILE" Turnkey \
-command=VerifySdk \
-platform=Mac \
-UpdateIfNeeded \
-EditorIO \
-EditorIOPort=59484 \
-project="$PROJECT_FILE" \
BuildCookRun \
-nop4 \
-utf8output \
-cook \
-project="$PROJECT_FILE" \
-target=LuckyWorld \
-unrealexe="$UE_EDITOR" \
-platform=Mac \
-installed \
-stage \
-archive \
-package \
-build \
-iterativecooking \
-pak \
-iostore \
-compressed \
-prereqs \
-archivedirectory="$ARCHIVE_DIR" \
-CrashReporter \
-clientconfig=Shipping \
# -nocompile \
# -nocompileuat \
# -nocompileeditor \
# -skipbuildeditor \
BUILD_EXIT_CODE=$?
echo ""
echo "🦾 Build process completed with exit code: $BUILD_EXIT_CODE"
# Debug check for where the app might be
echo "📂 Checking potential build output locations:"
echo "Saved directory contents (if exists):"
ls -la Saved 2>/dev/null || echo "Saved directory not found"
echo "Saved/StagedBuilds directory contents (if exists):"
ls -la Saved/StagedBuilds 2>/dev/null || echo "Saved/StagedBuilds directory not found"
echo "Builds directory contents:"
ls -la "$ARCHIVE_DIR" 2>/dev/null || echo "Builds directory not found"
echo "Builds/Mac directory contents (if exists):"
ls -la "$ARCHIVE_DIR/Mac" 2>/dev/null || echo "Builds/Mac directory not found"
# Find the app, regardless of build success
echo "🔍 Searching for .app bundles in Saved and Builds directories:"
APP_PATH=""
for search_dir in "$ARCHIVE_DIR" "$ARCHIVE_DIR/Mac" "Saved/StagedBuilds" "Saved/StagedBuilds/Mac" "Saved"; do
if [ -d "$search_dir" ]; then
echo "Searching in $search_dir"
APP_FOUND=$(find "$search_dir" -name "*.app" -type d | head -n 1)
if [ -n "$APP_FOUND" ]; then
APP_PATH="$APP_FOUND"
echo "Found app bundle at: $APP_PATH"
break
fi
fi
done
if [ -z "$APP_PATH" ]; then
echo "❌ No app bundle found. Build may have failed or output location is unexpected."
# Set a fake path for debug tests
if [ -n "$CI" ]; then
echo "This is a CI environment, continuing with error checks..."
else
echo "Exiting with error code from build"
exit $BUILD_EXIT_CODE
fi
else
echo "🦾 Build completed. Application path:"
echo "$APP_PATH"
echo ""
echo "🔍 Binary files that will need signing:"
DYLIB_COUNT=$(find "$APP_PATH" -name "*.dylib" | wc -l)
SO_COUNT=$(find "$APP_PATH" -name "*.so" | wc -l)
FRAMEWORKS=$(find "$APP_PATH" -path "*.framework/*" -type f -perm +111 | wc -l)
EXECUTABLES=$(find "$APP_PATH" -type f -perm +111 -not -path "*.framework/*" -not -name "*.dylib" -not -name "*.so" | wc -l)
echo "- $DYLIB_COUNT .dylib libraries"
echo "- $SO_COUNT .so libraries"
echo "- $FRAMEWORKS framework executables"
echo "- $EXECUTABLES other executables"
echo "Total binary files: $((DYLIB_COUNT + SO_COUNT + FRAMEWORKS + EXECUTABLES))"
echo ""
echo "🔍 Checking for PhysX and other special libraries (often need special handling):"
find "$APP_PATH" -name "*PhysX*" -o -name "*APEX*"
fi
# Update bundle ID in project settings
echo ""
echo "🔧 Checking for bundle ID in UE config..."
CONFIG_FILE="$PROJECT_ROOT/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 already correctly set in project config ✅"
else
echo "⚠️ Warning: Bundle ID may not be correctly set in DefaultGame.ini"
echo "Please ensure [/Script/MacTargetPlatform.MacTargetSettings] section exists with BundleIdentifier=com.luckyrobots.luckyworld"
fi
else
echo "⚠️ Config file not found at $CONFIG_FILE"
fi
# Post-build process - set bundle ID
echo ""
echo "🔧 Performing post-build fix for bundle ID..."
# First verify that APP_PATH is defined and exists
if [ -z "$APP_PATH" ]; then
echo "⚠️ APP_PATH is not defined, trying to find the app bundle again..."
# Try to find the app bundle in common locations
for search_dir in "$ARCHIVE_DIR" "$ARCHIVE_DIR/Mac" "Saved/StagedBuilds" "Saved/StagedBuilds/Mac" "Saved"; do
if [ -d "$search_dir" ]; then
APP_FOUND=$(find "$search_dir" -name "*.app" -type d | head -n 1)
if [ -n "$APP_FOUND" ]; then
APP_PATH="$APP_FOUND"
echo "✅ Found app bundle at: $APP_PATH"
break
fi
fi
done
if [ -z "$APP_PATH" ]; then
echo "❌ ERROR: Could not find any app bundle. Skipping bundle ID fix."
# Don't exit, just skip this part
APP_PATH=""
fi
fi
if [ -n "$APP_PATH" ] && [ -d "$APP_PATH" ]; then
INFO_PLIST="$APP_PATH/Contents/Info.plist"
if [ -f "$INFO_PLIST" ]; then
CURRENT_BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO_PLIST" 2>/dev/null)
echo "Current bundle ID: $CURRENT_BUNDLE_ID"
if [ $? -ne 0 ] || [ "$CURRENT_BUNDLE_ID" != "com.luckyrobots.luckyworld" ]; then
echo "Bundle ID mismatch or not set - fixing it!"
echo "Setting bundle identifier to com.luckyrobots.luckyworld"
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.luckyrobots.luckyworld" "$INFO_PLIST"
UPDATED_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO_PLIST" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Updated bundle ID: $UPDATED_ID"
else
echo "⚠️ Failed to update bundle ID"
fi
else
echo "Bundle ID is already correct: com.luckyrobots.luckyworld"
fi
# Set the application name to "LuckyWorld" instead of "LuckyWorld-Mac-Shipping"
echo "Setting application name to LuckyWorld..."
/usr/libexec/PlistBuddy -c "Set :CFBundleName LuckyWorld" "$INFO_PLIST"
UPDATED_NAME=$(/usr/libexec/PlistBuddy -c "Print :CFBundleName" "$INFO_PLIST" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Updated app name: $UPDATED_NAME"
else
echo "⚠️ Failed to update app name"
fi
# Find and repair nested app bundles as well (like CrashReportClient.app)
echo "Checking for nested app bundles..."
NESTED_APPS=$(find "$APP_PATH" -name "*.app" -type d 2>/dev/null)
if [ -n "$NESTED_APPS" ]; then
echo "Found nested app bundles, fixing 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="com.luckyrobots.luckyworld.$NESTED_NAME"
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" 2>/dev/null)
if [ $? -eq 0 ]; then
echo "Updated nested app bundle ID: $UPDATED_ID"
else
echo "⚠️ Failed to update nested app bundle ID for $NESTED_APP"
fi
fi
done
else
echo "No nested app bundles found."
fi
else
echo "⚠️ Info.plist not found at $INFO_PLIST"
fi
else
echo "⚠️ App bundle not found or APP_PATH is not valid. Skipping bundle ID fix."
fi