2010-11-01 19 views
13

Tôi đã làm việc để thiết kế một cấu hình Makefile (một trong đó hỗ trợ riêng biệt 'gỡ rối' và 'phát hành' mục tiêu), và đã đi qua một vấn đề lạ mà dường như là một lỗi trong GNU thực hiện.Lỗi trong GNU thực hiện: các biến mục tiêu cụ thể không được mở rộng trong các quy tắc ngầm định?

Dường như GNU thực hiện không mở rộng các biến mục tiêu cụ thể một cách chính xác khi các biến đó được tham chiếu trong một quy tắc ngầm định. Đây là một Makefile đơn giản hóa trong đó cho thấy vấn đề này:

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] 

Khi xác định mục tiêu 'debug' để làm, CONFIG được thiết lập để 'gỡ rối', và BUILDDIR và mục tiêu là mở rộng tương tự như vậy đúng. Tuy nhiên, trong quy tắc ngầm định để xây dựng tệp nguồn từ đối tượng, $ @ được mở rộng như thể CONFIG không tồn tại.

Đây là kết quả từ việc sử dụng Makefile này:

$ 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 

Điều này cho thấy BUILDDIR đang được mở rộng tốt, nhưng kết quả là $ @ không phải là. Nếu tôi sau đó nhận xét ra các mục tiêu đặc điểm kỹ thuật khác nhau và tự đặt CONFIG: = debug (dòng nhận xét ở trên), tôi nhận được những gì tôi mong đợi:

$ 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 

Tôi đã thử nghiệm điều này với cả hai make-3,81 trên Gentoo và MinGW và make-3.82 trên Gentoo. Tất cả đều có hành vi tương tự.

Tôi cảm thấy khó tin rằng tôi sẽ là người đầu tiên gặp vấn đề này, vì vậy tôi đoán tôi có thể đang làm điều gì đó sai - nhưng tôi sẽ thành thật: Tôi không thấy Tôi có thể. :)

Có bất kỳ bậc thầy nào ở ngoài đó có thể làm sáng tỏ vấn đề này không? Cảm ơn!

+0

thể trùng lặp của [Biến mục tiêu cụ thể như điều kiện tiên quyết trong một Makefile] (http://stackoverflow.com/questions/1340060/target-specific-variables-as-prerequisites-in-a-makefile) –

Trả lời

7

Như Beta đã chỉ ra, điều này thực sự không phải là một lỗi trong làm kể từ khi giới hạn được mô tả trong tài liệu hướng dẫn (tôi đoán tôi phải đã bỏ lỡ một phần đặc biệt -- lấy làm tiếc).

Trong mọi trường hợp, tôi thực sự có thể giải quyết vấn đề này bằng cách làm điều gì đó đơn giản hơn.Vì tất cả những gì tôi cần là gán một biến dựa trên mục tiêu, tôi thấy rằng tôi có thể sử dụng biến số $(MAKECMDGOALS) để mở rộng thư mục xây dựng đúng cách. Loại bỏ $ (config) thay đổi và viết lại Makefile như sau thực hiện chính xác những gì tôi cần:

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] 

này sau đó đưa ra kết quả đúng:

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

này muốn của phá vỡ tất nhiên nếu có nhiều các mục tiêu được chỉ định trên dòng lệnh (vì $ (MAKECMDGOALS) chứa một danh sách được phân cách bằng dấu cách), nhưng đối phó với đó không phải là quá nhiều vấn đề.

+0

