Automatyzacja kompilacji Unity za pomocą CI/CD

Kompletny projekt przykładowy – obejmujący projekt Unity, konfigurację Jenkins, szablony konteneryzacji w Dockerze oraz skrypty — dostępny jest tutaj: https://github.com/AndreiMaksimovich/Unity-Build-and-Test-Automation

Dlaczego warto automatyzować buildy?

Oszczędność czasu

W trakcie pracy nad projektem tworzy się setki, jeśli nie tysiące buildów dla QA, testów wewnętrznych I publikacji. Automatyzacja tych kroków pozwala zaoszczędzić ogromne ilości czasu, znacznie większe niż wymaga stworzenie samego systemu automatyzacji.

Zapewnienie spójności

Ręczne procesy są obciążone błędami ludzkimi. Pipeline buildów gwarantuje, że każdy build przechodzi przez te same etapy w odpowiedniej kolejności.

Automatyzacja kosztownych zadań

Pipeline to idealne miejsce, by przenieść procesy czasochłonne lub obciążające zasoby:

  • kompresja i optymalizacja assetów
  • analiza statyczna kodu / bezpieczeństwa
  • łączenie meshy i materiałów
  • generowanie shaderów / kodu
  • pełnej jakości wypiekanie oświetlenia (light baking)
  • analiza scen i prefabów

Przykładowy prosty workflow CI/CD

Gdy jesteś zadowolony z lokalnych zmian i je commitujesz, nadchodzi czas na testowy build. Z automatyzacją, pojedynczy klik w interfejsie WWW uruchamia maszynę buildową, która:

  1. Klonuje repozytorium Git
  2. Przepala (rebake) oświetlenie
  3. Uruchamia unit i PlayMode testy, raportując błędy
  4. Buduje wersje na Android, iOS i Web
  5. Wysyła buildy na lokalne urządzenia testowe
  6. Publikuje testowe buildy do Google Play i App Store (TestFlight)

Dla uproszczenia pomijam szczegóły etapu 6 – kwestie podpisywania kluczy, profilów provisioning, zarządzania sekretami to temat na osobny artykuł.

Przyjrzyjmy się teraz API Unity i narzędziom systemowym niezbędnym do zbudowania takiego systemu.

API Unity i narzędzia wspierające automatyzację

Uruchamianie skryptów edytora przez CLI

Możesz uruchomić Unity Editor z CLI, wskazując statyczną metodę w skrypcie edytora, która zostanie wykonana, a potem editor sam się zamknie:

{UNITY_EDITOR_PATH} -batchmode -quit -projectPath {UNITY_PROJECT_PATH} {EDITOR_CLASS_NAME}.{EDITOR_CLASS_METHOD}

Dokumentacja: https://docs.unity3d.com/2020.1/Documentation/Manual/CommandLineArguments.html

Manipulacja scenami Unity bez GUI

Sceny można otwierać, edytować i zapisywać przez skrypt, bez interakcji z GUI:

EditorSceneManager.OpenScene(scenePath);

// modyfikacje sceny

EditorSceneManager.SaveOpenScenes();

Dokumentacja: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/SceneManagement.EditorSceneManager.html

Light Baking

Oświetlenie można wypiekać automatycznie, programowo, na aktualnie otwartej scenie:

Lightmapping.Bake();

Dokumentacja: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/Lightmapping.Bake.html

Zmiana targetu builda

Można na bieżąco zmieniać aktywny target builda przez skrypt:

EditorUserBuildSettings.SwitchActiveBuildTarget(namedBuildTarget, target);

Dokumentacja: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/EditorUserBuildSettings.SwitchActiveBuildTarget.html

Addressables

Możesz zbudować Addressables przez skrypt:

AddressableAssetSettings.BuildPlayerContent();

Dokumentacja: https://docs.unity3d.com/Packages/com.unity.addressables@1.15/manual/BuildPlayerContent.html

Ustawienia Playera

Można programowo zmieniać ustawienia builda, np. załadować keystore dla Androida ze storage’u, użyć go w procesie, a potem odrzucić te zmiany.

PlayerSettings.Android.keystoreName = "PathToKeystore";
PlayerSettings.Android.keystorePass = "";
PlayerSettings.Android.keyaliasName = "KeyaliasName";
PlayerSettings.Android.keyaliasPass = "";

Dokumentacja:

Budowanie projektu Unity przez kod

Build Options:

var buildPlayerOptions = new BuildPlayerOptions {
    locationPathName = {PATH},
    target = {BUILD_TARGET},
    extraScriptingDefines = {SCRIPT_DEFINES},
    options = {BUILD_OPTIONS},
    scenes = {SCENES}
};

Dokumentacja: https://docs.unity3d.com/ScriptReference/BuildPlayerOptions.html 

Build Pipeline:

BuildPipeline.BuildPlayer(buildPlayerOptions);

Dokumentacja: https://docs.unity3d.com/6000.2/Documentation/ScriptReference/BuildPipeline.BuildPlayer.html

