Makefile, dependensi header

97

Katakanlah saya memiliki makefile dengan aturan tersebut

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

Saya ingin * .o dibangun kembali setiap kali file header berubah. Daripada mengerjakan daftar dependensi, setiap kali file header /includeberubah, maka semua objek di dir harus dibangun kembali.

Saya tidak bisa memikirkan cara yang baik untuk mengubah aturan untuk mengakomodasi ini, saya terbuka untuk saran. Poin bonus jika daftar tajuk tidak harus menggunakan hardcode

Mike
sumber
Setelah menulis jawaban saya di bawah ini, saya melihat di daftar terkait dan menemukan: stackoverflow.com/questions/297514/… yang tampaknya merupakan duplikat. Jawaban Chris Dodd setara dengan jawaban saya, meskipun menggunakan konvensi penamaan yang berbeda.
dmckee --- mantan moderator anak kucing

Jawaban:

116

Jika Anda menggunakan kompiler GNU, kompilator dapat menyusun daftar dependensi untuk Anda. Fragmen makefile:

depend: .depend

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

include .depend

atau

depend: .depend

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

include .depend

di mana SRCSvariabel menunjuk ke seluruh daftar file sumber Anda.

Ada juga alatnya makedepend, tapi saya tidak pernah menyukainyagcc -MM

dmckee
sumber
2
Saya suka trik ini, tetapi bagaimana saya bisa dependmenjalankan hanya ketika file sumber telah berubah? Tampaknya berjalan setiap saat ...
kejar
2
@ Chase: Yah, saya telah salah membuat ketergantungan pada file objek, padahal seharusnya jelas-jelas ada pada sumber dan memiliki urutan ketergantungan yang salah untuk kedua target juga. Itulah yang saya dapatkan dari mengetik dari memori. Coba sekarang.
dmckee --- kucing mantan moderator
4
Apakah itu cara untuk menambahkan sebelum setiap file beberapa awalan untuk menunjukkan bahwa itu ada di direktori lain misalnya build/file.o ?
RiaD
Saya mengubah SRCS menjadi OBJEK, di mana OBJEK adalah daftar file * .o saya. Itu tampaknya mencegah dependensi berjalan setiap waktu dan juga menangkap perubahan pada file header saja. Ini sepertinya berlawanan dengan komentar sebelumnya..ap saya melewatkan sesuatu?
BigBrownBear00
2
Mengapa tanda titik koma diperlukan? jika saya mencobanya tanpa itu, atau dengan -MF ./.depend tidak menjadi argumen terakhir, itu hanya menyimpan dependensi file terakhir di $ (SRCS).
humodz
72

Kebanyakan jawaban ternyata rumit atau salah. Namun contoh sederhana dan kuat telah diposting di tempat lain [ codereview ]. Memang opsi yang disediakan oleh preprocessor gnu agak membingungkan. Namun, penghapusan semua direktori dari target build dengan -MMdidokumentasikan dan bukan bug [ gpp ]:

Secara default CPP mengambil nama file input utama, menghapus semua komponen direktori dan sufiks file seperti '.c', dan menambahkan sufiks objek biasa platform.

Opsi (agak lebih baru) -MMDmungkin yang Anda inginkan. Untuk kelengkapan, contoh makefile yang mendukung beberapa dir src dan direktori build dengan beberapa komentar. Untuk versi sederhana tanpa build dirs lihat [ 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)

Metode ini berfungsi karena jika ada beberapa baris dependensi untuk satu target, dependensi hanya digabungkan, misalnya:

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

setara dengan:

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

seperti yang disebutkan di: Makefile multiple dependency lines untuk satu target?

Sophie
sumber
1
Saya suka solusi ini. Saya tidak ingin mengetik perintah make depend. Berguna !!
Robert
1
Ada kesalahan ejaan dalam nilai variabel OBJ: the CPPshould readCPPS
ctrucza
1
Ini adalah jawaban pilihan saya; 1 untuk Anda. Ini adalah satu-satunya di halaman ini yang masuk akal, dan mencakup (untuk apa yang bisa saya lihat) semua situasi di mana kompilasi ulang diperlukan (menghindari kompilasi yang tidak perlu, namun cukup)
Joost
1
Di luar kotak, ini gagal menemukan tajuk untuk saya meskipun hpp dan cpp keduanya berada di direktori yang sama.
villasv
1
jika Anda memiliki file sumber ( a.cpp, b.cpp) di ./src/, bukankah substitusi itu akan berhasil $(OBJ)=./build/src/a.o ./build/src/b.o?
galois
26

Seperti yang saya posting di sini, gcc dapat membuat dependensi dan mengkompilasi pada saat yang bersamaan:

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

-include $(DEPS)

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

Parameter '-MF' menentukan file untuk menyimpan dependensi.

Tanda hubung di awal '-include' memberitahu Make untuk melanjutkan jika file .d tidak ada (misalnya pada kompilasi pertama).

Perhatikan bahwa tampaknya ada bug di gcc terkait opsi -o. Jika Anda mengeset nama file objek menjadi obj / _file__c.o maka file .d yang dihasilkan akan tetap berisi file .o, bukan obj / _file__c.o.