Cảm ơn. Câu trả lời này là [cuộc sống tiết kiệm] (http://stackoverflow.com/a/21057818/376535). –

+0

50 tiền thưởng đầu tiên của tôi là câu trả lời sai! Sau đó, tôi đã cho 100 về điều này. –

9

Về cơ bản, hãy tính toán DAG của các phụ thuộc và tạo danh sách các quy tắc phải được chạy trước khi chạy bất kỳ quy tắc nào. Gán một giá trị đích cụ thể là một cái gì đó mà Thực hiện khi chạy một quy tắc, mà đến sau. Đây là một hạn chế nghiêm trọng (mà tôi và những người khác đã phàn nàn về trước đây), nhưng tôi sẽ không gọi nó là một lỗi vì nó được mô tả trong tài liệu. Theo hướng dẫn sử dụng GNUMake:

6.11 Target-specific Variable Values: "Như với các biến tự động, các giá trị này chỉ có sẵn trong ngữ cảnh công thức của mục tiêu (và trong các nhiệm vụ đích cụ thể khác)."

Và "bối cảnh công thức của một mục tiêu" có nghĩa là các lệnh, không phải là điều kiện tiên quyết:

10.5.3 Automatic variables: "[biến tự động] không thể được truy cập trực tiếp trong danh sách điều kiện tiên quyết của một quy tắc"

Có một số cách để giải quyết vấn đề này. Bạn có thể sử dụng Secondary Expansion, nếu phiên bản Make GNUMake của bạn có nó (3.81 không, tôi không biết về 3.82). Hoặc bạn có thể làm mà không có biến mục tiêu cụ thể:

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

Trước hết, cảm ơn câu trả lời! Nếu hệ thống xây dựng của chúng tôi chỉ có hai mục tiêu cấu hình (gỡ lỗi và giải phóng), tôi có thể đã đưa ra đề xuất của bạn, nhưng chúng tôi có sáu mục tiêu cấu hình khác nhau - sao chép tất cả các mục tiêu đó trong Makefile sẽ khá lộn xộn, nhưng chắc chắn không phải là không thể. Đối với phần mở rộng phụ, nó dường như được hỗ trợ từ make-3.81, nhưng khi tôi cố gắng sử dụng nó, làm cho chỉ cần treo, dường như bị mắc kẹt trong một vòng lặp vô hạn. Tôi đã từ bỏ cố gắng tìm ra những gì đã xảy ra ngay sau khi tôi phát hiện biến $ (MAKECMDGOALS). Cảm ơn một lần nữa! – Falken

+0

Thông tin rất hữu ích - không xứng đáng được duy trì mà không có bất kỳ phiếu bầu nào ... Bạn có thể giải thích ngắn gọn cách mở rộng phụ có thể giải quyết vấn đề này không? –

+0

Đây là tất cả phần nào trùng lặp, nhưng các bit trùng lặp sau đó có thể được tóm tắt thành một hàm Thực hiện được gọi một lần cho mỗi cấu hình mong muốn. – Novelocrat

2

Dưới đây là cách giải quyết sự cố mà không cần MAKECMDGOALS nội tâm. Tha vấn đề về cơ bản là các quy tắc bạn chỉ định trong Makefile là một biểu đồ tĩnh. Các bài tập cụ thể theo mục tiêu được sử dụng trong khi thực thi của các cơ quan quy tắc, nhưng không được sử dụng trong quá trình biên soạn của chúng.

Giải pháp cho điều này là nắm quyền kiểm soát việc biên soạn quy tắc: sử dụng cấu trúc giống như macro của GNU Make để tạo quy tắc. Sau đó, chúng ta có toàn quyền kiểm soát: chúng ta có thể gắn vật liệu biến đổi vào mục tiêu, điều kiện tiên quyết hoặc công thức.

Đây là phiên bản của tôi về bạn 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 

Bây giờ, chú ý đến lợi thế: Tôi có thể xây dựng cả debugrelease mục tiêu trong một đi, bởi vì tôi đã instantiated cả hai quy tắc từ mẫu!

$ 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 

Hơn nữa, tôi đã lấy tự do để thêm đối số vĩ mô vào dòng lệnh cc cũng có, do đó các module nhận được một MODE vĩ mô mà nói với họ như thế nào họ đang được biên soạn.

Chúng tôi có thể sử dụng biến hướng khác nhau để thiết lập CFLAGS hoặc bất kỳ thứ gì khác. Xem những gì sẽ xảy ra nếu chúng ta vá ở trên như thế này:

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

Cuối cùng, chúng ta có thể kết hợp điều đó với MAKECMDGOALS. Chúng tôi có thể kiểm tra MAKECMDGOALS và lọc ra các chế độ xây dựng không được chỉ định ở đó. Nếu được gọi là make release, chúng tôi không cần mở rộng quy tắc debug. Patch:

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

Lưu ý rằng tôi đơn giản hóa mọi thứ bằng cách cuộn các mục tiêu debug:release: vào BUILDRULE vĩ mô.

$ 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 
Các vấn đề liên quan