Manejo de todas las combinaciones de símbolos en el archivo makefile.
Supongamos que he diseñado una biblioteca en C++ y quiero probar exhaustivamente todas las características.
Algunas de estas características están definidas en tiempo de compilación, a través de símbolos que están definidos o no.
// library.h
A foo( const B& b )
{
#ifdef OPTION_X
... hazlo de esa manera
#else
... hazlo de otra manera
#endif
}
Construyo un programa de prueba que quiero compilar y ejecutar para todas las configuraciones posibles, para asegurarme de que todas las pruebas pasen:
// mytest.cpp
#include "library.h"
int main()
{
... some test code
#ifdef OPTION_X
... hazlo de esa manera
#else
... hazlo de otra manera
#endif
... más cosas con más opciones
}
Si tengo 1 opción (llamémosla “A”), quiero ejecutar las pruebas si está “activada” (_AY
para la opción “A”: Sí) o “desactivada” (_AN
para la opción “A”: No)
Mi archivo makefile contiene esto:
.PHONY: test
test: BUILD/mytest_AY BUILD/mytest_AN
BUILD/mytest_AY
BUILD/mytest_AN
BUILD/mytest_AY: CXXFLAGS+=-DOPTION_A
BUILD/mytest_AY: mytest.cpp
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_AN: mytest.cpp
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)
Esto está bien.
Pero ahora, si tengo 2 opciones para probar (digamos “A” y “B”), se entiende el punto:
Tendré 4 objetivos para compilar y ejecutar:
test: BUILD/mytest_AYBY BUILD/mytest_ANBY BUILD/mytest_AYBN BUILD/mytest_ANBN
BUILD/mytest_AYBY
BUILD/mytest_ANBY
BUILD/mytest_AYBN
BUILD/mytest_ANBN
BUILD/mytest_AYBN: CXXFLAGS+=-DOPTION_A
BUILD/mytest_AYBY: CXXFLAGS+="-DOPTION_A -DOPTION_B"
BUILD/mytest_ANBY: CXXFLAGS+=-DOPTION_B
BUILD/mytest_AYBY: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_ANBY: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_AYBN: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
BUILD/mytest_ANBN: mytest.cpp
$(CXX) $(CFLAGS) -o $@ $< $(LDFLAGS)
Tengo dos preguntas:
-
¿Existe alguna manera de tener una única regla/receta en lugar de las cuatro? Son todas iguales, excepto por el nombre del objetivo.
-
Este enfoque, si bien es válido para 1 o 2 opciones en tiempo de compilación, no se escala bien.
Con 3 opciones ya sería bastante engorroso, y con más se convierte en una pesadilla.
¿Cómo manejarías esa situación?
davy.ai
Para tener una única regla/receta para todos los objetivos, el archivo Make puede utilizar una variable para mantener la lista de todas las configuraciones posibles y utilizar una regla de patrón para construir cada objetivo. Aquí tienes un ejemplo:
En esta solución, el archivo Make utiliza un bucle
foreach
para construir y ejecutar todos los programas de prueba configurados. La variableOPTIONS
contiene una lista de configuraciones posibles, y la funciónpatsubst
se expande a la lista de todos los objetivos posibles. La regla de patrónBUILD/mytest_%_Y
yBUILD/mytest_%_N
define la receta para cada objetivo. El símbolo%
en la regla es un comodín que coincide con cualquier cadena.Para un enfoque escalable, el archivo Make puede utilizar un archivo de configuración que contenga todas las posibles combinaciones de opciones de tiempo de compilación. Aquí tienes un ejemplo:
En esta solución, el archivo Make utiliza una declaración
define
para crear una variableCONFIGS
que contiene todas las posibles combinaciones de opciones de tiempo de compilación. La variable se limpia con la funciónstrip
para eliminar espacios adicionales. La funciónpatsubst
se expande a una lista de todos los objetivos posibles. La regla de patrónBUILD/mytest_%
define la receta para cada objetivo. La funcióneval
evalúa la cadena y asigna el resultado a la variableoptstr
, que contiene la lista de opciones para pasar al compilador. La funciónsubst
reemplaza la marca Y/N con la opción correspondiente, y la funciónnotdir
elimina la parte del directorio del nombre del objetivo.