¿Cuál es una buena manera de organizar proyectos con dependencias compartidas en Mercurial?
project-management subrepos (3)
Actualmente, me estoy moviendo de un sistema de control de versiones heredado y moviendo el proyecto de mi grupo a mercurial. Como un ejemplo de los tipos de código que estoy moviendo, tengo una solución de más de 25 proyectos de Visual Studio, que contiene varias áreas de aplicación separadas que todas dependen del código común. Mirando por encima de Stack Overflow, la pregunta más cercana que encontré fue esta , pero meramente mencionó el control de la versión. Estoy buscando un poco más de asesoramiento sobre técnicas específicas de implementación de Mercurial para administrar estas dependencias.
Una vista simplificada de las dependencias se parece a lo siguiente. (Esto es solo para ilustración y ejemplo, las dependencias reales son significativamente más complejas pero similares en naturaleza).
Common Lib 1
/ | /
---- | -----
/ | / /
App 1 Common Lib 2 / App 2
/ | / /
------- | ------ |
/ | /|
App 3 App 4 App 5
Los módulos de Common Lib serían códigos compartidos, esto sería una DLL o SO o alguna otra biblioteca que se usaría entre todas las aplicaciones simultáneamente, tanto en la compilación como en el tiempo de ejecución. De lo contrario, las aplicaciones podrían ejecutarse independientemente una de la otra.
Tengo un par de objetivos al configurar mis repositorios mercuriales:
- Dé a cada aplicación o grupo de componentes significativo su propio repositorio.
- Haga que cada repositorio sea independiente.
- Haga que la suma total del proyecto sea independiente.
- Facilite la compilación de la base de código completa a la vez. (eventualmente, todos estos programas y bibliotecas terminan en un único instalador).
- Mantenlo simple.
Otro punto es que tengo un servidor configurado donde tengo repositorios separados para cada uno de estos proyectos.
Veo un par de maneras de presentar estos proyectos.
1. Crea un repositorio "Shell" que contenga todo.
Esto usaría subrepos basados en url (por ejemplo, en el .hgsub, haría algo como App1 = https://my.server/repo/app1
.) Establecido, se vería como el siguiente:
+---------------------------+
| Main Repository |
| | +---------------------+ |
| +-| Build | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 1 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| Common Lib 2 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 1 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 2 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 3 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 4 | |
| | +---------------------+ |
| | +---------------------+ |
| +-| App 5 | |
| +---------------------+ |
+---------------------------+
Cada carpeta principal en el repositorio de shell contendría un subrepo, uno para cada área del proyecto. Las dependencias serían relativas: por ejemplo, dado que la aplicación 4 necesita Common Lib 2, simplemente usaría rutas relativas para hacer referencia a esa biblioteca común.
Pros de este enfoque:
- Cada biblioteca se baja una vez y solo una vez.
- Los subreos de Mercurial se asegurarán de que se use la misma versión de la biblioteca en todos los proyectos de manera automática, ya que solo existe una versión del subrepo en el proyecto.
- Es fácil encontrar cada recurso.
Contras de este enfoque:
- No puedo trabajar en una aplicación de forma independiente. Por ejemplo, si trabajo en la aplicación 2 y necesita un cambio en las bibliotecas comunes, todas las demás aplicaciones tendrán que realizar esos cambios ahora mismo.
- Si selecciono un repositorio de aplicaciones por sí mismo, tengo que averiguar (o saber) qué otros repositorios dependientes se requieren a mano si deseo compilarlo.
- Las dependencias no están muy separadas; sería tentador insertar una nueva función en cualquier lugar, ya que era fácil obtener todas las características.
2. Tener subrepos dependientes totalmente contenidos.
En este enfoque, cada aplicación tendría su propio repositorio (como antes) pero esta vez también contiene subrepositorios: uno para su propia fuente y otro para cada subrepositorio dependiente. Un repositorio general contendría cada uno de estos repositorios de proyectos y sabría cómo construir la solución completa. Esto se vería así:
+-----------------------------------------------------------------------+
| Main Repository |
| +--------------------+ +--------------------+ +--------------------+ |
| | Build | | Common Lib 1 | | Common Lib 2 | |
| +--------------------+ | | +--------------+ | | | +--------------+ | |
| | +-| Lib 1 Source | | | +-| Common Lib 1 | | |
| | +--------------+ | | | +--------------+ | |
| | | | | +--------------+ | |
| | | | +-| Lib 2 Source | | |
| | | | +--------------+ | |
| +--------------------+ +--------------------+ |
| +--------------------+ +--------------------+ +---------------------+ |
| | App 1 | | App 2 | | App 3 | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | +-| Common Lib 1 | | | +-| Common Lib 1 | | | +-| Common Lib 2 | | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | | +--------------+ | | | +--------------+ | | | +--------------+ | |
| | +-| App 1 Source | | | +-| App 2 Source | | | +-| App 3 Source | | |
| | +--------------+ | | +--------------+ | | +--------------+ | |
| +--------------------+ +--------------------+ +---------------------+ |
| +--------------------+ +--------------------+ |
| | App 4 | | App 5 | |
| | | +--------------+ | | | +--------------+ | |
| | +-| Common Lib 2 | | | +-| Common Lib 1 | | |
| | | +--------------+ | | | +--------------+ | |
| | | +--------------+ | | | +--------------+ | |
| | +-| App 4 Source | | | +-| Common Lib 2 | | |
| | +--------------+ | | | +--------------+ | |
| +--------------------+ + | +--------------+ | |
| | +-| App 5 Source | | |
| | +--------------+ | |
| +--------------------+ |
+-----------------------------------------------------------------------+
Pros:
- Cada aplicación se puede construir por sí misma, independientemente la una de la otra.
- Las versiones dependientes de las bibliotecas se pueden rastrear por aplicación, en lugar de a nivel mundial. Se necesita un acto explícito de insertar un subrepo en el proyecto para agregar una nueva dependencia.
Contras:
- Al hacer la compilación final, cada aplicación podría estar usando una versión diferente de una biblioteca compartida. (Puede necesitar escribir herramientas para sincronizar los subrepos comunes lib. Eww.)
- Si quiero construir la fuente completa, termino tirando de las bibliotecas compartidas varias veces. En el caso de Common Lib 1, tendría que sacarlo ocho (!) Veces.
3. No incluya dependencias en absoluto como subrepos: tráigalos como parte de la compilación.
Este enfoque se parecería mucho al enfoque 1, excepto que las bibliotecas comunes solo se extraerían como parte de la compilación. Cada aplicación sabría qué repos necesarios, y los pondría en la ubicación común.
Pros:
- Cada aplicación podría compilarse por sí misma.
- Las bibliotecas comunes solo tendrían que ser extraídas una vez.
Contras:
- Tendríamos que hacer un seguimiento de las versiones de las bibliotecas utilizadas actualmente por cada aplicación. Esto duplica las funciones de subrepo.
- Tendríamos que construir una infraestructura para respaldar esto, lo que significa más cosas en los scripts de compilación. Ugh.
4. ¿Qué más?
¿Hay otra forma de manejarlo? ¿Una mejor manera? ¿De qué maneras lo has intentado y has tenido éxito? ¿De qué formas lo has intentado pero has odiado? Actualmente estoy inclinado hacia 1, pero la falta de independencia de la aplicación, cuando debería ser capaz, realmente me molesta. ¿Hay alguna manera de obtener la buena separación del método 2 sin la enorme duplicación de código y la pesadilla de mantenimiento de la dependencia, sin tener que escribir scripts para manejarlo (como en la opción 3)?
La administración de las dependencias es un aspecto importante de la organización de un proyecto, a mis ojos. Expusiste en gran detalle varias soluciones, basadas en la función de subrepos de Mercurial, y estoy de acuerdo con todos los pros / contras que me diste.
Creo que los SCM no son muy adecuados para la gestión de dependencias. Prefiero tener una herramienta dedicada para eso (esta sería tu solución n ° 3).
Mi proyecto actual está en Java. Fue construido con Apache Ant , y primero configuré Apache Ivy como una herramienta de administración de dependencias. Al final, la configuración consistió en algunos archivos de configuración de Ivy en un directorio compartido, y un archivo XML que enumeraba las dependencias para cada módulo del proyecto. Los objetivos Ant pueden invocar Ivy, por lo que agregué dos nuevas acciones en cada módulo: "resolver dependencias" y "desplegar el artefacto construido". La implementación agrega el resultado del buid (llamado artefacto) en el directorio compartido. La resolución de dependencias significa resolver transitoriamente las dependencias del módulo y copiar los artefactos resueltos en la carpeta "lib" de las fuentes del módulo.
Esta solución es aplicable a un proyecto de C ++, ya que Ivy no es específico para administrar dependencias de Java: los artefactos pueden ser cualquier cosa. En C ++, los artefactos producidos por un módulo serían:
- un so / dll en tiempo de ejecución
- los archivos de cabecera en tiempo de compilación.
Esta no es una solución perfecta: Ivy no es fácil de configurar, usted todavía tiene que decirle a su script de compilación qué dependencias usar, y no tiene acceso directo a las fuentes de las dependencias para fines de depuración. Pero terminas con repositorios SCM independientes.
En nuestro proyecto, cambiamos de Ant + Ivy a Apache Maven , que se ocupa tanto de la compilación como de la administración de las dependencias. Los artefactos se implementan en un Archiva de Apache en lugar de una carpeta compartida. Esta es una gran mejora, pero funcionará solo para proyectos Java.
Lo que quiere hacer es tener cada proyecto en su propio directorio como en (1). A continuación, etiquete las versiones operativas de sus dependencias y guarde la etiqueta en algún archivo para compilación como:
App1/.dependencies: CommonLib1 tag-20100515 CommonLib2 tag-20100510 App2/.dependencies: CommonLib1 tag-20100510 CommonLib2 tag-20100510
Luego, utiliza sus scripts de compilación para compilar las bibliotecas basadas en la etiqueta específica e incluye esas bibliotecas creadas como objetos derivados para sus aplicaciones. Si el tiempo de compilación es un problema, puede tener la versión etiquetada que está en uso para esas bibliotecas preconstruidas y guardadas en alguna parte.
Nota (los principios de diseño son los mismos si se diseña el esquema de la base de datos, el modelo de objeto o la creación del producto):
- No enlazar con el código en otros proyectos (encapsulación de rupturas)
- No tiene múltiples copias de las bibliotecas en su repositorio (modularidad)
Solucionamos un problema similar usando subversion.
Cada aplicación y cada Common Lib
tienen sus propios repositorios.
Cada aplicación tiene un directorio Libs
que contiene los dlls dependientes.
por lo que la aplicación solo obtiene una actualización de Common Lib
si se proporciona un nuevo conjunto de dlls.
sin embargo, la actualización de la carpeta lib no es trivial porque los sub-dlls dependientes deben coincidir con la versión correcta.