Podcast
Videos
September 6, 2022
Nov 2022
12 Min

Bitrise CI für iOS optimal nutzen - projektübergreifende Konfigurationen

Mit der Zeit stecken wir viel Arbeit in die CI-Setups unserer Projekte. Die bisherigen Punkte aus Teil 1 und Teil 2 dieser Artikelserie decken nur die Grundlagen ab, um den Einstieg zu erleichtern. Tatsächlich gibt es eine Vielzahl weiterer Funktionen und Möglichkeiten, die man mit einem CI machen kann.

Um sich ein Bild von den Möglichkeiten zu machen, öffnet einfach die Step-Bibliothek, indem ihr auf einen der "+"-Buttons zwischen euren bestehenden Steps klickt und entdeckt, was dort möglich ist. Es gibt auch ein offizielles Community-Forum von Bitrise, in dem ihr viele How-Tos für verschiedene Aufgaben finden, Fragen stellen und neue Funktionen anfordern könnt.

So wie wir es mit unserem Code tun, wäre es toll, unsere Konfigurationen über mehrere Projekte hinweg wiederzuverwenden. Bei der Konfiguration des "deploy"-Workflows ist euch vielleicht schon aufgefallen, dass wir einige Step-Details kopieren und einfügen mussten. Aber keine Sorge, Bitrise hat eine Lösung für beide Probleme:

  1. Alle Konfigurationsdetails werden in einer YAML-Konfigurationsdatei gespeichert.
  2. Utility-Workflows können innerhalb von Workflows definiert und zusammengestellt werden.
  3. Darüber hinaus können Environment-Variablen wie Secret Vars definiert werden.

Um das alles besser zu verstehen, werfen wir einen Blick auf unser aktuelles Setup. Dazu öffnen wir die Konfigurationsdatei, indem wir auf den Tab .bitrise.yml klicken:

Beachtet, dass wir in der YAML-Datei die gesamte Konfiguration der trigger_map sowie eine Liste der workflows sehen können, die mit deploy beginnen, und alle auszuführenden Steps enthält. Außerdem ist neben jedem Step eine Version angegeben (z.B. @4.0.3) und einige haben auch zusätzliche Optionen konfiguriert. Da wir alle Elemente dieser YAML-Datei mit dem Workflow-Editor bearbeiten können, müsst ihr nicht alle Details der Konfigurationsdatei genau verstehen oder die Datei manuell bearbeiten. Tatsächlich könntet ihr den gesamten Inhalt der YAML-Datei aus diesem Projekt kopieren, in ein anderes Projekt einfügen und dann speichern. Das würde ohne Weiteres funktionieren.

Allerdings hat das andere Projekt natürlich einen anderen Namen und kann sich auch in anderer Weise unterscheiden, wie z.B. im Pfad zur Datei Info.plist. Aber genau wie beim Programmieren können wir diese Unterschiede in (Umgebungs-)Variablen extrahieren und für jedes Projekt unterschiedliche Werte festlegen, während die Konfiguration gleich bleibt. In der Tat erstellt Bitrise standardmäßig ein paar Umgebungsvariablen für uns, die wir sehen können, wenn wir in der YAML-Datei ganz nach unten scrollen:

Dort sind bereits drei Variablen definiert, nämlich BITRISE_PROJECT_PATH, BITRISE_SCHEME und BITRISE_EXPORT_METHOD. Wenn ihr euch an den Anfang dieses Artikels erinnert, sind das genau die drei Dinge, die Bitrise uns gefragt hat, als wir unser Projekt zum ersten Mal erstellt haben. Hier wurden sie also festgelegt. Eine Übersicht über alle Umgebungsvariablen erhalten wir auch, wenn wir im Workflow-Editor zur Registerkarte Env Vars wechseln:

