วิธีการสร้าง Makefile C ++ SIMPLE


303

เราจำเป็นต้องใช้ Makefile เพื่อดึงทุกอย่างเข้าด้วยกันสำหรับโครงการของเรา แต่อาจารย์ของเราไม่เคยแสดงให้เราเห็นถึงวิธีการ

ฉันมีเพียงไฟล์เดียวเท่านั้นa3driver.cpp. คนขับนำเข้าชั้นเรียนจากสถานที่"/user/cse232/Examples/example32.sequence.cpp"คนขับรถนำเข้าชั้นเรียนจากสถานที่

แค่นั้นแหละ. ทุกสิ่งทุกอย่างมีอยู่ในนั้นด้วย.cppนั้นด้วย

ฉันจะสร้าง Makefile ง่ายๆที่สร้างไฟล์เรียกทำงานได้a3a.exeอย่างไร


9
.EXE ดังนั้น Windows แน่นอน ในความคิดที่สอง ... เส้นทางคือ Unix-style อาจใช้ Mingw-32
Nathan Osman

2
ถอนหายใจ ฉันคิดว่าคุณต้องเรียนรู้พื้นฐานของการซื้อขายทุกครั้งแม้ว่าคุณจะไม่เคยใช้มัน เพียงแค่ต้องเข้าใจวิธีการทำงานของสิ่งต่าง ๆ อย่างไรก็ตามโอกาสที่ดีนั้นคุณจะต้องพัฒนาใน IDE เสมอเช่น Eclipse คุณจะได้รับคำตอบที่นี่สำหรับกรณีบรรทัดเดียวที่เรียบง่ายของคุณและมีบทเรียนเว็บมากมาย แต่ถ้าคุณต้องการความรู้ในเชิงลึกคุณจะไม่สามารถเอาชนะหนังสือ O'reilly (เช่นเดียวกับหัวข้อ s / w ส่วนใหญ่) amazon.com/Managing-Projects-Make-Nutshell-Handbooks/dp/ ...... เลือกสำเนามือสองจาก amazon, half.com, betterworldbooks eBay
Mawg พูดว่าการคืนสถานะโมนิก้า

2
ลิงก์ที่โพสต์โดย @Dennis คือตอนนี้ตาย แต่วัสดุเดียวกับที่สามารถพบได้ในนี้หน้า archive.org
Guilherme Salomé

ฉันชอบความคิดของบุคคลนี้ ( hiltmon.com/blog/2013/07/03/… ) โครงสร้างโครงการสามารถปรับเปลี่ยนได้ง่ายเพื่อให้เหมาะกับ และฉันก็เห็นด้วยว่าควรใช้เวลาของนักพัฒนาในสิ่งอื่นนอกเหนือจาก automake / autoconf เครื่องมือเหล่านี้มีที่ของตัวเอง แต่อาจไม่ใช่สำหรับโครงการภายใน ฉันกำลังสร้างสคริปต์ที่จะสร้างโครงสร้างโครงการดังกล่าว
ไดสุเกะอารามากิ

@ GuilhermeSaloméขอบคุณฉันเชื่อว่านี่เป็นแบบฝึกหัดที่ง่ายและสมบูรณ์ที่สุด
Hareen Laks

คำตอบ:


560

ตั้งแต่นี้เป็น Unix, executables ไม่มีส่วนขยายใด ๆ

สิ่งหนึ่งที่ควรทราบก็คือ root-configคือยูทิลิตี้ที่ให้การรวบรวมที่ถูกต้องและการเชื่อมโยงธง; และไลบรารีที่เหมาะสมสำหรับการสร้างแอปพลิเคชันกับรูท นั่นเป็นเพียงรายละเอียดที่เกี่ยวข้องกับผู้ชมดั้งเดิมสำหรับเอกสารนี้

ทำให้ฉันเป็นเด็ก

