Makefile dengan file sumber di direktori yang berbeda

135

Saya punya proyek di mana struktur direktori seperti ini:

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

Bagaimana saya harus menulis file makefile yang berada di bagian / src (atau di mana pun benar-benar) yang dapat mengisi / menautkan pada file sumber c / c ++ di bagian? / Src?

Dapatkah saya melakukan sesuatu seperti -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Jika itu akan berhasil, apakah ada cara yang lebih mudah untuk melakukannya. Saya telah melihat proyek di mana ada file makefile di masing-masing bagian yang sesuai? folder. [di posting ini saya menggunakan tanda tanya seperti pada sintaks bash]

devin
sumber
1
Dalam manual gnu asli ( gnu.org/software/make/manual/html_node/Phony-Target.html ) di bawah Phony Sasaran ada contoh recursive invocation, bahwa coule akan cukup elegan.
Frank Nocke
Alat apa yang Anda gunakan untuk membuat grafik teks itu?
Khalid Hussain

Jawaban:

114

Cara tradisional adalah untuk memiliki Makefiledi setiap subdirektori ( part1, part2, dll) yang memungkinkan Anda untuk membangun mereka secara independen. Selanjutnya, miliki Makefiledi direktori root proyek yang membangun semuanya. "Root" Makefileakan terlihat seperti berikut:

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

Karena setiap baris dalam target make dijalankan di shell-nya sendiri, tidak perlu khawatir tentang melintasi kembali pohon direktori atau ke direktori lain.

Saya sarankan melihat GNU make manual bagian 5.7 ; ini sangat membantu.

akhan
sumber
26
Ini harus menjadi +$(MAKE) -C part1dll. Ini memungkinkan kontrol pekerjaan Make untuk bekerja ke dalam subdirektori.
ephemient
26
Ini adalah pendekatan klasik dan digunakan secara luas, tetapi kurang optimal dalam beberapa hal yang menjadi lebih buruk seiring dengan pertumbuhan proyek. Dave Hinton memiliki pointer untuk diikuti.
dmckee --- ex-moderator kitten
89

Jika Anda memiliki kode dalam satu subdirektori bergantung pada kode di subdirektori lain, Anda mungkin lebih baik dengan satu makefile di tingkat atas.

Lihat Membuat Rekursif Dianggap Berbahaya untuk dasar pemikiran penuh, tetapi pada dasarnya Anda ingin membuat memiliki informasi lengkap yang diperlukan untuk memutuskan apakah suatu file perlu dibangun kembali atau tidak, dan tidak akan ada jika Anda hanya memberi tahu sekitar sepertiga dari proyek Anda.

Tautan di atas tampaknya tidak dapat dijangkau. Dokumen yang sama dapat dijangkau di sini:

dave4420
sumber
3
Terima kasih, tidak sadar akan hal ini. Sangat berguna untuk mengetahui "cara yang benar" dalam melakukan sesuatu alih-alih cara yang "hanya bekerja" atau diterima sebagai standar.
tjklemz
3
Merek rekursif dianggap berbahaya, kembali ketika itu benar-benar tidak berfungsi dengan baik. Tidak dianggap berbahaya akhir-akhir ini, pada kenyataannya, ini adalah cara autotools / automake mengelola proyek yang lebih besar.
Edwin Buck
36

Opsi VPATH mungkin berguna, yang memberi tahu direktori apa yang harus dicari untuk kode sumber. Anda masih membutuhkan opsi -I untuk setiap path yang disertakan. Sebuah contoh:

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

OutputExecutable: part1api.o part2api.o part3api.o

Ini secara otomatis akan menemukan file partXapi.cpp yang cocok di salah satu direktori yang ditentukan VPATH dan mengkompilasinya. Namun, ini lebih berguna ketika direktori src Anda dipecah menjadi subdirektori. Untuk apa yang Anda gambarkan, seperti yang dikatakan orang lain, Anda mungkin lebih baik dengan makefile untuk setiap bagian, terutama jika setiap bagian dapat berdiri sendiri.

CodeGoat
sumber
2
Saya tidak percaya jawaban sempurna sederhana ini tidak mendapat lebih banyak suara. Ini +1 dari saya.
Nicholas Hamilton
2
Saya punya beberapa file sumber umum di direktori yang lebih tinggi untuk beberapa proyek berbeda dalam subfolder, VPATH=..bekerja untuk saya!
EkriirkE
23

