2009-12-23 77 views
63

我是一個非常新的makefile,我想使用makefile創建目錄。我的項目目錄是這樣的使用make文件創建目錄

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

我想把所有的對象和輸出到各自的輸出文件夾。我想在編譯後創建這樣的文件夾結構。

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

我嘗試了幾個選項,但不能成功。請幫助我使用make文件製作目錄。我發佈我的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 [email protected] 

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

title: $(DIR_TARGET) $(OBJS) 

在此先感謝。如果我也犯了錯誤,請引導我。

回答

59

這樣做 - 假設一個類Unix的環境。

MKDIR_P = mkdir -p 

.PHONY: directories 

all: directories program 

directories: ${OUT_DIR} 

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

這將在頂層目錄中運行 - 或$定義{} OUT_DIR本來相對它在哪裏運行到正確的是。當然,如果你遵循Peter Miller的「Recursive Make Considered Harmful」論文的規定,那麼你仍然會在頂層目錄中運行make。

我正在玩這個(RMCH)。它需要一些適應我用作測試場的軟件套件。該套件有十幾個獨立的程序,源碼遍佈15個目錄,其中一些共享。但有一點小心,可以做到。 OTOH,它可能不適合新手。


正如註釋中所述,將'mkdir'命令列爲'目錄'的動作是錯誤的。正如評論中所指出的那樣,還有其他方法可以解決導致「不知道如何進行輸出/調試」的錯誤。一個是刪除對「目錄」行的依賴。這是有效的,因爲'mkdir -p'不會產生錯誤,如果所有被要求創建的目錄已經存在。另一種是顯示的機制,它只會在不存在的情況下嘗試創建目錄。 '修正'版本是我昨晚想到的 - 但是這兩種技術都可以工作(如果輸出/調試存在但是文件而不是目錄都有問題)。

+0

謝謝喬納森。當我試圖說我得到一個錯誤「make:***沒有規則,使目標'輸出/調試',需要'目錄'。停止。」 但我現在不會爲此擔心。將堅持基本規則。 :)。感謝您的指導。而且我只從頂層目錄運行「make」。 – Jabez 2009-12-23 06:49:19

+0

只需刪除目錄下的$ {OUT_DIR} :,那麼它應該可以工作。 – 2009-12-23 07:02:55

+0

它的工作。謝謝Doc。 – Jabez 2009-12-23 08:41:55

2

鑑於你是新手,我會說不要嘗試這樣做。這絕對是可能的,但會不必要地使你的Makefile複雜化。堅持簡單的方法,直到你對製作更舒適。

這樣說,一種方法建立在一個不同於源目錄的目錄是VPATH;我更喜歡pattern rules

+0

謝謝。將堅持基本規則。 – Jabez 2009-12-23 06:49:59

102

在我看來,目錄不應該被視爲你的makefile的目標,無論是在技術上還是在設計意義上。你應該創建文件,如果一個文件創建需要一個新的目錄,然後安靜地創建相關文件的規則內的目錄。

如果你的目標通常或「圖案」文件,只是使用make的內部變量$(@D),這意味着‘當前目標所在目錄’(CMP,與[email protected]目標)。例如,

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

title: $(OBJS) 

然後,你有效地做同樣的:爲所有人創造$(OBJS)目錄,但你給它不太複雜的方式。

在各種應用程序中使用相同的策略(文件是目標,永遠不會有目錄)。例如,git版本控制系統不存儲目錄。


注:如果你要使用它,它可能是引進一個方便實用的變量和利用make的擴展規則。

[email protected] -p $(@D) 

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

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

title: $(OBJS) 
+8

儘管將目錄需求鏈接到文件在我看來是更好的選擇,但您的解決方案還有一個明顯的缺點,即mkdir進程將由make文件爲每個重建的文件調用,其中大多數文件不需要該目錄再次。當適用於非Linux構建系統(如Windows)時,它實際上會導致無法阻止的錯誤輸出,因爲沒有與mkdir命令相當的-p,並且更重要的是,由於shell調用不是微創的,所以其數量巨大。 – mtalexan 2013-12-30 18:27:45

4

我剛剛想出了一個相當合理的解決方案,可以讓你定義要構建的文件並自動創建目錄。首先,定義一個變量ALL_TARGET_FILES,其中包含將生成makefile的每個文件的文件名。然後使用以下代碼:

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

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED 
$(dir $(1)): 
    mkdir -p [email protected] 

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

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

以下是它的工作原理。我定義了一個函數depend_on_dir,該函數接受一個文件名並生成一個規則,該規則使文件依賴於包含它的目錄,然後定義一個規則以在必要時創建該目錄。然後我用foreachcall這個函數對每個文件名和eval的結果。

請注意,您需要支持eval的GNU make版本,我認爲這是版本3.81和更高版本。

+0

創建一個變量來保存「makefile將生成的每個文件的文件名」是一種繁重的要求 - 我喜歡定義我的頂級目標,然後定義他們依賴的東西等等。擁有所有目標文件的平面列表違背了makefile規範的層次性質,並且在目標文件依賴於運行時計算時可能不會(很容易)。 – 2017-11-28 16:59:18

3

所有解決方案(包括已接受的解決方案)都有其各自評論中所述的一些問題。 accepted answer by @jonathan-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) 
1

操作系統的獨立性對我很重要,所以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 > [email protected] 

w/h/a/t/things.dat: | $(TARGET_DIRS) 
    echo whatever happens > [email protected] 

例如,上面的運行將創建:

$ 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 
3

或者,KISS。

DIRS=build build/bins 

... 

$(shell mkdir -p $(DIRS)) 

這將在解析Makefile後創建所有的目錄。

+0

我喜歡這種方法,因爲我不必用處理目錄的命令混淆每個目標。 – 2017-11-28 17:01:10