Buat direktori menggunakan file make

101

Saya sangat baru di makefile dan saya ingin membuat direktori menggunakan makefile. Direktori proyek saya seperti ini

+--Project  
   +--output  
   +--source  
     +Testfile.cpp  
   +Makefile  

Saya ingin meletakkan semua objek dan keluaran ke folder keluaran masing-masing. Saya ingin membuat struktur folder yang akan menjadi seperti ini setelah kompilasi.

+--Project
   +--output
     +--debug (or release)
       +--objs
         +Testfile.o
       +Testfile (my executable file)
   +--source
     +Testfile.cpp
   +Makefile

Saya mencoba dengan beberapa pilihan, tetapi tidak berhasil. Tolong bantu saya membuat direktori menggunakan make file. Saya memposting Makefile saya untuk pertimbangan Anda.

#---------------------------------------------------------------------
# Input dirs, names, files
#---------------------------------------------------------------------
OUTPUT_ROOT := output/

TITLE_NAME := TestProj 

ifdef DEBUG 
    TITLE_NAME += _DEBUG
else
ifdef RELEASE
    TITLE_NAME += _RELEASE
endif
endif


# Include all the source files here with the directory tree
SOURCES := \
        source/TestFile.cpp \

#---------------------------------------------------------------------
# configs
#---------------------------------------------------------------------
ifdef DEBUG
OUT_DIR     := $(OUTPUT_ROOT)debug
CC_FLAGS    := -c -Wall
else
ifdef RELEASE
OUT_DIR     := $(OUTPUT_ROOT)release
CC_FLAGS    := -c -Wall
else
$(error no build type defined)
endif
endif

# Put objects in the output directory.
OUT_O_DIR   := $(OUT_DIR)/objs

#---------------------------------------------------------------------
# settings
#---------------------------------------------------------------------
OBJS = $(SOURCES:.cpp=.o)
DIRS = $(subst /,/,$(sort $(dir $(OBJS))))
DIR_TARGET = $(OUT_DIR)

OUTPUT_TARGET = $(OUT_DIR)/$(TITLE_NAME)

CC_FLAGS +=   

LCF_FLAGS := 

LD_FLAGS := 

#---------------------------------------------------------------------
# executables
#---------------------------------------------------------------------
MD := mkdir
RM := rm
CC := g++

#---------------------------------------------------------------------
# rules
#---------------------------------------------------------------------
.PHONY: all clean title 

all: title 

clean:
    $(RM) -rf $(OUT_DIR)

$(DIR_TARGET):
    $(MD) -p $(DIRS)

.cpp.o: 
    @$(CC) -c $< -o $@

$(OBJS): $(OUT_O_DIR)/%.o: %.cpp
    @$(CC) -c $< -o $@

title: $(DIR_TARGET) $(OBJS)

Terima kasih sebelumnya. Tolong bimbing saya jika saya membuat kesalahan juga.

Yabes
sumber

Jawaban:

88

Ini akan melakukannya - dengan asumsi lingkungan mirip Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

${OUT_DIR}:
        ${MKDIR_P} ${OUT_DIR}

Ini harus dijalankan di direktori level teratas - atau definisi $ {OUT_DIR} harus benar relatif terhadap tempatnya dijalankan. Tentu saja, jika Anda mengikuti dekrit dari makalah Peter Miller " Rekursif Membuat Dianggap Berbahaya ", Anda akan menjalankan make di direktori tingkat atas.

Saya sedang bermain dengan (RMCH) ini sekarang. Dibutuhkan sedikit adaptasi dengan rangkaian perangkat lunak yang saya gunakan sebagai uji coba. Suite ini memiliki selusin program terpisah yang dibangun dengan sumber yang tersebar di 15 direktori, beberapa di antaranya dibagikan. Tapi dengan sedikit hati-hati, itu bisa dilakukan. OTOH, ini mungkin tidak cocok untuk pemula.