หรือคุณไม่เคยลืมครั้งแรกที่คุณทำ

การสนทนาเบื้องต้นเกี่ยวกับ make และวิธีเขียน makefile อย่างง่าย

ทำคืออะไร แล้วทำไมฉันถึงต้องแคร์?

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

จริงๆแล้วคุณสามารถใช้ Make เพื่อสิ่งอื่นได้เช่นกัน แต่ฉันจะไม่พูดเกี่ยวกับเรื่องนี้

Makefile เล็กน้อย

สมมติว่าคุณมีไดเรกทอรีที่มี: tool tool.cc tool.o support.cc support.hhและ support.oขึ้นอยู่กับrootและควรจะรวบรวมเป็นโปรแกรมที่เรียกว่าtoolและสมมติว่าคุณได้แฮ็คไฟล์ต้นฉบับ (ซึ่งหมายความว่าที่มีอยู่toolล้าสมัยแล้ว) และต้องการ รวบรวมโปรแกรม

คุณสามารถทำได้

  1. ตรวจสอบว่าอย่างใดอย่างหนึ่งsupport.ccหรือsupport.hhใหม่กว่าsupport.oและถ้าเป็นเช่นนั้นเรียกใช้คำสั่งเช่น

    g++ -g -c -pthread -I/sw/include/root support.cc
  2. ตรวจสอบว่าอย่างใดอย่างหนึ่งsupport.hhหรือtool.ccใหม่กว่าtool.oและถ้าเป็นเช่นนั้นเรียกใช้คำสั่งเช่น

    g++ -g  -c -pthread -I/sw/include/root tool.cc
  3. ตรวจสอบว่าtool.oใหม่กว่าtoolและถ้าเป็นเช่นนั้นเรียกใช้คำสั่งเช่น

    g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
    -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
    -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

วุ้ย ช่างเป็นเรื่องยุ่งยาก! มีหลายสิ่งที่ต้องจดจำและหลายโอกาสที่จะทำผิดพลาด (BTW - รายละเอียดของบรรทัดคำสั่งที่แสดงที่นี่ขึ้นอยู่กับสภาพแวดล้อมซอฟต์แวร์ของเรารายการเหล่านี้ใช้ได้กับคอมพิวเตอร์ของฉัน)

แน่นอนคุณสามารถรันทั้งสามคำสั่งได้ทุกครั้ง อาจใช้งานได้ แต่ไม่สามารถปรับขนาดให้เหมาะสมกับซอฟต์แวร์ชิ้นใหญ่ (เช่น DOGS ซึ่งใช้เวลามากกว่า 15 นาทีในการรวบรวมจากพื้นดินบน MacBook ของฉัน)

แต่คุณสามารถเขียนไฟล์ที่เรียกว่าmakefileดังนี้:

tool: tool.o support.o
    g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
        -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
        -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

tool.o: tool.cc support.hh
    g++ -g  -c -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -pthread -I/sw/include/root support.cc

และเพียงพิมพ์ makeที่บรรทัดคำสั่ง ซึ่งจะดำเนินการตามสามขั้นตอนที่แสดงด้านบนโดยอัตโนมัติ

บรรทัดที่ไม่มีการย่อหน้าที่นี่มีรูปแบบ"target: dependencies"และแจ้งให้ Make ว่าคำสั่งที่เกี่ยวข้อง (บรรทัดที่เยื้อง) ควรรันหากการพึ่งพาใด ๆ นั้นใหม่กว่าเป้าหมาย นั่นคือบรรทัดการพึ่งพาอธิบายถึงตรรกะของสิ่งที่ต้องสร้างใหม่เพื่อรองรับการเปลี่ยนแปลงในไฟล์ต่างๆ หากsupport.ccการเปลี่ยนแปลงนั้นหมายความว่าsupport.oจะต้องสร้างใหม่ แต่tool.oสามารถปล่อยไว้ตามลำพัง เมื่อsupport.oการเปลี่ยนแปลงtoolต้องถูกสร้างใหม่

