Tạo thư mục bằng cách sử dụng tạo tệp


101

Tôi là người mới làm quen với makefiles và tôi muốn tạo thư mục bằng makefile. Thư mục dự án của tôi như thế này

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

Tôi muốn đặt tất cả các đối tượng và đầu ra vào thư mục đầu ra tương ứng. Tôi muốn tạo cấu trúc thư mục giống như thế này sau khi biên dịch.

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

Tôi đã thử với một số tùy chọn, nhưng không thể thành công. Vui lòng giúp tôi tạo thư mục bằng cách sử dụng tệp tin. Tôi đang đăng Makefile của tôi để bạn xem xét.

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

Cảm ơn trước. Xin vui lòng hướng dẫn tôi nếu tôi cũng mắc lỗi.

Câu trả lời:


88

Điều này sẽ làm được - giả sử một môi trường giống Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

Điều này sẽ phải được chạy trong thư mục cấp cao nhất - hoặc định nghĩa của $ {OUT_DIR} sẽ phải chính xác liên quan đến nơi nó được chạy. Tất nhiên, nếu bạn làm theo các sắc lệnh trong bài báo " Đệ quy làm cho bị coi là có hại " của Peter Miller , thì bạn vẫn sẽ chạy make trong thư mục cấp cao nhất.

Tôi đang chơi với cái này (RMCH) vào lúc này. Nó cần một chút thích ứng với bộ phần mềm mà tôi đang sử dụng làm nền tảng thử nghiệm. Bộ này có hàng chục chương trình riêng biệt được xây dựng với nguồn trải rộng trên 15 thư mục, một số chương trình được chia sẻ. Nhưng với một chút cẩn thận, nó có thể được thực hiện. OTOH, nó có thể không thích hợp cho người mới.


Như đã lưu ý trong các nhận xét, liệt kê lệnh 'mkdir' làm hành động cho 'thư mục' là sai. Như đã lưu ý trong các nhận xét, có nhiều cách khác để sửa lỗi 'không biết cách tạo / gỡ lỗi' dẫn đến kết quả. Một là loại bỏ sự phụ thuộc vào dòng 'thư mục'. Điều này hoạt động vì 'mkdir -p' không tạo ra lỗi nếu tất cả các thư mục mà nó được yêu cầu tạo đã tồn tại. Cơ chế còn lại là cơ chế được hiển thị, sẽ chỉ cố gắng tạo thư mục nếu nó không tồn tại. Phiên bản 'như được sửa đổi' là những gì tôi đã nghĩ đến đêm qua - nhưng cả hai kỹ thuật đều hoạt động (và cả hai đều có vấn đề nếu tồn tại đầu ra / gỡ lỗi nhưng là một tệp chứ không phải là một thư mục).


Cảm ơn Jonathan. khi tôi thử thì gặp lỗi "make: *** Không có quy tắc nào để tạo output/debug', needed by thư mục đích '. Dừng lại." Nhưng tôi sẽ không lo lắng về điều đó bây giờ. sẽ gắn bó với các quy tắc cơ bản. :). Cảm ơn bạn đã hướng dẫn. Và tôi đang chạy "make" chỉ từ thư mục cấp trên cùng.
Jabez

Chỉ cần xóa $ {OUT_DIR} phía sau các thư mục :, thì nó sẽ hoạt động.
Doc Brown

Việc thực hiện điều này đòi hỏi bạn phải nắm bắt được mọi trường hợp có thể xảy ra khi bạn sử dụng các thư mục từ dòng lệnh. Hơn nữa, bạn không thể thực hiện bất kỳ tập tin tạo xây dựng quy tắc phụ thuộc vào directoriesmà không khiến họ luôn xây dựng lại ..
mtalexan

@mtalexan Bạn đã cung cấp một số nhận xét giải thích điều gì sai với một số câu trả lời trong số này nhưng bạn chưa đề xuất câu trả lời thay thế. Lo lắng khi nghe giải pháp của bạn cho vấn đề này.
Samuel

@Samuel Tôi đã chỉ ra các vấn đề mà không đưa ra giải pháp vì tôi đang tìm kiếm điều tương tự và không bao giờ tìm ra giải pháp. Cuối cùng tôi chỉ đối phó với sự sụp đổ của một giải pháp kém lý tưởng.
mtalexan

