Bagaimana cara membuat Makefile untuk proyek C dengan subdirektori SRC, OBJ, dan BIN?

95

Beberapa bulan yang lalu, saya datang dengan generik berikut Makefileuntuk tugas sekolah:

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# 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!"

Ini pada dasarnya akan mengkompilasi setiap .cdan .hfile untuk menghasilkan .ofile dan projectnamesemua yang dapat dieksekusi dalam folder yang sama.

Sekarang, saya ingin mendorongnya sedikit. Bagaimana cara menulis Makefile untuk mengkompilasi proyek C dengan struktur direktori berikut?

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

Dengan kata lain, saya ingin memiliki Makefile yang mengkompilasi sumber C dari ./src/ke ./obj/dan kemudian menautkan semuanya untuk membuat file yang dapat dieksekusi ./bin/.

Saya telah mencoba membaca Makefile yang berbeda, tetapi saya tidak dapat membuatnya berfungsi untuk struktur proyek di atas; sebaliknya, proyek gagal untuk dikompilasi dengan segala macam kesalahan. Tentu, saya bisa menggunakan IDE yang lengkap (Monodevelop, Anjuta, dll.), Tetapi sejujurnya saya lebih suka menggunakan gEdit dan terminal lama yang bagus.

Apakah ada guru yang bisa memberi saya solusi yang berfungsi, atau informasi yang jelas tentang bagaimana ini bisa dilakukan? Terima kasih!

** PERBARUI (v4) **

Solusi terakhir:

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# 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!"
Yanick Rochon
sumber
Apa pertanyaan spesifiknya di sini?
Oliver Charlesworth
Saya tidak yakin saya mengerti apa yang ingin Anda lakukan.
Tom
Memperbarui Makefile. Saya semakin dekat, tetapi saya mengalami masalah dengan variabel otomatis, jadi sepertinya
Yanick Rochon
Saya baru saja menemukan solusi. Jika seseorang ingin menemukan sesuatu yang lebih baik, Makefile masih bisa ditingkatkan.
Yanick Rochon
2
@YanickRochon Saya tidak bermaksud mengkritik kemampuan bahasa Inggris Anda. Tetapi agar target PHONY masuk akal, Anda pasti tidak dapat menulis BANANA;) gnu.org/software/make/manual/html_node/Phony-T Target.html
joni

Jawaban:

34

Pertama, $(OBJECTS)aturan Anda bermasalah, karena:

  1. tidak pandang bulu, menjadikan semua sumber prasyarat dari setiap objek,
  2. itu sering menggunakan sumber yang salah (seperti yang Anda temukan dengan file1.odan file2.o)
  3. ia mencoba membangun file yang dapat dieksekusi alih-alih berhenti di objek, dan
  4. nama target ( foo.o) bukanlah yang sebenarnya akan dihasilkan oleh aturan ( obj/foo.o).

Saya menyarankan yang berikut:

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

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

The $(TARGET)aturan memiliki masalah yang sama bahwa nama target tidak benar-benar menggambarkan apa aturan membangun. Oleh karena itu, jika Anda mengetik makebeberapa kali, Make akan membangun kembali target setiap kali, meskipun tidak ada alasan untuk itu. Perubahan kecil memperbaiki bahwa:

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

Setelah semuanya beres, Anda mungkin mempertimbangkan penanganan ketergantungan yang lebih canggih; jika Anda memodifikasi salah satu file header, makefile ini tidak akan tahu objek / executable mana yang harus dibangun kembali. Tapi itu bisa menunggu hari lain.

EDIT:
Maaf, saya menghilangkan bagian dari $(OBJECTS)aturan di atas; Saya sudah memperbaikinya. (Saya harap saya dapat menggunakan "teguran" di dalam sampel kode.)

Beta
sumber
dengan perubahan yang Anda sarankan, saya mendapatkan: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: Apakah Anda memiliki banyak mainfungsi? Mungkin satu masuk file1.cdan satu masuk main.c? Jika demikian, Anda tidak akan dapat menghubungkan objek-objek ini; hanya ada satu mainfile yang dapat dieksekusi.
Beta
Tidak, saya tidak. Semuanya berfungsi dengan baik dengan versi terakhir yang saya posting di pertanyaan. Ketika saya mengubah Makefile saya menjadi apa yang Anda sarankan (dan saya memahami manfaat dari apa yang Anda katakan) itulah yang saya dapatkan. Saya baru saja menempelkan file1.ctetapi memberikan pesan yang sama ke setiap file proyek. Dan main.cadalah satu - satunya dengan fungsi utama ... dan main.cimpor file1.hdan file2.h(tidak ada hubungan antara file1.cdan file2.c), tapi saya ragu masalahnya berasal dari sana.
Yanick Rochon
@Yanick Rochon: Saya membuat kesalahan saat menempelkan baris pertama $(OBJECTS)aturan saya ; Saya sudah mengeditnya. Dengan garis buruk saya mendapat kesalahan, tapi bukan yang Anda dapatkan ...
Beta
6

Anda bisa menambahkan -Itanda ke tanda kompilator (CFLAGS) untuk menunjukkan di mana kompilator harus mencari file sumber, dan tanda -o untuk menunjukkan di mana biner harus ditinggalkan:

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

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

Untuk meletakkan file objek ke dalam objdirektori, gunakan -oopsi saat kompilasi. Juga, lihat variabel$@ dan $< otomatis .

Misalnya, pertimbangkan Makefile sederhana ini

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

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

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

Perbarui>

Dengan melihat makefile Anda, saya menyadari Anda menggunakan -obendera. Baik. Lanjutkan menggunakannya, tetapi tambahkan variabel direktori target untuk menunjukkan di mana file keluaran harus ditulis.

Tom
sumber
Bisakah kamu lebih spesifik? Apakah maksud Anda menambahkan -l ...ke CFLAGSdan ... sudah ada -oargumen ke linker ( LINKER)
Yanick Rochon
Ya, CFLAGS, dan ya, tetap gunakan -o, tambahkan saja variabel TARGETPATH.
Tom
Terima kasih, saya telah melakukan modifikasi, tetapi sepertinya saya masih melewatkan sesuatu (lihat pembaruan tentang pertanyaan)
Yanick Rochon
hanya make, dari tempat Makefile duduk
Yanick Rochon
Tidak bisakah Anda membaca perintah yang sedang dieksekusi? misalnya gcc -c yadayada. Cukup yakin ada variabel yang tidak mengandung apa yang Anda harapkan
Tom
-1

Saya telah berhenti menulis makefile akhir-akhir ini, jika niat Anda untuk belajar, silakan, jika tidak, Anda memiliki generator makefile yang bagus yang dilengkapi dengan CDT eclipse. Jika Anda menginginkan pemeliharaan / beberapa dukungan proyek dalam pohon build Anda, lihat yang berikut ini -

https://github.com/dmoulding/boilermake Menurut saya ini cukup bagus ..!

Kamath
sumber
3
Berbasis opini. Tidak menjawab pertanyaan OP. Mengasumsikan lingkungan Eclipse.
Nathaniel Johnson