คำสั่งที่เกี่ยวข้องกับแต่ละบรรทัดอ้างอิงถูกตั้งค่าด้วยแท็บ (ดูด้านล่าง) ควรแก้ไขเป้าหมาย (หรืออย่างน้อยก็แตะเพื่อปรับปรุงเวลาแก้ไข)

ตัวแปรกฎในตัวและสารพัดอื่น ๆ

ณ จุดนี้ makefile ของเราเพียงจำงานที่ต้องทำ แต่เรายังต้องคิดและพิมพ์คำสั่งที่จำเป็นทุกอย่างครบถ้วน มันไม่จำเป็นต้องเป็นแบบนั้น: Make เป็นภาษาที่ทรงพลังด้วยตัวแปรฟังก์ชั่นการจัดการข้อความและกฎระเบียบในตัวทั้งหมดซึ่งทำให้เราง่ายขึ้นมาก

ทำตัวแปร

$(VAR)ไวยากรณ์สำหรับการเข้าถึงตัวแปรให้เป็น

ไวยากรณ์สำหรับการกำหนดให้กับตัวแปร Make คือ: VAR = A text value of some kind (หรือVAR := A different text value but ignore this for the moment)

คุณสามารถใช้ตัวแปรในกฎเช่น makefile รุ่นปรับปรุงของเรา:

CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
       -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
       -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
       -lm -ldl

tool: tool.o support.o
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

ซึ่งอ่านได้มากกว่านี้เล็กน้อย แต่ยังคงต้องการการพิมพ์จำนวนมาก

ทำหน้าที่

GNU ทำหน้าที่สนับสนุนฟังก์ชั่นที่หลากหลายสำหรับการเข้าถึงข้อมูลจากระบบไฟล์หรือคำสั่งอื่น ๆ ในระบบ ในกรณีนี้เราสนใจ$(shell ...)ที่จะขยายออกไปยังเอาท์พุทของอาร์กิวเมนต์และ$(subst opat,npat,text)แทนที่อินสแตนซ์ทั้งหมดopatด้วยnpatในข้อความ

การใช้ประโยชน์จากสิ่งนี้ทำให้เรา:

CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

tool: $(OBJS)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

ซึ่งง่ายต่อการพิมพ์และอ่านได้มากขึ้น

สังเกตว่า

  1. เรายังคงระบุการอ้างอิงอย่างชัดเจนสำหรับแต่ละอ็อบเจ็กต์ไฟล์และไฟล์สั่งการสุดท้าย
  2. เราต้องพิมพ์กฎการคอมไพล์สำหรับไฟล์ต้นฉบับอย่างชัดเจน

กฎโดยนัยและรูปแบบ

โดยทั่วไปเราคาดหวังว่าไฟล์ต้นฉบับ C ++ ทั้งหมดควรได้รับการปฏิบัติเช่นเดียวกันและ Make จัดเตรียมสามวิธีในการระบุสิ่งนี้:

  1. กฎต่อท้าย (ถือว่าล้าสมัยใน GNU make แต่เก็บไว้เพื่อความเข้ากันได้ย้อนหลัง)
  2. กฎเกณฑ์โดยปริยาย
  3. กฎรูปแบบ

มีการสร้างกฎโดยปริยายและจะมีการพูดคุยกันเล็กน้อยด้านล่าง กฎรูปแบบที่ระบุไว้ในรูปแบบเช่น

%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $<

ซึ่งหมายความว่าอ็อบเจ็กต์ไฟล์ถูกสร้างจากไฟล์ต้นฉบับ C โดยการรันคำสั่งที่แสดงซึ่งตัวแปร "อัตโนมัติ" $<ขยายเป็นชื่อของการขึ้นต่อกันครั้งแรก

กฎในตัว

