< Zurück zu den Artikeln

Tuist: Unser Weg zur Projektgenerierung

Xcode-Projektdateien haben eine recht komplizierte Struktur und erzeugen unnötige Komplexität, wo es auch einfachere Lösungen gäbe. Daher war es für uns sehr reizvoll, auf Projekterstellung umzusteigen. Die Projekterstellung hat viele Vorteile – der Verzicht auf Xcode-Dateien bedeutet weniger Merge-Konflikte (ehrlich gesagt sind Merge-Konflikte inzwischen eine echte Seltenheit!) und übersichtlichere Projekteinstellungen. Wenn du nicht gerade ein Zauberer 🧙‍ bist, kannst du eine Projektdatei wahrscheinlich nicht so einfach lesen (und selbst wenn du es könntest, ist es immer noch sehr schwer, sich darin auszukennen).

Für die tatsächliche Projekterstellung benötigten wir ein Drittanbieter-Tool. tuist hat sich alsgute Wahl erwiesen. Die Möglichkeit, unsere Projekte in Swift zu konfigurieren, hat uns eine Menge Flexibilität verschafft. Tuist ist einfach zu bedienen und gleichzeitig extrem leistungsstark!

In diesem Blogbeitrag möchte ich die komplizierteren Aspekte der Integration unseres eigenen Projekt-Templates mit tuist durchgehen. Falls du einen genaueren Blick darauf werfen willst, ist das gesamte Setup in diesem Repository verfügbar.

Ziel

Bevor wir mit der Einrichtung von tuist beginnen, müssen wir definieren, was wir von der Einrichtung "erwarten". Zunächst verwenden wir ein „xcodeproj“ pro Projekt, sodass wir unsere Konfiguration recht spezifisch halten können, wobei eine Erweiterung auch möglich sein sollte (z. B. modulare Architektur). In „Project.swift“ sollte nur die Konfiguration enthalten sein, die sich vom Template unterscheidet – zum Beispiel bestimmte Abhängigkeiten oder der Projektname. Dies wird ermöglicht durch Projektbeschreibungs-Helper. Der größte Teil der Konfiguration befindet sich im Verzeichnis `Tuist`, in dem sich auch die Helper befinden. Die Definition eines neuen Projekts sollte nun eine Sache weniger Zeilen sein ✨.

Projekt-Template

Wie bereits angemerkt soll der größte Teil der Konfiguration mit Beschreibungs-Helpern erfolgen. Hierzu gibt es folgende Möglichkeiten:

extension Project {  
	public static func project(name: String) -> Project {  
		Project(name: name)  
	}  
}

Diese „static func“ kann dann in unserer „Project.swift“ verwendet werden. Wir können bei Bedarf zusätzliche Parameter für eine höhere Konfigurierbarkeit hinzufügen. Ich würde dazu raten, mit einem einzigen „Project“ und einem „Target“ zu beginnen, für das du zunächst „Sources“ und „Resources“ hinzufügst. Heben dir die Konfigurationen, Schemata und Build-Phasen für später auf – versuchen lieber, das Projekt zunächst kompilierbar zu machen und offene Punkte später zu erledigen. Diese Aufgabe ist eher banal, deshalb gehen wir direkt über zu den Konfigurationen. 😎

Konfigurationen

In unseren Apps verwenden wir fünf verschiedene App-Konfigurationen (Debug, Beta-Development, Beta-Stage, Beta-Production und Release), die es uns ermöglichen, während der Entwicklung zwischen verschiedenen Umgebungen zu wechseln. Für diese Konfigurationen haben wir eine enum AppCustomConfiguration erstellt, die für jede Konfiguration unterschiedliche Einstellungen definiert. Du solltest auf jeden Fall unterschiedliche Einstellungen für Debug und Release nutzen. 

Zu unserer vorherigen Erweiterung kommt dann:

settings: CustomSettings = CustomSettings(configurations: [.debug,  
                                                                                      	.betaDevelopment,  
                                                                                      	.betaStage,  
                                                                                      	.betaProduction,  
                                                                                      	.release]

Wobei CustomSettings nur ein Helper-Struct für syntaktischen Zucker ist. Dieses Setup ermöglicht es uns, die verwendeten Konfigurationen zu ändern, wenn wir zum Beispiel „.betaStage“ nicht benötigen. Es lässt sich überdies leicht auf das erweitern, was du in deinem Projekt benötigs. 

Die Schemakonfiguration funktioniert sehr ähnlich.

Abstraktion

Das Gute an den ProjectDescriptionHelpers ist die Tatsache, dass sich Aspekte leicht trennen lassen und auch ein kompliziertes Setup dank Einsatz mehrerer Dateien leicht lesbar bleibt. Auch wenn du die ProjectDescriptionHelpers an sich nicht benötigst, empfehlen wir ihre Anwendung, damit deine „Project.swift“ nicht übermäßig aufgebläht wird.

Code Signing

Tuist ist standardmäßig auf automatisches Code Signing eingestellt. Wenn du also manuelles Signing verwendest, musst du einige zusätzliche Konfigurationen vornehmen. In „Project.Settings“ können wir diese Werte hinzufügen:

"CODE_SIGN_STYLE": "Manual"  
"PROVISIONING_PROFILE_SPECIFIER": SettingValue(stringLiteral: "match InHouse cz.ackee.enterprise.\(name).\(identifierName)")

Wie du siehst, verwenden wir ein standardisiertes Bereitstellungsprofil mit Hilfe von Fastlane Match, um das Setup zu vereinfachen. „identifierName“ bezeichnet die aktuelle Schema-Beschreibung (z. B. ist die Beschreibung von Debug „debug“); „name“ ist der Name unseres Projekts. Das endgültige Bereitstellungsprofil sieht am Ende wie folgt aus: „match InHouse cz.ackee.enterprise.Project.debug“.

In „Target.Settings“ müssen wir überdies „CODE_SIGN_IDENTITY“ ändern, wo wir für Konfigurationen mit Ausnahme von Debug Folgendes einstellen müssen: „"CODE_SIGN_IDENTITY": "iPhone Distribution"“ Dies hängt allerdings davon ab, wie du das Code Signing in *deinem* Projekt verwendest. Dieser Teil hat zu Anfang der Migration die meisten Befürchtungen verursacht – doch, wie sich herausstellte, zu Unrecht ‍👩‍⚖‍

Habe ich alles richtig gemacht?

Wie du also siehst, ist auch eine kompliziertere Konfiguration möglich, doch manchmal braucht es etwas Mühe, damit sie richtig gelingt. Um dich dabei zu unterstützen, empfehle ich xcdiff wärmstens. Dieses Tool ist großartig! Mit xcdiff kannst du leicht erkennen, wo die Unterschiede zwischen dem generierten und dem alten Projekt liegen. Es wird dir vermutlich nicht gelingen, die Projekte exakt anzugleichen, doch es ist äußerst hilfreich, sie durchzugehen und auf Aspekte zu prüfen, die geändert werden müssen. Aus Erfahrung empfiehlt es sich, die verschiedenen Einstellungen zu prüfen, da die Codestruktur von Natur aus unterschiedlich ist und dir nicht viel sagen wird.

Tuist und CI

Das größte Kopfzerbrechen bereitet uns der Betrieb eines mit Tuist erstellten Projekts auf unserer CI. Der Grund war letztendlich ganz einfach – „tuist generate“ fügt nur die Dateien hinzu, die bereits existieren, doch wenn du in der Build-Phase einige Dateien generierst, werden diese nicht hinzugefügt. Das bedeutet, dass der Build fehlschlagen würde 😞. Um dieses Problem zu beheben, nutzen wir „Setup.swift“. Dort können wir alle Dateien, die wir generieren, vorab erstellen, so dass sie immer zum generierten Projekt hinzugefügt werden. In „Setup.swift“ funktioniert dies wie im Folgenden Snippet dargestellt:

let file = “path_to_your_generated_file” let upCommands: [Up] = [.custom(name: "\(file)'s intermediate directories", meet: ["mkdir", "-p", parentDirectory(for: file)], isMet: ["test", "-e", parentDirectory(for: file)]), .custom(name: "\(file)", meet: ["touch", file], isMet: ["test", "-e", file])]

Dies mag kompliziert aussehen, doch im Grunde genommen werden hier nur alle Zwischenverzeichnisse vor dem „touch“ der Datei erstellt. Anschließend musst du nur noch „tuist up“ vor „tuist generate“ in Ihrem CI-Setup laufen lassen und alles sollte einsatzbereit sein.

Unser Project.swift

Die endgültige Konfiguration befindet sich nicht in den Beschreibungs-Helpern, sondern im Stammordner. Dies ist der einfachste Teil und die endgültige Version kann wie folgt aussehen:

import ProjectDescription  
import ProjectDescriptionHelpers  
  
let project = Project.project(name: "ProjectTemplate",  
                          	        projectVersion: Version(0, 1, 0),  
                          	        platform: .iOS,  
                          	dependencies: [  
                            	.cocoapods(path: "."),  
                            	.carthage(name: "ACKategories"),  
                            	.carthage(name: "Alamofire"),  
                            	.carthage(name: "ReactiveCocoa"),])

Ganz einfach, oder? 😍

Tuist: Abschließende Überlegungen

Dies ist tatsächlich nur ein Schnelldurchlauf durch die Aspekte, denen wir begegnet sind. Im Übrigen verweisen wir auf die tuist-Dokumentation, die wirklich erschöpfend ist und das meiste abdecken sollte, was du benötigst. Hier kannst du dir unser Projekt-Template ansehen. Bislang war es ein reines Vergnügen, mit tuist zu arbeiten, und es sollte unsere Projekte in Zukunft sowohl wartbarer als auch *spaßiger* machen. Mit anderen Worten: Ich hoffe, du probierst es aus und meldest dich bei uns, wenn du Fragen hast! 🚀

Marek Fořt
Marek Fořt
iOS Developer

Beratungsbedarf? Lassen Sie uns über Ihr Projekt sprechen.

Kontakt aufnehmen >