Ir al contenido

Blog


Cómo DoorDash utiliza XcodeGen para eliminar los conflictos de fusión de proyectos

31 de mayo de 2023

|
Md Al Mamun

Md Al Mamun

Michael Thole

Michael Thole

En DoorDash, trabajamos para implementar procesos eficientes que puedan mitigar los conflictos comunes dentro de un gran equipo de desarrollo de iOS. Parte de esos esfuerzos implican el uso de XcodeGen, una interfaz de línea de comandos (CLI), para reducir los conflictos de fusión dentro de nuestros diversos equipos de iOS. Aquí discutiremos su implementación para gestionar los intrincados escenarios de negocio y los exigentes requisitos de la aplicación Dasher, que permite a nuestros conductores recibir, recoger y entregar de forma segura los pedidos a los clientes. Con múltiples configuraciones, dependencias, mecanismos de localización, scripts de precompilación y postcompilación, y un equipo en constante crecimiento, existen grandes riesgos de inestabilidad en la base de código y falta de escalabilidad en el desarrollo de una aplicación de este tipo. Resolver los conflictos comunes de fusión de xcodeproj puede llevar mucho tiempo, pero es una parte fundamental para mantener la velocidad de los desarrolladores y garantizar el buen funcionamiento de un equipo grande y complejo. 

Al examinar el desarrollo de nuestra aplicación de conductor, podemos profundizar en los desafíos de mantener un proyecto Xcode para un equipo grande, incluyendo los dolores de crecimiento que enfrentan los grandes equipos y las dificultades de refactorización de grandes proyectos. Demostramos cómo XcodeGen puede resolver estos problemas e ilustramos cómo los equipos de DoorDash Consumer y Driver lo utilizaron con éxito para superar los desafíos. Por último, compartimos nuestras ideas y experiencias sobre cómo utilizar XcodeGen en un proyecto a gran escala.

Por qué los conflictos de fusión de xcodeproj perjudican la colaboración

Xcode es una herramienta esencial para los desarrolladores de iOS y se utiliza para diversas tareas, como escribir código, crear interfaces de usuario y analizar las métricas de las tiendas de aplicaciones, entre otras muchas cosas. A pesar de su importancia, la complejidad de los archivos xcodeproj puede dificultar la gestión eficaz de los proyectos y garantizar una colaboración fluida entre los miembros del equipo. Un proyecto puede consistir en cualquier cosa, desde diez archivos a miles de archivos, incluyendo el código fuente, activos, scripts y archivos de texto sin formato. Xcode organiza estos archivos en un orden aparentemente arbitrario, que parece estar influido por cuándo y cómo los incorporan los desarrolladores. 

En Xcode, todo el código fuente y los activos deben estar vinculados a un destino para formar parte del producto final, lo que significa que cada vez que se añade un archivo, Xcode crea una referencia de archivo para garantizar su inclusión en el destino. Para mantener todo esto, Xcode aloja el archivo pbxproj dentro del proyecto xcodeproj, que contiene la estructura completa del proyecto. Para un equipo pequeño, las modificaciones continuas del proyecto Xcode pueden no ser habituales. Mientras el equipo no esté fusionando código o ajustando archivos de proyecto de Xcode con frecuencia, es probable que no tenga problemas de conflictos de fusión. Sin embargo, a medida que un equipo crece o incorpora herramientas de integración continua, pueden enfrentarse a un aumento significativo de los conflictos de fusión, lo que puede tener un impacto considerable en su velocidad de desarrollo.

El archivo pbxproj consiste en código generado por máquina, que no puede ser revisado fácilmente por humanos. A menos que haya un conflicto de fusión, este código rara vez se examina, lo que significa que los desarrolladores tienen un conocimiento y una comprensión limitados sobre cómo Xcode gestiona estos elementos. Como resultado, los problemas sólo pueden surgir cuando un equipo se amplía, lo que dificulta la eficiencia y la eficacia.   

Pero cuando un equipo móvil se amplía, la eficiencia y la velocidad se vuelven cruciales. Xcode, en particular el código generado por la máquina dentro del archivo pbxproj, puede plantear retos para el crecimiento. Al ser complejo y difícil de revisar para los humanos, los desarrolladores no saben cómo gestiona Xcode los archivos.

Esta complejidad aumenta a medida que varios desarrolladores añaden y eliminan archivos, actualizan referencias a archivos y realizan cambios en los ajustes, fases, configuraciones y esquemas de compilación. En última instancia, los frecuentes conflictos de fusión ralentizan el progreso del equipo y limitan su capacidad para escalar eficazmente.