Make มีโฮสต์ทั้งหมดของกฎในตัวซึ่งหมายความว่าบ่อยครั้งมากโครงการสามารถคอมไพล์โดย makefile ที่เรียบง่ายมากแน่นอน

GNU ที่สร้างขึ้นในกฎสำหรับไฟล์ต้นฉบับ C คือไฟล์ที่จัดแสดงด้านบน ในทำนองเดียวกันเราสร้างไฟล์อ็อบเจ็กต์จากไฟล์ต้นฉบับ C ++ ด้วยกฎ$(CXX) -c $(CPPFLAGS) $(CFLAGS)ดังนี้

ไฟล์วัตถุเดียวมีการเชื่อมโยงโดยใช้$(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)แต่สิ่งนี้จะไม่ทำงานในกรณีของเราเพราะเราต้องการเชื่อมโยงหลายวัตถุไฟล์

ตัวแปรที่ใช้โดยกฎในตัว

กฎในตัวใช้ชุดของตัวแปรมาตรฐานที่อนุญาตให้คุณระบุข้อมูลสภาพแวดล้อมท้องถิ่น (เช่นตำแหน่งที่จะค้นหาไฟล์ ROOT include) โดยไม่ต้องเขียนกฎทั้งหมดอีกครั้ง สิ่งที่น่าสนใจสำหรับเรามากที่สุดคือ:

  • CC - คอมไพเลอร์ C ที่จะใช้
  • CXX - คอมไพเลอร์ C ++ ที่ใช้
  • LD - ตัวเชื่อมโยงที่จะใช้
  • CFLAGS - การรวบรวมธงสำหรับไฟล์ต้นฉบับ C
  • CXXFLAGS - ธงการรวบรวมสำหรับไฟล์ต้นฉบับ C ++
  • CPPFLAGS - แฟล็กสำหรับ c-preprocessor (โดยทั่วไปจะมีพา ธ ไฟล์และสัญลักษณ์ที่กำหนดไว้ในบรรทัดคำสั่ง) ซึ่งใช้โดย C และ C ++
  • LDFLAGS - ธงลิงเกอร์
  • LDLIBS - ห้องสมุดเชื่อมโยง

Makefile ขั้นพื้นฐาน

ด้วยการใช้ประโยชน์จากกฎในตัวเราสามารถทำให้ makefile ของเราง่ายขึ้นไปที่:

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh

support.o: support.hh support.cc

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) tool

เราได้เพิ่มเป้าหมายมาตรฐานหลายอย่างที่ดำเนินการพิเศษ (เช่นการล้างไดเรกทอรีแหล่งที่มา)

โปรดทราบว่าเมื่อทำการเรียกใช้โดยไม่มีอาร์กิวเมนต์จะใช้เป้าหมายแรกที่พบในไฟล์ (ในกรณีนี้ทั้งหมด) แต่คุณยังสามารถตั้งชื่อเป้าหมายเพื่อรับสิ่งที่ทำให้ make cleanลบไฟล์วัตถุในกรณีนี้

เรายังคงมีการอ้างอิงฮาร์ดโค้ดทั้งหมด

การปรับปรุงที่ลึกลับบางอย่าง

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

depend: .depend

.depend: $(SRCS)
    $(RM) ./.depend
    $(CXX) $(CPPFLAGS) -MM $^>>./.depend;

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) *~ .depend

include .depend

สังเกตว่า

  1. ไม่มีเส้นอ้างอิงใด ๆ สำหรับไฟล์ต้นฉบับอีกต่อไป!?!
  2. มีเวทย์มนตร์แปลก ๆ ที่เกี่ยวข้องกับ. ขึ้นอยู่กับ
  3. ถ้าคุณทำmakeแล้วls -Aคุณจะเห็นไฟล์ชื่อ.dependซึ่งมีสิ่งที่ดูเหมือนว่าทำให้สายการพึ่งพา

