Makefiles พร้อมไฟล์ต้นฉบับในไดเรกทอรีที่แตกต่างกัน


135

ฉันมีโครงการที่โครงสร้างไดเรกทอรีเป็นเช่นนี้:

                         $projectroot
                              |
              +---------------+----------------+
              |               |                |
            part1/          part2/           part3/
              |               |                |
       +------+-----+     +---+----+       +---+-----+
       |      |     |     |        |       |         |
     data/   src/  inc/  src/     inc/   src/       inc/

ฉันจะเขียน makefile ที่จะอยู่ใน part / src (หรือที่ไหนก็ได้) ที่สามารถ comple / link บนไฟล์ต้นฉบับ c / c ++ ในบางส่วนได้อย่างไร / src?

ฉันสามารถทำสิ่งที่ชอบ -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

หากได้ผลมีวิธีที่ง่ายกว่านี้ไหม ฉันเคยเห็นโครงการที่มี makefile ในแต่ละส่วนที่เกี่ยวข้องหรือไม่ โฟลเดอร์ [ในโพสต์นี้ฉันใช้เครื่องหมายคำถามเช่นในไวยากรณ์ bash]



1
ในคู่มือ gnu ดั้งเดิม ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) ภายใต้ Phony Targets มีตัวอย่างอยู่recursive invocationซึ่ง coule นั้นค่อนข้างหรูหรา
Frank Nocke

คุณใช้เครื่องมืออะไรในการสร้างกราฟิกข้อความนั้น
Khalid Hussain

คำตอบ:


114

วิธีแบบดั้งเดิมคือการมีMakefileในแต่ละไดเรกทอรีย่อย ( part1, part2ฯลฯ ) ช่วยให้คุณสามารถสร้างพวกเขาเป็นอิสระ นอกจากนี้มีMakefileในไดเรกทอรีรากของโครงการที่สร้างทุกอย่าง "ราก" Makefileจะมีลักษณะดังนี้:

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

เนื่องจากแต่ละบรรทัดใน make target ถูกรันในเชลล์ของตัวเองจึงไม่จำเป็นต้องกังวลเกี่ยวกับการย้อนกลับข้อมูลไดเร็กทอรีทรีหรือไดเร็กทอรีอื่น

ฉันขอแนะนำให้ดูที่GNU make manual ส่วน 5.7 ; เป็นประโยชน์มาก


26
สิ่งนี้ควร+$(MAKE) -C part1เป็นต้นซึ่งทำให้การควบคุมงานของ Make ทำงานในไดเร็กทอรีย่อย
ephemient

26
นี่เป็นวิธีการคลาสสิกและใช้กันอย่างแพร่หลาย แต่ก็ไม่เหมาะสมในหลาย ๆ วิธีที่แย่ลงเมื่อโครงการเติบโตขึ้น Dave Hinton มีตัวชี้ที่จะปฏิบัติตาม
dmckee --- อดีตผู้ดูแลลูกแมว

89

หากคุณมีโค้ดในไดเร็กทอรีย่อยหนึ่งขึ้นอยู่กับโค้ดในไดเร็กทอรีย่อยอื่นคุณน่าจะดีกว่าด้วย makefile เดียวที่ระดับบนสุด

ดูการทำซ้ำถือว่าเป็นอันตรายสำหรับเหตุผลทั้งหมด แต่โดยพื้นฐานแล้วคุณต้องการให้มีข้อมูลทั้งหมดที่จำเป็นในการตัดสินใจว่าจะต้องสร้างไฟล์ใหม่หรือไม่และจะไม่มีสิ่งนั้นหากคุณบอกเพียงหนึ่งในสามของ โครงการของคุณ

ดูเหมือนว่าลิงก์ด้านบนจะไม่สามารถเข้าถึงได้ เอกสารเดียวกันสามารถเข้าถึงได้ที่นี่:


