Makefile การอ้างอิงส่วนหัว


98

สมมติว่าฉันมี makefile กับกฎ

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

ฉันต้องการให้ * .o สร้างใหม่ทุกครั้งที่ไฟล์ส่วนหัวมีการเปลี่ยนแปลง แทนที่จะหารายการของการอ้างอิงเมื่อใดก็ตามที่ไฟล์ส่วนหัวมี/includeการเปลี่ยนแปลงอ็อบเจ็กต์ทั้งหมดใน dir จะต้องถูกสร้างใหม่

ฉันคิดวิธีที่ดีในการเปลี่ยนแปลงกฎเพื่อรองรับสิ่งนี้ไม่ได้ฉันยินดีรับข้อเสนอแนะ คะแนนโบนัสหากรายการส่วนหัวไม่จำเป็นต้องฮาร์ดโค้ด


หลังจากเขียนคำตอบของฉันด้านล่างฉันดูในรายการที่เกี่ยวข้องและพบว่า: stackoverflow.com/questions/297514/…ซึ่งดูเหมือนจะซ้ำกัน คำตอบของ Chris Dodd เทียบเท่ากับของฉันแม้ว่าจะใช้หลักการตั้งชื่อที่แตกต่างกัน
dmckee --- อดีตผู้ดูแลลูกแมว

คำตอบ:


117

หากคุณใช้คอมไพเลอร์ GNU คอมไพลเลอร์สามารถรวบรวมรายการการอ้างอิงสำหรับคุณได้ Makefile ส่วนย่อย:

depend: .depend

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

include .depend

หรือ

depend: .depend

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

include .depend

ที่SRCSเป็นตัวแปรที่ชี้ไปยังรายชื่อทั้งหมดของคุณจากไฟล์ที่มา

นอกจากนี้ยังมีเครื่องมือmakedependแต่ฉันไม่เคยชอบมันมากเท่าgcc -MM


2
ฉันชอบเคล็ดลับนี้ แต่ฉันdependจะเรียกใช้เฉพาะเมื่อไฟล์ต้นฉบับมีการเปลี่ยนแปลงได้อย่างไร ดูเหมือนว่าจะทำงานทุกครั้งโดยไม่คำนึงถึง ...
ไล่

2
@chase: ฉันได้ทำการพึ่งพาไฟล์ออบเจ็กต์อย่างไม่ถูกต้องเมื่อเห็นได้ชัดว่ามันควรจะอยู่บนแหล่งที่มาและมีลำดับการพึ่งพาผิดสำหรับสองเป้าหมายด้วย นั่นคือสิ่งที่ฉันได้รับจากการพิมพ์จากหน่วยความจำ ลองเลย
dmckee --- อดีตผู้ดูแลลูกแมว

4
เป็นวิธีเพิ่มก่อนทุกไฟล์คำนำหน้าเพื่อแสดงว่าอยู่ในไดเร็กทอรีอื่นbuild/file.oหรือไม่?
RiaD

ฉันเปลี่ยน SRCS เป็น OBJECTS โดยที่ OBJECTS คือรายการไฟล์ * .o ของฉัน ดูเหมือนว่าจะป้องกันไม่ให้ขึ้นอยู่กับการทำงานทุกครั้งและยังพบการเปลี่ยนแปลงในไฟล์ส่วนหัวเท่านั้น ดูเหมือนว่าจะสวนทางกับความเห็นก่อนหน้านี้ .. ฉันพลาดอะไรไปหรือเปล่า?
BigBrownBear00

2
เหตุใดอัฒภาคจึงจำเป็น? ถ้าฉันลองโดยไม่มีมันหรือด้วย -MF ./.depend ไม่ใช่อาร์กิวเมนต์สุดท้ายมันจะบันทึกเฉพาะการอ้างอิงของไฟล์สุดท้ายใน $ (SRCS)
humodz

75

คำตอบส่วนใหญ่ซับซ้อนหรือผิดพลาดอย่างน่าประหลาดใจ อย่างไรก็ตามมีการโพสต์ตัวอย่างที่เรียบง่ายและมีประสิทธิภาพไว้ที่อื่น [ codereview ] เป็นที่ยอมรับว่าตัวเลือกที่มีให้โดยตัวประมวลผลล่วงหน้า gnu นั้นค่อนข้างสับสน อย่างไรก็ตามการลบไดเร็กทอรีทั้งหมดออกจากเป้าหมายการสร้างด้วย-MMมีการบันทึกไว้และไม่ใช่ข้อผิดพลาด [ gpp ]:

โดยค่าเริ่มต้น CPP จะใช้ชื่อของไฟล์อินพุตหลักลบ ส่วนประกอบไดเร็กทอรีและส่วนต่อท้ายไฟล์เช่น ".c" และต่อท้ายอ็อบเจ็กต์ตามปกติของแพลตฟอร์ม

