使用make文件创建目录


101

我是makefile的新手,我想使用makefile创建目录。我的项目目录是这样的

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

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

title: $(DIR_TARGET) $(OBJS)

提前致谢。如果我也犯了任何错误,请指导我。

Answers:


88

可以做到这一点-假设是一个类似Unix的环境。

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

这必须在顶级目录中运行-否则$ {OUT_DIR}的定义必须相对于其运行位置正确。当然,如果您遵循彼得·米勒(Peter Miller)的“ 递归使有害 ” 的法令,那么无论如何您都将在顶层目录中运行make。

我现在正在玩这个(RMCH)。它需要适应我用作测试基础的软件套件。该套件具有十二个独立的程序,这些程序的源代码分布在15个目录中,其中一些是共享的。但是只要稍加注意,就可以做到。OTOH,它可能不适合新手。


如注释中所述,将“ mkdir”命令列为“目录”的操作是错误的。如评论中所述,还有其他方法可以解决导致“不知道如何进行输出/调试”错误。一种是删除对“目录”行的依赖。这行得通,因为如果要求创建的所有目录都已经存在,则“ mkdir -p”不会生成错误。另一个是所示的机制,它将仅在目录不存在时尝试创建目录。昨晚我想到的是“修改后的”版本-但是两种技术都可以工作(如果存在输出/调试,但它们是文件而不是目录,两者都存在问题)。


谢谢乔纳森。当我尝试得到一个错误“ make:*** No rule to make target output/debug', needed by directory'。Stop。”。但是我现在不会为此担心。将遵守基本规则。:)。感谢您的指导。而且我只从顶层目录运行“ make”。
贾比斯,

只需删除目录:后面的$ {OUT_DIR},即可使用。
Doc Brown

要实现这一点,您需要捕获所有可能的情况,但是您都可以从命令行使用目录。此外,你不能让任何文件生成生成规则依赖directories而不会使总重建..
mtalexan

@mtalexan您提供了一些评论,解释了其中一些答案的问题,但您尚未提出替代答案。渴望听到您针对此问题的解决方案。
塞缪尔

@Samuel我指出了问题,但没有提供解决方案,因为我一直在寻找同一件事,却从未找到解决方案。我最终只是解决了一个不太理想的解决方案的失败。
mtalexan

135

我认为,从技术或设计的角度来看,目录均不应视为makefile的目标。您应该创建文件,如果文件创建需要新目录,请在规则中为相关文件静默创建目录。

如果您定位的是常规文件或“模式”文件,则只需使用make的内部变量$(@D),即“当前目标所在的目录”($@目标为cmp。)。例如,

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

title: $(OBJS)

然后,您实际上在做同样的事情:为all创建目录$(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
虽然我认为将目录需求链接到文件是一个更好的选择,但是您的解决方案还存在一个重大缺陷,即对于每个要重建的单个文件,make文件都会调用mkdir进程,其中大多数都不需要目录再次。当适用于非Linux构建系统(例如Windows)时,由于没有-m等价于mkdir命令,因此实际上会导致不可阻挡的错误输出,更重要的是,由于shell调用不是微创的,因此会产生大量开销。
mtalexan

1
为了避免尝试创建目录(如果已存在),我没有直接调用mkdir,而是执行以下操作:$(shell [!-d $(@ D)] && mkdir -p $(@ D))
Brady

26

或者,KISS。

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

解析Makefile之后,这将创建所有目录。


3
我喜欢这种方法,因为我不必使用命令来使每个目标杂乱无章来处理目录。
肯·威廉姆斯

1
我只需要创建目录(如果不存在)。这个答案非常适合我的问题。
杰夫·帕尔

不仅如此,这还可以防止每个目录的修改后的时间戳触发不必要的构建步骤。这应该是答案
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

这绝对不是您想要的。每当您触摸文件时,您也会触摸目录。并且由于该文件取决于目录,因此该文件似乎已过时,从而迫使对其进行重建。

但是,您可以通过告诉make忽略目录的时间戳来轻松打破此循环。这是通过将目录声明为仅订购的前提条件来完成的:

# 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修改了父目录上的任何统计数据?这对我来说毫无意义。dir的mtime仅取决于其包含的文件名。我无法重现您的问题。
约翰·布勒(JohanBoulé)

@JohanBouléDebian。
cmaster-恢复莫妮卡

并且您是否为此类损坏的行为填充了错误?
约翰·布勒(JohanBoulé)

6

所有解决方案,包括被接受的解决方案,在各自的注释中都有提及。在通过@乔纳森-莱弗勒接受的答案已经是相当不错,但并没有考虑到效果的先决条件不一定要建在顺序(期间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,该函数使用一个文件名并生成一个规则,使该文件依赖于包含该文件的目录,然后定义一个规则,以在必要时创建该目录。然后我使用foreachcall每个文件名,此功能eval的结果。

请注意,您需要一个支持的GNU make版本eval,我认为该版本是3.81及更高版本。


创建一个包含“ makefile将要构建的每个文件的文件名”的变量是一种繁重的要求-我想定义我的顶级目标,然后定义它们所依赖的东西,等等。拥有所有目标文件的完整列表违背了makefile规范的层次结构性质,并且当目标文件依赖于运行时计算时可能(轻松)实现。
肯·威廉姆斯

3

鉴于您是新手,所以我说不要尝试这样做。这是绝对可能的,但不必要地会使您的Makefile复杂化。坚持简单的方法,直到您对make更加满意为止。

也就是说,建立与源目录不同的目录的一种方法是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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.