สร้างไดเร็กทอรีโดยใช้ make file


101

ฉันเป็นมือใหม่มากสำหรับ makefiles และฉันต้องการสร้างไดเร็กทอรีโดยใช้ makefile ไดเร็กทอรีโปรเจ็กต์ของฉันเป็นแบบนี้

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

ฉันต้องการใส่อ็อบเจ็กต์ทั้งหมดและเอาต์พุตลงในโฟลเดอร์เอาต์พุตตามลำดับ ฉันต้องการสร้างโครงสร้างโฟลเดอร์ซึ่งจะเป็นเช่นนี้หลังจากรวบรวม

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

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

#---------------------------------------------------------------------
# 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)

ขอบคุณล่วงหน้า. โปรดชี้แนะฉันหากฉันทำผิดพลาดด้วย

คำตอบ:


89

สิ่งนี้จะทำได้ - สมมติว่ามีสภาพแวดล้อมเหมือน Unix

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

สิ่งนี้จะต้องถูกเรียกใช้ในไดเร็กทอรีระดับบนสุด - มิฉะนั้นคำจำกัดความของ $ {OUT_DIR} จะต้องถูกต้องเมื่อเทียบกับตำแหน่งที่รัน แน่นอนว่าถ้าคุณทำตามคำสั่งของกระดาษ" Recursive Make ถือว่าเป็นอันตราย " ของ Peter Miller คุณก็จะได้รับการสร้างขึ้นในไดเรกทอรีระดับบนสุด

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


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


ขอบคุณโจนาธาน เมื่อฉันพยายามพบว่ามีข้อผิดพลาด "make: *** ไม่มีกฎในการสร้างoutput/debug', needed by ไดเรกทอรีเป้าหมาย" หยุด " แต่ฉันจะไม่กังวลเกี่ยวกับเรื่องนั้นในตอนนี้ จะยึดตามกฎพื้นฐาน :). ขอบคุณที่ชี้แนะครับ. และฉันกำลังเรียกใช้ "make" จากไดเร็กทอรี toplevel เท่านั้น
Jabez

เพียงแค่ลบ $ {OUT_DIR} ที่อยู่เบื้องหลังไดเร็กทอรี: จากนั้นควรใช้งานได้
Doc Brown

การดำเนินการนี้ต้องการให้คุณตรวจจับทุกกรณีที่เป็นไปได้ที่คุณใช้ไดเร็กทอรีจากบรรทัดคำสั่ง นอกจากนี้คุณไม่สามารถสร้างไฟล์ใด ๆ ที่สร้างกฎการสร้างขึ้นdirectoriesโดยไม่ทำให้ไฟล์เหล่านั้นต้องสร้างใหม่เสมอ ..
mtalexan

@mtalexan คุณได้ให้ความคิดเห็นสองสามข้อเพื่ออธิบายว่ามีอะไรผิดปกติกับคำตอบเหล่านี้ แต่คุณยังไม่ได้เสนอคำตอบอื่น กังวลที่จะได้ยินวิธีแก้ปัญหาของคุณสำหรับปัญหานี้
Samuel

@ ซามูเอลฉันชี้ให้เห็นปัญหาโดยไม่ได้ให้วิธีแก้ปัญหาเพราะฉันกำลังมองหาสิ่งเดียวกันและไม่พบวิธีแก้ปัญหา ฉันลงเอยด้วยการจัดการกับการหลุดออกจากทางออกที่ดีน้อยกว่า
mtalexan

135

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

หากคุณกำหนดเป้าหมายไฟล์ปกติหรือ "มีลวดลาย" เพียงแค่ใช้makeตัวแปรภายใน$(@D)ซึ่งหมายความว่า "ไดเร็กทอรีที่เป้าหมายปัจจุบันอยู่" (cmp. with $@for the target) ตัวอย่างเช่น,

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

title: $(OBJS)

จากนั้นคุณจะทำสิ่งเดียวกันได้อย่างมีประสิทธิภาพ: สร้างไดเรกทอรีสำหรับทุกคน$(OBJS)แต่คุณจะทำในวิธีที่ซับซ้อนน้อยกว่า

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


