At DoorDash, we work to implement efficient processes that can mitigate common conflicts within a large iOS development team. Part of those efforts involve using XcodeGen, a command line interface (CLI), to reduce merging conflicts within our various iOS teams. Here we will discuss its implementation to manage the intricate business scenarios and demanding requirements of the Dasher app, which lets our drivers receive, pick up, and securely deliver orders to customers. With multiple configurations, dependencies, localization mechanisms, pre-build and post-build scripts, and a constantly scaling team, there are high risks for codebase instability and lack of scalability in developing such an app. Resolving common xcodeproj merge conflicts can be time-consuming, but it's a critical part of maintaining developer velocity and ensuring the smooth operation of a large and complex team.
En examinant le développement de notre application de chauffeur, nous pouvons plonger dans les défis de la maintenance d'un projet Xcode pour une grande équipe, y compris les problèmes de croissance auxquels les grandes équipes sont confrontées et les difficultés de refactorisation des grands projets. Nous démontrons comment XcodeGen peut résoudre ces problèmes et nous illustrons comment les équipes DoorDash Consumer et Driver l'ont utilisé avec succès pour surmonter les défis. Enfin, nous partageons nos idées et nos expériences sur la façon d'utiliser XcodeGen dans un projet à grande échelle.
Pourquoi les conflits de fusion de xcodeproj nuisent à la collaboration
Xcode est un outil essentiel pour les développeurs iOS et est utilisé pour diverses tâches telles que l'écriture de code, la construction d'interfaces utilisateur et l'analyse des métriques de l'app store, parmi beaucoup d'autres choses. Malgré son importance, la complexité des fichiers xcodeproj peut rendre difficile la gestion efficace des projets et assurer une collaboration transparente entre les membres de l'équipe. Un projet peut comprendre de dix à plusieurs milliers de fichiers, y compris du code source, des ressources, des scripts et des fichiers texte. Xcode classe ces fichiers dans un ordre apparemment arbitraire, qui semble être influencé par le moment et la manière dont les développeurs les intègrent.
In Xcode, all source code and assets must be linked to a target in order to be part of the final product, which means that every time a file is added, Xcode creates a file reference to ensure its inclusion in the target. To maintain all of these, Xcode houses the pbxproj file within the xcodeproj project, which holds the project's complete structure. For a small team, continuous Xcode project modifications might not be a common occurrence. As long as the team isn't frequently merging code or adjusting Xcode project files, they will likely remain untroubled by merge conflict issues. However, as a team grows or incorporates continuous integration tools, they may face a significant increase in merge conflicts, which can have a considerable impact on their development velocity.
Le fichier pbxproj est constitué de code généré par une machine, qui ne peut pas être facilement examiné par des humains. Sauf en cas de conflit de fusion, ce code est rarement examiné, ce qui signifie que les développeurs ont une connaissance et une compréhension limitées de la manière dont Xcode gère ces éléments. Par conséquent, les problèmes peuvent n'apparaître que lorsqu'une équipe s'agrandit, ce qui nuit à l'efficacité et à l'efficience.
But when a mobile team scales, efficiency and speed become crucial. Xcode, particularly the machine-generated code within the pbxproj file, may pose challenges to growth. Because it is complex and difficult for humans to review, developers don't know how Xcode manages files.
Cette complexité s'accroît au fur et à mesure que les développeurs ajoutent et suppriment des fichiers, mettent à jour les références de fichiers et modifient les paramètres de construction, les phases, les configurations et les schémas. En fin de compte, les conflits de fusion fréquents ralentissent les progrès de l'équipe et limitent sa capacité à évoluer efficacement.
It is therefore essential to have a process in place that can manage Xcode file references and structure, plus a well-equipped toolset that can mitigate these challenges and improve the team's ability to manage project files and execute tasks efficiently. By addressing the pbxproj file's complexity and implementing a streamlined process to manage Xcode files, a mobile team can scale effectively, avoid bottlenecks, and maximize its efficiency and speed.
Anyone who frequently collaborates on Xcode projects is likely familiar with the challenges of addressing merge conflicts stemming from project files that are difficult to read. It's hard to determine an appropriate merge conflict resolution just by looking at the raw, automatically generated XML. Additionally, without recompiling it's not clear whether a resolution is successful. Figure 1 shows an example of file structure change and cause conflicts:
La résolution des conflits de fusion soulève trois questions principales :
- Time-consuming and error-prone - Carefully comparing conflicting versions of an xcodeproj file can be daunting for complex or large projects. And, despite best efforts, any mistakes or overlooked conflicts can cause problems that require additional time and effort to fix.
- Risk of losing changes - If a merge conflict is not resolved properly, there is a risk that some changes may be lost or overwritten. This can be particularly problematic if lost changes are critical to a project's functionality.
- Potential for project corruption - In some cases, a merge conflict can corrupt the project so severely that it can't be opened in Xcode. Work may stop until the project is restored to a previous version or - worst-case scenario - the project may have to be restarted from scratch.
En général, les conflits de fusion au sein d'un fichier xcodeproj peuvent présenter des défis notables pour les développeurs et les équipes travaillant sur des projets Xcode. Cependant, des outils comme XcodeGen peuvent aider à contourner et à résoudre les conflits de fusion plus rapidement, en améliorant la cohérence et la fiabilité tout en minimisant les risques.
Comment gérer le remaniement d'un projet
The multitude of targets, files, and dependencies involved in refactoring large and complex projects can prove challenging. It's difficult to identify and modify relevant parts of the project without breaking or affecting other parts. Beyond this, Xcode projects often rely on external libraries or frameworks such as CocoaPods or Swift Package Manager (SPM). Refactoring may require updating these dependencies to ensure compatibility with the updated project structure.
La collaboration avec les autres membres de l'équipe peut également constituer un défi. La communication et la coordination des changements peuvent être nécessaires pour éviter les conflits et s'assurer que tout le monde travaille avec la même structure et la même configuration de projet.
It's essential to maintain compatibility with different versions of Xcode and other tools when refactoring an existing Xcode project. Backward compatibility with older versions of Xcode or other tools frequently is required to ensure that all team members can continue to build and use the project.
At DoorDash, we often undertake project refactoring for similar reasons. For example, DoorDash's Caviar is the world's largest marketplace for premium restaurants and food enthusiasts, while the DoorDash marketplace offers a variety of merchants that cater to everyone and every occasion. Although these products serve different needs, the engineering teams can share many tools, technologies, and codebases.
Nous comprenons que la duplication du code conduit naturellement au partage du code et à la construction de modules partagés. Cependant, la décomposition de projets monolithiques en modules nécessite beaucoup de remaniement. Plus d'une étape est nécessaire pour créer chaque module, nous devons donc utiliser les précautions, les outils et les processus appropriés pour maintenir le projet stable tout en continuant à travailler pour atteindre nos objectifs.
Il existe deux options pour remanier un projet :
- Top-down approach - This method involves rebuilding the project from scratch. Although previous learning and smart tools can be used to make the process safer and faster, it requires commiting to a full rewrite.
- Bottom-up approach - This method, which is generally considered to be most practical and safe, runs everything as-is, with changes made incrementally.
In a recent article - Adopting SwiftUI with a Bottom-Up Approach to Minimize Risk - we discussed both of these approaches in detail. In our SwiftUI and Combine adaptation, a bottom-up approach proved to be much more effective in laying the groundwork because we had to undergo numerous project restructurings and modifications before ultimately achieving modularization.
Lorsque nous avons adopté SwiftUI dans le projet DasherDriver, nous savions que nous aurions besoin de déplacer des fichiers et des répertoires, de changer de cible et d'entreprendre diverses autres tâches liées au projet Xcode au fur et à mesure que nous apporterions des modifications majeures au code. Toutes ces tâches au début du processus de restructuration demandent beaucoup de temps. En outre, comme il s'agissait d'un processus continu impliquant une itération constante du travail d'ingénierie et du travail sur le produit, le risque de conflits de fusion augmentait à mesure que l'équipe élargie apportait des changements de code et modifiait la structure du projet. Pour éviter les ralentissements, nous savions que nous devions éviter les conflits à tout prix.
Compte tenu des difficultés rencontrées par notre équipe pour lire rapidement les fichiers de projet Xcode, nous avons choisi de traiter les problèmes de conflit de fusion dans un premier temps. Nous avons reconnu que s'appuyer uniquement sur l'IDE Xcode ne suffirait pas à maintenir l'efficacité. En utilisant l'interface de programmation XcodeGen, nous avons réussi à progresser dans la phase de remaniement sans rencontrer d'obstacles significatifs.
Restez informé grâce aux mises à jour hebdomadaires
Abonnez-vous à notre blog d'ingénierie pour recevoir régulièrement des informations sur les projets les plus intéressants sur lesquels notre équipe travaille.
Please enter a valid email address.
Merci de vous être abonné !
Comment XcodeGen aide à réduire les erreurs de fusion
XcodeGen's ability to define project specs in YAML or JSON files offers several benefits over manual project creation and maintenance. It provides consistency and ease of maintenance, which is crucial in large-scale projects. By defining a project's structure and settings in a single YAML file, it's possible to ensure that projects have a consistent structure and configuration. This makes it easier to maintain and update projects over time. Changes can be made by simply editing the YAML file and then regenerating the Xcode project to apply those changes.
XcodeGen améliore également la collaboration. La spécification du projet étant stockée dans un fichier texte, elle peut facilement être versionnée et partagée avec les membres de l'équipe. Cela permet à tout le monde de travailler sur la même spécification de projet et de garantir la même configuration de projet à tous les niveaux. Les membres de l'équipe peuvent collaborer plus efficacement, éviter les conflits et savoir que les changements sont appliqués correctement.
XcodeGen allows for flexibility and customization in project development. With this tool, users can customize different aspects of their project - including build settings, targets, and schemes - to fit their specific needs and requirements. This level of customization is particularly useful for projects that require unique settings or have specific requirements that can't be achieved through standard settings. XcodeGen also integrates smoothly with other tools and libraries, such as CocoaPods, SPM, and Fastlane, making it easy to manage external dependencies in projects and automate common tasks like building and deploying apps.
To understand the power of XcodeGen, we need to understand the mess of .pbxproj. Xcode generates GUID for each file reference and uses that reference everywhere else inside the .pbxproj. To show this, we created a file inside an example project named - MyExampleApp.swift - Observe in the following code segment that Xcode assigned a GUID for that. Wherever in the project we use or refer to that file, Xcode will use the GUID to link properly.
Let's walk through some real-world scenarios. The test project has the structure shown in Figure 2:
Developer A decided to create another group named - Code - and moved all Swift files inside. After that operation, the project structure then looks like what is shown in Figure 3:
En raison de cette opération Xcode, le fichier .pbxproj est modifié. Les modifications apportées à notre fichier git apparaissent comme le montre la figure 4 :
These are substantial changes, all of them unreadable, for just one Xcode IDE operation. Now things get really ugly when Developer B adds a code file in the root hierarchy even before the initial changes get into the main branch. Developer B's local branch Xcode project layout appears as shown in Figure 5:
It's clear now where the merge conflict is starting. Any Xcode project merge conflict requires a manual, time-intensive process. Worse still, there's an excellent chance that something else has inadvertently been messed up that won't make itself known until much later.
Instead, let's manage the same kind of situation using the magic of XcodeGen. After following the instructions from here, we have project.yml in our project directory. We populate the YML as follows:
L'avantage du fichier YAML est qu'il peut être lu depuis le début pour comprendre immédiatement l'objectif :
- Nous avons défini le nom du projet
- Nous avons fixé les options globales les plus importantes
- Nous avons ensuite défini une cible avec un répertoire de code source
- And we're done!
Après avoir généré le projet à l'aide de la commande XcodeGen, l'IDE Xcode correspondra exactement à ce que nous décrivons dans le fichier YML. Mais nous avons franchi des étapes techniques majeures :
- C'est nous, et non l'IDE, qui contrôlons la structure du projet.
- Nous contrôlons les paramètres et ils seront reflétés en conséquence
- Nous venons d'éviter un conflit de fusion de fichiers de projet
L'instantané de l'IDE du projet apparaît comme le montre la figure 6 :
What if, despite our efforts, a merge conflict still needs to be resolved? We can just discard the incoming .pbxproj and run XcodeGen to generate the file again - and we are done. In essence, minutes or hours of complex merge conflict resolution can be reduced to less than a minute.
Inconvénients du processus XcodeGen
It's always disruptive to make any changes on a bigger project and with a larger team. Consequently, we must make sure we have a way to onboard everyone so that they can adapt quickly to changes. In our case, we made a change in our workflow that required for a time doing frequent one-to-one sessions. We also created a dedicated support channel to ensure everyone was on the same page. This phase, however, was short and quickly evolved into a routine daily process.
L'utilisation de XcodeGen présente quelques inconvénients potentiels dans certains cas :
- Lack of support for Xcode features - XcodeGen does not support all of the features and settings available in Xcode. For example, XcodeGen cannot create custom code snippets or manage localization files. If a project relies on these features, Xcode may still be required for some tasks.
- Limited documentation and community support - Because XcodeGen is a relatively new tool, there may not be as much documentation and community support available as for other tools. This can make it harder for new users to get started and troubleshoot any issues they encounter.
- Potential for conflicts with Xcode - Because XcodeGen generates an Xcode project from a YAML file, the generated project may not always be in sync with the actual contents of the project on disk. If changes are made outside of XcodeGen, there is a risk that those changes will be overwritten or lost when the project regenerates.
Bien que XcodeGen offre de nombreux avantages, il peut ne pas être la bonne solution pour chaque projet ou équipe de développement. Il est important d'évaluer soigneusement les besoins et les exigences avant de décider d'utiliser XcodeGen dans votre flux de travail.
Impact de l'utilisation de XcodeGen : Plus de vélocité
Voici les principaux problèmes que XcodeGen peut aider à résoudre :
- Reduce project merge conflict to nearly zero - After applying XcodeGen to both the DoorDash and Dasher apps, our team of more than 100 engineers suffered no project merge conflicts! The XcodeGen integration was a breeze for our team and began paying for itself almost immediately. Even factoring in the effort invested in integrating XcodeGen, we already have saved significant time and will continue to do so into the future.
- Modularity and code sharing - Caviar and DoorDash are built from the same project in two different targets using XcodeGen and a templated XcodeGen target. Without a CLI-based tool, there would be massive risk for both projects. XcodeGen immunized us from that risk.
- Migrating from Cocoapods to SPM - At one time, all Xcode projects were a heap of Pod tangles. Pods had lots of technical limitations and then Apple brought SPM to manage packaging and modularity. Migrating code from Pods to SPM was a daunting process and XcodeGen was our savior there as well.
En utilisant XcodeGen pour générer des projets Xcode, nous nous sommes assurés que nos projets ont une structure et une configuration cohérentes. Ce processus facilite la maintenance et la mise à jour des projets au fil du temps tout en réduisant le risque d'erreurs et d'incohérences. La spécification du projet étant stockée dans un fichier texte, nous avons créé des modèles à réutiliser dans nos différents projets. L'utilisation de XcodeGen permet à tout le monde de travailler avec les mêmes spécifications et la même configuration de projet. Il nous a permis de personnaliser divers aspects de notre projet, tels que les paramètres de construction, les cibles et les schémas, tout en nous donnant la flexibilité d'adapter nos projets à des besoins et des exigences spécifiques.
Conclusion
DoorDash faced challenges with scaling teams and resolving merge conflicts in Xcode projects. By adopting a tool that allowed more maintainable and human-readable project configurations, XcodeGen helped resolve merge conflicts more easily and streamlined development processes. XcodeGen is particularly valuable for larger teams dealing with complex project structures. Although XcodeGen may not be necessary for smaller teams, it has become an important part of DoorDash's toolset until (and unless) Apple provides a built-in solution in Xcode. In short, if you are facing similar challenges in your Xcode project development, it's worth your time to evaluate whether XcodeGen can be your solution.