ฉันจะสร้าง Makefile สำหรับโปรเจ็กต์ C ที่มีไดเร็กทอรีย่อย SRC, OBJ และ BIN ได้อย่างไร


95

ไม่กี่เดือนที่ผ่านมาฉันคิดเรื่องทั่วไปMakefileสำหรับงานโรงเรียนดังต่อไปนี้:

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

นี้โดยทั่วไปจะรวบรวมทุก.cและ.hไฟล์เพื่อสร้าง.oไฟล์และปฏิบัติการprojectnameทั้งหมดในโฟลเดอร์เดียวกัน

ตอนนี้ฉันต้องการผลักดันสิ่งนี้เล็กน้อย ฉันจะเขียน Makefile เพื่อรวบรวมโปรเจ็กต์ C ด้วยโครงสร้างไดเร็กทอรีต่อไปนี้ได้อย่างไร

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

ในคำอื่น ๆ ผมอยากจะมี Makefile ที่รวบรวมแหล่ง C จาก./src/เข้าและแล้วทุกอย่างเชื่อมโยงไปสร้างในที่ปฏิบัติการ./obj/./bin/

ฉันพยายามอ่าน Makefiles ต่างๆแล้ว แต่ฉันไม่สามารถทำให้มันใช้งานได้กับโครงสร้างโครงการด้านบน แต่โครงการไม่สามารถรวบรวมข้อผิดพลาดทุกประเภท แน่นอนว่าฉันสามารถใช้ IDE แบบเต็มได้ (Monodevelop, Anjuta ฯลฯ ) แต่ฉันชอบที่จะใช้ gEdit และเทอร์มินัล ol ที่ดี

มีกูรูที่สามารถให้วิธีแก้ปัญหาหรือข้อมูลที่ชัดเจนเกี่ยวกับวิธีการนี้ได้หรือไม่? ขอบคุณ!

** อัปเดต (v4) **

ทางออกสุดท้าย:

# ------------------------------------------------
# Generic Makefile
#
# Author: yanick.rochon@gmail.com
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"

คำถามเฉพาะที่นี่คืออะไร?
Oliver Charlesworth

ฉันไม่แน่ใจว่าฉันเข้าใจสิ่งที่คุณต้องการทำ
ทอม

อัปเดตไฟล์Makefile. ฉันเข้าใกล้ แต่ฉันมีปัญหากับตัวแปรอัตโนมัติดังนั้นดูเหมือนว่าอย่างไรก็ตาม
Yanick Rochon

ฉันเพิ่งค้นพบวิธีแก้ปัญหา หากมีคนสนใจที่จะค้นหาสิ่งที่ดีกว่า Makefile ยังสามารถปรับปรุงได้
Yanick Rochon

2
@YanickRochon ฉันไม่ได้ตั้งใจจะวิจารณ์ทักษะภาษาอังกฤษของคุณ แต่สำหรับเป้าหมายปลอมจะทำให้ความรู้สึกใด ๆ ที่คุณแน่นอนที่สุดไม่สามารถเขียนกล้วย;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

คำตอบ:


34

ประการแรก$(OBJECTS)กฎของคุณมีปัญหาเนื่องจาก:

  1. มันเป็นสิ่งที่ไม่เลือกปฏิบัติทำให้แหล่งที่มาทั้งหมดมีข้อกำหนดเบื้องต้นของทุกออบเจ็กต์
  2. มักใช้แหล่งที่มาที่ไม่ถูกต้อง (ตามที่คุณค้นพบfile1.oและfile2.o)
  3. มันพยายามสร้างไฟล์ปฏิบัติการแทนที่จะหยุดอยู่ที่วัตถุและ
  4. ชื่อของเป้าหมาย ( foo.o) ไม่ใช่สิ่งที่กฎจะสร้าง ( obj/foo.o)

ฉันขอแนะนำสิ่งต่อไปนี้:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

$(TARGET)กฎมีปัญหาเดียวกันว่าชื่อเป้าหมายไม่จริงอธิบายสิ่งที่กฎสร้าง ด้วยเหตุนี้หากคุณพิมพ์makeหลายครั้ง Make จะสร้างเป้าหมายใหม่ทุกครั้งแม้ว่าจะไม่มีเหตุผลก็ตาม การเปลี่ยนแปลงเล็กน้อยแก้ไขว่า:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

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

แก้ไข:
ขออภัยฉันข้ามส่วนหนึ่งของ$(OBJECTS)กฎข้างต้นไป ฉันได้แก้ไขแล้ว (ฉันหวังว่าฉันจะใช้ "ขีดฆ่า" ในตัวอย่างโค้ด)