-MMDตัวเลือก(ค่อนข้างใหม่กว่า) น่าจะเป็นสิ่งที่คุณต้องการ เพื่อความสมบูรณ์ตัวอย่างของ makefile ที่รองรับ src dirs หลาย ๆ ตัวและ build dirs พร้อมความคิดเห็นบางส่วน สำหรับเวอร์ชันธรรมดาที่ไม่มีบิวด์ไดร์โปรดดู [ 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)

วิธีนี้ใช้ได้ผลเพราะหากมีบรรทัดการอ้างอิงหลายบรรทัดสำหรับเป้าหมายเดียวการอ้างอิงจะถูกรวมเข้าด้วยกันเช่น:

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

เทียบเท่ากับ:

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

ตามที่กล่าวไว้ที่: Makefile หลายบรรทัดการอ้างอิงสำหรับเป้าหมายเดียว?


1
ฉันชอบวิธีนี้ ฉันไม่ต้องการพิมพ์คำสั่ง make depend มีประโยชน์ !!
Robert

1
มีข้อผิดพลาดในการสะกดในค่าตัวแปร OBJ: CPPควรอ่านCPPS
ctrucza

1
นี่คือคำตอบที่ฉันต้องการ +1 สำหรับคุณ นี่เป็นเพียงเรื่องเดียวในหน้านี้ที่เหมาะสมและครอบคลุม (สำหรับสิ่งที่ฉันเห็น) ทุกสถานการณ์ที่จำเป็นต้องมีการคอมไพล์ซ้ำ (หลีกเลี่ยงการคอมไพล์ที่ไม่จำเป็น แต่ก็เพียงพอแล้ว)
Joost

1
นอกกรอบสิ่งนี้ล้มเหลวในการค้นหาส่วนหัวสำหรับฉันแม้ว่า hpp และ cpp จะอยู่ใน dir เดียวกัน
Villasv

1
หากคุณมีไฟล์ต้นฉบับ ( a.cpp, b.cpp) อยู่ใน./src/นั้นการทดแทนจะ$(OBJ)=./build/src/a.o ./build/src/b.oไม่ทำหรือไม่?
galois

26

ดังที่ฉันโพสต์ไว้ที่นี่ gcc สามารถสร้างการอ้างอิงและรวบรวมในเวลาเดียวกัน:

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

-include $(DEPS)

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

พารามิเตอร์ '-MF' ระบุไฟล์เพื่อเก็บการอ้างอิงใน.

เส้นประที่จุดเริ่มต้นของ '-include' จะบอกให้ Make ดำเนินการต่อเมื่อไม่มีไฟล์. d (เช่นในการคอมไพล์ครั้งแรก)

โปรดทราบว่ามีข้อบกพร่องใน gcc เกี่ยวกับตัวเลือก -o หากคุณตั้งชื่อไฟล์ออบเจ็กต์เป็น obj / _file__c.o ไฟล์ที่สร้างขึ้น. d จะยังคงมีไฟล์ . o ไม่ใช่ obj / _file__c.o


4
เมื่อฉันลองสิ่งนี้ส่งผลให้ไฟล์. o ทั้งหมดของฉันถูกสร้างเป็นไฟล์เปล่า ฉันมีวัตถุของฉันในโฟลเดอร์ย่อย build (ดังนั้น $ OBJECTS จึงมี build / main.o build / smbus.o build / etc ... ) และแน่นอนว่าจะสร้างไฟล์. d ตามที่คุณอธิบายด้วยข้อผิดพลาดที่ชัดเจน ไม่ได้สร้างไฟล์. o เลยในขณะที่จะลบ -MM และ -MF
bobpaul

1
การใช้ -MT จะแก้ไขบันทึกในบรรทัดสุดท้ายของคำตอบของคุณซึ่งจะอัปเดตเป้าหมายของรายการอ้างอิงแต่ละรายการ
Godric Seer

3
@bobpaul เพราะman gccบอกเป็น-MMนัย-Eซึ่ง "หยุดหลังจากการประมวลผลล่วงหน้า" คุณต้องการ-MMDแทน: stackoverflow.com/a/30142139/895245
Ciro Santilli 郝海东冠状病六四事件法轮功

23

แล้วสิ่งที่ชอบ:

includes = $(wildcard include/*.h)

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

คุณยังสามารถใช้สัญลักษณ์แทนได้โดยตรง แต่ฉันมักจะพบว่าฉันต้องการมันมากกว่าหนึ่งที่

โปรดทราบว่าสิ่งนี้ใช้ได้ดีกับโครงการขนาดเล็กเท่านั้นเนื่องจากถือว่าไฟล์ออบเจ็กต์ทุกไฟล์ขึ้นอยู่กับไฟล์ส่วนหัวทุกไฟล์


ขอบคุณฉันทำให้เรื่องนี้ซับซ้อนเกินความจำเป็น
Mike

15
วิธีนี้ใช้ได้ผล แต่ปัญหาคือไฟล์ออบเจ็กต์ทุกไฟล์จะถูกคอมไพล์ใหม่ทุกครั้งที่มีการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ เช่นถ้าคุณมีไฟล์ต้นทาง / ส่วนหัว 100 ไฟล์และคุณทำการเปลี่ยนแปลงเพียงเล็กน้อยเป็นไฟล์เดียวทั้งหมด 100 ไฟล์จะถูกคอมไพล์ใหม่ .
Nicholas Hamilton

1
คุณควรอัปเดตคำตอบของคุณเพื่อบอกว่านี่เป็นวิธีที่ไม่มีประสิทธิภาพมากเพราะจะสร้างไฟล์ทั้งหมดใหม่ทุกครั้งที่มีการเปลี่ยนแปลงไฟล์ส่วนหัวใด ๆ คำตอบอื่น ๆ ดีกว่ามาก
xaxxon

2
นี่เป็นทางออกที่แย่มาก แน่นอนว่ามันจะทำงานในโปรเจ็กต์ขนาดเล็ก แต่สำหรับทีมงานและการสร้างขนาดใดก็ตามสิ่งนี้จะนำไปสู่เวลาในการรวบรวมที่แย่มากและเทียบเท่ากับการทำงานmake clean allทุกครั้ง
Julien Guertault

ในการทดสอบของฉันสิ่งนี้ไม่ได้ผลเลย gccบรรทัดจะไม่ทำงานเลย แต่ในตัวกฎ ( %o: %.cกฎ) จะถูกดำเนินการแทน
Penghe Geng

4

โซลูชันของ Martin ข้างต้นใช้งานได้ดี แต่ไม่จัดการไฟล์. o ที่อยู่ในไดเร็กทอรีย่อย Godric ชี้ให้เห็นว่าแฟล็ก -MT ดูแลปัญหานั้น แต่จะป้องกันไม่ให้เขียนไฟล์. o อย่างถูกต้องในเวลาเดียวกัน สิ่งต่อไปนี้จะดูแลทั้งสองปัญหา:

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

-include $(DEPS)

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

3

สิ่งนี้จะทำงานได้ดีและยังจัดการกับ subdirs ที่ระบุ:

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

ทดสอบด้วย gcc 4.8.3


3

นี่คือสองซับ:

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

สิ่งนี้ใช้ได้กับสูตรการสร้างเริ่มต้นตราบใดที่คุณมีรายการไฟล์ออบเจ็กต์ทั้งหมดในไฟล์OBJS.


1

ฉันชอบวิธีนี้มากกว่าคำตอบที่ยอมรับโดย Michael Williamson มันจับการเปลี่ยนแปลงของแหล่งที่มา + ไฟล์แบบอินไลน์จากนั้นแหล่งที่มา + ส่วนหัวและในที่สุดก็เป็นแหล่งที่มาเท่านั้น ข้อดีคือห้องสมุดทั้งหมดจะไม่ถูกคอมไพล์ใหม่หากมีการเปลี่ยนแปลงเพียงเล็กน้อยเท่านั้น ไม่ใช่การพิจารณาโครงการที่มีไฟล์สองสามไฟล์หากคุณมี 10 หรือ 100 แหล่งคุณจะสังเกตเห็นความแตกต่าง

COMMAND= gcc -Wall -Iinclude ...

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

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

%.o: %.cpp
    $(COMMAND)

2
สิ่งนี้ใช้ได้เฉพาะในกรณีที่คุณไม่มีไฟล์ส่วนหัวที่ต้องการการคอมไพล์ใหม่ของไฟล์ cpp อื่นจากนั้นไฟล์การนำไปใช้งานที่เกี่ยวข้อง
matec


0

คำตอบของ Sophie เวอร์ชันแก้ไขเล็กน้อยซึ่งอนุญาตให้ส่งไฟล์ * .d ไปยังโฟลเดอร์อื่น (ฉันจะวางเฉพาะส่วนที่น่าสนใจที่สร้างไฟล์อ้างอิงเท่านั้น):

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

สังเกตว่าพารามิเตอร์

-MT $@

ถูกใช้เพื่อให้แน่ใจว่าเป้าหมาย (เช่นชื่อไฟล์อ็อบเจ็กต์) ในไฟล์ * .d ที่สร้างขึ้นมีพา ธ แบบเต็มไปยังไฟล์ * .o ไม่ใช่แค่ชื่อไฟล์

ฉันไม่รู้ว่าทำไมไม่จำเป็นต้องใช้พารามิเตอร์นี้เมื่อใช้ -MMD ร่วมกับ -c (เช่นเดียวกับในเวอร์ชันของโซฟี) ในชุดค่าผสมนี้ดูเหมือนว่าจะเขียนเส้นทางแบบเต็มของไฟล์ * .o ลงในไฟล์ * .d หากไม่มีชุดค่าผสมนี้ -MMD ยังเขียนเฉพาะชื่อไฟล์ที่บริสุทธิ์โดยไม่มีส่วนประกอบไดเร็กทอรีใด ๆ ลงในไฟล์ * .d อาจมีใครรู้ว่าทำไม -MMD จึงเขียนพา ธ เต็มเมื่อรวมกับ -c ฉันไม่พบคำแนะนำใด ๆ ในหน้า g ++ man

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.