การอ่านอื่น ๆ

รู้ข้อบกพร่องและบันทึกทางประวัติศาสตร์

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

(นี่ถูกคัดลอกมาจาก wiki post ที่ฉันเขียนสำหรับนักศึกษาระดับบัณฑิตศึกษาสาขาฟิสิกส์)


9
วิธีการในการสร้างการพึ่งพานี้ล้าสมัยและเป็นอันตรายจริง ดูสินค้าทุกประเภทรถยนต์พึ่งพา-Generation
Maxim Egorushkin

5
-pthreadธงทำให้เกิดgccการกำหนดแมโครที่-D_REENTRANTจำเป็นไม่จำเป็น
Maxim Egorushkin

8
@ jcoe มันไม่จำเป็นต้องมี preprocessor pass เพื่อสร้างการพึ่งพา การทำงานที่ไม่จำเป็นมันแค่กระจายความร้อนไปที่ขั้วน้ำแข็งและในระดับที่ใหญ่กว่านั้นก็ใกล้จะถึงความตายอันร้อนแรงของจักรวาลของเรา
Maxim Egorushkin

2
อาจเป็น "อันตราย" เป็นตาดมากเกินไป แต่เนื่องจากขั้นตอนหรือเป้าหมายการพึ่งพาที่ชัดแจ้งนั้นล้าสมัยมาตั้งแต่อย่างน้อย GCC 3 ฉันคิดว่าเราทุกคนควรจะผ่านพวกเขาไป bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/ …
hmijail mourns ลาออก

2
จริงๆแล้วคำตอบที่ได้รับการยอมรับไม่ควรขึ้นอยู่กับซอฟต์แวร์เฉพาะ ( root-config) ควรเสนอทางเลือกทั่วไปที่มีความสามารถเท่ากันถ้ามีหรือควรจะทิ้งไว้ ฉันไม่ได้ลงคะแนนเนื่องจากรายชื่อและคำอธิบายของมาโครที่ใช้บ่อยที่สุด
diod สีเขียว

56

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

ต่อไปนี้เป็นตัวอย่างแบบย่อ (โปรดทราบว่าฉันกำลังใช้ช่องว่าง 4 ช่องที่ฉันควรใช้แท็บ Stack Overflow จะไม่ให้ฉันใช้แท็บ):

a3driver: a3driver.o
    g++ -o a3driver a3driver.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

เมื่อคุณพิมพ์ makeมันจะเลือกส่วนแรก (a3driver) a3driver ขึ้นอยู่กับ a3driver.o ดังนั้นมันจะไปยังส่วนนั้น a3driver.o ขึ้นอยู่กับ a3driver.cpp ดังนั้นมันจะทำงานก็ต่อเมื่อ a3driver.cpp เปลี่ยนไปตั้งแต่ครั้งล่าสุด สมมติว่ามันมี (หรือไม่เคยถูกเรียกใช้) ก็จะรวบรวม a3driver.cpp เป็นไฟล์. o จากนั้นกลับไปที่ a3driver และรวบรวมไฟล์ปฏิบัติการขั้นสุดท้าย

เนื่องจากมีเพียงไฟล์เดียวจึงสามารถลดเป็น:

a3driver: a3driver.cpp
    g++ -o a3driver a3driver.cpp

เหตุผลที่ฉันแสดงตัวอย่างแรกคือมันแสดงพลังของ makefiles หากคุณต้องการรวบรวมไฟล์อื่นคุณสามารถเพิ่มหัวข้ออื่นได้ นี่คือตัวอย่างของ secondFile.cpp (ซึ่งโหลดในส่วนหัวชื่อ secondFile.h):

a3driver: a3driver.o secondFile.o
    g++ -o a3driver a3driver.o secondFile.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

secondFile.o: secondFile.cpp secondFile.h
    g++ -c secondFile.cpp