135

Theo ý kiến ​​của tôi, các thư mục không nên được coi là mục tiêu của makefile của bạn, cả về mặt kỹ thuật hay thiết kế. Bạn nên tạo tệp và nếu việc tạo tệp cần một thư mục mới thì hãy lặng lẽ tạo thư mục trong quy tắc cho tệp có liên quan.

Nếu bạn đang nhắm mục tiêu tệp thông thường hoặc tệp "theo mẫu", chỉ cần sử dụng makebiến nội bộ của $(@D), có nghĩa là "thư mục mà mục tiêu hiện tại nằm trong" (cmp. With $@for target). Ví dụ,

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

title: $(OBJS)

Sau đó, hiệu quả bạn đang làm tương tự: tạo thư mục cho tất cả $(OBJS), nhưng bạn sẽ làm điều đó theo cách ít phức tạp hơn.

Chính sách tương tự (tệp là mục tiêu, không bao giờ là thư mục) được sử dụng trong các ứng dụng khác nhau. Ví dụ, githệ thống kiểm soát sửa đổi không lưu trữ các thư mục.


Lưu ý: Nếu bạn định sử dụng nó, có thể hữu ích khi giới thiệu một biến tiện lợi và sử dụng makecác quy tắc mở rộng của.

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
Mặc dù liên kết yêu cầu thư mục với các tệp là một lựa chọn tốt hơn theo ý kiến ​​của tôi, nhưng giải pháp của bạn cũng có nhược điểm đáng kể là quá trình mkdir sẽ được gọi bởi tệp tạo cho mỗi tệp được xây dựng lại, hầu hết trong số đó sẽ không cần thực hiện thư mục một lần nữa. Khi thích ứng với các hệ thống xây dựng không phải Linux như Windows, nó thực sự gây ra cả đầu ra lỗi không thể chặn vì không có -p tương đương với lệnh mkdir và quan trọng hơn là một lượng chi phí khổng lồ vì lệnh gọi shell không xâm lấn tối thiểu.
mtalexan

1
Thay vì gọi trực tiếp mkdir, tôi đã làm như sau để tránh cố gắng tạo thư mục nếu nó đã tồn tại: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Brady

26

Hoặc, HÔN.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Điều này sẽ tạo tất cả các thư mục sau khi Makefile được phân tích cú pháp.


3
Tôi thích cách tiếp cận này vì tôi không phải xáo trộn từng mục tiêu với các lệnh để xử lý các thư mục.
Ken Williams

1
Tôi chỉ cần tạo thư mục nếu nó không tồn tại. Câu trả lời này phù hợp hoàn hảo cho vấn đề của tôi.
Jeff Pal

Không chỉ vậy, điều này ngăn không cho các dấu thời gian đã sửa đổi của mỗi thư mục kích hoạt một bước xây dựng không cần thiết. Đây sẽ là câu trả lời
Assimilater

6
Điều này tốt hơn: $(info $(shell mkdir -p $(DIRS)))Không có $(info ...), đầu ra của mkdirlệnh sẽ được dán vào Makefile , dẫn đến lỗi cú pháp. Lệnh $(info ...)gọi đảm bảo rằng a) các lỗi (nếu có) được hiển thị cho người dùng và b) lệnh gọi hàm mở rộng thành hư không.
cmaster - Khôi phục monica

10

maketrong và ngoài bản thân, xử lý các mục tiêu thư mục giống như mục tiêu tệp. Vì vậy, thật dễ dàng để viết các quy tắc như sau:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Vấn đề duy nhất với điều đó là, dấu thời gian của thư mục phụ thuộc vào những gì được thực hiện với các tệp bên trong. Đối với các quy tắc ở trên, điều này dẫn đến kết quả sau:

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

Đây chắc chắn không phải là điều bạn muốn. Bất cứ khi nào bạn chạm vào tệp, bạn cũng chạm vào thư mục. Và vì tệp phụ thuộc vào thư mục, do đó, tệp dường như đã lỗi thời, buộc nó phải được xây dựng lại.