3
ขอบคุณไม่ทราบเรื่องนี้ การรู้ "วิธีที่ถูกต้อง" ในการทำสิ่งต่าง ๆ นั้นมีประโยชน์มากแทนที่จะเป็นวิธีที่ "ได้ผล" หรือได้รับการยอมรับว่าเป็นมาตรฐาน
tjklemz

3
การทำซ้ำถือว่าเป็นอันตรายย้อนกลับไปเมื่อมันไม่ได้ผลจริงๆ วันนี้ไม่ถือว่าเป็นอันตรายในความเป็นจริงมันเป็นวิธีที่เครื่องมืออัตโนมัติ / อัตโนมัติจัดการโครงการขนาดใหญ่
Edwin Buck

36

ตัวเลือก VPATH อาจมีประโยชน์ซึ่งจะบอกให้ทราบว่าไดเรกทอรีใดที่จะค้นหาซอร์สโค้ด คุณยังต้องการตัวเลือก -I สำหรับแต่ละเส้นทางรวม ตัวอย่าง:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc
VPATH=part1/src:part2/src:part3/src

OutputExecutable: part1api.o part2api.o part3api.o

สิ่งนี้จะค้นหาไฟล์ partXapi.cpp ที่ตรงกันโดยอัตโนมัติในไดเร็กทอรีที่ระบุ VPATH และคอมไพล์ อย่างไรก็ตามสิ่งนี้มีประโยชน์มากกว่าเมื่อไดเร็กทอรี src ของคุณแบ่งออกเป็นไดเร็กทอรีย่อย สำหรับสิ่งที่คุณอธิบายตามที่คนอื่น ๆ บอกคุณน่าจะดีกว่าด้วย makefile สำหรับแต่ละส่วนโดยเฉพาะอย่างยิ่งถ้าแต่ละส่วนสามารถยืนอยู่คนเดียวได้


2
ฉันไม่อยากจะเชื่อเลยว่าคำตอบที่สมบูรณ์แบบง่ายๆนี้ไม่ได้รับการโหวตมากกว่านี้ +1 จากฉัน
Nicholas Hamilton

2
ฉันมีซอร์สไฟล์ทั่วไปในไดเร็กทอรีที่สูงขึ้นสำหรับโปรเจ็กต์ที่แตกต่างกันหลายโปรเจ็กต์ในโฟลเดอร์ย่อยVPATH=..ใช้ได้กับฉัน!
EkriirkE

23

คุณสามารถเพิ่มกฎใน Makefile รูทของคุณเพื่อคอมไพล์ไฟล์ cpp ที่จำเป็นในไดเร็กทอรีอื่น ๆ ตัวอย่าง Makefile ด้านล่างนี้ควรเป็นจุดเริ่มต้นที่ดีในการพาคุณไปยังที่ที่คุณต้องการ

CC = กรัม ++
target = cppTest
OTHERDIR = .. / .. / someotherpath / ใน / โครงการ / src

SOURCE = cppTest.cpp
แหล่งที่มา = $ (OTHERDIR) /file.cpp

## นิยามแหล่งที่มาปลายทาง
รวม = -I./ $ (AN_INCLUDE_DIR)  
รวม = -I. $ (OTHERDIR) /../ inc
## จบเพิ่มเติมรวม