Seperti disebutkan dalam komentar, mencantumkan perintah 'mkdir' sebagai tindakan untuk 'direktori' salah. Seperti yang juga disebutkan di komentar, ada cara lain untuk memperbaiki kesalahan 'tidak tahu bagaimana membuat output / debug' yang dihasilkan. Salah satunya adalah menghapus ketergantungan pada baris 'direktori'. Ini berfungsi karena 'mkdir -p' tidak menghasilkan kesalahan jika semua direktori yang diminta untuk dibuat sudah ada. Yang lainnya adalah mekanisme yang ditunjukkan, yang hanya akan mencoba membuat direktori jika tidak ada. Versi 'sebagaimana telah diubah' adalah apa yang ada dalam pikiran saya tadi malam - tetapi kedua teknik bekerja (dan keduanya memiliki masalah jika output / debug ada tetapi lebih merupakan file daripada direktori).

Jonathan Leffler
sumber
Terima kasih Jonathan. ketika saya mencoba itu saya mendapat kesalahan "make: *** Tidak ada aturan untuk membuat output/debug', needed by direktori target '. Berhenti." Tapi saya tidak akan khawatir tentang itu sekarang. akan tetap berpegang pada aturan dasar. :). Terima kasih telah membimbing. Dan saya menjalankan "make" hanya dari direktori tingkat atas.
Yabes
Hapus saja $ {OUT_DIR} di belakang direktori :, maka itu akan berfungsi.
Doc Brown
Menerapkan ini mengharuskan Anda menangkap setiap kemungkinan kasus di mana Anda menggunakan direktori dari baris perintah. Selain itu, Anda tidak dapat membuat aturan pembuatan file bergantung pada directoriestanpa menyebabkannya selalu membangun kembali ..
mtalexan
@mtalexan Anda telah memberikan beberapa komentar yang menjelaskan apa yang salah dengan beberapa jawaban ini tetapi Anda belum mengajukan jawaban alternatif. Ingin tahu solusi Anda untuk masalah ini.
Samuel
@Samuel Saya menunjukkan masalah tanpa memberikan solusi karena saya mencari hal yang sama dan tidak pernah menemukan solusi. Saya akhirnya hanya berurusan dengan jatuhnya solusi yang kurang ideal.
mtalexan
135

Menurut pendapat saya, direktori tidak boleh dianggap sebagai target makefile Anda, baik secara teknis maupun desain. Anda harus membuat file dan jika pembuatan file membutuhkan direktori baru, maka diam-diam buat direktori di dalam aturan untuk file yang relevan.

Jika Anda menargetkan file biasa atau "berpola", cukup gunakan makevariabel internal $(@D), yang berarti "direktori tempat target saat ini berada" (cmp. Dengan $@untuk target). Sebagai contoh,

$(OUT_O_DIR)/%.o: %.cpp
        @mkdir -p $(@D)
        @$(CC) -c $< -o $@

title: $(OBJS)

Kemudian, Anda secara efektif melakukan hal yang sama: membuat direktori untuk semua $(OBJS), tetapi Anda akan melakukannya dengan cara yang tidak terlalu rumit.

Kebijakan yang sama (file adalah target, direktori tidak pernah) digunakan dalam berbagai aplikasi. Misalnya, gitsistem kontrol revisi tidak menyimpan direktori.


Catatan: Jika Anda akan menggunakannya, mungkin berguna untuk memperkenalkan variabel kemudahan dan memanfaatkan makeaturan perluasan.

dir_guard=@mkdir -p $(@D)

$(OUT_O_DIR)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -c $< -o $@

$(OUT_O_DIR_DEBUG)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -g -c $< -o $@

title: $(OBJS)
P Shved
sumber
12
Meskipun menautkan persyaratan direktori ke file adalah opsi yang lebih baik menurut saya, solusi Anda juga memiliki kelemahan yang signifikan bahwa proses mkdir akan dipanggil oleh file make untuk setiap file yang dibangun kembali, yang sebagian besar tidak perlu membuat direktori lagi. Ketika diadaptasi ke sistem build non-Linux seperti Windows, ini sebenarnya menyebabkan output kesalahan yang tidak dapat diblokir karena tidak ada -p yang setara dengan perintah mkdir, dan yang lebih penting adalah overhead dalam jumlah besar karena pemanggilan shell tidak invasif minimal.
mtalexan
1
Alih-alih langsung memanggil mkdir, saya melakukan hal berikut untuk menghindari mencoba membuat direktori jika sudah ada: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady
26

Atau, Cium.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Ini akan membuat semua direktori setelah Makefile diurai.

