2009-09-21 8 views
5

Pronto empiezo a mantener una línea de productos que contienen variantes del mismo software integrado. Como he estado jugando con git por un año y lo aprecio mucho, es probable que lo use para el control de la fuente.Llevando un registro de las variantes del código fuente

Hay varias opciones que puedo ver para mantener las variantes del firmware, pero ninguna me agrada demasiado. ¿Cuáles son las mejores prácticas que aplica para su propio trabajo?

alternativas que se me ocurren:

  • define. Preprocesamiento. Ventajas: todo está siempre presente en el código fuente, es más difícil perderse la actualización de uno de los productos. Contras: más difícil de leer. Podría estar bien si solo tenemos dos variantes, cuando se convierta en cuatro o más será un dolor. Además, parece más difícil aplicar el principio DRY (No repetir).

  • una rama por variante de producto. Cuando se incluyen cambios que se aplican a todos los productos, el cambio debe fusionarse con los demás productos. Contras: si una confirmación contiene tanto cambios para todos los productos como cambios para la variante específica, habrá problemas. Por supuesto, puede asegurarse de que una confirmación contenga solo un tipo de cambio: este producto cambia o toda la familia cambia. ¿Pero intenta forzar eso en un equipo? Además, la fusión no funcionaría; en su lugar, deberíamos ser selectivos. ¿Derecha?

  • a core repository como un submódulo. Haga que todos los archivos que contienen la funcionalidad principal sean un repositorio en sí mismo. Todos los productos contienen una versión del repositorio central como un submódulo. Contras: No puedo ver que finalmente no habrá variantes del submódulo central. Entonces estamos en problemas otra vez, y luego usaríamos define o algo malo otra vez. ¿Un repositorio central con ramas? Luego volvemos a la alternativa anterior: un cambio que se aplica a todas las sucursales debe fusionarse, pero la fusión también incluye las cosas específicas del producto.

  • crear un repositorio por módulo. Por ejemplo, un repositorio para un controlador de pantalla, otro para el hardware de administración de energía, y otro para la interfaz de entrada del usuario, ... Pros: buena modularidad. ¡Crea un nuevo producto simplemente seleccionando los módulos que necesitas como submódulos! Todos los submódulos pueden tener ramas, si por ejemplo una variante usa el hardware de una manera diferente. Contras: Montones y montones de módulos, cada uno haciendo un seguimiento de un par de archivos (un archivo de inclusión y un archivo fuente). Una molestia. ¿Alguien hace una actualización importante en algún módulo? Luego, alguien debe incluir el cambio en las otras ramas de este módulo, si corresponde. Entonces alguien también debe actualizar el submódulo en cada repositorio de productos. Mucho trabajo, y nos gusta perder el lado de la instantánea de git.

¿Cómo lo hace, y cómo ha estado funcionando? ¿O cómo lo harías?

Tengo la sensación de que debería tener experiencia con la cosecha de cerezas.

+0

Con una solución de rama por producto, siempre puede realizar un desarrollo en una rama (s) swperate, y luego fusionar esta rama en ramas de variantes (por productos). –

Respuesta

5

Intentaré ir por #define s tanto como sea posible. Con un código adecuado, puede minimizar el impacto en la legibilidad y las repeticiones.

Pero al mismo tiempo, el enfoque #define se puede combinar con seguridad con división y bifurcación, cuya aplicación depende de la naturaleza de la base de código.

+0

Las definiciones son la peor solución posible. Hacen que el código fuente que ve en el editor sea diferente del que ve el compilador. – xpmatteo

+2

Y 'si's hacen que el código fuente que ve en el editor sea diferente al que se ejecuta. Todo eso se llama programación. –

+0

El objetivo de los lenguajes de alto nivel es facilitar la programación para los humanos. Tener que interpretar en su mente los IF en el momento de la compilación y también los IF en el tiempo de ejecución hace que sea mucho más difícil y propenso a errores entender lo que hace el programa. – xpmatteo