VPATH = $ (OTHERDIR)
OBJ = $ (เข้าร่วม $ (addsuffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## แก้ไขปลายทางการอ้างอิงให้เป็น ../.dep สัมพันธ์กับ src dir
DEPENDS = $ (เข้าร่วม $ (addsuffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## กฎเริ่มต้นดำเนินการ
ทั้งหมด: $ (เป้าหมาย)
        @true

## กฎความสะอาด
สะอาด
        @ -rm -f $ (เป้าหมาย) $ (OBJ) $ (DEPENDS)


## กฎสำหรับการกำหนดเป้าหมายที่แท้จริง
$ (เป้าหมาย): $ (OBJ)
        @echo "============="
        @echo "เชื่อมโยงเป้าหมาย $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - ลิงค์เสร็จแล้ว -

## กฎการคอมไพล์ทั่วไป
% .o:% .cpp
        @mkdir -p $ (ผ $ @)
        @echo "============="
        @echo "รวบรวม $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## กฎสำหรับไฟล์ออบเจ็กต์จากไฟล์ cpp
## ไฟล์ออบเจ็กต์สำหรับแต่ละไฟล์จะอยู่ในไดเร็กทอรี obj
## หนึ่งระดับขึ้นจากไดเร็กทอรีต้นทางจริง
../obj/%.o:% .cpp
        @mkdir -p $ (ผ $ @)
        @echo "============="
        @echo "รวบรวม $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# กฎสำหรับ "ไดเรกทอรีอื่น" คุณจะต้องมีหนึ่งข้อต่อผบ. "อื่น ๆ "
$ (OTHERDIR) /../ obj /%. o:% .cpp
        @mkdir -p $ (ผ $ @)
        @echo "============="
        @echo "รวบรวม $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## สร้างกฎการพึ่งพา
../.dep/%.d:% .cpp
        @mkdir -p $ (ผ $ @)
        @echo "============="
        @echo สร้างไฟล์อ้างอิงสำหรับ $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## กฎการอ้างอิงสำหรับไดเรกทอรี "อื่น ๆ "
$ (OTHERDIR) /../. dep /%. d:% .cpp
        @mkdir -p $ (ผ $ @)
        @echo "============="
        @echo สร้างไฟล์อ้างอิงสำหรับ $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## รวมไฟล์อ้างอิง
- รวม $ (DEPENDS)


7
ฉันรู้ว่าตอนนี้ค่อนข้างเก่าแล้ว แต่ทั้ง SOURCE และ INCLUDE ถูกเขียนทับโดยงานอื่นในบรรทัดต่อไปหรือไม่?
skelliam

@skelliam ใช่ค่ะ
nabroyan

1
แนวทางนี้ละเมิดหลักการ DRY เป็นความคิดที่ไม่ดีที่จะทำซ้ำรหัสสำหรับ "ไดเรกทอรีอื่น" แต่ละรายการ
Jesus H

20

หากแหล่งที่มาถูกกระจายไปในหลายโฟลเดอร์และเป็นเรื่องที่สมเหตุสมผลที่จะมี Makefiles แต่ละรายการตามที่แนะนำไว้ก่อนหน้านี้การสร้างซ้ำเป็นแนวทางที่ดี แต่สำหรับโปรเจ็กต์ขนาดเล็กฉันพบว่าการแสดงรายการไฟล์ต้นฉบับทั้งหมดในMakefileนั้นง่ายกว่าด้วยพา ธ สัมพัทธ์ ไปยังMakefileดังนี้:

# common sources
COMMON_SRC := ./main.cpp \
              ../src1/somefile.cpp \
              ../src1/somefile2.cpp \
              ../src2/somefile3.cpp \

จากนั้นฉันสามารถตั้งค่าVPATHวิธีนี้:

VPATH := ../src1:../src2

จากนั้นฉันสร้างวัตถุ:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC)))

ตอนนี้กฎนั้นง่ายมาก:

# the "common" object files
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile
    @echo creating $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

และการสร้างผลลัพธ์นั้นง่ายยิ่งขึ้น:

# This will make the cbsdk shared library
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS)
    @echo building output ...
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS)

เราสามารถทำให้การVPATHสร้างเป็นแบบอัตโนมัติโดย:

VPATH := $(dir $(COMMON_SRC))

หรือใช้ข้อเท็จจริงที่sortลบรายการที่ซ้ำกัน (แม้ว่าจะไม่สำคัญก็ตาม):

VPATH := $(sort  $(dir $(COMMON_SRC)))

ใช้งานได้ดีสำหรับโปรเจ็กต์ขนาดเล็กที่คุณต้องการเพิ่มไลบรารีแบบกำหนดเองบางไลบรารีและจัดให้อยู่ในโฟลเดอร์แยก ขอบคุณมาก
zitroneneis