ด้วยการเปลี่ยนแปลงที่คุณแนะนำฉันได้รับ:obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here
Yanick Rochon

@ Yanick Rochon: คุณมีหลายmainฟังก์ชันหรือไม่? อาจจะเป็นหนึ่งในfile1.cหนึ่งในmain.c? ถ้าเป็นเช่นนั้นคุณจะไม่สามารถเชื่อมโยงวัตถุเหล่านี้ได้ สามารถมีได้เพียงหนึ่งเดียวmainในไฟล์ปฏิบัติการ
เบต้า

ไม่ฉันไม่ทำ ทุกอย่างทำงานได้ดีกับเวอร์ชันล่าสุดที่ฉันโพสต์ไว้ในคำถาม เมื่อฉันเปลี่ยน Makefile เป็นสิ่งที่คุณแนะนำ (และฉันเข้าใจประโยชน์ของสิ่งที่คุณพูด) นั่นคือสิ่งที่ฉันได้รับ ฉันเพิ่งวางfile1.cแต่มันให้ข้อความเดียวกันกับทุกไฟล์ของโครงการ และmain.cเป็นเครื่องเดียวที่มีฟังก์ชันหลัก ... และmain.cนำเข้าfile1.hและfile2.h(ไม่มีความสัมพันธ์ระหว่างfile1.cและfile2.c) แต่ฉันสงสัยว่าปัญหามาจากที่นั่น
Yanick Rochon

@ Yanick Rochon: ฉันทำผิดพลาดในการวางบรรทัดแรกของ$(OBJECTS)กฎของฉัน ฉันแก้ไขแล้ว ด้วยสายเสียฉันได้รับข้อผิดพลาด แต่ไม่ใช่รายการที่คุณได้รับ ...
Beta

6

คุณสามารถเพิ่ม-Iแฟล็กลงในแฟล็กคอมไพเลอร์ (CFLAGS) เพื่อระบุตำแหน่งที่คอมไพลเลอร์ควรค้นหาไฟล์ต้นทางและแฟล็ก -o เพื่อระบุตำแหน่งที่ควรปล่อยไบนารี:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

ในการวางไฟล์ออบเจ็กต์ลงในobjไดเร็กทอรีให้ใช้-oอ็อพชันเมื่อคอมไพล์ นอกจากนี้ให้ดูที่$@และอัตโนมัติตัวแปร$<

ตัวอย่างเช่นพิจารณา Makefile แบบธรรมดานี้

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

%.o: %.c 
   $(CC) $(CFLAGS) -c $< -o $(OBJDIR)/$@

อัปเดต>

จากการดู makefile ของคุณฉันรู้ว่าคุณกำลังใช้-oแฟล็ก ดี. ใช้มันต่อไป แต่เพิ่มตัวแปรไดเร็กทอรีเป้าหมายเพื่อระบุตำแหน่งที่ควรเขียนไฟล์เอาต์พุต


คุณสามารถเจาะจงมากขึ้นได้ไหม คุณหมายถึงการเพิ่ม-l ...ไปCFLAGSและ ... มีแล้ว-oอาร์กิวเมนต์ลิงเกอร์ ( LINKER)
Yanick Rochon

ใช่ CFLAGS และใช่ใช้ -o ต่อไปเพียงแค่เพิ่มตัวแปร TARGETPATH
ทอม

ขอบคุณฉันได้ทำการแก้ไขแล้ว แต่ดูเหมือนว่าฉันยังขาดอะไรบางอย่างอยู่ (ดูการอัปเดตในคำถาม)
Yanick Rochon

เพียงแค่makeจากที่ Makefile นั่ง
Yanick Rochon

คุณไม่สามารถอ่านคำสั่งที่กำลังดำเนินการได้หรือไม่? ตัวอย่างเช่น gcc -c yadayada ค่อนข้างแน่ใจว่ามีตัวแปรที่ไม่มีสิ่งที่คุณคาดหวัง
ทอม

-1

วันนี้ฉันหยุดเขียน makefiles แล้วถ้าคุณตั้งใจจะเรียนรู้ต่อไปมิฉะนั้นคุณจะมีเครื่องกำเนิด makefile ที่ดีที่มาพร้อมกับ eclipse CDT หากคุณต้องการความสามารถในการบำรุงรักษา / การสนับสนุนหลายโครงการในโครงสร้างการสร้างของคุณโปรดดูสิ่งต่อไปนี้ -

https://github.com/dmoulding/boilermakeเจอดีงาม .. !


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