5

No estoy seguro de si esa es la "mejor práctica", pero el proyecto Scintilla usa algo durante años que todavía es bastante manejable. Tiene una sola rama común a todas las plataformas (principalmente Windows/GTK +/Mac, pero con variantes para VMS, Fox, etc.).

De alguna manera, utiliza la primera opción, que es bastante común: usa define para administrar pequeñas porciones específicas de la plataforma dentro de las fuentes, donde no es práctico o imposible poner código común.
Tenga en cuenta que esta opción no es posible con algunos idiomas (por ejemplo, Java).

Pero el mecanismo principal para la portabilidad es usar OO: abstrae algunas operaciones (dibujo, muestra el menú contextual, etc.) y usa un archivo de plataforma (o varias) por objetivo, proporcionando la implementación concreta.
El archivo MAKE compila solo los archivos adecuados y usa los enlaces para obtener el código correcto.

+0

OO sería bueno, pero este es un código incrustado en ensamblador, sin siquiera un compilador de C disponible. Entiendo que los conceptos de OO se pueden aplicar de todos modos. Ensamblar y vincular solo los archivos necesarios es una opción, pero creo que dos de esos archivos (uno por variante) serían muy similares, repitiéndose uno al otro. – Gauthier

+0

Tener archivos 'compat', ya sea uno o uno por plataforma única, creo que es una buena idea. –

+0

La solución alternativa para Java es usar un complemento adicional como Antenna for Eclipse. No es perfecto, pero bastante útil. – David

2

Creo que la respuesta adecuada depende en parte de cuán radicalmente diferentes son las variantes.

Si hay porciones pequeñas que son diferentes, la compilación condicional en un solo archivo de origen es razonable. Si las implementaciones variantes solo son consistentes en la interfaz de llamada, entonces puede ser mejor usar archivos separados. Puede incluir implementaciones radicalmente variantes en un solo archivo con compilación condicional; lo complicado que es depende del volumen del código de variante. Si se trata, por ejemplo, de cuatro variantes de aproximadamente 100 líneas cada una, tal vez un archivo esté bien. Si se trata de cuatro variantes de 100, 300, 500 y 900 líneas, entonces un archivo probablemente sea una mala idea.

No necesita necesariamente las variantes en ramas separadas; de hecho, solo deberías usar ramas cuando sea necesario (¡pero úsalas cuando sea necesario!). Puede tener los cuatro archivos, por ejemplo, todos en una rama común, siempre visibles. Puede hacer arreglos para que la compilación recoja la variante correcta. Una posibilidad (hay muchos otros) está recopilando un archivo de fuente única que sabe qué variante de origen para incluir dado el entorno de compilación actual:

#include "config.h" 
#if defined(USE_VARIANT_A) 
#include "variant_a.c" 
#elif defined(USE_VARIANT_B) 
#include "variant_b.c" 
#else 
#include "basecase.c" 
#endif 
+0

Prefiero hacer esto en el archivo MAKE, no me gusta incluir c archivos con #include: no aparecerían en mi archivo MAKE. Acepto que no hay una solución mágica, la solución depende de la varianza de las variantes :) Lo que me temo es comenzar con #defines y ver que la familia de productos crece a 10 variantes. – Gauthier

+0

Si está generando archivos makef (en lugar de utilizar un archivo MAKE controlado por versión), entonces vaya con el enfoque directo de listar los archivos correctos en el archivo MAKE. Este enfoque funciona bien (no necesariamente recomendado, pero funciona) cuando el archivo MAKE no se genera dinámicamente. Mi mensaje fundamental es "un tamaño no sirve para todos" y diferentes soluciones son relevantes según los detalles. Si crees que puedes tener 10 elementos variantes, diseña tu sistema para que funcione con tantas variantes como esa. No dice si se trata de 10 decisiones binarias, o 1 de 10 alternativas, o alguna otra cosa. –

