Skip to content

MajdataPlay build #2939

MajdataPlay build

MajdataPlay build #2939

Workflow file for this run

name: Build Project
run-name: MajdataPlay build
on:
push:
branches: [ "dev", "main", "fastlane-ios", "test_workflow", "github-ci" ]
pull_request:
branches: [ "main" ]
permissions:
contents: write
concurrency:
group: nightly-${{ github.ref }}
cancel-in-progress: true
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_VERSION: 6000.3.17f1
IOS_BUILD_NUMBER_OFFSET: 3162
jobs:
build:
name: Build (Windows/Linux)
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
strategy:
fail-fast: false
matrix:
configuration: [Release]
platform: [StandaloneWindows64, StandaloneLinux64]
env:
CONFIGURATION: ${{ matrix.configuration }}
TARGET_PLATFORM: ${{ matrix.platform }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Extract version from unity project
run: |
VERSION=$(grep -m1 'bundleVersion:' ProjectSettings/ProjectSettings.asset | awk '{print $2}')
VERSION_WITH_HASH="${VERSION}-$(date +%Y%m%d)-${GITHUB_SHA::7}"
echo "BUNDLE_VERSION=$VERSION" >> $GITHUB_ENV
echo "BUNDLE_VERSION_WITH_HASH=$VERSION_WITH_HASH" >> $GITHUB_ENV
- name: Cache Unity Library
uses: actions/cache@v4
with:
path: Library
key: Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-${{ hashFiles('ProjectSettings/ProjectVersion.txt', 'ProjectSettings/**', 'Packages/manifest.json', 'Packages/packages-lock.json') }}
restore-keys: |
Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-
- name: Build Release
uses: game-ci/unity-builder@v4
with:
unityVersion: ${{ env.UNITY_VERSION }}
targetPlatform: ${{ env.TARGET_PLATFORM }}
versioning: Custom
version: ${{ env.BUNDLE_VERSION_WITH_HASH }}
buildName: MajdataPlay
buildsPath: ${{ env.CONFIGURATION }}
- name: Package output as zip
run: |
set -euo pipefail
mkdir -p out
ZIP_NAME="MajdataPlay-${{ env.TARGET_PLATFORM }}-nightly.zip"
cd "${{ env.CONFIGURATION }}"
zip -qry "../out/${ZIP_NAME}" .
cd -
ls -la out
- name: Upload build outputs as workflow artifact
uses: actions/upload-artifact@v4
with:
name: out-${{ env.TARGET_PLATFORM }}
path: out/*
if-no-files-found: error
retention-days: 7
build-android:
name: Build (Android)
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
env:
CONFIGURATION: Release
TARGET_PLATFORM: Android
PROJECT_ROOT: ${{ github.workspace }}
UNITY_PROJECT_EXPORT_PATH: Release/Android/MajdataPlay
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Extract version from unity project
run: |
VERSION=$(grep -m1 'bundleVersion:' ProjectSettings/ProjectSettings.asset | awk '{print $2}')
VERSION_WITH_HASH="${VERSION}-$(date +%Y%m%d)-${GITHUB_SHA::7}"
echo "BUNDLE_VERSION=$VERSION" >> $GITHUB_ENV
echo "BUNDLE_VERSION_WITH_HASH=$VERSION_WITH_HASH" >> $GITHUB_ENV
- name: Calculate Android version code
run: |
COMMIT_COUNT=$(git rev-list HEAD --count)
COUNT=$((COMMIT_COUNT + 100000))
echo "BUNDLE_VERSION_CODE=$COUNT" >> $GITHUB_ENV
echo "BUNDLE_VERSION_CODE_FOR_APK=$COMMIT_COUNT" >> $GITHUB_ENV
echo "Android AAB version code: $COUNT"
echo "Android APK version code: $COMMIT_COUNT"
- name: Cache Unity Library
uses: actions/cache@v4
with:
path: Library
key: Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-${{ hashFiles('ProjectSettings/ProjectVersion.txt', 'ProjectSettings/**', 'Packages/manifest.json', 'Packages/packages-lock.json') }}
restore-keys: |
Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-
- name: Setup Ruby and Fastlane
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
- name: Install Fastlane
run: gem install fastlane
- name: Export Android Project
uses: game-ci/unity-builder@v4
with:
unityVersion: ${{ env.UNITY_VERSION }}
targetPlatform: ${{ env.TARGET_PLATFORM }}
versioning: Custom
version: ${{ env.BUNDLE_VERSION_WITH_HASH }}
androidVersionCode: ${{ env.BUNDLE_VERSION_CODE_FOR_APK }}
buildName: MajdataPlay
buildsPath: ${{ env.CONFIGURATION }}
androidExportType: androidStudioProject
- name: Fix Directory Permissions
run: sudo chown -R $USER:$USER ${{ env.CONFIGURATION }}/${{ env.TARGET_PLATFORM }}
- name: Setup Java & Gradle Cache
uses: actions/setup-java@v5
with:
distribution: 'zulu'
java-version: '17'
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '9.1.0'
- name: Install Android NDK via SDK Manager
run: |
echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "ndk;23.1.7779620"
- name: Fix Android SDK and NDK paths
run: |
cd ${{ env.UNITY_PROJECT_EXPORT_PATH }}
BAD_NDK="/opt/unity/Editor/Data/PlaybackEngines/AndroidPlayer/NDK"
BAD_SDK="/opt/unity/Editor/Data/PlaybackEngines/AndroidPlayer/SDK"
GOOD_NDK="$ANDROID_HOME/ndk/23.1.7779620"
GOOD_SDK="$ANDROID_HOME"
find . -type f \( -name "*.gradle" -o -name "*.properties" \) -exec sed -i "s|$BAD_NDK|$GOOD_NDK|g" {} +
find . -type f \( -name "*.gradle" -o -name "*.properties" \) -exec sed -i "s|$BAD_SDK|$GOOD_SDK|g" {} +
echo "sdk.dir=$GOOD_SDK" > local.properties
echo "ndk.dir=$GOOD_NDK" >> local.properties
- name: Generate Gradle Wrapper
run: |
cd ${{ env.UNITY_PROJECT_EXPORT_PATH }}
gradle wrapper
- name: Prepare Keystore
run: |
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > android-publish.keystore
echo "KEYSTORE_PATH=$(pwd)/android-publish.keystore" >> $GITHUB_ENV
- name: Create Fastfile
run: |
mkdir -p fastlane
cat << 'EOF' > fastlane/Fastfile
default_platform(:android)
platform :android do
before_all do
gradle_path = File.join(
ENV['PROJECT_ROOT'],
ENV['UNITY_PROJECT_EXPORT_PATH'],
'launcher',
'build.gradle'
)
content = File.read(gradle_path)
content = content.gsub(/versionCode\s+\d+/, "versionCode #{ENV['BUNDLE_VERSION_CODE']}")
content = content.gsub(/versionName\s+['"].*?['"]/, "versionName '#{ENV['BUNDLE_VERSION_WITH_HASH']}'")
signing_block = <<-GRADLE
// --- Injected by Fastlane ---
android {
signingConfigs {
release {
storeFile file("#{ENV['KEYSTORE_PATH']}")
storePassword "#{ENV['ANDROID_KEYSTORE_PASS']}"
keyAlias "#{ENV['ANDROID_KEYALIAS_NAME']}"
keyPassword "#{ENV['ANDROID_KEYALIAS_PASS']}"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
GRADLE
File.open(gradle_path, 'w') { |file| file.puts(content + signing_block) }
end
desc "Build foss version (APK)"
lane :build_foss do
gradle(
project_dir: ENV['UNITY_PROJECT_EXPORT_PATH'],
task: "assemble",
build_type: "Release",
flags: "--build-cache"
)
end
desc "Build Play version (AAB)"
lane :build_play do
gradle(
project_dir: ENV['UNITY_PROJECT_EXPORT_PATH'],
task: "bundle",
build_type: "Release",
flags: "--build-cache"
)
end
end
EOF
- name: Build foss (APK)
env:
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
ANDROID_KEYALIAS_NAME: ${{ secrets.ANDROID_KEYALIAS_NAME }}
ANDROID_KEYALIAS_PASS: ${{ secrets.ANDROID_KEYALIAS_PASS }}
run: |
ROOT="${{ env.UNITY_PROJECT_EXPORT_PATH }}"
# Unity property
find "$ROOT" -type f -name "gradle.properties" \
-exec sed -i 's/^unity\.targetSdkVersion=.*/unity.targetSdkVersion=29/' {} +
# AGP 9 syntax
find "$ROOT" -type f -name "*.gradle" \
-exec sed -i -E 's/(targetSdk[[:space:]]+)[0-9]+/\129/g' {} +
# Old AGP syntax, if any
find "$ROOT" -type f -name "*.gradle" \
-exec sed -i -E 's/(targetSdkVersion[[:space:]]+)[0-9]+/\129/g' {} +
echo "=== Final SDK configuration ==="
grep -RIn \
--include='*.gradle' \
--include='*.properties' \
-E 'targetSdk|targetSdkVersion|unity.targetSdkVersion' \
"$ROOT"
fastlane build_foss
- name: Collect Android APKs
run: |
set -euo pipefail
mkdir -p out
OUTPUT_DIR="${{ env.UNITY_PROJECT_EXPORT_PATH }}/launcher/build/outputs/apk/release"
for file in "$OUTPUT_DIR"/*.apk; do
if [[ "$file" == *"v7a"* ]]; then
cp "$file" "out/MajdataPlay-Android-${{ env.BUNDLE_VERSION_WITH_HASH }}-nightly.armeabi-v7a.apk"
elif [[ "$file" == *"v8a"* ]]; then
cp "$file" "out/MajdataPlay-Android-${{ env.BUNDLE_VERSION_WITH_HASH }}-nightly.arm64-v8a.apk"
else
cp "$file" "out/MajdataPlay-Android-${{ env.BUNDLE_VERSION_WITH_HASH }}-nightly.apk"
fi
done
ls -la out
- name: Build Play (AAB)
env:
ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
ANDROID_KEYALIAS_NAME: ${{ secrets.ANDROID_KEYALIAS_NAME }}
ANDROID_KEYALIAS_PASS: ${{ secrets.ANDROID_KEYALIAS_PASS }}
run: |
ROOT="${{ env.UNITY_PROJECT_EXPORT_PATH }}"
find "$ROOT" -type f -name "gradle.properties" \
-exec sed -i 's/^unity\.targetSdkVersion=.*/unity.targetSdkVersion=36/' {} +
find "$ROOT" -type f -name "*.gradle" \
-exec sed -i -E 's/(targetSdk[[:space:]]+)[0-9]+/\136/g' {} +
find "$ROOT" -type f -name "*.gradle" \
-exec sed -i -E 's/(targetSdkVersion[[:space:]]+)[0-9]+/\136/g' {} +
echo "=== Final SDK configuration ==="
grep -RIn \
--include='*.gradle' \
--include='*.properties' \
-E 'targetSdk|targetSdkVersion|unity.targetSdkVersion' \
"$ROOT"
fastlane build_play
- name: Collect Android AAB
run: |
set -euo pipefail
mkdir -p out
cp "${{ env.UNITY_PROJECT_EXPORT_PATH }}/launcher/build/outputs/bundle/release/launcher-release.aab" "out/MajdataPlay-Android-${{ env.BUNDLE_VERSION_WITH_HASH }}-nightly.aab"
ls -la out
- name: Upload foss APK outputs as workflow artifact
uses: actions/upload-artifact@v4
with:
name: MajdataPlay-Android-foss-APK
path: out/*.apk
if-no-files-found: error
retention-days: 7
- name: Upload Play AAB outputs as workflow artifact
uses: actions/upload-artifact@v4
with:
name: MajdataPlay-Android-Play-AAB
path: out/*.aab
if-no-files-found: error
retention-days: 7
- name: Upload to Google Play
uses: r0adkll/upload-google-play@v1
continue-on-error: true
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_DEPLOY_ACCOUNT_JSON }}
packageName: net.majdata.majdataplay
releaseFiles: out/MajdataPlay-Android-${{ env.BUNDLE_VERSION_WITH_HASH }}-nightly.aab
track: internal
status: completed
build-apple:
name: Build (macOS/iOS)
runs-on: macos-26
if: ${{ github.event_name == 'push' }}
strategy:
fail-fast: false
matrix:
configuration: [Release]
platform: [StandaloneOSX, iOS]
env:
CONFIGURATION: ${{ matrix.configuration }}
TARGET_PLATFORM: ${{ matrix.platform }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Extract version from unity project
run: |
VERSION=$(grep -m1 'bundleVersion:' ProjectSettings/ProjectSettings.asset | awk '{print $2}')
VERSION_WITH_HASH="${VERSION}-${GITHUB_SHA::7}"
echo "BUNDLE_VERSION=$VERSION" >> $GITHUB_ENV
echo "BUNDLE_VERSION_WITH_HASH=$VERSION_WITH_HASH" >> $GITHUB_ENV
- name: Calculate Apple build number
run: |
BUILD_NUMBER=$((GITHUB_RUN_NUMBER + IOS_BUILD_NUMBER_OFFSET))
echo "BUNDLE_VERSION_CODE=$BUILD_NUMBER" >> $GITHUB_ENV
echo "Apple build number: $BUILD_NUMBER"
- name: Cache Unity Library
uses: actions/cache@v4
with:
path: Library
key: Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-${{ hashFiles('ProjectSettings/ProjectVersion.txt', 'ProjectSettings/**', 'Packages/manifest.json', 'Packages/packages-lock.json') }}
restore-keys: |
Library-${{ runner.os }}-${{ env.TARGET_PLATFORM }}-
- name: Build Release
uses: game-ci/unity-builder@v4
with:
unityVersion: ${{ env.UNITY_VERSION }}
targetPlatform: ${{ env.TARGET_PLATFORM }}
versioning: Custom
version: ${{ env.BUNDLE_VERSION }}
buildName: MajdataPlay
buildsPath: ${{ env.CONFIGURATION }}
- name: Import Apple certificate
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.APPSTORE_CERTIFICATES_FILE_BASE64 }}
p12-password: ${{ secrets.APPSTORE_CERTIFICATES_PASSWORD }}
- name: Install provisioning profile
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
run: |
set -euo pipefail
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
echo "${{ secrets.APPSTORE_PROFILE_BASE64 }}" | base64 --decode > ~/Library/MobileDevice/Provisioning\ Profiles/appstore.mobileprovision
ls -la ~/Library/MobileDevice/Provisioning\ Profiles
- name: Setup Ruby
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3'
- name: Install fastlane
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
run: |
gem install bundler
cat > Gemfile <<'EOF'
source "https://rubygems.org"
gem "fastlane", "2.236.1"
gem "xcodeproj"
gem "multi_json"
EOF
bundle install
- name: Build IPA Release
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
env:
APPSTORE_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }}
APPSTORE_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
APPSTORE_PRIVATE_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPSTORE_PROFILE_NAME: ${{ secrets.APPSTORE_PROFILE_NAME }}
IOS_PROJECT_PATH: ${{ env.CONFIGURATION }}/iOS/MajdataPlay/Unity-iPhone.xcodeproj
IOS_EXPORT_DIR: ${{ env.CONFIGURATION }}/iOS/export
run: |
set -euo pipefail
mkdir -p fastlane
cat > fastlane/Fastfile <<'EOF'
default_platform(:ios)
platform :ios do
lane :ci do
update_code_signing_settings(
use_automatic_signing: false,
path: ENV["IOS_PROJECT_PATH"],
team_id: ENV["APPLE_TEAM_ID"],
targets: ["Unity-iPhone"],
profile_name: ENV["APPSTORE_PROFILE_NAME"],
code_sign_identity: "Apple Distribution"
)
update_code_signing_settings(
use_automatic_signing: false,
path: ENV["IOS_PROJECT_PATH"],
team_id: ENV["APPLE_TEAM_ID"],
targets: ["UnityFramework"],
code_sign_identity: "Apple Distribution"
)
increment_version_number(
xcodeproj: ENV["IOS_PROJECT_PATH"],
version_number: ENV["BUNDLE_VERSION"]
)
increment_build_number(
xcodeproj: ENV["IOS_PROJECT_PATH"],
build_number: ENV["BUNDLE_VERSION_CODE"]
)
UI.message("Version: #{ENV["BUNDLE_VERSION"]}")
UI.message("Build number: #{ENV["BUNDLE_VERSION_CODE"]}")
gym(
project: ENV["IOS_PROJECT_PATH"],
scheme: "Unity-iPhone",
configuration: ENV["CONFIGURATION"],
export_method: "app-store",
output_directory: ENV["IOS_EXPORT_DIR"],
output_name: "MajdataPlay.ipa",
clean: true,
export_options: {
method: "app-store",
signingStyle: "manual",
provisioningProfiles: {
"net.majdata.majdataplay" => ENV["APPSTORE_PROFILE_NAME"]
}
}
)
end
end
EOF
bundle exec fastlane ios ci
- name: Verify IPA build number
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
run: |
set -euo pipefail
IPA_PATH="${{ env.CONFIGURATION }}/iOS/export/MajdataPlay.ipa"
TMP_DIR="$(mktemp -d)"
unzip -q "$IPA_PATH" -d "$TMP_DIR"
APP_DIR="$(find "$TMP_DIR/Payload" -maxdepth 1 -name "*.app" -type d | head -n 1)"
INFO_PLIST="$APP_DIR/Info.plist"
echo "App bundle: $APP_DIR"
echo "Info.plist: $INFO_PLIST"
echo "CFBundleIdentifier:"
/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$INFO_PLIST"
echo "CFBundleShortVersionString:"
/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$INFO_PLIST" || true
echo "CFBundleVersion:"
/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$INFO_PLIST"
- name: Upload to TestFlight
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
continue-on-error: true
env:
APPSTORE_KEY_ID: ${{ secrets.APPSTORE_KEY_ID }}
APPSTORE_ISSUER_ID: ${{ secrets.APPSTORE_ISSUER_ID }}
APPSTORE_PRIVATE_KEY: ${{ secrets.APPSTORE_PRIVATE_KEY }}
IOS_EXPORT_DIR: ${{ env.CONFIGURATION }}/iOS/export
GITHUB_SHA: ${{ github.sha }}
run: |
set -euo pipefail
BUILD_AT="$(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S')"
export BUILD_AT
mkdir -p fastlane
cat > fastlane/Fastfile <<'EOF'
default_platform(:ios)
platform :ios do
lane :ci do
api_key = app_store_connect_api_key(
key_id: ENV["APPSTORE_KEY_ID"],
issuer_id: ENV["APPSTORE_ISSUER_ID"],
key_content: ENV["APPSTORE_PRIVATE_KEY"]
)
upload_to_testflight(
api_key: api_key,
ipa: File.join(ENV["IOS_EXPORT_DIR"], "MajdataPlay.ipa"),
distribute_external: true,
groups: ["beta"],
changelog: "Automated CI build\nCommit: #{ENV["GITHUB_SHA"]}\nBuild: #{ENV["BUNDLE_VERSION_CODE"]}\nBuild at: #{ENV["BUILD_AT"]} (UTC+8)",
submit_beta_review: true,
skip_waiting_for_build_processing: false
)
end
end
EOF
bundle exec fastlane ios ci
- name: Package macOS output as zip
if: ${{ env.TARGET_PLATFORM == 'StandaloneOSX' }}
run: |
set -euo pipefail
mkdir -p out
ZIP_NAME="MajdataPlay-StandaloneOSX-nightly.zip"
cd "${{ env.CONFIGURATION }}"
zip -qry "../out/${ZIP_NAME}" .
cd -
ls -la out
- name: Collect iOS IPA to out
if: ${{ env.TARGET_PLATFORM == 'iOS' }}
run: |
set -euo pipefail
mkdir -p out
cp "${{ env.CONFIGURATION }}/iOS/export/MajdataPlay.ipa" out/
ls -la out
- name: Upload build outputs as workflow artifact
uses: actions/upload-artifact@v4
with:
name: out-${{ env.TARGET_PLATFORM }}
path: out/*
if-no-files-found: error
retention-days: 7
publish-nightly:
name: Publish nightly release (replace only after all builds succeed)
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
needs: [build, build-android, build-apple]
steps:
- name: Checkout for tagging
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all build artifacts
uses: actions/download-artifact@v4
with:
path: collected
- name: Show collected files
run: |
set -euo pipefail
ls -R collected || true
- name: Move nightly tag to this commit
run: |
set -e
git tag -f nightly "${GITHUB_SHA}"
git push origin -f nightly
- name: Delete old nightly release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if gh release view nightly >/dev/null 2>&1; then
gh release delete nightly -y
else
echo "No existing nightly release."
fi
- name: Create new nightly release with notes
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
BUILD_AT="$(TZ=Asia/Shanghai date '+%Y-%m-%d %H:%M:%S')"
NOTES_FILE="$(mktemp)"
printf "Automated nightly build.\nCommit: %s\nBuild at: %s (UTC+8)\n" \
"${GITHUB_SHA}" \
"${BUILD_AT}" \
> "${NOTES_FILE}"
gh release create nightly \
--title "Nightly" \
--notes-file "${NOTES_FILE}" \
--prerelease
- name: Upload all artifacts to the new nightly release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release upload nightly collected/**/* --clobber