Anda dapat menambahkan aturan ke root Makefile Anda untuk mengkompilasi file cpp yang diperlukan di direktori lain. Contoh Makefile di bawah ini harus menjadi awal yang baik untuk membawa Anda ke tempat yang Anda inginkan.

CC = g ++
TARGET = cppTest
OTHERDIR = .. / .. / someotherpath / in / project / src

SOURCE = cppTest.cpp
SOURCE = $ (OTHERDIR) /file.cpp

## Definisi sumber akhir
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
TERMASUK = -I. $ (OTHERDIR) /../ inc
## dan lebih banyak lagi termasuk

VPATH = $ (OTHERDIR)
OBJ = $ (gabung $ (adduffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o)))) 

## Perbaiki tujuan ketergantungan menjadi ../.dep relatif terhadap direktori src
TERGANTUNG = $ (gabung $ (adduffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Aturan default dijalankan
semua: $ (TARGET)
        @benar

## Aturan Bersih
bersih:
        @ -rm -f $ (TARGET) $ (OBJ) $ (TERGANTUNG)


## Aturan untuk membuat target yang sebenarnya
$ (TARGET): $ (OBJ)
        @echo "============="
        @echo "Menghubungkan target $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Tautan selesai -

## Aturan kompilasi generik
% .o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Kompilasi $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Aturan untuk file objek dari file cpp
## File objek untuk setiap file diletakkan di direktori obj
## naik satu tingkat dari direktori sumber aktual.
../obj/%.o:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Kompilasi $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Aturan untuk "direktori lain" Anda akan memerlukan satu per direktori "lainnya"
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo "Kompilasi $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Buat aturan ketergantungan
../.dep/%.d:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Membangun file dependensi sebesar $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Aturan ketergantungan untuk direktori "lain"
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (dir $ @)
        @echo "============="
        @echo Membangun file dependensi sebesar $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Sertakan file ketergantungan
-sertakan $ (TERGANTUNG)

RC.
sumber
7
Saya tahu ini sudah cukup lama tetapi, bukankah SUMBER dan TERMASUK ditimpa oleh tugas lain satu baris kemudian?
skelliam
@skelliam ya, benar.
nabroyan
1
Pendekatan ini melanggar prinsip KERING. Merupakan ide yang buruk untuk menduplikasi kode untuk setiap "direktori lain"
Jesus H
20

Jika sumber tersebar di banyak folder, dan masuk akal untuk memiliki Makefile individual maka seperti yang disarankan sebelumnya, make rekursif adalah pendekatan yang baik, tetapi untuk proyek yang lebih kecil saya merasa lebih mudah untuk membuat daftar semua file sumber di Makefile dengan jalur relatif mereka ke Makefile seperti ini:

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

Saya kemudian dapat mengatur VPATHseperti ini:

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

Lalu saya membangun objek:

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

Sekarang aturannya sederhana:

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

Dan membangun output lebih mudah:

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

Satu bahkan dapat membuat VPATHgenerasi otomatis oleh:

VPATH := $(dir $(COMMON_SRC))

Atau menggunakan fakta yang sortmenghapus duplikat (meskipun seharusnya tidak masalah):

VPATH := $(sort  $(dir $(COMMON_SRC)))
gagah
sumber
ini berfungsi baik untuk proyek yang lebih kecil di mana Anda hanya ingin menambahkan beberapa perpustakaan khusus dan memilikinya di folder yang terpisah. Terima kasih banyak
zitroneneis
6

Saya pikir lebih baik menunjukkan bahwa menggunakan Make (rekursif atau tidak) adalah sesuatu yang biasanya Anda ingin hindari, karena dibandingkan dengan alat saat ini, sulit untuk belajar, mempertahankan, dan skala.

Ini adalah alat yang luar biasa tetapi penggunaan langsungnya harus dianggap usang pada tahun 2010+.

Kecuali, tentu saja, Anda bekerja di lingkungan khusus yaitu dengan proyek warisan dll.

Gunakan IDE, CMake atau, jika Anda kesulitan, Autotools .

(diedit karena downvotes, ty Honza untuk menunjukkan)

IlDan
sumber
1
Akan bagus untuk menjelaskan downvotes. Saya juga membuat Makefiles sendiri.
IlDan
2
Saya mendukung saran "jangan lakukan itu" - KDevelop memiliki antarmuka yang sangat grafis untuk mengkonfigurasi Make dan sistem build lainnya, dan sementara saya menulis Makefiles sendiri, KDevelop adalah apa yang saya berikan kepada teman kerja saya - tetapi saya jangan berpikir tautan Autotools membantu. Untuk memahami Autotools, Anda perlu memahami m4, libtool, automake, autoconf, shell, make, dan pada dasarnya seluruh tumpukan.
ephemient
7
Downvotes mungkin karena Anda menjawab pertanyaan Anda sendiri. Pertanyaannya bukan tentang apakah seseorang harus menggunakan Makefiles atau tidak. Itu tentang bagaimana menulisnya.
Honza
8
alangkah baiknya jika Anda bisa, dalam beberapa kalimat, menjelaskan bagaimana alat modern membuat hidup lebih mudah daripada menulis Makefile. Apakah mereka memberikan abstraksi tingkat yang lebih tinggi? atau apa?
Abhishek Anand
1
xterm, vi, bash, makefile == menguasai dunia, cmake adalah untuk orang yang tidak bisa meretas kode.
μολὼν.λαβέ
2

Posting RC sangat bermanfaat. Saya tidak pernah berpikir untuk menggunakan fungsi $ (dir $ @), tetapi ia melakukan apa yang saya butuhkan.

Di parentDir, memiliki banyak direktori dengan file sumber di dalamnya: dirA, dirB, dirC. Berbagai file bergantung pada file objek di direktori lain, jadi saya ingin dapat membuat satu file dari dalam satu direktori, dan meminta agar membuat dependensi itu dengan memanggil makefile yang terkait dengan dependensi itu.

Pada dasarnya, saya membuat satu Makefile di parentDir yang memiliki (di antara banyak hal lain) aturan umum yang mirip dengan RC:

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

Setiap subdirektori menyertakan makefile tingkat atas ini untuk mewarisi aturan umum ini. Di Makefile masing-masing subdirektori, saya menulis aturan khusus untuk setiap file sehingga saya bisa melacak semua yang bergantung pada masing-masing file.

Setiap kali saya perlu membuat file, saya menggunakan (pada dasarnya) aturan ini untuk secara rekursif membuat semua / semua dependensi. Sempurna!

CATATAN: ada utilitas yang disebut "makepp" yang tampaknya melakukan tugas ini bahkan lebih secara intuitif, tetapi demi portabilitas dan tidak bergantung pada alat lain, saya memilih untuk melakukannya dengan cara ini.

Semoga ini membantu!

jvriesem
sumber
2

Penggunaan Make secara Rekursif

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

Ini memungkinkan untuk makedipisah menjadi beberapa pekerjaan dan menggunakan banyak core

EhevuTov
sumber
2
Bagaimana ini lebih baik daripada melakukan sesuatu seperti make -j4?
devin
1
@devin seperti yang saya lihat, itu tidak lebih baik , itu hanya memungkinkan make untuk menggunakan kontrol pekerjaan sama sekali. Saat menjalankan proses make yang terpisah, itu tidak tunduk pada kontrol pekerjaan.
Michael Pankov
1

Saya mencari sesuatu seperti ini dan setelah beberapa kali mencoba dan jatuh saya membuat makefile saya sendiri, saya tahu itu bukan "cara idiomatis" tetapi ini adalah permulaan untuk memahami make dan ini bekerja untuk saya, mungkin Anda bisa mencoba dalam proyek Anda.

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!

Saya tahu itu sederhana dan bagi sebagian orang flag saya salah, tetapi seperti yang saya katakan ini adalah Makefile pertama saya untuk mengkompilasi proyek saya dalam beberapa dir dan menghubungkan semua itu bersama-sama untuk membuat bin saya.

Saya menerima saran: D

Matheus Vinícius de Andrade
sumber
-1

Saya sarankan untuk menggunakan autotools:

//## Tempatkan file objek yang dihasilkan (.o) ke direktori yang sama dengan file sumbernya, untuk menghindari tabrakan saat make non-rekursif digunakan.

AUTOMAKE_OPTIONS = subdir-objects

hanya memasukkannya ke dalam Makefile.amhal-hal sederhana lainnya.

Ini tutorialnya .

pengguna2763812
sumber