Wie ihr seht, sind sie alle gleich. Und wenn wir uns die beiden folgenden Abschnitte ansehen, sehen wir auch, dass es sogar möglich ist, einige Variablen in bestimmten Workflows bei Bedarf zu überschreiben. Seid euch jedoch bewusst, dass Umgebungsvariablen ein von den geheimen Variablen unabhängiges Konzept sind. Obwohl sie sehr ähnlich verwendet werden (einfach durch Voranstellen von $), werden geheime Variablen nirgendwo angezeigt, nicht einmal in der YAML-Konfigurationsdatei. Umgebungsvariablen hingegen sollen in Build-Logs und in der YAML-Datei angezeigt werden. Deshalb listet der Workflow-Editor die geheimen Variablen im Tab "Secrets" auch separat auf:

Dort sehen wir die beiden Variablen, die wir bei der Konfiguration des Deployments hinzugefügt haben.Um unsere YAML-Konfiguration so wiederverwendbar wie möglich zu machen, sollten wir alle Optionen, die sich von Projekt zu Projekt ändern können, in Umgebungsvariablen extrahieren. Ein Beispiel ist der Pfad zur Datei Info.plist, den wir im Step "Set Xcode Project Build Number" konfiguriert haben. Der einfachste Weg, sie in eine Umgebungsvariable zu extrahieren, besteht darin, die Registerkarte "Env Vars" zu öffnen, im oberen Bereich auf die Schaltfläche "Add new" zu klicken, einen Namen zu vergeben (z.B. INFO_PLIST_PATH) und den Wert festzulegen ($BITRISE_SOURCE_DIR/App/SupportingFiles/Info.plist):

Als nächstes öffnen wir die Steps, in denen wir diesen Wert bereits verwendet haben, löschen ihn dort und klicken auf die Schaltfläche "Insert Variable" (die nur erscheint, wenn das Textfeld ausgewählt ist). Dann wählen wir dort unsere neue Variable INFO_PLIST_PATH aus.

Großartig. Eine weitere Möglichkeit, unsere YAML-Datei leichter wiederverwendbar zu machen, besteht darin, unsere Steps zukunftssicher zu gestalten. Da Steps bei Bitrise Fehler aufweisen oder veraltet sein können, werden sie von Zeit zu Zeit versioniert und aktualisiert. (Eigentlich wird diese Arbeit sogar öffentlich auf GitHub erledigt.) Um immer die neueste Version eines Steps zu nutzen, müssen wir unsere Version auf "always latest" setzen. Dies muss für jeden Step einzeln durchgeführt werden, aber es ist eine schnelle Angelegenheit, die sich lohnt. Also, lasst uns das machen und speichern.

Zuletzt möchten wir noch die Redundanz innerhalb unserer Konfigurationsdatei reduzieren, indem wir wiederverwendbare Teile in ihre eigenen Workflows extrahieren. Es wäre aber nicht sinnvoll, diese Teile nur für sich allein als Workflows auszuführen. Zudem muss dem Workflow-Editor angezeigt werden, dass sie hier sind, um innerhalb von Workflows erstellt zu werden. Deshalb gibt Bitrise uns die Möglichkeit, so genannte "Utility-Workflows" zu erstellen, indem wir ihren Namen einfach mit einem Unterstrich (_) beginnen.Beginnen wir mit dem Extraktionsprozess mit den drei Aufgaben, die wir normalerweise am Anfang immer benötigen, indem wir einen Utility-Workflow namens "_begin" erstellen, der auf dem bestehenden Workflow "deploy" basiert:

Dies führt zu einem neuen Workflow mit den gleichen Steps wie beim Workflow "deploy". Wir wollen aber nur die ersten drei Steps behalten, also klicken wir die anderen Steps nacheinander an und löschen sie mit dem Button oben rechts oder dem riesigen Button ganz unten (beide machen das Gleiche):

Die drei Steps unseres neuen Utility-Workflows sind jedoch immer noch Teil der "primären" und "deployten" Workflows. Also öffnen wir diese beiden Workflows nacheinander und löschen die drei Steps dort. Nun klicken wir innerhalb von "primary" oben auf den Button "Add Workflow before" und wählen "_begin".