6

ฉันคิดว่าเป็นการดีกว่าที่จะชี้ให้เห็นว่าการใช้ Make (เรียกซ้ำหรือไม่) เป็นสิ่งที่คุณอาจต้องการหลีกเลี่ยงเนื่องจากเมื่อเทียบกับเครื่องมือในปัจจุบันแล้วการเรียนรู้ดูแลรักษาและปรับขนาดทำได้ยาก

เป็นเครื่องมือที่ยอดเยี่ยม แต่การใช้งานโดยตรงควรถือว่าล้าสมัยในปี 2010+

เว้นแต่คุณกำลังทำงานในสภาพแวดล้อมพิเศษเช่นกับโครงการเดิมเป็นต้น

ใช้ IDE, CMakeหรือหากคุณกำลัง cored หนักAutotools

(แก้ไขเนื่องจาก downvotes ty Honza เพื่อชี้ให้เห็น)


1
จะเป็นการดีที่จะมีการอธิบายการโหวตลงคะแนน ฉันเคยทำ Makefiles ด้วยตัวเองด้วย
IlDan

2
ฉันโหวตให้คำแนะนำ "อย่าทำอย่างนั้น" - KDevelop มีอินเทอร์เฟซแบบกราฟิกสำหรับกำหนดค่า Make และระบบบิลด์อื่น ๆ และในขณะที่ฉันเขียน Makefiles ด้วยตัวเอง KDevelop คือสิ่งที่ฉันมอบให้กับเพื่อนร่วมงานของฉัน - แต่ฉัน อย่าคิดว่าลิงก์ Autotools จะช่วยได้ ในการทำความเข้าใจ Autotools คุณต้องเข้าใจ m4, libtool, automake, autoconf, shell, make และโดยทั่วไปสแต็กทั้งหมด
ephemient

7
การโหวตลดลงอาจเป็นเพราะคุณกำลังตอบคำถามของคุณเอง คำถามไม่ได้เกี่ยวกับว่าควรใช้ Makefiles หรือไม่ มันเกี่ยวกับวิธีการเขียน
Honza

8
คงจะดีไม่น้อยถ้าคุณทำได้ในสองสามประโยคอธิบายว่าเครื่องมือที่ทันสมัยทำให้ชีวิตง่ายขึ้นในการเขียน Makefile ได้อย่างไร พวกเขาให้นามธรรมในระดับที่สูงขึ้นหรือไม่? หรืออะไร?
Abhishek Anand

1
xterm, vi, ทุบตี, makefile == ครองโลก, cmake สำหรับคนที่แฮ็กโค้ดไม่ได้
μολὼν.λαβέ

2

โพสต์ของ RC มีประโยชน์มาก ฉันไม่เคยคิดเกี่ยวกับการใช้ฟังก์ชัน $ (dir $ @) แต่มันทำในสิ่งที่ฉันต้องการให้ทำ

ใน parentDir มีไดเร็กทอรีจำนวนมากที่มีไฟล์ต้นฉบับอยู่ในนั้น: dirA, dirB, dirC ไฟล์ต่างๆขึ้นอยู่กับไฟล์อ็อบเจ็กต์ในไดเร็กทอรีอื่นดังนั้นฉันจึงต้องการสร้างไฟล์หนึ่งไฟล์จากภายในไดเร็กทอรีเดียวและทำให้การอ้างอิงนั้นขึ้นอยู่กับการเรียก makefile ที่เกี่ยวข้องกับการอ้างอิงนั้น

โดยพื้นฐานแล้วฉันสร้าง Makefile หนึ่งรายการใน parentDir ที่มีกฎทั่วไปคล้ายกับ RC:

%.o : %.cpp
        @mkdir -p $(dir $@)
        @echo "============="
        @echo "Compiling $<"
        @$(CC) $(CFLAGS) -c $< -o $@