Nick Zalutskiy
sumber
3
Saya suka pendekatan ini karena saya tidak perlu mengacaukan setiap target dengan perintah untuk menangani direktori.
Ken Williams
1
Saya hanya perlu membuat direktori jika tidak ada. Jawaban ini sangat cocok untuk masalah saya.
Jeff Pal
Tidak hanya itu, ini mencegah stempel waktu yang dimodifikasi dari setiap direktori memicu langkah build yang tidak perlu. Ini harus menjadi jawabannya
Assimilater
6
Ini lebih baik: $(info $(shell mkdir -p $(DIRS)))Tanpa $(info ...), output dari mkdirperintah akan ditempelkan ke Makefile , yang menyebabkan kesalahan sintaks paling baik. The $(info ...)panggilan memastikan bahwa a) kesalahan (jika ada) yang terlihat kepada pengguna, dan b) bahwa fungsi panggilan mengembang apa-apa.
cmaster
10

makedi, dan di luar dirinya sendiri, menangani target direktori sama seperti target file. Jadi, mudah untuk menulis aturan seperti ini:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Satu-satunya masalah adalah, bahwa stempel waktu direktori bergantung pada apa yang dilakukan pada file di dalamnya. Untuk aturan di atas, ini mengarah pada hasil sebagai berikut:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget

Ini jelas bukan yang Anda inginkan. Setiap kali Anda menyentuh file tersebut, Anda juga menyentuh direktori. Dan karena file tersebut bergantung pada direktori, akibatnya file tersebut tampak kedaluwarsa, memaksanya untuk dibangun kembali.

Namun, Anda dapat dengan mudah menghentikan putaran ini dengan memberi tahu make untuk mengabaikan stempel waktu direktori . Ini dilakukan dengan mendeklarasikan direktori sebagai prasyarat hanya-pesanan:

# The pipe symbol tells make that the following prerequisites are order-only
#                           |
#                           v
outDir/someTarget: Makefile | outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Ini dengan benar menghasilkan:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
make: 'outDir/someTarget' is up to date.

TL; DR:

Tulis aturan untuk membuat direktori:

$(OUT_DIR):
    mkdir -p $(OUT_DIR)

Dan memiliki target untuk barang-barang di dalamnya bergantung pada direktori yang hanya dapat dipesan:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)
cmaster - kembalikan monica
sumber
Pada OS / FS rusak apa yang Anda lihat touchmengubah data stat di direktori induk? Itu tidak masuk akal bagi saya. Mtime dir hanya bergantung pada nama file yang dikandungnya. Saya tidak dapat mereproduksi masalah Anda.
Johan Boulé
@ JohanEdit
cmaster - memulihkan monica
Dan apakah Anda mengisi bug untuk perilaku yang rusak seperti itu?
Johan Boulé
6

Semua solusi termasuk yang diterima memiliki beberapa masalah seperti yang dinyatakan dalam komentar masing-masing. The diterima jawaban dengan @ jonathan-Leffler sudah cukup baik tetapi tidak memperhitungkan efek itu prasyarat belum tentu akan dibangun dalam rangka (selama make -jmisalnya). Namun hanya memindahkan directoriesprasyarat dari alluntuk programmemprovokasi pembangunan kembali pada setiap menjalankan AFAICT. Solusi berikut tidak memiliki masalah itu dan AFAICS berfungsi sebagaimana mestinya.

MKDIR_P := mkdir -p
OUT_DIR := build

.PHONY: directories all clean

all: $(OUT_DIR)/program

directories: $(OUT_DIR)

$(OUT_DIR):
    ${MKDIR_P} $(OUT_DIR)

$(OUT_DIR)/program: | directories
    touch $(OUT_DIR)/program

clean:
    rm -rf $(OUT_DIR)
stefanct
sumber
4

Saya baru saja menemukan solusi yang cukup masuk akal yang memungkinkan Anda menentukan file yang akan dibangun dan direktori dibuat secara otomatis. Pertama, tentukan variabel ALL_TARGET_FILESyang menyimpan nama file dari setiap file yang akan dibuat oleh makefile Anda. Kemudian gunakan kode berikut:

define depend_on_dir
$(1): | $(dir $(1))

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p $$@

$(dir $(1))_DIRECTORY_RULE_IS_DEFINED := 1
endif
endef

