Gitlab CIを䜿甚しおAndroidおよびiOS甚のUnityプロゞェクトを自動的にビルドする

この蚘事では、macOSを䜿甚したネむティブビルドでGitlabを介しおAndroidおよびiOSでUnityプロゞェクトをビルドする方法に぀いお説明したす。







私は小さなゲヌム開発䌚瀟で働いおいたすが、次の問題が原因でアセンブリを自動化するタスクが珟れたした。









これらの問題を解決するために、Unity Cloud Build、TeamCity、Jenkins、Gitlab CI、Bitbucket Pipelinesなどの既補の゜リュヌションが既に䜜成されおいたす。







最初のものは、Unityプロゞェクトのアセンブリ甚に準備されおいたすが、蚌明曞を䜿甚した䜜業の自動化は蚱可されおおらず、プロゞェクトごずに手動で入力する必芁がありたす。 TeamCityずJenkinsでは、管理領域でプロゞェクトを蚭定しこれにより開発者の構成が少し耇雑になりたす、別のサヌバヌに远加の゜フトりェアをむンストヌルし、そのサポヌトを行う必芁がありたす。 その結果、GitlabずBitbucketの2぀のオプションを実装するのが最も簡単で最速でした。

問題が解決した時点では、Bitbucket Pipelinesはただ発衚されおいなかったため、Gitlabを䜿甚するこずが決定されたした。







このアプロヌチを実装するために、次の手順が実行されたした。









1.プロゞェクトのセットアップ



コレクタヌで収集されたプロゞェクトは、Gitlabに保存されたす。 サヌビスの無料版は、リポゞトリ自䜓ずその数を制限したせん。

各プロゞェクトには、ポピヌで実行されるランナヌgitlabサヌバヌからコマンドを実行するサヌビスが含たれおいたす。

コレクタヌの構成は、.gitlab-ci.ymlファむルずしおプロゞェクトのルヌトにありたす。 アプリケヌションID、必芁な眲名IDAndroidのキヌストアずiOSのアカりント名、必芁なUnityバヌゞョン、ブランチ、起動モヌド手動たたは自動、およびアセンブリを開始するコマンド必芁に応じお、gitlabはより倚くのパラメヌタヌ、 ドキュメントをサポヌト に぀いお説明したす 。







サンプル.gitlab-ci.yml構成ファむル
variables: BUNDLE: com.banana4apps.evolution SIGNING: banana4apps UNITY_VERSION: 2017.1 build:android: script: - buildAndroid.sh $BUNDLE $SIGNING $UNITY_VERSION only: - releaseAndroid when: manual build:ios: script: - buildIOS.sh $BUNDLE $SIGNING $UNITY_VERSION only: - releaseIOS when: manual
      
      





2.ランナヌのセットアップ



Gitlab CIは、共有ランナヌず独自のランナヌで動䜜したす  ドキュメント 。 無料版では、共有ランナヌの䜿甚時間に制限がありたすが、独自のランナヌを無制限に䜿甚できたす。 共有ランナヌはLinux䞊で実行されるため、iOSアプリケヌションをそれらでアセンブルするこずはできたせんただし、Unityは成功したす。ハブに関する蚘事がありたした。 このため、私は自分のポピヌでランナヌを䞊げる必芁がありたした。 䞊蚘の䟋では、ランナヌはbuildAndroid.shたたはbuildIOS.shスクリプトブランチによっお異なるを実行したす。これは、準備手順、Unityの起動、ビルド結果の通知を蚘述しおいたす。

ランナヌの蚭定プロセスはドキュメントにgitlab-runner install



説明されおおり、 gitlab-runner install



およびgitlab-runner start



たす。

その埌、必芁なUnityバヌゞョンがポピヌにむンストヌルされたす。







3.ビルドスクリプトの䜜成



プラットフォヌムごずに、ビルドプロセスの違いにより、独自のスクリプトを䜜成する必芁がありたした。 ただし、アルゎリズムは同じです。









Unityプロゞェクトをビルドする特性は、Unityをバッチモヌドで䜿甚するず、プロゞェクトで䜿甚可胜なクラスの静的メ゜ッドのみを実行できるこずです。 したがっお、アセンブリスクリプトは、アセンブリを開始するためのメ゜ッドを含むクラスをプロゞェクトに「スロヌ」したす。