วิธีนี้ถ้าคุณเปลี่ยนบางสิ่งใน secondFile.cpp หรือ secondFile.h และคอมไพล์ใหม่ก็จะคอมไพล์ secondFile.cpp ใหม่เท่านั้น (ไม่ใช่ a3driver.cpp) หรืออีกวิธีหนึ่งถ้าคุณเปลี่ยนบางสิ่งใน a3driver.cpp มันจะไม่คอมไพล์ secondFile.cpp ใหม่

แจ้งให้เราทราบหากคุณมีคำถามใด ๆ เกี่ยวกับเรื่องนี้

นอกจากนี้ยังเป็นแบบดั้งเดิมที่จะรวมส่วนชื่อ "ทั้งหมด" และส่วนชื่อ "สะอาด" "all" โดยปกติจะสร้าง executables ทั้งหมดและ "clean" จะลบ "build artifacts" เช่นไฟล์. o และไฟล์ executables:

all: a3driver ;

clean:
    # -f so this will succeed even if the files don't exist
    rm -f a3driver a3driver.o

แก้ไข: ฉันไม่ได้สังเกตเห็นคุณบน Windows ผมคิดว่าแตกต่างเพียงมีการเปลี่ยนแปลงไป-o a3driver-o a3driver.exe


รหัสแน่นอนที่ฉันพยายามจะใช้คือ: p4a.exe: p4driver.cpp g ++ -o p4a p4driver.cpp แต่มันบอกฉันว่า "ตัวแยกขาดหายไป" ฉันใช้ TAB แต่ก็ยังบอกฉันว่า ความคิดใด ๆ
Befall

2
เท่าที่ฉันสามารถบอกได้ข้อความแสดงข้อผิดพลาดจะปรากฏขึ้นก็ต่อเมื่อคุณมีช่องว่าง ตรวจสอบให้แน่ใจว่าคุณไม่มีบรรทัดที่ขึ้นต้นด้วยช่องว่าง (แท็บช่องว่าง + จะให้ข้อผิดพลาดนั้น) นั่นเป็นสิ่งเดียวที่ฉันนึกได้ ..
เบรนแดน Long

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

35

ทำไมทุกคนถึงต้องการลิสต์ไฟล์ต้นฉบับ? คำสั่งค้นหาอย่างง่ายสามารถดูแลได้อย่างง่ายดาย

นี่คือตัวอย่างของ Makefile C ++ ที่เรียบง่าย เพียงวางในไดเรกทอรีที่มี.Cไฟล์แล้วพิมพ์make...

appname := myapp

CXX := clang++
CXXFLAGS := -std=c++11