$(foreach file,$(ALL_TARGET_FILES),$(eval $(call depend_on_dir,$(file))))

Begini cara kerjanya. Saya mendefinisikan sebuah fungsi depend_on_diryang mengambil nama file dan menghasilkan aturan yang membuat file bergantung pada direktori yang berisi dan kemudian mendefinisikan aturan untuk membuat direktori itu jika perlu. Kemudian saya menggunakan foreachuntuk callfungsi ini pada setiap nama file daneval hasilnya.

Perhatikan bahwa Anda memerlukan versi GNU make yang mendukung eval, yang menurut saya adalah versi 3.81 dan lebih tinggi.

Ryan C. Thompson
sumber
Membuat variabel yang menampung "nama file dari setiap file yang akan dibuat oleh makefile Anda" adalah jenis persyaratan yang berat - Saya suka menentukan target tingkat atas saya, lalu hal-hal yang mereka andalkan, dan seterusnya. Memiliki daftar datar dari semua file target bertentangan dengan sifat hierarki dari spesifikasi makefile, dan mungkin tidak (dengan mudah) dimungkinkan ketika file target bergantung pada perhitungan runtime.
Ken Williams
3

mengingat Anda adalah seorang pemula, saya akan mengatakan jangan mencoba melakukan ini dulu. itu pasti mungkin, tetapi akan memperumit Makefile Anda tidak perlu. tetap berpegang pada cara sederhana sampai Anda lebih nyaman dengan make.

Meskipun demikian , salah satu cara untuk membangun direktori yang berbeda dari direktori sumber adalah VPATH ; Saya lebih suka aturan pola

hanya seseorang
sumber
3

Kemandirian OS sangat penting bagi saya, jadi mkdir -pbukan pilihan. Saya membuat rangkaian fungsi ini yang digunakan evaluntuk membuat target direktori dengan prasyarat pada direktori induk. Ini memiliki manfaat yang make -j 2akan berfungsi tanpa masalah karena dependensi ditentukan dengan benar.

# convenience function for getting parent directory, will eventually return ./
#     $(call get_parent_dir,somewhere/on/earth/) -> somewhere/on/
get_parent_dir=$(dir $(patsubst %/,%,$1))

# function to create directory targets.
# All directories have order-only-prerequisites on their parent directories
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
TARGET_DIRS:=
define make_dirs_recursively
TARGET_DIRS+=$1
$1: | $(if $(subst ./,,$(call get_parent_dir,$1)),$(call get_parent_dir,$1))
    mkdir $1
endef

# function to recursively get all directories 
#     $(call get_all_dirs,things/and/places/) -> things/ things/and/ things/and/places/
#     $(call get_all_dirs,things/and/places) -> things/ things/and/
get_all_dirs=$(if $(subst ./,,$(dir $1)),$(call get_all_dirs,$(call get_parent_dir,$1)) $1)

# function to turn all targets into directories
#     $(call get_all_target_dirs,obj/a.o obj/three/b.o) -> obj/ obj/three/
get_all_target_dirs=$(sort $(foreach target,$1,$(call get_all_dirs,$(dir $(target)))))

# create target dirs
create_dirs=$(foreach dirname,$(call get_all_target_dirs,$1),$(eval $(call make_dirs_recursively,$(dirname))))

TARGETS := w/h/a/t/e/v/e/r/things.dat w/h/a/t/things.dat

all: $(TARGETS)

# this must be placed after your .DEFAULT_GOAL, or you can manually state what it is
# https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
$(call create_dirs,$(TARGETS))

# $(TARGET_DIRS) needs to be an order-only-prerequisite
w/h/a/t/e/v/e/r/things.dat: w/h/a/t/things.dat | $(TARGET_DIRS)
    echo whatever happens > $@

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > $@

Misalnya, menjalankan di atas akan membuat:

$ make
mkdir w/
mkdir w/h/
mkdir w/h/a/
mkdir w/h/a/t/
mkdir w/h/a/t/e/
mkdir w/h/a/t/e/v/
mkdir w/h/a/t/e/v/e/
mkdir w/h/a/t/e/v/e/r/
echo whatever happens > w/h/a/t/things.dat
echo whatever happens > w/h/a/t/e/v/e/r/things.dat
SimplyKnownAsG
sumber