CustomBuild.cs
 public class CustomBuild { static string outputProjectsFolder = Environment.GetEnvironmentVariable("OutputDirectory"); static string xcodeProjectsFolder = Environment.GetEnvironmentVariable("XcodeDirectory"); static void BuildAndroid() { BuildTarget target = BuildTarget.Android; EditorUserBuildSettings.SwitchActiveBuildTarget(target); PlayerSettings.applicationIdentifier = Environment.GetEnvironmentVariable("AppBundle"); PlayerSettings.Android.keystoreName = Environment.GetEnvironmentVariable("KeystoreName"); PlayerSettings.Android.keystorePass = Environment.GetEnvironmentVariable("KeystorePassword"); PlayerSettings.Android.keyaliasName = Environment.GetEnvironmentVariable("KeyAlias"); PlayerSettings.Android.keyaliasPass = Environment.GetEnvironmentVariable("KeyPassword"); BuildPipeline.BuildPlayer(GetScenes(), string.Format("{0}/{1}.apk" , outputProjectsFolder, PlayerSettings.applicationIdentifier), target, options); } static void BuildIOS() { BuildTarget target = BuildTarget.iOS; EditorUserBuildSettings.SwitchActiveBuildTarget(target); PlayerSettings.applicationIdentifier = Environment.GetEnvironmentVariable("AppBundle"); PlayerSettings.iOS.appleDeveloperTeamID = Environment.GetEnvironmentVariable("GymTeamId"); BuildPipeline.BuildPlayer(GetScenes(), xcodeProjectsFolder, target, options); } //        static string[] GetScenes() { var projectScenes = EditorBuildSettings.scenes; List<string> scenesToBuild = new List<string>(); for (int i = 0; i < projectScenes.Length; i++) { if (projectScenes[i].enabled) { scenesToBuild.Add(projectScenes[i].path); } } return scenesToBuild.ToArray(); } }
      
      





Environment.GetEnvironmentVariableメ゜ッドは、bashスクリプトで以前に指定された環境倉数の倀を取埗したす。







Androidビルドスクリプトの䟋







buildAndroid.sh
 GREEN='\033[0;32m' RED='\033[0;33m' NC='\033[0m' # No Color export COMMIT=$(git log -1 --oneline —no-merges) if [ "$1" = "" ]; then echo -e "${RED}You must provide application Id${NC}" exit 1 fi export ANDROID_HOME=/Library/Android export OutputDirectory=./ export AppBundle=$1 if [ "$2" = "account1" ]; then export KeystoreName="$CI_DATA_PATH/keystores/account1.keystore" export KeystorePassword="..." export KeyAlias="..." export KeyPassword="..." elif [ "$2" = "account2" ]; then export KeystoreName="$CI_DATA_PATH/keystores/account2.keystore" export KeystorePassword="..." export KeyAlias="..." export KeyPassword="..." else echo "${RED}No keystore config found for $2${NC}" exit 1 fi echo -e "${GREEN}BundleId: ${AppBundle}${NC}" echo -e "${GREEN}Signing: ${KeyAlias}${NC}" #      mkdir -p $CI_PROJECT_DIR/Assets/Editor && cp $CI_DATA_PATH/CustomBuild.cs "$_" #   Unity if [ "$3" = "5.5" ]; then /Applications/Unity5.5/Unity.app/Contents/MacOS/Unity -buildTarget android -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.Android -quit -logFile /dev/stdout -username "..." -password "..." elif [ "$3" = "2017.1" ]; then /Applications/Unity2017.1/Unity.app/Contents/MacOS/Unity -buildTarget android -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.Android -quit -logFile /dev/stdout -username "..." -password "..." else /Applications/Unity5.6.4/Unity.app/Contents/MacOS/Unity -buildTarget android -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.Android -quit -logFile /dev/stdout -username "..." -password "..." fi #  ,   apk export APK="${CI_PROJECT_DIR}/${OutputDirectory}/${AppBundle}.${CI_BUILD_ID}.apk" echo "Testing apk exists: ${APK}..." if [ -f ${APK} ]; then echo -e "${GREEN}BUILD FOR ANDROID SUCCESS${NC}" #  apk      aws s3 cp ${APK} s3://ci-data/android/${AppBundle}.${CI_BUILD_ID}.apk --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers echo "<html><title>Download apk: ${AppBundle}</title><body><a href=\"https://ci-data.s3.amazonaws.com/android/${AppBundle}.${CI_BUILD_ID}.apk\">Install<br><br><strong>${AppBundle}</strong><br><small>${COMMIT}<br>(build ${CI_BUILD_ID} - android)</small></a></body></html>" >> ${CI_PROJECT_DIR}/download.html #  html      aws s3 cp ${CI_PROJECT_DIR}/download.html s3://ci-data/android/${AppBundle}.${CI_BUILD_ID}.html --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers #    Slack ${CI_DATA_PATH}/notifySlack.sh android success "https://ci-data.s3.amazonaws.com/android/${AppBundle}.${CI_BUILD_ID}.html" exit 0 else echo -e "${RED}BUILD FOR ANDROID FAILED${NC}" ${CI_DATA_PATH}/notifySlack.sh android failure exit 1 fi
      
      





iOS甚のサンプルビルドスクリプト

プロゞェクトのアセンブリは、UnityからのXcodeプロゞェクトの圢成ずXcodeプロゞェクトのアセンブリずいう2぀のステップで実行されたす。 開発者はXcodeプロゞェクトに盎接圱響を䞎えるこずはできたせん。これは制限をもたらしたす。プロゞェクト蚭定、アセンブリ情報を盎接倉曎するこずはできたせん。

たた、iOSのビルド機胜では、テストデバむスをアプリケヌションのプロビゞョニングプロファむルに登録する必芁がありたす。 Xcodeプロゞェクトをビルドするには、ビルドする前にAppleの開発者コン゜ヌルで蚌明曞、プロビゞョニングプロファむル、およびアプリケヌションIDを䜜成する必芁がありたす。

Fastlaneは、このプロセスを自動化するために䜿甚されたす。 このツヌルは、蚌明曞ずプロファむルを䜜成および同期し、ビルドずメタ情報をiTunes Connectにアップロヌドできるようにしたす。







XcodeにアクセスせずにUnityプロゞェクトを構築する堎合、埮劙な違いがありたす。









アドホックビルドbuildAdhocIOS.sh
 GREEN='\033[0;32m' RED='\033[0;33m' NC='\033[0m' # No Color export COMMIT=$(git log -1 --oneline --no-merges) if [ "$1" = "" ]; then echo -e "${RED}You must provide application Id${NC}" exit 1 fi if [ "$2" = "account1" ]; then #    fastlane  export AccountName="account email" export AccountDesc="account description" export FastlanePassword="..." export GymExportTeamId="..." export FastlaneRepository="fastlane-keys.git" export ProduceTeamName="team name" else echo "${RED}No keystore config found for $2${NC}" exit 1 fi echo -e "${GREEN}BundleId: ${AppBundle}${NC}" echo -e "${GREEN}Account: ${AccountDesc} (${2})${NC}" #      mkdir -p $CI_PROJECT_DIR/Assets/Editor && cp $CI_DATA_PATH/CustomBuild.cs "$_" #   Unity if [ "$3" = "5.5" ]; then /Applications/Unity5.5/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." elif [ "$3" = "2017.1" ]; then /Applications/Unity2017.1/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." else /Applications/Unity5.6.4/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." fi # ,  Unity  XCode  XCODE_FILES="${CI_PROJECT_DIR}/${XcodeDirectory}" if [ -d ${XCODE_FILES} ]; then #    Apple Developer Console export PRODUCE_APP_IDENTIFIER=${AppBundle} export PRODUCE_APP_NAME=${AppBundle} export PRODUCE_USERNAME=${AccountName} export PRODUCE_SKU=${AppBundle} # skip_itc     itunes connect -  adhoc   fastlane produce --app_version "1.0" --language "English" --skip_itc #    code signing keys and profiles cd "${CI_PROJECT_DIR}/${XcodeDirectory}" rm -f Matchfile echo "git_url \"${FastlaneRepository}\"" >> Matchfile echo "app_identifier [\"${AppBundle}\"]" >> Matchfile echo "username \"${AccountName}\"" >> Matchfile # ,      export MATCH_PASSWORD='...' #     ,    # force_for_new_devices true     ,    developer console fastlane match adhoc --force_for_new_devices true #  Gymfile   XCode project   Ad-Hoc  rm -f Gymfile echo "export_options(" >> Gymfile echo " manifest: {" >> Gymfile echo " appURL: \"https://ci-data.s3.amazonaws.com/ios/${AppBundle}.${CI_BUILD_ID}.ipa\"," >> Gymfile echo " displayImageURL: \"https://ci-data.s3.amazonaws.com/ios-icon.png\"," >> Gymfile echo " fullSizeImageURL: \"https://ci-data.s3.amazonaws.com/ios-icon-big.png\"" >> Gymfile echo " }," >> Gymfile echo ")" >> Gymfile fastlane gym --scheme "Unity-iPhone" --export_method ${GYM_EXPORT_METHOD} --xcargs "DEVELOPMENT_TEAM=\"${GYM_EXPORT_TEAM_ID}\" PROVISIONING_PROFILE_SPECIFIER=\"match AdHoc ${AppBundle}\" CODE_SIGN_IDENTITY=\"iPhone Distribution: ${AccountDesc}\"" -o "${CI_PROJECT_DIR}/" -n "${AppBundle}.${CI_BUILD_ID}.ipa" #      S3 export IPA="${CI_PROJECT_DIR}/${AppBundle}.${CI_BUILD_ID}.ipa" ls -l "${CI_PROJECT_DIR}/${XcodeDirectory}/*.ipa" echo "Testing ipa exists: ${IPA}..." if [ -f ${IPA} ]; then echo -e "Begin uploading to S3..." aws s3 cp ${CI_PROJECT_DIR}/${AppBundle}.${CI_BUILD_ID}.ipa s3://ci-data/ios/${AppBundle}.${CI_BUILD_ID}.ipa --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers aws s3 cp ${CI_PROJECT_DIR}/manifest.plist s3://ci-data/ios/${AppBundle}.${CI_BUILD_ID}.plist --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers echo "<html><title>Download ipa: ${AppBundle}</title>" >> ${CI_PROJECT_DIR}/download.html echo "<body><a href=\"itms-services://?action=download-manifest&url=https://ci-data.s3.amazonaws.com/ios/${AppBundle}.${CI_BUILD_ID}.plist\">Install<br><br><strong>${AppBundle}</strong><br><small>${COMMIT}<br>(build ${CI_BUILD_ID} - iOS)</small></a></body></html>" >> ${CI_PROJECT_DIR}/download.html aws s3 cp ${CI_PROJECT_DIR}/download.html s3://ci-data/ios/${AppBundle}.${CI_BUILD_ID}.html --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers ${CI_DATA_PATH}/notifySlack.sh ios ad-hoc "https://ci-data.s3.amazonaws.com/ios/${AppBundle}.${CI_BUILD_ID}.html" echo -e "${GREEN}BUILD AD-HOC FOR IOS SUCCESS${NC}" exit 0 else echo -e "${RED}BUILD AD-HOC FOR IOS FAILED${NC}" ${CI_DATA_PATH}/notifySlack.sh ios failure exit 1 fi else echo -e "${RED}BUILD FOR IOS FAILED${NC}" ${CI_DATA_PATH}/notifySlack.sh ios failure exit 1 fi
      
      





リリヌスビルドbuildIOS.sh
 GREEN='\033[0;32m' RED='\033[0;33m' NC='\033[0m' # No Color export COMMIT=$(git log -1 --oneline --no-merges) if [ "$1" = "" ]; then echo -e "${RED}You must provide application Id${NC}" exit 1 fi if [ "$2" = "account1" ]; then #    fastlane  export AccountName="account email" export AccountDesc="account description" export FastlanePassword="..." export GymExportTeamId="..." export FastlaneRepository="fastlane-keys.git" export ProduceTeamName="team name" else echo "${RED}No keystore config found for $2${NC}" exit 1 fi echo -e "${GREEN}BundleId: ${AppBundle}${NC}" echo -e "${GREEN}Account: ${AccountDesc} (${2})${NC}" #      mkdir -p $CI_PROJECT_DIR/Assets/Editor && cp $CI_DATA_PATH/CustomBuild.cs "$_" #   Unity if [ "$3" = "5.5" ]; then /Applications/Unity5.5/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." elif [ "$3" = "2017.1" ]; then /Applications/Unity2017.1/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." else /Applications/Unity5.6.4/Unity.app/Contents/MacOS/Unity -buildTarget ios -projectPath $CI_PROJECT_DIR -batchmode -executeMethod CustomBuild.IOS -quit -logFile /dev/stdout -username "..." -password "..." fi # ,  Unity  XCode  XCODE_FILES="${CI_PROJECT_DIR}/${XcodeDirectory}" if [ -d ${XCODE_FILES} ]; then #    Apple Developer Console and Itunes Connect export PRODUCE_APP_IDENTIFIER=${AppBundle} export PRODUCE_APP_NAME=${AppBundle} export PRODUCE_USERNAME=${AccountName} export PRODUCE_SKU=${AppBundle} fastlane produce --app_version "1.0" --language "English" #    code signing keys and profiles cd "${CI_PROJECT_DIR}/${XcodeDirectory}" rm -f Matchfile echo "git_url \"${FastlaneRepository}\"" >> Matchfile echo "app_identifier [\"${AppBundle}\"]" >> Matchfile echo "username \"${AccountName}\"" >> Matchfile # ,      export MATCH_PASSWORD='...' #    fastlane match appstore #   XCode fastlane gym --scheme "Unity-iPhone" --xcargs "DEVELOPMENT_TEAM=\"${GymExportTeamId}\" PROVISIONING_PROFILE_SPECIFIER=\"match AppStore ${AppBundle}\" CODE_SIGN_IDENTITY=\"iPhone Distribution: ${AccountDesc}\"" -o "${CI_PROJECT_DIR}/" -n "${AppBundle}.${CI_BUILD_ID}.ipa" #   itunes connect export IPA="${CI_PROJECT_DIR}/${AppBundle}.${CI_BUILD_ID}.ipa" ls -l "${CI_PROJECT_DIR}/${XcodeDirectory}/*.ipa" echo "Testing ipa exists: ${IPA}..." if [ -f ${IPA} ]; then rm -f Deliverfile echo "app_identifier \"${AppBundle}\"" >> Deliverfile echo "username \"${AccountName}\"" >> Deliverfile echo "ipa \"${IPA}\"" >> Deliverfile echo "submit_for_review false" >> Deliverfile echo "force true" >> Deliverfile fastlane deliver echo -e "${GREEN}BUILD FOR IOS SUCCESS${NC}" exit 0 else echo -e "${RED}BUILD FOR IOS FAILED${NC}" ${CI_DATA_PATH}/notifySlack.sh ios failure exit 1 fi else echo -e "${RED}BUILD FOR IOS FAILED${NC}" ${CI_DATA_PATH}/notifySlack.sh ios failure exit 1 fi
      
      





他の人がこのシステムでどのように働くか





ビルドログを衚瀺するためのむンタヌフェむス







画像







結果



したがっお、結果のシステムは䜿いやすく、サヌバヌ偎からチェックず怜蚌を远加できたすコヌドスタむル、テスト。マネヌゞャヌはSlackのアセンブリぞのリンクを衚瀺し、iOSのアセンブリに問題はありたせん。







マむナス-そのサポヌトは、Unityの新しいバヌゞョンを远加し、IDに眲名し、ケシの健康を確保するために必芁です。







珟圚、2人のランナヌが働いおおり玄2幎、4000を超えるアセンブリがシステムを通過しおいたす。 ビルド速床は、ランナヌの特性ずプロゞェクト内のアセットの数に䟝存したす。これらは毎回むンポヌトされ、Androidでは3〜30分、iOSでは10〜60分で倉化するためです。








All Articles