Makefile, phụ thuộc tiêu đề


97

Giả sử tôi có một makefile với quy tắc

%.o: %.c
 gcc -Wall -Iinclude ...

Tôi muốn * .o được xây dựng lại bất cứ khi nào tệp tiêu đề thay đổi. Thay vì tạo ra một danh sách các phụ thuộc, bất cứ khi nào bất kỳ tệp tiêu đề nào /includethay đổi, thì tất cả các đối tượng trong dir phải được xây dựng lại.

Tôi không thể nghĩ ra cách hay để thay đổi quy tắc để phù hợp với điều này, tôi sẵn sàng tiếp nhận các đề xuất. Điểm thưởng nếu danh sách các tiêu đề không phải được mã hóa cứng


Sau khi viết câu trả lời của mình bên dưới, tôi đã xem danh sách liên quan và thấy: stackoverflow.com/questions/297514/… có vẻ như là một bản sao. Câu trả lời của Chris Dodd tương đương với câu trả lời của tôi, mặc dù nó sử dụng một quy ước đặt tên khác.
dmckee --- ex-moderator kitten.

Câu trả lời:


116

Nếu bạn đang sử dụng trình biên dịch GNU, trình biên dịch có thể tập hợp danh sách các phần phụ thuộc cho bạn. Đoạn Makefile:

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ -MF  ./.depend;

include .depend

hoặc là

depend: .depend

.depend: $(SRCS)
        rm -f ./.depend
        $(CC) $(CFLAGS) -MM $^ > ./.depend;

include .depend

đâu SRCSlà một biến trỏ đến toàn bộ danh sách các tệp nguồn của bạn.

Cũng có công cụ này makedepend, nhưng tôi chưa bao giờ thích nó nhiều nhưgcc -MM


2
Tôi thích thủ thuật này, nhưng làm cách nào dependđể chỉ chạy khi tệp nguồn đã thay đổi? Nó dường như chạy mọi lúc mọi nơi bất chấp ...
đuổi theo

2
@chase: Chà, tôi đã tạo sai sự phụ thuộc vào các tệp đối tượng, khi nó rõ ràng phải nằm trên các nguồn và cũng có thứ tự phụ thuộc sai cho hai mục tiêu. Đó là những gì tôi nhận được khi gõ từ bộ nhớ. Thử ngay bây giờ.
dmckee --- cựu điều hành kitten

4
Có cách nào để thêm trước mỗi tệp một số tiền tố để cho thấy rằng nó nằm trong một thư mục khác, ví dụ build/file.o?
RiaD 12/10/12

Tôi đã thay đổi SRCS thành OBJECTS, trong đó OBJECTS là danh sách các tệp * .o của tôi. Điều đó dường như ngăn phụ thuộc chạy mọi lúc và cũng chỉ bắt các thay đổi đối với các tệp tiêu đề. Điều này có vẻ trái ngược với những nhận xét trước đây..tôi có thiếu một cái gì đó không?
BigBrownBear00

2
Tại sao dấu chấm phẩy lại cần thiết? nếu tôi thử mà không có nó hoặc với -MF ./.depend không phải là đối số cuối cùng, nó chỉ lưu các phần phụ thuộc của tệp cuối cùng trong $ (SRCS).
humodz

71

Hầu hết các câu trả lời đều phức tạp hoặc sai một cách đáng ngạc nhiên. Tuy nhiên, các ví dụ đơn giản và mạnh mẽ đã được đăng ở nơi khác [ codereview ]. Phải thừa nhận rằng các tùy chọn được cung cấp bởi bộ tiền xử lý gnu hơi khó hiểu. Tuy nhiên, việc xóa tất cả các thư mục khỏi mục tiêu xây dựng với -MMđược ghi lại và không phải là lỗi [ gpp ]:

Theo mặc định, CPP lấy tên của tệp đầu vào chính, xóa bất kỳ thành phần thư mục nào và bất kỳ hậu tố tệp nào chẳng hạn như '.c' và thêm vào hậu tố đối tượng thông thường của nền tảng.

-MMDTùy chọn (hơi mới hơn) có thể là những gì bạn muốn. Để có tính hoàn chỉnh, hãy lấy một ví dụ về makefile hỗ trợ nhiều mã src và xây dựng các dirs với một số chú thích. Đối với một phiên bản đơn giản không có trình xây dựng, hãy xem [ codereview ].

CXX = clang++
CXX_FLAGS = -Wfatal-errors -Wall -Wextra -Wpedantic -Wconversion -Wshadow

# Final binary
BIN = mybin
# Put all auto generated stuff to this build dir.
BUILD_DIR = ./build