หมายเหตุ:หากคุณจะใช้มันอาจเป็นประโยชน์ในการแนะนำตัวแปรความสะดวกและใช้makeกฎการขยายของ

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)

12
ในขณะที่การเชื่อมโยงข้อกำหนดไดเรกทอรีกับไฟล์เป็นตัวเลือกที่ดีกว่าในความคิดของฉัน แต่โซลูชันของคุณยังมีข้อเสียเปรียบที่สำคัญที่กระบวนการ mkdir จะถูกเรียกโดยไฟล์ make สำหรับทุกไฟล์ที่สร้างขึ้นมาใหม่ซึ่งส่วนใหญ่ไม่จำเป็นต้องสร้าง ไดเรกทอรีอีกครั้ง เมื่อปรับให้เข้ากับระบบบิลด์ที่ไม่ใช่ Linux เช่น Windows จะทำให้เกิดข้อผิดพลาดที่ไม่สามารถบล็อกได้เนื่องจากไม่มี -p เทียบเท่ากับคำสั่ง mkdir และที่สำคัญกว่านั้นคือค่าใช้จ่ายจำนวนมหาศาลเนื่องจากการเรียกเชลล์ไม่ได้ถูกบุกรุกน้อยที่สุด
mtalexan

1
แทนที่จะเรียก mkdir โดยตรงฉันทำสิ่งต่อไปนี้เพื่อหลีกเลี่ยงการพยายามสร้างไดเรกทอรีหากมีอยู่แล้ว: $ (shell [! -d $ (@ D)] && mkdir -p $ (@ D))
Brady

26

หรือ KISS

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

สิ่งนี้จะสร้างไดเรกทอรีทั้งหมดหลังจากที่แยกวิเคราะห์ Makefile แล้ว


3
ฉันชอบแนวทางนี้เพราะฉันไม่ต้องถ่วงแต่ละเป้าหมายด้วยคำสั่งในการจัดการไดเร็กทอรี
Ken Williams

1
ฉันแค่ต้องการสร้างไดเร็กทอรีถ้าไม่มีอยู่ คำตอบนี้เหมาะกับปัญหาของฉัน
Jeff Pal

ไม่เพียงเท่านั้นสิ่งนี้จะป้องกันไม่ให้การประทับเวลาที่แก้ไขของแต่ละไดเร็กทอรีทริกเกอร์ขั้นตอนการสร้างที่ไม่จำเป็น นี่น่าจะเป็นคำตอบ
Assimilater

6
สิ่งนี้ดีกว่า: $(info $(shell mkdir -p $(DIRS)))หากไม่มี$(info ...)ผลลัพธ์ของmkdirคำสั่งจะถูกวางลงใน Makefileซึ่งนำไปสู่ข้อผิดพลาดทางไวยากรณ์ที่ดีที่สุด การ$(info ...)เรียกใช้ช่วยให้มั่นใจได้ว่า a) ข้อผิดพลาด (ถ้ามี) จะปรากฏให้ผู้ใช้เห็นและ b) การเรียกใช้ฟังก์ชันจะไม่ขยายออกไป
cmaster - คืนสถานะโมนิกา

10

makeในและปิดตัวเองจัดการไดเร็กทอรีเป้าหมายเช่นเดียวกับไฟล์เป้าหมาย ดังนั้นจึงเป็นเรื่องง่ายที่จะเขียนกฎเช่นนี้:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

ปัญหาเดียวคือการประทับเวลาของไดเรกทอรีขึ้นอยู่กับสิ่งที่ทำกับไฟล์ที่อยู่ภายใน สำหรับกฎข้างต้นสิ่งนี้นำไปสู่ผลลัพธ์ต่อไปนี้:

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

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

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

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

outDir:
    mkdir -p outDir

สิ่งนี้ให้ผลอย่างถูกต้อง:

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

TL; DR:

เขียนกฎเพื่อสร้างไดเร็กทอรี:

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

และมีเป้าหมายสำหรับสิ่งที่อยู่ภายในขึ้นอยู่กับลำดับไดเรกทอรีเท่านั้น:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)