Martin Fido
sumber
4
Ketika saya mencoba ini menghasilkan semua file .o saya dibuat sebagai file kosong. Saya memiliki objek saya dalam subfolder build (jadi $ OBJECTS berisi build / main.o build / smbus.o build / etc ...) dan itu pasti membuat file .d seperti yang Anda jelaskan dengan bug yang terlihat, tetapi tentu saja sama sekali tidak membangun file .o, sedangkan jika saya menghapus -MM dan -MF.
bobpaul
1
Menggunakan -MT akan menyelesaikan catatan di baris terakhir jawaban Anda yang memperbarui target dari setiap daftar ketergantungan.
Godric Seer
3
@bobpaul karena man gcckata -MMmenyiratkan -E, yang "berhenti setelah preprocessing". -MMDSebagai gantinya, Anda membutuhkan : stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东 冠状 病 六四 事件 '20
23

Bagaimana dengan sesuatu seperti:

includes = $(wildcard include/*.h)

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

Anda juga dapat menggunakan wildcard secara langsung, tetapi saya cenderung merasa bahwa saya membutuhkannya di lebih dari satu tempat.

Perhatikan bahwa ini hanya berfungsi dengan baik pada proyek kecil, karena mengasumsikan bahwa setiap file objek bergantung pada setiap file header.

Michael Williamson
sumber
terima kasih, saya membuat ini menjadi jauh lebih rumit dari yang dibutuhkan
Mike
15
Ini berfungsi, namun, masalah dengan ini adalah bahwa setiap file objek akan dikompilasi ulang, setiap kali ada perubahan kecil, yaitu, jika Anda memiliki 100 file sumber / header, dan Anda membuat perubahan kecil hanya pada satu, semua 100 dikompilasi ulang .
Nicholas Hamilton
1
Anda harus benar-benar memperbarui jawaban Anda untuk mengatakan bahwa ini adalah cara yang sangat tidak efisien untuk melakukannya karena ia membangun kembali SEMUA file setiap kali file header apa pun diubah. Jawaban lainnya jauh lebih baik.
xaxxon
2
Ini adalah solusi yang sangat buruk. Tentu itu akan bekerja pada proyek kecil, tetapi untuk tim ukuran produksi dan build apa pun, ini akan menyebabkan waktu kompilasi yang buruk dan menjadi setara dengan berjalan make clean allsetiap saat.
Julien Guertault
Dalam pengujian saya, ini tidak berhasil sama sekali. The gccgaris tidak dijalankan sama sekali, tapi built-in aturan (yang %o: %.caturan) dijalankan sebagai gantinya.
Penghe Geng
4

Solusi Martin di atas berfungsi dengan baik, tetapi tidak menangani file .o yang berada di subdirektori. Godric menunjukkan bahwa flag -MT menangani masalah itu, tetapi secara bersamaan mencegah file .o ditulis dengan benar. Berikut ini akan menangani kedua masalah tersebut:

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

-include $(DEPS)

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

Ini akan melakukan pekerjaan dengan baik, dan bahkan menangani subdir yang ditentukan:

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

mengujinya dengan gcc 4.8.3

g24l
sumber
3

Ini dua baris:

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

Ini berfungsi dengan resep make default, selama Anda memiliki daftar semua file objek Anda OBJS.

tbodt
sumber
1

Saya lebih suka solusi ini, daripada jawaban yang diterima oleh Michael Williamson, ini menangkap perubahan pada sumber + file sebaris, lalu sumber + tajuk, dan akhirnya hanya sumber. Keuntungan di sini adalah bahwa seluruh pustaka tidak dikompilasi ulang jika hanya dilakukan sedikit perubahan. Bukan pertimbangan besar untuk proyek dengan beberapa file, tetapi jika Anda memiliki 10 atau 100 sumber, Anda akan melihat perbedaannya.

COMMAND= gcc -Wall -Iinclude ...

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

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

%.o: %.cpp
    $(COMMAND)
Nicholas Hamilton
sumber
2
Ini hanya berfungsi jika Anda tidak memiliki apa pun di file header yang memerlukan kompilasi ulang file cpp apa pun selain file implementasi yang sesuai.
matec
0

Pekerjaan berikut untuk saya:

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

-include $(DEPS)

%.o: %.cpp
    $(CXX) $(CFLAGS) -MMD -c -o $@ $<
Marcel Keller
sumber
0

Versi jawaban Sophie yang sedikit dimodifikasi yang memungkinkan untuk mengeluarkan file * .d ke folder berbeda (saya hanya akan menempelkan bagian menarik yang menghasilkan file dependensi):

$(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 $@

Perhatikan bahwa parameternya

-MT $@

digunakan untuk memastikan bahwa target (yaitu nama file objek) dalam file * .d yang dihasilkan berisi jalur lengkap ke file * .o dan bukan hanya nama file.

Saya tidak tahu mengapa parameter ini TIDAK diperlukan saat menggunakan -MMD dalam kombinasi dengan -c (seperti dalam versi Sophie ). Dalam kombinasi ini tampaknya menulis jalur lengkap dari file * .o ke dalam file * .d. Tanpa kombinasi ini, -MMD juga menulis hanya nama file murni tanpa komponen direktori ke dalam file * .d. Mungkin ada yang tahu mengapa -MMD menulis jalur lengkap jika digabungkan dengan -c. Saya belum menemukan petunjuk apa pun di halaman manual g ++.

MaximumFPS
sumber