# List of all .cpp source files.
CPP = main.cpp $(wildcard dir1/*.cpp) $(wildcard dir2/*.cpp)

# All .o files go to build dir.
OBJ = $(CPP:%.cpp=$(BUILD_DIR)/%.o)
# Gcc/Clang will create these .d files containing dependencies.
DEP = $(OBJ:%.o=%.d)

# Default target named after the binary.
$(BIN) : $(BUILD_DIR)/$(BIN)

# Actual target of the binary - depends on all .o files.
$(BUILD_DIR)/$(BIN) : $(OBJ)
    # Create build directories - same structure as sources.
    mkdir -p $(@D)
    # Just link all the object files.
    $(CXX) $(CXX_FLAGS) $^ -o $@

# Include all .d files
-include $(DEP)

# Build target for every single object file.
# The potential dependency on header files is covered
# by calling `-include $(DEP)`.
$(BUILD_DIR)/%.o : %.cpp
    mkdir -p $(@D)
    # The -MMD flags additionaly creates a .d file with
    # the same name as the .o file.
    $(CXX) $(CXX_FLAGS) -MMD -c $< -o $@

.PHONY : clean
clean :
    # This should remove all generated files.
    -rm $(BUILD_DIR)/$(BIN) $(OBJ) $(DEP)

Phương pháp này hoạt động bởi vì nếu có nhiều dòng phụ thuộc cho một mục tiêu duy nhất, các dòng phụ thuộc sẽ được nối đơn giản, ví dụ:

a.o: a.h
a.o: a.c
    ./cmd

tương đương với:

a.o: a.c a.h
    ./cmd

như đã đề cập tại: Makefile nhiều dòng phụ thuộc cho một mục tiêu duy nhất?


1
Tôi thích giải pháp này. Tôi không muốn nhập lệnh thực hiện phụ thuộc. Hữu ích !!
Robert

1
Có một lỗi chính tả trong các giá trị biến OBJ: các CPPnên đọcCPPS
ctrucza

1
Đây là câu trả lời ưa thích của tôi; +1 cho bạn. Đây là người duy nhất trên trang này có ý nghĩa, và bìa (cho những gì tôi có thể nhìn thấy) tất cả các tình huống mà biên dịch lại là cần thiết (tránh lập không cần thiết, nhưng đủ)
Joost

1
Ngoài ra, điều này không thể tìm thấy tiêu đề cho tôi mặc dù hpp và cpp đều nằm trên cùng một dir.
biệt thựv

1
nếu bạn có các tệp nguồn ( a.cpp, b.cpp) của mình ./src/, liệu sự thay thế đó có thực hiện được $(OBJ)=./build/src/a.o ./build/src/b.okhông?
galois

26

Như tôi đã đăng ở đây, gcc có thể tạo các phụ thuộc và biên dịch cùng một lúc:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MF $(patsubst %.o,%.d,$@) -o $@ $<

Tham số '-MF' chỉ định một tệp để lưu trữ các phần phụ thuộc.

Dấu gạch ngang ở đầu '-include' cho biết Make tiếp tục khi tệp .d không tồn tại (ví dụ: trong lần biên dịch đầu tiên).

Lưu ý rằng dường như có một lỗi trong gcc liên quan đến tùy chọn -o. Nếu bạn đặt tên tệp đối tượng thành obj / _file__c.o thì tệp .d được tạo sẽ vẫn chứa tệp .o, không phải obj / _file__c.o.


4
Khi tôi thử điều này, kết quả là tất cả các tệp .o của tôi được tạo dưới dạng tệp trống. Tôi có các đối tượng của mình trong một thư mục con xây dựng (vì vậy $ OBJECTS chứa build / main.o build / smbus.o build / etc ...) và điều đó chắc chắn tạo ra các tệp .d như bạn đã mô tả với lỗi rõ ràng, nhưng chắc chắn hoàn toàn không tạo tệp .o, ngược lại, nếu tôi xóa -MM và -MF.
bobpaul

1
Sử dụng -MT sẽ giải quyết ghi chú trong những dòng cuối cùng của câu trả lời của bạn, cập nhật mục tiêu của mỗi danh sách phụ thuộc.
Godric Seer

3
@bobpaul bởi vì man gccnói -MMngụ ý -E, "dừng sau khi xử lý trước". -MMDThay vào đó, bạn cần : stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

23

Làm thế nào về một cái gì đó như:

includes = $(wildcard include/*.h)

%.o: %.c ${includes}
    gcc -Wall -Iinclude ...

Bạn cũng có thể sử dụng trực tiếp các ký tự đại diện, nhưng tôi có xu hướng thấy mình cần chúng ở nhiều nơi.

Lưu ý rằng điều này chỉ hoạt động tốt trên các dự án nhỏ, vì nó giả định rằng mọi tệp đối tượng phụ thuộc vào mọi tệp tiêu đề.


cảm ơn, tôi đã làm ra điều này là rất nhiều phức tạp hơn là cần thiết
Mike

15
Tuy nhiên, điều này hoạt động, vấn đề với điều này là mọi tệp đối tượng được biên dịch lại, mỗi khi thực hiện một thay đổi nhỏ, tức là nếu bạn có 100 tệp nguồn / tiêu đề và bạn chỉ thực hiện một thay đổi nhỏ cho một tệp, tất cả 100 tệp sẽ được biên dịch lại .
Nicholas Hamilton

1
Bạn thực sự nên cập nhật câu trả lời của mình để nói rằng đây là một cách làm rất kém hiệu quả vì nó xây dựng lại TẤT CẢ các tệp mỗi khi BẤT KỲ tệp tiêu đề nào được thay đổi. Các câu trả lời khác tốt hơn nhiều.
xaxxon.

2
Đây là một giải pháp rất tồi. Chắc chắn nó sẽ hoạt động trên một dự án nhỏ, nhưng đối với bất kỳ nhóm sản xuất và xây dựng nào, điều này sẽ dẫn đến thời gian biên dịch khủng khiếp và tương đương với việc chạy make clean allmỗi lần.
Julien Guertault

Trong thử nghiệm của tôi, điều này không hoạt động chút nào. Các gccdòng không được thực hiện ở tất cả, nhưng được xây dựng trong quy tắc ( %o: %.crule) được thực hiện để thay thế.
Penghe Geng

4

Giải pháp của Martin ở trên hoạt động tốt, nhưng không xử lý các tệp .o nằm trong các thư mục con. Godric chỉ ra rằng cờ -MT xử lý vấn đề đó, nhưng nó đồng thời ngăn không cho tệp .o được viết chính xác. Phần sau sẽ giải quyết cả hai vấn đề đó:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.c
    $(CC) $(CFLAGS) -MM -MT $@ -MF $(patsubst %.o,%.d,$@) $<
    $(CC) $(CFLAGS) -o $@ $<

3

Điều này sẽ thực hiện công việc tốt và thậm chí xử lý các mã con đang được chỉ định:

    $(CC) $(CFLAGS) -MD -o $@ $<

đã thử nghiệm nó với gcc 4.8.3


3

Đây là một hai lớp lót:

CPPFLAGS = -MMD
-include $(OBJS:.c=.d)

Điều này hoạt động với công thức làm mặc định, miễn là bạn có danh sách tất cả các tệp đối tượng của mình OBJS.


1

Tôi thích giải pháp này hơn, hơn câu trả lời được chấp nhận bởi Michael Williamson, nó bắt các thay đổi đối với nguồn + tệp nội tuyến, sau đó là nguồn + tiêu đề và cuối cùng là chỉ nguồn. Ưu điểm ở đây là toàn bộ thư viện không được biên dịch lại nếu chỉ thực hiện một vài thay đổi. Không phải là một sự cân nhắc lớn đối với một dự án có một vài tệp, hãy ghi nếu bạn có 10 hoặc 100 nguồn, bạn sẽ nhận thấy sự khác biệt.

COMMAND= gcc -Wall -Iinclude ...

%.o: %.cpp %.inl
    $(COMMAND)

%.o: %.cpp %.hpp
    $(COMMAND)

%.o: %.cpp
    $(COMMAND)

2
Điều này chỉ hoạt động nếu bạn không có bất kỳ thứ gì trong tệp tiêu đề của mình mà yêu cầu biên dịch lại bất kỳ tệp cpp nào khác sau đó là tệp triển khai tương ứng.
matec

0

Những điều sau đây phù hợp với tôi:

DEPS := $(OBJS:.o=.d)

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<

0

Một phiên bản sửa đổi một chút của câu trả lời của Sophie cho phép xuất các tệp * .d sang một thư mục khác (tôi sẽ chỉ dán phần thú vị tạo ra các tệp phụ thuộc):

$(OBJDIR)/%.o: %.cpp
# Generate dependency file
    mkdir -p $(@D:$(OBJDIR)%=$(DEPDIR)%)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MM -MT $@ $< -MF $(@:$(OBJDIR)/%.o=$(DEPDIR)/%.d)
# Generate object file
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $< -o $@

Lưu ý rằng tham số

-MT $@

được sử dụng để đảm bảo rằng các mục tiêu (tức là tên tệp đối tượng) trong tệp * .d được tạo chứa đường dẫn đầy đủ đến tệp * .o chứ không chỉ tên tệp.

Tôi không biết tại sao tham số này KHÔNG cần thiết khi sử dụng -MMD kết hợp với -c (như trong phiên bản của Sophie ). Trong sự kết hợp này, dường như ghi đường dẫn đầy đủ của các tệp * .o vào các tệp * .d. Nếu không có sự kết hợp này, -MMD cũng chỉ ghi tên tệp thuần mà không có bất kỳ thành phần thư mục nào vào tệp * .d. Có lẽ ai đó biết tại sao -MMD viết đường dẫn đầy đủ khi kết hợp với -c. Tôi không tìm thấy bất kỳ gợi ý nào trong trang người đàn ông g ++.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.