2010-11-01 20 views
13

我一直在設計一個多配置的Makefile(一個支持單獨的'debug'和'release'目標),並且遇到了一個奇怪的問題,它似乎是GNU make中的一個bug。GNU make中的錯誤:特定於目標的變量未在隱式規則中擴展?

GNU make似乎並沒有在隱式規則中引用這些變量時正確地擴展特定於目標的變量。下面是一個簡單的Makefile這說明此問題:

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] 

當指定目標「調試」之作,配置設置爲「調試」,並BUILDDIR和TARGET同樣適當擴大。但是,在從對象中構建源文件的隱式規則中,$ @被展開,就好像CONFIG不存在一樣。

下面是使用這個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 

這表明BUILDDIR正在擴大罰款,但由此產生的$ @並非如此。如果我再註釋掉目標變量規範和手動設置CONFIG:=調試(上面的註釋行),我得到了我所期望的:

$ 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 

我既讓-3.81的Gentoo和測試這MinGW和Gentoo上的make-3.82。所有表現出相同的行爲。

我覺得很難相信我會第一個遇到這個問題,所以我猜測我可能只是在做錯事 - 但我會說實話:我不明白我可以是。 :)

是否有任何使大師可以在這個問題上有所瞭解?謝謝!

+0

可能重複(http://stackoverflow.com/questions/1340060/target-specific-variables-as-prerequisites-in-a-makefile) – 2011-03-01 21:49:46

回答

7

在Beta已經指出的那樣,這確實是由於限制文檔中(我想我一定是錯過了描述是不是在化妝的錯誤特定部分 - 對不起)。

在任何情況下,我實際上都可以通過做更簡單的事情來解決這個問題。由於我只需要根據目標分配變量,因此我發現我可以使用$(MAKECMDGOALS)變量來正確地擴展構建目錄。省去了$(CONFIG)變量和重寫Makefile文件如下面的不正是我需要的:

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] 

這則給出正確的結果:

$ 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'. 

當然休息的這個意志如果有多個在命令行中指定的目標(因爲$(MAKECMDGOALS)包含一個空格分隔的列表),但是處理這個問題並不是太多問題。

+0

謝謝。這個答案是[救星](http://stackoverflow.com/a/21057818/376535)。 – 2014-01-11 02:30:56

+0

我的第一個50獎賞是錯誤的答案!然後我給了100。 – 2015-04-25 07:42:15

9

基本上,使運行任何規則之前,制定出依賴關係的DAG,並創建必須運行規則的列表。分配一個特定於目標的值是Make在運行規則時所做的事情,稍後會發生。這是一個嚴重的限制(我和其他人之前都抱怨過),但我不會把它稱爲一個錯誤,因爲它在文檔中有描述。根據GNUMake手冊:

6.11 Target-specific Variable Values:「與自動變量一樣,這些值只能在目標配方的上下文(以及其他目標特定的賦值)中使用。」

和「目標的配方的情況下」是指命令,而不是prereqs:

10.5.3 Automatic variables:「[自動變量]不能在規則的前提列表中直接訪問。」

有幾種解決方法。你可以使用Secondary Expansion,如果你的Make GNUMake版本有它(3.81沒有,我不知道大約3.82)。或者你也可以不用特定目標變量:

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

首先,感謝您的答案!如果我們的構建系統只有兩個配置目標(調試和發佈),我可能會提出你的建議,但我們實際上有六個不同的配置目標 - 在Makefile中重複所有這些都相當混亂,但絕對不是不可能的。至於Secondary Expansion,它自make-3以來似乎受到支持。81,但是當我嘗試使用它時,make只會掛起,看起來陷入了無限循環。當我發現$(MAKECMDGOALS)變量時,我放棄了試圖找出錯誤的地方。再次感謝! – Falken 2010-11-02 07:07:12

+0

非常有用的信息 - 不值得留下沒有任何增票...你會介意簡單地解釋如何二次擴張可以解決這個問題嗎? – 2012-07-18 02:30:15

+0

這些都有些重複,但是重複的位可以全部抽象成Make函數,每個期望的配置調用一次。 – Novelocrat 2014-01-12 19:07:57

2

下面是如何解決這個問題沒有MAKECMDGOALS內省。 Tha問題基本上是你在Makefile中指定的規則構成一個靜態圖。特定於目標的分配在規則主體的執行期間使用,但不在編譯期間使用。

解決方案是抓住規則編譯的控制:使用GNU Make的宏類結構來生成規則。然後我們完全控制:我們可以將可變材料粘貼到目標,前提條件或配方中。

這裏是我的版本的您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 

現在,請注意優勢:我可以一氣呵成既建立和debug目標release,因爲我已經從實例化模板這兩個規則!

$ 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 

而且,我已經採取自由的宏參數添加到cc命令行也,使模塊接收MODE宏告訴他們,他們是如何被編譯。

我們可以使用變量間接設置不同的CFLAGS或其他。看,如果我們打補丁上面這樣會發生什麼:

--- 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) 

運行:

$ 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 

最後,我們可以結合起來,與MAKECMDGOALS。我們可以檢查MAKECMDGOALS並過濾出那裏沒有指定的構建模式。如果調用make release,我們不需要擴展debug規則。修補程序:

--- 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 

注意,我通過滾動debug:release:性指標納入宏觀BUILDRULE簡化的東西。

$ 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 
[在一個生成文件靶特異性變量作爲先決條件]的
相關問題