srcfiles := $(shell find . -name "*.C")
objects  := $(patsubst %.C, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

2
เหตุผลที่จะไม่ค้นหาไฟล์ต้นฉบับโดยอัตโนมัติคือไฟล์หนึ่งอาจมีเป้าหมายบิลด์ที่ต่างกันซึ่งต้องการไฟล์ต่างกัน
hmijail mourns ลาออกจากตำแหน่ง

เห็นด้วย @hmijail เช่นเดียวกับ submodules ที่มีแหล่งข้อมูล / ส่วนหัวมากมายที่คุณไม่ต้องการรวบรวม / เชื่อมโยง ... และไม่ต้องสงสัยว่ามีสถานการณ์อื่น ๆ อีกมากมายที่การค้นหา / ใช้อย่างละเอียดไม่เหมาะสม
วิศวกร

ทำไมต้องใช้ "shell find" และไม่ใช่ "wildcard" แทน?
โนแลน

1
@Nolan เพื่อค้นหาไฟล์ต้นฉบับในแผนผังไดเรกทอรีซอร์ส
AlejandroVD

13

คุณมีสองทางเลือก

ตัวเลือกที่ 1: makefile ที่ง่ายที่สุด = ไม่สร้าง

เปลี่ยนชื่อ "a3driver.cpp" เป็น "a3a.cpp" จากนั้นให้เขียนบรรทัดคำสั่ง:

nmake a3a.exe

และนั่นคือมัน หากคุณใช้ GNU Make ให้ใช้ "make" หรือ "gmake" หรืออะไรก็ตาม

ตัวเลือก 2: makefile 2 บรรทัด

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj

3
นี่จะเป็นคำตอบที่ยอดเยี่ยมหากไม่ได้คาดการณ์ล่วงหน้าหลายอย่างเกี่ยวกับรายละเอียดของสภาพแวดล้อมของ OP ใช่พวกเขาอยู่ใน Windows nmakeแต่ไม่ได้หมายความว่าพวกเขาจะใช้ linkบรรทัดคำสั่งยังมีลักษณะที่เฉพาะเจาะจงมากที่จะเรียบเรียงโดยเฉพาะอย่างยิ่งและควรที่เอกสารอย่างน้อยที่สุดที่หนึ่ง
tripleee

6

ไฟล์ Make ของคุณจะมีหนึ่งหรือสองกฎการพึ่งพาทั้งนี้ขึ้นอยู่กับว่าคุณรวบรวมและเชื่อมโยงกับคำสั่งเดียวหรือด้วยคำสั่งเดียวสำหรับการรวบรวมและหนึ่งสำหรับการเชื่อมโยง

การพึ่งพาเป็นต้นไม้ของกฎที่มีลักษณะเช่นนี้ (โปรดทราบว่าการเยื้องจะต้องเป็นแท็บ):

main_target : source1 source2 etc
    command to build main_target from sources

source1 : dependents for source1
    command to build source1

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

ดังนั้น makefile ของคุณจะมีหน้าตาแบบนี้

a3a.exe : a3driver.obj 
    link /out:a3a.exe a3driver.obj

a3driver.obj : a3driver.cpp
    cc a3driver.cpp

6

ฉันขอแนะนำ (โปรดทราบว่าการเยื้องเป็นแท็บ):

tool: tool.o file1.o file2.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

หรือ

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o

ข้อเสนอแนะหลังจะดีกว่าเล็กน้อยเนื่องจากจะนำ GNU Make กฎมาใช้โดยปริยาย อย่างไรก็ตามในการทำงานไฟล์ต้นฉบับจะต้องมีชื่อเดียวกันกับไฟล์เรียกทำงานขั้นสุดท้าย (เช่น: tool.cและtool)

ประกาศไม่จำเป็นต้องประกาศแหล่งที่มา ไฟล์อ็อบเจ็กต์ระดับกลางถูกสร้างขึ้นโดยใช้กฎโดยนัย ดังนั้นMakefileงานนี้สำหรับ C และ C ++ (และสำหรับ Fortran ฯลฯ ... )

โดยค่าเริ่มต้น Makefile จะใช้$(CC)เป็นตัวเชื่อมโยงด้วย $(CC)ไม่ทำงานสำหรับการลิงก์ไฟล์วัตถุ C ++ เราปรับเปลี่ยนLINK.oเพียงเพราะสิ่งนั้น หากคุณต้องการรวบรวมรหัส C คุณไม่จำเป็นต้องบังคับLINK.oค่า

แน่นอนว่าคุณยังสามารถเพิ่มธงสะสมของคุณกับตัวแปรและเพิ่มห้องสมุดของคุณCFLAGS LDLIBSตัวอย่างเช่น:

CFLAGS = -Wall
LDLIBS = -lm

หมายเหตุด้านหนึ่ง: หากคุณต้องใช้ไลบรารีภายนอกฉันแนะนำให้ใช้ pkg-configเพื่อตั้งค่าอย่างถูกต้องCFLAGSและLDLIBS:

CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)