Por lo tanto, es esencial contar con un proceso que pueda gestionar las referencias y la estructura de los archivos de Xcode, además de un conjunto de herramientas bien equipadas que puedan mitigar estos desafíos y mejorar la capacidad del equipo para gestionar los archivos del proyecto y ejecutar las tareas de manera eficiente. Al abordar la complejidad del archivo pbxproj e implementar un proceso optimizado para gestionar los archivos de Xcode, un equipo móvil puede escalar con eficacia, evitar cuellos de botella y maximizar su eficiencia y velocidad.

Cualquiera que colabore con frecuencia en proyectos Xcode probablemente esté familiarizado con los retos de resolver conflictos de fusión derivados de archivos de proyecto difíciles de leer. Es difícil determinar una resolución adecuada de un conflicto de fusión con sólo mirar el XML generado automáticamente. Además, sin recompilar no está claro si una resolución ha tenido éxito. La figura 1 muestra un ejemplo de cambio de estructura de archivos y conflictos de causa:

Figura 1: Conflicto de fusión pbxproj en Xcode
Figura 1: Conflicto de fusión pbxproj en Xcode

En la resolución de los conflictos de fusión intervienen tres cuestiones principales:

  • Lleva mucho tiempo y es propenso a errores - Comparar cuidadosamente las versiones conflictivas de un archivo xcodeproj puede ser desalentador para proyectos complejos o grandes. Y, a pesar de todos los esfuerzos, cualquier error o conflicto pasado por alto puede causar problemas que requieren tiempo y esfuerzo adicionales para solucionarlos.
  • Riesgo de perder cambios - Si un conflicto de fusión no se resuelve correctamente, existe el riesgo de que algunos cambios se pierdan o se sobrescriban. Esto puede ser especialmente problemático si los cambios perdidos son críticos para la funcionalidad de un proyecto.
  • Posible corrupción del proyecto - En algunos casos, un conflicto de fusión puede corromper el proyecto tan gravemente que no se puede abrir en Xcode. El trabajo puede detenerse hasta que el proyecto se restaure a una versión anterior o, en el peor de los casos, puede ser necesario reiniciar el proyecto desde cero.

En general, los conflictos de fusión dentro de un archivo xcodeproj pueden presentar desafíos notables para los desarrolladores y equipos que trabajan en proyectos Xcode. Sin embargo, herramientas como XcodeGen pueden ayudar a evitar y resolver los conflictos de fusión con mayor rapidez, mejorando la coherencia y la fiabilidad y minimizando los riesgos.

Cómo gestionar la refactorización de proyectos 

La multitud de objetivos, archivos y dependencias que conlleva la refactorización de proyectos grandes y complejos puede suponer todo un reto. Es difícil identificar y modificar las partes relevantes del proyecto sin romper o afectar a otras partes. Además, los proyectos de Xcode suelen depender de bibliotecas o marcos de trabajo externos, como CocoaPods o Swift Package Manager (SPM). La refactorización puede requerir la actualización de estas dependencias para garantizar la compatibilidad con la estructura actualizada del proyecto.

Colaborar con otros miembros del equipo también puede suponer un reto. La comunicación y la coordinación de los cambios pueden ser necesarias para evitar conflictos y garantizar que todos trabajan con la misma estructura y configuración del proyecto.

Es esencial mantener la compatibilidad con diferentes versiones de Xcode y otras herramientas al refactorizar un proyecto de Xcode existente. La compatibilidad con versiones anteriores de Xcode u otras herramientas suele ser necesaria para garantizar que todos los miembros del equipo puedan seguir creando y utilizando el proyecto.

En DoorDash, a menudo emprendemos la refactorización de proyectos por razones similares. Por ejemplo, Caviar de DoorDash es el mercado más grande del mundo para restaurantes premium y entusiastas de la comida, mientras que el mercado de DoorDash ofrece una variedad de comerciantes que atienden a todos y a cada ocasión. Aunque estos productos atienden necesidades diferentes, los equipos de ingeniería pueden compartir muchas herramientas, tecnologías y bases de código.

Entendemos que la duplicación de código conduce de forma natural a compartir código y crear módulos compartidos. Sin embargo, dividir proyectos monolíticos en módulos requiere mucha refactorización. Se necesita más de un paso para crear cada módulo, por lo que debemos utilizar las precauciones, herramientas y procesos adecuados para mantener la estabilidad del proyecto sin dejar de trabajar para alcanzar nuestros objetivos.