Uruchamianie testów

Możesz automatycznie uruchamiać testy jednostkowe (unit / EditMode) i PlayMode przez kod.

PlayMode Testy przez skrypt:

var testRunner = ScriptableObject.CreateInstance<TestRunnerApi>();
var filter = new Filter {
    targetPlatform = {PLATFORM},
    testMode = TestMode.PlayMode
};
var executionSettings = new ExecutionSettings {
    filters = new[] { filter }
};
testRunner.Execute(executionSettings);

Dokumentacja: https://docs.unity3d.com/Packages/com.unity.test-framework@2.0/api/UnityEditor.TestTools.TestRunner.Api.TestRunnerApi.html

Unit Testy przez CLI:

${UNITY_PATH} -runTests -batchmode -projectPath ${UNITY_PROJECT_PATH} -testPlatform EditMode -logfile stdout -testResults {TEST_RESULTS_PATH}

Dokumentacja: https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/reference-command-line.html

Budowanie projektu iOS z linii poleceń

Możesz zbudować i spakować projekt iOS bezpośrednio z linii poleceń, korzystając z xcodebuild.

1. Utwórz archiwum XCArchive

xcodebuild -workspace {WORKSPACE_PATH} -scheme {SCHEME_NAME} -archivePath {XCARCHIVE_PATH} archive

2. Przygotuj opcje eksportu (.plist)
Utwórz plik .plist z wybranymi ustawieniami eksportu:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plistPUBLIC"-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plistversion="1.0">
<dict>
    <key>method</key>
    <string>app-store</string>
    <key>teamID</key>
    <string>{YOUR_TEAM_ID}</string>
    <key>uploadBitcode</key>
    <true/>
    <key>uploadSymbols</key>
    <true/>
</dict>
</plist>

3. Zbuduj plik .ipa

xcodebuild -exportArchive -archivePath {XCARCHIVE_PATH} -exportPath {EXPORT_PATH} -exportOptionsPlist {OPTIONS_PLIST_PATH}

Dokumentacja: Apple Technical Note TN2339

Instalowanie testowych buildów na lokalnych urządzeniach iOS

Najprostszym sposobem instalacji plików .ipa lokalnie jest użycie Apple Configurator, który zawiera narzędzie cfgutil:

1. Zainstaluj Apple Configurator 2App Store link

2. Dodaj cfgutil do zmiennej PATH:

sudo ln -s "/Applications/Apple Configurator 2.app/Contents/MacOS/cfgutil" /usr/local/bin/cfgutil

3. Zainstaluj plik .ipa na podłączonym urządzeniu (przez USB):

cfgutil install-app {IPA_PATH}

Instalacja bezprzewodowa na urządzeniach iOS jest uciążliwa i mało niezawodna. W większości przypadków znacznie łatwiej jest zautomatyzować uploady do TestFlight i dystrybuować buildy tą drogą.

Instalowanie testowych buildów na lokalnych urządzeniach z Androidem

Możesz instalować pliki .apk bezpośrednio na urządzeniach z Androidem podłączonych kablem USB lub przez lokalne połączenie bezprzewodowe.

Konfiguracja bezprzewodowa:

adb pair {IP}:{PORT}
adb connect {IP}:{PORT}
adb install {APK_PATH}
adb shell am start -n {APP_PACKAGE}/{ENTRY_POINT}

Dokumentacja: https://developer.android.com/tools/adb

Automatyzacja uploadu do App Store TestFlight i Google Play Internal Testing

Proste przykłady użycia, podane bez kontekstu …

App Store

xcrun notarytool submit {IPA_PATH} \
--apple-id {APPLE_ID_EMAIL} \
--team-id {TEAM_ID} \
--password {APP_SPECIFIC_PASSWORD}

Google Play

gcloud auth activate-service-account \
--key-file={SERVICE_ACCOUNT_JSON} \
--project={GCP_PROJECT_ID}

gcloud firebase appdistribution:distribute {AAB_PATH} \
--app {FIREBASE_APP_ID} \
--testers {TESTERS} \
--release-notes {RELEASE_NOTE}

Podsumowanie

Powyższe API i przykłady obejmują większość tego, co potrzebujesz, by stworzyć własny pipeline do automatyzacji buildów w Unity. Dołóż integrację z Gitem, powiadomienia (np. Slack Webhooks), i masz prosty, ale potężny workflow.

Ostateczny krok to wybór narzędzia, które to wszystko połączy. W moim przykładzie używam Jenkins. Choć może wydawać się nieco przestarzały, ogrom dostępnych pluginów oraz możliwość wykorzystania zdalnych maszyn roboczych (np. do wypiekania oświetlenia na Windowsie z dedykowanym GPU) sprawia, że w przypadku lokalnie hostowanych systemów nadal jest dobrym wyborem.

Jednak praktycznie każde nowoczesne rozwiązanie CI/CD będzie działało – a API i skrypty pozostają takie same.