+0

Hasta ahora me refería a 1 de 10 alternativas. Gracias a Dios :) No entiendo por qué tener makefiles controlados por la versión (uno por variante) invalidaría este método? – Gauthier

6

usted debe esforzarse, tanto como sea posible, mantenga el código personalizado de cada variante en su propio conjunto de archivos. Luego, su sistema de compilación (Makefile o lo que sea) selecciona qué fuentes usar en función de la variante que está creando.

La ventaja de esto es que cuando trabajas en una variante particular, puedes ver todo su código junto, sin el código de otras variantes para confundir las cosas. La legibilidad es también mucho mejor que ensuciar la fuente con #ifdef, #elif, #endif, etc.

Ramas funcionan mejor cuando se sabe que en el futuro se tendrá que fusionar todas del código de la rama en el la rama principal (u otras ramas). No funciona tan bien solo para fusionar algunos cambios de una rama a otra (aunque ciertamente se puede hacer). Por lo tanto, mantener las ramas separadas para cada variante probablemente no arroje buenos resultados.

Si utiliza el enfoque mencionado anteriormente, no necesita intentar utilizar dichos trucos en el control de su versión para admitir la organización de su código.

+0

+1 para obtener consejos sobre la bifurcación solo si planea fusionar nuevamente (aunque hay excepciones). También por el riesgo de usar trucos de VCS para respaldar el código. Vea mi comentario a PhiLho sobre DRY con tales archivos de códigos personalizados. – Gauthier

+0

Cuando estoy trabajando en una variante en particular, a menudo quiero ver el código de las otras variantes. Este es un buen uso para el plegado de código. –

1

¡Te espera un mundo de dolor!

Hagas lo que hagas, necesitas un entorno de construcción automático. Por lo menos, necesita una forma automática de compilar todas las versiones diferentes de su firmware. Tuve problemas para corregir un error en una versión y romper la compilación de una versión diferente.

Idealmente, usted podría cargar los diferentes objetivos y ejecutar algunas pruebas de humo.

Si vas a la ruta # define, yo pondría el siguiente sitio donde se comprueba la variante:

#else 
    #error You MUST specify a variant! 
#endif 

Esto se asegurará de que todos los archivos se construyen para la misma variante durante el proceso de construcción.

+0

Claro, el #error. E incluso, si se definen varias definiciones de variantes, indefinidas todas menos una (o envíe otro #error). – Gauthier

0

Estoy enfrentando el mismo problema. Quiero compartir la fuente no solo entre los objetivos, sino a través de las plataformas (por ejemplo, Mono vs. Visual Studio. Parece que no hay ninguna manera de hacerlo fácilmente con solo la versión que proporciona la ramificación/etiquetado de Git. Porque lo ideal es que Me gustaría mantener una rama de código común, y alojar/especificar ramas específicas y simplemente fusionar el código común dentro y fuera de esas ramas. Pero no sé cómo hacer eso. ¿Tal vez una combinación de ramificación y etiquetado? ?

Obviamente, usaría la compilación de condiciones si fuera necesario donde pudiera, de modo que solo hubiera un archivo fuente bajo control de versión, pero para los demás archivos que solo deberían aparecer en una rama de host/destino, o donde los contenidos serían completamente diferentes, se necesita un mecanismo diferente. Si puede usar las técnicas OO para dar un paso al costado, el problema también sería deseable. mi.

Así que la respuesta parece ser que necesita algún tipo de mecanismo de configuración/gestión de compilación en la parte superior de git para gestionar esto. Pero eso parece agregar mucha complejidad, por lo que tal vez elegir cereza en una sucursal común no sería un mal camino a seguir. Especialmente si usa otras técnicas para mantener las variantes de fuente al mínimo. (¿El etiquetado podría ayudar a automatizar esto?).

Cuestiones relacionadas