คุณเห็น OS / FS ที่ใช้งานไม่ได้touchแก้ไขข้อมูลสถิติบนไดเร็กทอรีหลักหรือไม่ นั่นไม่สมเหตุสมผลสำหรับฉัน mtime ของ dir ขึ้นอยู่กับชื่อไฟล์ที่มีเท่านั้น ฉันไม่สามารถทำให้เกิดปัญหาของคุณได้
Johan Boulé

@ โยฮันบูเลเดเบียน.
cmaster - คืนสถานะโมนิกา

และคุณได้เติมเต็มข้อบกพร่องสำหรับพฤติกรรมที่ไม่ดีเช่นนี้หรือไม่?
Johan Boulé

6

วิธีแก้ปัญหาทั้งหมดรวมถึงวิธีที่ได้รับการยอมรับมีปัญหาบางประการตามที่ระบุไว้ในความคิดเห็นที่เกี่ยวข้อง คำตอบที่ได้รับการยอมรับโดยโจนาธาน @-Lefflerอยู่แล้วค่อนข้างดี แต่ไม่ได้คำนึงถึงผลกระทบที่ข้อกำหนดเบื้องต้นไม่จำเป็นต้องที่จะสร้างขึ้นในการสั่งซื้อ (ระหว่างmake -jตัวอย่าง) อย่างไรก็ตามเพียงแค่ย้ายdirectoriesข้อกำหนดเบื้องต้นจากallเพื่อprogramกระตุ้นการสร้างใหม่ในทุกครั้งที่รัน AFAICT วิธีแก้ปัญหาต่อไปนี้ไม่มีปัญหาและ AFAICS ทำงานได้ตามที่ตั้งใจไว้

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)

4

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

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))))

นี่คือวิธีการทำงาน ฉันกำหนดฟังก์ชันdepend_on_dirที่ใช้ชื่อไฟล์และสร้างกฎที่ทำให้ไฟล์ขึ้นอยู่กับไดเร็กทอรีที่มีมันจากนั้นกำหนดกฎเพื่อสร้างไดเร็กทอรีนั้นหากจำเป็น แล้วฉันจะใช้foreachเพื่อcallฟังก์ชั่นนี้ในแต่ละชื่อไฟล์และevalผลที่ตามมา

โปรดทราบว่าคุณจะต้องมี GNU เวอร์ชันที่รองรับevalซึ่งฉันคิดว่าเป็นเวอร์ชัน 3.81 ขึ้นไป


การสร้างตัวแปรที่เก็บ "ชื่อไฟล์ของทุกไฟล์ที่ makefile ของคุณจะสร้าง" เป็นข้อกำหนดที่ยุ่งยากมาก - ฉันต้องการกำหนดเป้าหมายระดับบนสุดของฉันจากนั้นก็ขึ้นอยู่กับสิ่งต่างๆ การมีรายการแบนของไฟล์เป้าหมายทั้งหมดขัดกับลักษณะลำดับชั้นของข้อกำหนด makefile และอาจไม่เป็นไปได้ (อย่างง่ายดาย) เมื่อไฟล์เป้าหมายขึ้นอยู่กับการคำนวณรันไทม์
Ken Williams

3

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

ที่กล่าวว่าทางเดียวที่จะสร้างในที่แตกต่างกันไดเรกทอรีจากไดเรกทอรีมาเป็นVPATH ; ฉันชอบกฎแบบแผน


3

ความเป็นอิสระของระบบปฏิบัติการเป็นสิ่งสำคัญสำหรับฉันดังนั้นจึงmkdir -pไม่ใช่ตัวเลือก ฉันสร้างชุดของฟังก์ชันที่ใช้evalสร้างไดเร็กทอรีเป้าหมายด้วยสิ่งที่จำเป็นต้องมีบนไดเร็กทอรีหลัก สิ่งนี้มีประโยชน์ที่make -j 2จะทำงานได้โดยไม่มีปัญหาเนื่องจากการอ้างอิงถูกกำหนดอย่างถูกต้อง

# 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 > $@

ตัวอย่างเช่นการเรียกใช้ข้างต้นจะสร้าง:

$ 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

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