Hay dos opciones para refactorizar un proyecto:

  • Enfoque descendente - Este método implica reconstruir el proyecto desde cero. Aunque el aprendizaje previo y las herramientas inteligentes pueden utilizarse para hacer el proceso más seguro y rápido, requiere comprometerse a una reescritura completa.
  • Enfoque ascendente - Este método, que suele considerarse el más práctico y seguro, ejecuta todo tal cual, con cambios realizados de forma incremental. 

En un artículo reciente - La adopción de SwiftUI con un enfoque ascendente para minimizar el riesgo - discutimos ambos enfoques en detalle. En nuestra adaptación de SwiftUI y Combine, un enfoque ascendente demostró ser mucho más eficaz para sentar las bases, ya que tuvimos que someternos a numerosas reestructuraciones y modificaciones del proyecto antes de lograr finalmente la modularización.

Cuando adoptamos SwiftUI en el proyecto DasherDriver, sabíamos que tendríamos que mover archivos y directorios, cambiar objetivos, y llevar a cabo varias otras tareas relacionadas con el proyecto Xcode a medida que realizábamos cambios importantes en el código. Todas estas tareas al inicio del proceso de reestructuración requieren una cantidad significativa de tiempo. Además, dado que se trataba de un proceso continuo que implicaba una iteración constante del trabajo de ingeniería y del trabajo de producto, existía un riesgo creciente de conflictos de fusión a medida que el equipo más grande instituía continuamente cambios de código y alteraba la estructura del proyecto. Para evitar ralentizaciones, sabíamos que debíamos evitar los conflictos a toda costa.

Dada la dificultad que tenía nuestro equipo para leer rápidamente los archivos de proyecto de Xcode, optamos por abordar inicialmente los problemas de conflictos de fusión. Reconocimos que confiar únicamente en el IDE de Xcode no bastaría para mantener la eficiencia. Empleando XcodeGen CLI, conseguimos avanzar en la fase de refactorización sin encontrarnos con obstáculos significativos.

Manténgase informado con las actualizaciones semanales

Suscríbase a nuestro blog de ingeniería para estar al día de los proyectos más interesantes en los que trabaja nuestro equipo.

Cómo ayuda XcodeGen a reducir los errores de fusión 

La capacidad de XcodeGen para definir las especificaciones del proyecto en archivos YAML o JSON ofrece varias ventajas sobre la creación y el mantenimiento manual de proyectos. Proporciona coherencia y facilidad de mantenimiento, lo que es crucial en proyectos a gran escala. Al definir la estructura y la configuración de un proyecto en un único archivo YAML, es posible garantizar que los proyectos tengan una estructura y una configuración coherentes. Esto facilita el mantenimiento y la actualización de los proyectos a lo largo del tiempo. Los cambios se pueden hacer simplemente editando el archivo YAML y luego regenerando el proyecto Xcode para aplicar esos cambios.

XcodeGen también mejora la colaboración. Con la especificación del proyecto almacenada en un archivo de texto, se puede versionar y compartir fácilmente con los miembros del equipo. Esto permite a todos trabajar en la misma especificación del proyecto y garantiza la misma configuración del proyecto en todos los ámbitos. Los miembros del equipo pueden colaborar de forma más eficaz, evitar conflictos y saber que los cambios se aplican correctamente.

XcodeGen permite flexibilidad y personalización en el desarrollo de proyectos. Con esta herramienta, los usuarios pueden personalizar distintos aspectos de su proyecto -incluidos los ajustes de compilación, los objetivos y los esquemas- para adaptarlos a sus necesidades y requisitos específicos. Este nivel de personalización es particularmente útil para proyectos que requieren configuraciones únicas o tienen requisitos específicos que no se pueden lograr a través de la configuración estándar. XcodeGen también se integra sin problemas con otras herramientas y bibliotecas, como CocoaPods, SPM y Fastlane, lo que facilita la gestión de dependencias externas en los proyectos y la automatización de tareas comunes como la creación y el despliegue de aplicaciones.