ผู้อ่านที่สนใจจะสังเกตเห็นว่าสิ่งนี้Makefileไม่ได้สร้างใหม่อย่างถูกต้องหากมีการเปลี่ยนแปลงส่วนหัว เพิ่มบรรทัดเหล่านี้เพื่อแก้ไขปัญหา:

override CPPFLAGS += -MMD
include $(wildcard *.d)

-MMDอนุญาตให้สร้างไฟล์. d ที่มีชิ้นส่วน Makefile เกี่ยวกับการขึ้นต่อกันของส่วนหัว บรรทัดที่สองเพิ่งใช้มัน

แน่นอน Makefile ที่เป็นลายลักษณ์อักษรที่ดีควรมีcleanและdistcleanกฎ:

clean:
    $(RM) *.o *.d

distclean: clean
    $(RM) tool

สังเกต$(RM)ว่าเท่ากับrm -fแต่เป็นวิธีปฏิบัติที่ดีที่จะไม่โทรrmโดยตรง

allกฎยังเป็นที่นิยม ในการทำงานควรเป็นกฎข้อแรกของไฟล์ของคุณ:

all: tool

คุณอาจเพิ่มinstallกฎ:

PREFIX = /usr/local
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

DESTDIRว่างเปล่าโดยค่าเริ่มต้น ผู้ใช้สามารถตั้งค่าให้ติดตั้งโปรแกรมของคุณที่ระบบทางเลือก (จำเป็นสำหรับกระบวนการรวบรวมข้าม) ดูแลแพคเกจสำหรับการกระจายหลายนอกจากนี้ยังอาจมีการเปลี่ยนแปลงเพื่อติดตั้งแพคเกจของคุณPREFIX/usr

หนึ่งคำสุดท้าย: อย่าวางไฟล์ต้นฉบับไว้ในไดเรกทอรีย่อย หากคุณต้องการทำเช่นนั้นให้เก็บไว้Makefileในไดเรกทอรีรากและใช้เส้นทางแบบเต็มเพื่อระบุไฟล์ของคุณ (เช่นsubdir/file.o)

ดังนั้นเพื่อสรุป Makefile แบบเต็มของคุณควรมีลักษณะดังนี้:

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)

all: tool
tool: tool.o file1.o file2.o
clean:
    $(RM) *.o *.d
distclean: clean
    $(RM) tool
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

ใกล้ถึงจุดสิ้นสุด: ไม่มีบรรทัดว่างระหว่างกฎหรือไม่ คำตอบของ John Knoellerอ้างว่า
Peter Mortensen

ไม่มีการดำเนินการตามmakeที่ฉันรู้ (GNU Make และ BSD Make) ต้องมีบรรทัดว่างระหว่างกฎ อย่างไรก็ตามมันมีmakeการใช้งานเป็นจำนวนมากพร้อมข้อบกพร่องของตัวเอง ^ Wspecificities
Jérôme Pouiller

5

ผมใช้คำตอบของ friedmud ฉันตรวจสอบสิ่งนี้เป็นระยะเวลาหนึ่งและดูเหมือนว่าจะเป็นวิธีที่ดีในการเริ่มต้น โซลูชันนี้ยังมีวิธีการที่กำหนดไว้อย่างดีในการเพิ่มธงคอมไพเลอร์ ฉันตอบอีกครั้งเพราะฉันทำการเปลี่ยนแปลงเพื่อให้มันทำงานในสภาพแวดล้อมของฉัน Ubuntu และ g ++ ตัวอย่างการทำงานเพิ่มเติมคือครูที่ดีที่สุดบางครั้ง

appname := myapp

CXX := g++
CXXFLAGS := -Wall -g

srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects  := $(patsubst %.cpp, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

Makefiles ดูเหมือนจะซับซ้อนมาก ฉันใช้อันใดอันหนึ่ง แต่เกิดข้อผิดพลาดเกี่ยวกับการไม่ลิงก์ในไลบรารี g ++ การกำหนดค่านี้แก้ไขปัญหานั้นได้

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