2010-11-01 6 views
13

He estado trabajando en el diseño de un archivo Makefile de configuración múltiple (uno que admite objetivos separados 'depurar' y 'liberar'), y he encontrado un extraño problema que parece ser un error en la creación de GNU.Error en la creación de GNU: ¿las variables específicas del destino no se expanden en las reglas implícitas?

Parece que la creación de GNU no está expandiendo las variables específicas de destino correctamente cuando se hace referencia a esas variables en una regla implícita. Aquí es un Makefile simplificado que muestra este problema:

all: 
    @echo specify configuration 'debug' or 'release' 

OBJS := foo.o bar.o 

BUILDDIR = .build/$(CONFIG) 

TARGET = $(addprefix $(BUILDDIR)/,$(OBJS)) 

debug: CONFIG := debug 
release: CONFIG := release 

#CONFIG := debug 

debug: $(TARGET) 
release: $(TARGET) 

clean: 
    rm -rf .build 

$(BUILDDIR)/%.o: %.c 
    @echo [$(BUILDDIR)/$*.o] should be [[email protected]] 
    @mkdir -p $(dir [email protected]) 
    $(CC) -c $< -o [email protected] 

Al especificar el objetivo 'depuración' para hacer, CONFIG se establece en 'depuración', y builddir y de destino se amplió asimismo correctamente. Sin embargo, en la regla implícita para compilar el archivo fuente desde el objeto, $ @ se expande como si CONFIG no existiera.

Aquí está la salida del uso de este Makefile:

$ make debug 
[.build/debug/foo.o] should be [.build//foo.o] 
cc -c foo.c -o .build//foo.o 
[.build/debug/bar.o] should be [.build//bar.o] 
cc -c bar.c -o .build//bar.o 

Esto demuestra que se está ampliando builddir bien, pero la resultante $ @ no lo es. Pues si yo comente el objetivo especificación de variable y ajustar manualmente CONFIG: = depuración (la línea comentada más arriba), me sale lo que esperaría:

$ make debug 
[.build/debug/foo.o] should be [.build/debug/foo.o] 
cc -c foo.c -o .build/debug/foo.o 
[.build/debug/bar.o] should be [.build/debug/bar.o] 
cc -c bar.c -o .build/debug/bar.o 

He probado esto con tanto maquillaje 3.81 por Gentoo y MinGW y make-3.82 en Gentoo. Todos exhiben el mismo comportamiento.

Me resulta difícil creer que sería el primero en encontrarme con este problema, así que supongo que probablemente estoy haciendo algo mal, pero voy a ser sincero: no veo cómo Yo podría ser. :)

¿Hay algún gurú de los fabricantes que pueda arrojar algo de luz sobre este tema? ¡Gracias!

+0

posible duplicado de [Variables Target-específicas como requisitos previos en un Makefile] (http://stackoverflow.com/questions/1340060/target-specific-variables-as-prerequisites-in-a-makefile) –

Respuesta

7

Como Beta ha señalado, este hecho no es un error en el maquillaje ya que la limitación se describe en la documentación (supongo que debe haber perdido esa parte en particular -- lo siento).

En cualquier caso, pude solucionar este problema haciendo algo aún más simple.Como todo lo que necesito es asignar una variable basada en el objetivo, descubrí que puedo usar la variable $(MAKECMDGOALS) para expandir el directorio de compilación correctamente. La eliminación de la variable $ (CONFIG) y volver a escribir el Makefile de la siguiente hace exactamente lo que necesito:

all: 
     @echo specify configuration 'debug' or 'release' 

OBJS := foo.o bar.o 

BUILDDIR := .build/$(MAKECMDGOALS) 

TARGET := $(addprefix $(BUILDDIR)/,$(OBJS)) 

debug: $(TARGET) 
release: $(TARGET) 

clean: 
     rm -rf .build 

$(BUILDDIR)/%.o: %.c 
     @echo [$(BUILDDIR)/$*.o] should be [[email protected]] 
     @mkdir -p $(dir [email protected]) 
     $(CC) -c $< -o [email protected] 

Esto entonces da el resultado correcto:

$ make debug 
[.build/debug/foo.o] should be [.build/debug/foo.o] 
cc -c foo.c -o .build/debug/foo.o 
[.build/debug/bar.o] should be [.build/debug/bar.o] 
cc -c bar.c -o .build/debug/bar.o 
$ make release 
[.build/release/foo.o] should be [.build/release/foo.o] 
cc -c foo.c -o .build/release/foo.o 
[.build/release/bar.o] should be [.build/release/bar.o] 
cc -c bar.c -o .build/release/bar.o 
$ make debug 
make: Nothing to be done for `debug'. 
$ make release 
make: Nothing to be done for `release'. 

Esta voluntad de ruptura por supuesto, si hay múltiples metas especificadas en la línea de comando (ya que $ (MAKECMDGOALS) contiene una lista separada por espacios), pero lidiar con eso no es un gran problema.

+0

Gracias. Esta respuesta es un [salvavidas] (http://stackoverflow.com/a/21057818/376535). –

+0

¡Mi primera recompensa de 50 fue en respuesta incorrecta! Luego di 100 en esto. –

9

Básicamente, hacer que funcione el DAG de dependencias y crea una lista de reglas que deben ejecutarse antes de ejecutar cualquier regla. Asignar un valor específico del objetivo es algo que hace Make cuando se ejecuta una regla, que viene después. Esta es una limitación seria (que yo y otros nos hemos quejado antes), pero no lo llamaría un error ya que está descrito en la documentación. Según el manual de GNUMake:

6.11 Target-specific Variable Values: "Al igual que con las variables automáticas, estos valores solo están disponibles dentro del contexto de la receta de un objetivo (y en otras asignaciones específicas del objetivo)."

Y "el contexto de la receta de un objetivo" se refiere a los comandos, no los PreReqs:

10.5.3 Automatic variables: "[variables automáticas] no se puede acceder directamente desde la lista de pre-requisito de una regla."

Hay un par de formas de evitar esto. Puede usar Secondary Expansion, si su versión de Make GNUMake lo tiene (3.81 no, no sé de 3.82). O se puede hacer sin variables específicas de la diana:

DEBUG_OBJS = $(addprefix $(BUILDDIR)/debug/,$(OBJS)) 
RELEASE_OBJS = $(addprefix $(BUILDDIR)/release/,$(OBJS)) 

debug: % : $(DEBUG_OBJS) 
release: $(RELEASE_OBJS) 

$(DEBUG_OBJS): $(BUILDDIR)/debug/%.o : %.cc 
$(RELEASE_OBJS): $(BUILDDIR)/release/%.o : %.cc 

$(DEBUG_OBJS) $(RELEASE_OBJS): 
    @echo making [email protected] from $^ 
    @mkdir -p $(dir [email protected])               
    $(CC) -c $< -o [email protected] 
+0

Antes que nada, ¡gracias por la respuesta! Si nuestro sistema de compilación solo tuviera dos objetivos de configuración (depuración y publicación), podría haber seguido su sugerencia, pero en realidad tenemos seis objetivos de configuración diferentes: duplicar todo eso en el archivo MAKE sería bastante desordenado, pero definitivamente no imposible. En cuanto a la expansión secundaria, parece ser compatible desde make-3.81, pero cuando traté de usarlo, make simplemente colgaba, aparentemente atrapado en un ciclo infinito. Dejé de intentar averiguar qué pasaba tan pronto como descubrí la variable $ (MAKECMDGOALS). ¡Gracias de nuevo! – Falken

+0

Información muy útil: no merece permanecer sin votos positivos. ¿Le importaría explicar brevemente cómo la expansión secundaria podría resolver esto? –

+0

Esto es algo duplicativo, pero los bits duplicados podrían resumirse en una función Make que se llama una vez por configuración deseada. – Novelocrat

2

Aquí se explica cómo resolver el problema sin MAKECMDGOALS introspección. El problema es básicamente que las reglas que especifiques en Makefile constituyen un gráfico estático. Las asignaciones específicas de destino se utilizan durante la ejecución de cuerpos de regla, pero no durante su compilación .

La solución a esto es tomar el control sobre la compilación de reglas: use las construcciones tipo macro de GNU Make para generar las reglas. Entonces tenemos un control total: podemos incluir material variable en el objetivo, requisito previo o receta.

aquí está mi versión de su Makefile

all: 
     @echo specify configuration 'debug' or 'release' 

OBJS := foo.o bar.o 

# BUILDDIR is a macro 
# $(call BUILDDIR,WORD) -> .build/WORD 
BUILDDIR = .build/$(1) 

# target is a macro 
# $(call TARGET,WORD) -> ./build/WORD/foo.o ./build/WORD/bar.o 
TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS)) 

# BUILDRULE is a macro: it builds a release or debug rule 
# or whatever word we pass as argument $(1) 
define BUILDRULE 
$(call BUILDDIR,$(1))/%.o: %.c 
     @echo [$(call BUILDDIR,$(1))/$$*.o] should be [[email protected]] 
     @mkdir -p $$(dir [email protected]) 
     $$(CC) -c -DMODE=$(1) $$< -o [email protected] 
endef 

debug: $(call TARGET,debug) 
release: $(call TARGET,release) 

# generate two build rules from macro 
$(eval $(call BUILDRULE,debug)) 
$(eval $(call BUILDRULE,release)) 

clean: 
     rm -rf .build 

Ahora, observe la ventaja: puedo construir ambos debug y release objetivos de una sola vez, porque he instancia ambas reglas de la plantilla!

$ make clean ; make debug release 
rm -rf .build 
[.build/debug/foo.o] should be [.build/debug/foo.o] 
cc -c -DMODE=debug foo.c -o .build/debug/foo.o 
[.build/debug/bar.o] should be [.build/debug/bar.o] 
cc -c -DMODE=debug bar.c -o .build/debug/bar.o 
[.build/release/foo.o] should be [.build/release/foo.o] 
cc -c -DMODE=release foo.c -o .build/release/foo.o 
[.build/release/bar.o] should be [.build/release/bar.o] 
cc -c -DMODE=release bar.c -o .build/release/bar.o 

Además, he tomado la libertad de añadir el argumento de macro en la línea de comandos cc también, de modo que los módulos reciben un MODE macro que les dice cómo están siendo compilados.

Podemos utilizar la indirección variable para configurar diferentes CFLAGS o lo que sea. Mira lo que sucede si parchear el anterior así:

--- a/Makefile 
+++ b/Makefile 
@@ -3,6 +3,9 @@ 

OBJS := foo.o bar.o 

+CFLAGS_debug = -O0 -g 
+CFLAGS_release = -O2 
+ 
# BUILDDIR is a macro 
# $(call BUILDDIR,WORD) -> .build/WORD 
BUILDDIR = .build/$(1) 
@@ -17,7 +20,7 @@ define BUILDRULE 
$(call BUILDDIR,$(1))/%.o: %.c 
     @echo [$(call BUILDDIR,$(1))/$$*.o] should be [[email protected]] 
     @mkdir -p $$(dir [email protected]) 
-  $$(CC) -c -DMODE=$(1) $$< -o [email protected] 
+  $$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o [email protected] 
endef 

debug: $(call TARGET,debug) 

Run:

$ make clean ; make debug release 
rm -rf .build 
[.build/debug/foo.o] should be [.build/debug/foo.o] 
cc -c -O0 -g -DMODE=debug foo.c -o .build/debug/foo.o 
[.build/debug/bar.o] should be [.build/debug/bar.o] 
cc -c -O0 -g -DMODE=debug bar.c -o .build/debug/bar.o 
[.build/release/foo.o] should be [.build/release/foo.o] 
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o 
[.build/release/bar.o] should be [.build/release/bar.o] 
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o 

Por último, podemos combinar eso con MAKECMDGOALS. Podemos inspeccionar MAKECMDGOALS y filtrar los modos de compilación que no están especificados allí. Si se llama al make release, no necesitamos las reglas debug para expandir. Parche:

--- a/Makefile 
+++ b/Makefile 
@@ -3,6 +3,11 @@ 

OBJS := foo.o bar.o 

+# List of build types, but only those mentioned on command line 
+BUILD_TYPES := $(filter $(MAKECMDGOALS),debug release) 
+ 
+$(warning "generating rules for BUILD_TYPES := $(BUILD_TYPES)") 
+ 
CFLAGS_debug = -O0 -g 
CFLAGS_release = -O2 

@@ -17,18 +22,15 @@ TARGET = $(addprefix $(call BUILDDIR,$(1))/,$(OBJS)) 
# BUILDRULE is a macro: it builds a release or debug rule 
# or whatever word we pass as argument $(1) 
define BUILDRULE 
+$(1): $(call TARGET,$(1)) 
$(call BUILDDIR,$(1))/%.o: %.c 
     @echo [$(call BUILDDIR,$(1))/$$*.o] should be [[email protected]] 
     @mkdir -p $$(dir [email protected]) 
     $$(CC) -c $$(CFLAGS_$(1)) -DMODE=$(1) $$< -o [email protected] 
endef 

-debug: $(call TARGET,debug) 
-release: $(call TARGET,release) 
- 
-# generate two build rules from macro 
-$(eval $(call BUILDRULE,debug)) 
-$(eval $(call BUILDRULE,release)) 
+$(foreach type,$(BUILD_TYPES),\ 
+ $(eval $(call BUILDRULE,$(type)))) 

clean: 
     rm -rf .build 

Tenga en cuenta que he simplificado las cosas haciendo rodar los objetivos y debug:release: en el BUILDRULE macro.

$ make clean ; make release 
Makefile:9: "generating rules for BUILD_TYPES := " 
rm -rf .build 
Makefile:9: "generating rules for BUILD_TYPES := release" 
[.build/release/foo.o] should be [.build/release/foo.o] 
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o 
[.build/release/bar.o] should be [.build/release/bar.o] 
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o 

$ make clean ; make release debug 
Makefile:9: "generating rules for BUILD_TYPES := " 
rm -rf .build 
Makefile:9: "generating rules for BUILD_TYPES := debug release" 
[.build/release/foo.o] should be [.build/release/foo.o] 
cc -c -O2 -DMODE=release foo.c -o .build/release/foo.o 
[.build/release/bar.o] should be [.build/release/bar.o] 
cc -c -O2 -DMODE=release bar.c -o .build/release/bar.o 
[.build/debug/foo.o] should be [.build/debug/foo.o] 
cc -c -O0 -g -DMODE=debug foo.c -o .build/debug/foo.o 
[.build/debug/bar.o] should be [.build/debug/bar.o] 
cc -c -O0 -g -DMODE=debug bar.c -o .build/debug/bar.o 
Cuestiones relacionadas