Para entender el poder de XcodeGen, tenemos que entender el lío de .pbxproj. Xcode genera un GUID para cada referencia de archivo y utiliza esa referencia en todas partes dentro del .pbxproj. Para mostrar esto, creamos un archivo dentro de un proyecto de ejemplo llamado "MyExampleApp.swift". Observe en el siguiente segmento de código que Xcode le asignó un GUID. En cualquier parte del proyecto que utilicemos o hagamos referencia a ese archivo, Xcode utilizará el GUID para enlazarlo correctamente.

Veamos algunos escenarios reales. El proyecto de prueba tiene la estructura que se muestra en la Figura 2:

Figura 2: Vista del navegador de proyectos de Xcode

El desarrollador A decidió crear otro grupo llamado "Código" y movió todos los archivos Swift dentro. Tras esta operación, la estructura del proyecto es la que se muestra en la Figura 3:

Figura 3: Cambio en la estructura del proyecto

Debido a esa operación de Xcode, el archivo .pbxproj cambia. Los cambios en nuestro archivo git aparecen como se muestra en la Figura 4:

Figura 4: Cambio de pbxproj

Se trata de cambios sustanciales, todos ellos ilegibles, para una sola operación del IDE de Xcode. Ahora las cosas se ponen realmente feas cuando el desarrollador B añade un archivo de código en la jerarquía raíz incluso antes de que los cambios iniciales lleguen a la rama principal. La disposición del proyecto Xcode de la rama local del desarrollador B aparece como se muestra en la figura 5: 

Figura 5: Cambio en la estructura del proyecto

Ahora está claro dónde empieza el conflicto de fusión. Cualquier conflicto de fusión de un proyecto de Xcode requiere un proceso manual que requiere mucho tiempo. Peor aún, hay una excelente posibilidad de que algo más se haya desordenado inadvertidamente que no se dará a conocer hasta mucho más tarde.
En su lugar, vamos a gestionar el mismo tipo de situación utilizando la magia de XcodeGen. Después de seguir las instrucciones de aquí, tenemos project.yml en el directorio de nuestro proyecto. Rellenamos el YML como sigue:

Lo sorprendente del archivo YAML es que se puede leer desde arriba para entender el objetivo de inmediato:

  • Definimos el nombre del proyecto 
  • Hemos fijado algunas opciones globales que más importan 
  • A continuación, definimos un objetivo con un directorio de código fuente
  • ¡Y ya está!

Después de generar el proyecto mediante el comando XcodeGen, Xcode IDE coincidirá exactamente con lo que describimos en el archivo YML. Pero hemos logrado importantes hitos técnicos: 

  1. Nosotros, y no IDE, controlamos la estructura del proyecto 
  2. Nosotros controlamos los ajustes y se reflejarán en consecuencia 
  3. Acabamos de evitar un conflicto de fusión de archivos de proyecto

La instantánea del IDE del proyecto aparece como se muestra en la Figura 6:   

Figura 6: Proyecto Xcode generado por XcodeGen

¿Qué pasa si, a pesar de nuestros esfuerzos, todavía hay que resolver un conflicto de fusión? Basta con descartar el .pbxproj entrante y ejecutar XcodeGen para generar de nuevo el archivo, y ya está. En esencia, minutos u horas de compleja resolución de conflictos de fusión pueden reducirse a menos de un minuto. 

Inconvenientes del proceso XcodeGen 

Hacer cambios en un proyecto de mayor envergadura y con un equipo más numeroso siempre supone un trastorno. En consecuencia, debemos asegurarnos de que tenemos una forma de incorporar a todo el mundo para que pueda adaptarse rápidamente a los cambios. En nuestro caso, hicimos un cambio en nuestro flujo de trabajo que requirió durante un tiempo hacer frecuentes sesiones individuales. También creamos un canal de soporte dedicado para asegurarnos de que todo el mundo estaba en la misma página. Esta fase, sin embargo, fue corta y rápidamente se convirtió en un proceso diario rutinario. 

En algunos casos, el uso de XcodeGen puede presentar algunos inconvenientes:

  • Falta de compatibilidad con las funciones de Xcode - XcodeGen no es compatible con todas las funciones y configuraciones disponibles en Xcode. Por ejemplo, XcodeGen no puede crear fragmentos de código personalizados ni gestionar archivos de localización. Si un proyecto se basa en estas características, Xcode todavía puede ser necesario para algunas tareas.
  • Documentación y soporte de la comunidad limitados: dado que XcodeGen es una herramienta relativamente nueva, es posible que no haya tanta documentación ni tanto soporte de la comunidad como en el caso de otras herramientas. Esto puede hacer que sea más difícil para los nuevos usuarios para empezar y solucionar cualquier problema que se encuentran.
  • Posibilidad de conflictos con Xcode - Dado que XcodeGen genera un proyecto Xcode a partir de un archivo YAML, el proyecto generado puede no estar siempre sincronizado con el contenido real del proyecto en disco. Si se realizan cambios fuera de XcodeGen, existe el riesgo de que esos cambios se sobrescriban o se pierdan cuando se regenere el proyecto.