ไดเร็กทอรีย่อยแต่ละรายการรวม makefile ระดับบนเพื่อสืบทอดกฎทั่วไปนี้ ใน Makefile ของไดเร็กทอรีย่อยแต่ละรายการฉันได้เขียนกฎที่กำหนดเองสำหรับแต่ละไฟล์เพื่อให้ฉันสามารถติดตามทุกอย่างที่แต่ละไฟล์ขึ้นอยู่กับ

เมื่อใดก็ตามที่ฉันต้องการสร้างไฟล์ฉันใช้ (โดยพื้นฐาน) กฎนี้เพื่อสร้างการอ้างอิงใด ๆ / ทั้งหมดซ้ำ ที่สมบูรณ์แบบ!

หมายเหตุ: มียูทิลิตี้ที่เรียกว่า "makepp" ซึ่งดูเหมือนจะทำงานนี้ได้อย่างสังหรณ์ใจมากขึ้น แต่เพื่อประโยชน์ในการพกพาและไม่ได้ขึ้นอยู่กับเครื่องมืออื่นฉันเลือกที่จะทำด้วยวิธีนี้

หวังว่านี่จะช่วยได้!


2

การใช้ Make ซ้ำ

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

สิ่งนี้ช่วยให้makeสามารถแยกออกเป็นงานและใช้หลายคอร์ได้


2
ดีกว่าทำแบบนี้ยังmake -j4ไง?
devin

1
@devin อย่างที่ฉันเห็นมันไม่ได้ดีไปกว่าแต่เพียงแค่เปิด makeใช้งานการควบคุมงาน เมื่อเรียกใช้กระบวนการทำแยกกันจะไม่อยู่ภายใต้การควบคุมงาน
Michael Pankov

1

ฉันกำลังมองหาบางอย่างเช่นนี้และหลังจากลองผิดลองถูกฉันสร้าง makefile ของตัวเองฉันรู้ว่านั่นไม่ใช่ "วิธีการใช้สำนวน" แต่เป็นการเริ่มต้นที่จะเข้าใจการสร้างและวิธีนี้ได้ผลสำหรับฉันบางทีคุณอาจลองทำโครงการ

PROJ_NAME=mono

CPP_FILES=$(shell find . -name "*.cpp")

S_OBJ=$(patsubst %.cpp, %.o, $(CPP_FILES))

CXXFLAGS=-c \
         -g \
        -Wall

all: $(PROJ_NAME)
    @echo Running application
    @echo
    @./$(PROJ_NAME)

$(PROJ_NAME): $(S_OBJ)
    @echo Linking objects...
    @g++ -o $@ $^

%.o: %.cpp %.h
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS) -o $@

main.o: main.cpp
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS)

clean:
    @echo Removing secondary things
    @rm -r -f objects $(S_OBJ) $(PROJ_NAME)
    @echo Done!

ฉันรู้ว่ามันง่ายและสำหรับบางคนแฟล็กของฉันผิด แต่อย่างที่บอกว่านี่เป็น Makefile ตัวแรกของฉันที่รวบรวมโปรเจ็กต์ของฉันในไดร์หลาย ๆ อันและเชื่อมโยงทั้งหมดเข้าด้วยกันเพื่อสร้างถังขยะของฉัน

ฉันยอมรับข้อเสนอแนะ: D


-1

ฉันแนะนำให้ใช้autotools:

//## วางอ็อบเจ็กต์ไฟล์ที่สร้างขึ้น (.o) ไว้ในไดเร็กทอรีเดียวกันกับไฟล์ต้นทางเพื่อหลีกเลี่ยงการชนกันเมื่อใช้การสร้างแบบไม่เรียกซ้ำ

AUTOMAKE_OPTIONS = subdir-objects

เพียงแค่รวมไว้ในMakefile.amสิ่งที่ค่อนข้างง่ายอื่น ๆ

นี่คือการกวดวิชา

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