Man beachte, dass der "_begin"-Workflow nun direkt im "primären" Workflow angezeigt und ausgegraut wird, um anzuzeigen, dass er nicht direkt Teil des aktuellen Workflows ist. Wiederholen wir die gleichen Schritte für den Workflow "deploy" und speichern ihn.
Auf diese Weise können wir noch weitere Steps in kombinierbare Utility-Workflows extrahieren. Berücksichtigt dabei, dass Utility-Workflows entweder vor oder nach den "normalen" Workflow-Steps platziert werden können. Oben gibt es auch einen "Neu anordnen"-Button, mit dem ihr umsortieren könnt.
Ein gutes Set von Utility-Workflows zum Einstieg ist das folgende:

  1. _begin: Übernimmt alle Vorarbeiten, die in jedem Workflow erforderlich sind.
  2. _run-linters: Lässt alle Linter laufen, um den Codestil durchzusetzen und Konflikte zu vermeiden.
  3. _prepare-build: Installiert alle Abhängigkeiten wie Tools & Frameworks.
  4. _run-tests: Führt Unit- und UI-Tests durch und schlägt fehl, wenn einer von ihnen nicht erfolgreich ist.
  5. _upload-to-connect: Erstellt ein Archiv und lädt es in App Store Connect hoch.
  6. _end: Erledigt alle Aufräumarbeiten, die in jedem Workflow erforderlich sind.

Die ersten fünf sollten eingerichtet werden, bevor der eigentliche Workflow läuft, der letzte sollte danach durchgeführt werden. Auf diese Weise beinhalten die beiden oben definierten Workflows im Grunde keine eigenen Steps mehr, sondern bestehen nur noch aus komponierten Utility-Workflows, was auch ihre YAML-Definition viel lesbarer macht:

primary:
       before_run:
       - _begin
       - _run-linters
       - _prepare-build
       - _run-tests
       after_run:
       - _end

   deploy:
       before_run:
       - _begin
       - _prepare-build
       - _upload-to-connect
       after_run:
       - _end

Wie wir feststellen, besteht der einzige Unterschied darin, dass der "primary" Workflow Linter und Tests ausführt, die wir für "deploy" beide überspringen. Stattdessen fügen wir dort den _upload-to-connect Utility-Workflow hinzu. Wenn wir uns jemals entscheiden sollten, dass wir unsere Tests auch im Workflow "deploy" ausführen müssen, könnten wir das mit einer einzigen Textzeile konfigurieren. Optisch sieht das Ergebnis so aus:

Nachdem unsere YAML-Konfigurationsdatei nun wirklich wiederverwendbar ist, ist das Teilen umso wirkungsvoller geworden. Um dies zu beweisen, ist hier ein GitHub Gist mit der endgültigen Konfigurationsdatei unseres Beispielprojekts, das wir gemeinsam konfiguriert haben:

format_version: '7'
   default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
   project_type: ios
   trigger_map:
   - push_branch: stable
     workflow: primary
   - pull_request_source_branch: "*"
     workflow: primary
   - tag: "*"
     workflow: deploy
   workflows:
     deploy:
       before_run:
       - _begin
       - _prepare-build
       - _run-tests
       - _upload-to-connect
       after_run:
       - _end
     primary:
       before_run:
       - _begin
       - _run-linters
       - _prepare-build
       - _run-tests
       after_run:
       - _end
     _begin:
       steps:
       - activate-ssh-key:
           run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
       - git-clone: {}
       - cache-pull: {}
     _run-linters:
       steps:
       - swiftlint:
           inputs:
           - strict: 'yes'
           - linting_path: "$BITRISE_SOURCE_DIR"
       before_run: []
     _prepare-build:
       steps:
       - script:
           title: Install Dependencies using Accio
           inputs:
           - content: |-
               #!/usr/bin/env bash

               # fail step if any command fails (-e) & debug log (-x)
               set -e -x

               # install Accio
               brew tap JamitLabs/Accio https://github.com/JamitLabs/Accio.git
               brew install accio

               # install dependencies using Accio
               accio install
       - certificate-and-profile-installer: {}
       before_run: []
     _run-tests:
       steps:
       - xcode-test:
           inputs:
           - project_path: "$BITRISE_PROJECT_PATH"
           - scheme: "$BITRISE_SCHEME"
       before_run: []
     _upload-to-connect:
       steps:
       - set-xcode-build-number:
           inputs:
           - plist_path: "$INFO_PLIST_PATH"
       - xcode-archive:
           inputs:
           - project_path: "$BITRISE_PROJECT_PATH"
           - scheme: "$BITRISE_SCHEME"
           - export_method: "$BITRISE_EXPORT_METHOD"
       - deploy-to-itunesconnect-application-loader:
           title: Deploy to App Store Connect - Application Loader
           inputs:
           - app_password: "$APPLE_ID_PASSWORD"
           - itunescon_user: "$APPLE_ID"
       before_run: []
     _end:
       steps:
       - deploy-to-bitrise-io: {}
       - cache-push:
           inputs:
           - cache_paths: |-
               $BITRISE_CACHE_DIR
               ~/Library/Caches/Accio/Cache -> Package.resolved
       before_run: []
   app:
     envs:
     - opts:
         is_expand: false
       BITRISE_PROJECT_PATH: NewProjectTemplate.xcodeproj
     - opts:
         is_expand: false
       BITRISE_SCHEME: App
     - opts:
         is_expand: false
       BITRISE_EXPORT_METHOD: app-store
     - INFO_PLIST_PATH: "$BITRISE_SOURCE_DIR/App/SupportingFiles/Info.plist"
       opts:
         is_expand: false