Aunque XcodeGen ofrece muchas ventajas, puede que no sea la solución adecuada para todos los proyectos o equipos de desarrollo. Es importante evaluar detenidamente las necesidades y los requisitos antes de decidir si utilizar XcodeGen en su flujo de trabajo.

Impacto del uso de XcodeGen: Más velocidad 

Éstos son los principales problemas que XcodeGen puede ayudar a resolver: 

  • Después de aplicar XcodeGen a las aplicaciones DoorDash y Dasher, nuestro equipo de más de 100 ingenieros no sufrió ningún conflicto de fusión de proyectos. La integración de XcodeGen fue pan comido para nuestro equipo y empezó a amortizarse casi de inmediato. Incluso teniendo en cuenta el esfuerzo invertido en la integración de XcodeGen, ya hemos ahorrado mucho tiempo y seguiremos haciéndolo en el futuro.
  • Modularidad y código compartido - Caviar y DoorDash se construyen a partir del mismo proyecto en dos objetivos diferentes utilizando XcodeGen y un objetivo XcodeGen con plantillas. Sin una herramienta basada en CLI, habría un riesgo masivo para ambos proyectos. XcodeGen nos inmunizó de ese riesgo. 
  • Migrando de Cocoapods a SPM - En una época, todos los proyectos de Xcode eran un montón de enredos de Pods. Pods tenía un montón de limitaciones técnicas y luego Apple trajo SPM para gestionar el empaquetado y la modularidad. Migrar código de Pods a SPM fue un proceso desalentador y XcodeGen fue nuestro salvador allí también.

Al utilizar XcodeGen para generar proyectos Xcode, nos hemos asegurado de que nuestros proyectos tengan una estructura y configuración coherentes. Este proceso facilita el mantenimiento y la actualización de los proyectos a lo largo del tiempo, al tiempo que reduce el riesgo de errores e incoherencias. Como la especificación del proyecto se almacena en un archivo de texto, hemos creado plantillas para reutilizarlas en nuestros distintos proyectos. El uso de XcodeGen permite a todos trabajar con las mismas especificaciones y configuración de proyecto. Nos permitió personalizar varios aspectos de nuestro proyecto, como la configuración de compilación, los objetivos y los esquemas, al tiempo que nos daba la flexibilidad de adaptar nuestros proyectos a necesidades y requisitos específicos.

Conclusión

DoorDash se enfrentaba a retos relacionados con la ampliación de equipos y la resolución de conflictos de fusión en proyectos Xcode. Al adoptar una herramienta que permitía configuraciones de proyecto más legibles y fáciles de mantener, XcodeGen ayudó a resolver los conflictos de fusión más fácilmente y agilizó los procesos de desarrollo. XcodeGen es especialmente valioso para equipos grandes que trabajan con estructuras de proyecto complejas. Aunque XcodeGen puede no ser necesario para los equipos más pequeños, se ha convertido en una parte importante del conjunto de herramientas de DoorDash hasta que (y a menos que) Apple proporcione una solución integrada en Xcode. En resumen, si usted se enfrenta a retos similares en el desarrollo de su proyecto Xcode, vale la pena su tiempo para evaluar si XcodeGen puede ser su solución.

About the Authors

  • Md Al Mamun

    Mamun is a Software Engineer at DoorDash, since December 2021, working on the iOS DasherDriver team.

  • Michael Thole

    Michael is a software engineer at DoorDash, since late 2019, working on the iOS platform team.

Trabajos relacionados

Ubicación
Toronto, ON
Departamento
Ingeniería
Ubicación
New York, NY; San Francisco, CA; Sunnyvale, CA; Los Angeles, CA; Seattle, WA
Departamento
Ingeniería
Ubicación
San Francisco, CA; Sunnyvale, CA
Departamento
Ingeniería
Ubicación
Seattle, WA; San Francisco, CA; Sunnyvale, CA
Departamento
Ingeniería
Ubicación
Seattle, WA; San Francisco, CA; Sunnyvale, CA
Departamento
Ingeniería