Tuy nhiên, bạn có thể dễ dàng phá vỡ vòng lặp này bằng cách yêu cầu thực hiện bỏ qua dấu thời gian của thư mục . Điều này được thực hiện bằng cách khai báo thư mục là trang web tiên quyết chỉ đặt hàng:

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

outDir:
    mkdir -p outDir

Điều này chính xác mang lại:

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

TL; DR:

Viết quy tắc để tạo thư mục:

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

Và có các mục tiêu cho những thứ bên trong phụ thuộc vào chỉ thứ tự thư mục:

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

Trên OS / FS bị hỏng, bạn có thấy touchsửa đổi bất kỳ dữ liệu thống kê nào trên thư mục mẹ không? Điều đó không có ý nghĩa đối với tôi. Thời gian của một dir chỉ phụ thuộc vào tên tệp mà nó chứa. Tôi không thể tái tạo vấn đề của bạn.
Johan Boulé

@ JohanBoulé Debian.
cmaster - phục hồi monica

Và bạn đã điền vào một lỗi cho một hành vi bị hỏng như vậy?
Johan Boulé

6

Tất cả các giải pháp bao gồm cả giải pháp được chấp nhận đều có một số vấn đề như đã nêu trong các nhận xét tương ứng của họ. Các câu trả lời được chấp nhận bởi @ jonathan-Leffler đã khá tốt nhưng không tính đến ảnh hưởng mà điều kiện tiên quyết không nhất thiết phải được xây dựng theo thứ tự (trong make -jví dụ). Tuy nhiên, chỉ cần chuyển directoriesđiều kiện tiên quyết từ allsang programkích hoạt các bản dựng lại trên mỗi lần chạy AFAICT. Giải pháp sau đây không có vấn đề đó và AFAICS hoạt động như dự định.

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

Tôi vừa đưa ra một giải pháp khá hợp lý cho phép bạn xác định các tệp để xây dựng và có các thư mục được tạo tự động. Đầu tiên, hãy xác định một biến ALL_TARGET_FILESchứa tên tệp của mọi tệp mà tệp makefile của bạn sẽ được tạo. Sau đó sử dụng mã sau:

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

Đây là cách nó hoạt động. Tôi xác định một hàm depend_on_dirlấy tên tệp và tạo quy tắc làm cho tệp phụ thuộc vào thư mục chứa nó và sau đó xác định quy tắc để tạo thư mục đó nếu cần. Sau đó, tôi sử dụng foreachđể callchức năng này trên mỗi tên tập tin vàeval kết quả.

Lưu ý rằng bạn sẽ cần phiên bản GNU make hỗ trợ eval, tôi nghĩ là phiên bản 3.81 trở lên.


Tạo một biến chứa "tên tệp của mọi tệp mà makefile của bạn sẽ tạo" là một yêu cầu khó khăn - tôi muốn xác định các mục tiêu cấp cao nhất của mình, sau đó là những thứ chúng phụ thuộc vào, v.v. Việc có một danh sách phẳng tất cả các tệp đích đi ngược lại bản chất phân cấp của đặc tả makefile và có thể không (dễ dàng) khi các tệp đích phụ thuộc vào tính toán thời gian chạy.
Ken Williams

3

cho rằng bạn là một người mới, tôi muốn nói rằng đừng cố gắng làm điều này. nó chắc chắn có thể, nhưng sẽ không cần thiết làm phức tạp Makefile của bạn. hãy làm theo những cách đơn giản cho đến khi bạn cảm thấy thoải mái hơn.

điều đó nói rằng, một cách để xây dựng trong một thư mục khác với thư mục nguồn là VPATH ; tôi thích các quy tắc mẫu hơn


3

Tính độc lập của hệ điều hành là rất quan trọng đối với tôi, vì vậy mkdir -pkhông phải là một lựa chọn. Tôi đã tạo loạt hàm này dùng evalđể tạo mục tiêu thư mục với điều kiện tiên quyết trên thư mục mẹ. Điều này có lợi ích mà make -j 2sẽ hoạt động mà không có vấn đề vì các phụ thuộc được xác định chính xác.

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

Ví dụ: chạy phần trên sẽ tạo ra:

$ 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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.