Einfach kopieren, in euer eigenes iOS-Projekt einfügen und die Umgebungs- und Geheimvariablen auf die richtigen Werte eurer App setzen. Wir bereiten sogar ähnliche YAML-Konfigurationsdateien für Swift Framework-Autoren und sogar für Android vor, die ihr mit eurem Team teilen könnt. Wir hoffen, dass uns dies in Zukunft viel Zeit sparen wird.
Wir freuen uns über Feedback und Verbesserungsvorschläge:

JamitLabs/BitriseTemplates

Ich hoffe, dass diese Artikel-Serie euch hilft, mit CI in euren iOS-Projekten zu beginnen! 🤖 ✅

Andreas Link
Andreas Link
Anh Dung Pham
Anh Dung Pham
Cihat Gündüz
Cihat Gündüz
Andreas Link
Ekrem Sentürk
Eva Maria Stock
Eva-Marie Stock
Andreas Link
Giulia Maier
Inken Marei Kolthoff
Inken Marei Kolthoff
Janina Baumann
Janina Baumann
Janina Bokeloh
Janina Bokeloh
Jeanette Schmidt
Jeanette Schmidt
Jens Krug
Jens Krug
Kajorn Pathomkeerati
Kajorn Pathomkeerati
Karl Barth
Karl Barth
Kay Dollt
Kay Dollt
Murat Yilmaz
Murat Yilmaz
Thorsten Hack
Thorsten Hack
Thorsten Hack
Thorsten Hack
Inken Marei Kolthoff
Cynthia Murat
Inhaltsverzeichnis

Weitere Artikel

Accio – SwiftPM für iOS
Cihat Gündüz
26.11.2022
14 Min

Accio – SwiftPM für iOS

Auf der WWDC 2019 ist der lang ersehnte SwiftPM-Support in Xcode erschienen.

Artikel lesen
Buglife - Geniale Feedback und Bug Reporting Möglichkeit für iOS und Android Apps
Inken Marei Kolthoff
26.11.2022
6 Min

Buglife - Geniale Feedback und Bug Reporting Möglichkeit für iOS und Android Apps

Buglife ist ein Framework um Bugs bzw. Verbesserungsvorschläge einfach durch Endnutzer zu melden.

Artikel lesen
3 Jahre Jamit Labs!
Jeanette Schmidt
26.11.2022
3 Min

3 Jahre Jamit Labs!

Das sich im letzten Jahr bereits einiges bei uns getan hat, hatten wir schon in unserem Jahresrückblick erzählt, doch damit nicht genug:

Artikel lesen

Jetzt kostenloses Strategiegespräch sichern!

Die Beratungen sind grundsätzlich schnell ausgebucht, deshalb fülle jetzt in 2 Minuten das kurze Formular aus.

Jetzt Strategiegespräch sichern