在Makefile中从子目录获取源文件

78

我有一个使用Makefile构建的C++库,之前所有的源代码都在一个目录中,Makefile像这样运行:

SOURCES = $(wildcard *.cpp)

这个命令一直很好用。

现在我添加了一些位于子目录(比如 subdir)中的源代码文件。我知道可以这样做:

SOURCES = $(wildcard *.cpp) $(wildcard subdir/*.cpp)

但是我想找到一种避免手动指定subdir的方法,即让wildcard自动查找子目录,或者以某种方式生成子目录列表,并使用多个wildcard函数进行扩展。此时,拥有非递归解决方案(仅扩展第一层)就足够了。

我没有找到任何东西 - 我最好的猜测是使用find -type d来列出子目录,但感觉像是一种hack。是否有任何内置的方法可以实现这个功能?


1
可能是 GNU make 中的递归通配符? 的重复问题。 - Jeroen
1
@Jeroen 应该反过来,因为这个问题有一个更好的答案(使用 **)。 - user1804599
6个回答

102

这应该可以解决问题:

SOURCES = $(wildcard *.cpp) $(wildcard */*.cpp)
如果你改变主意想要递归解决方案(即到任意深度),这是可以做到的,但它涉及一些更强大的Make函数。你知道的,那些允许你做一些你真的不应该做的事情的函数。
编辑: Jack Kelly指出,在某些平台上,使用GNUMake 3.81,$(wildcard **/*.cpp)可以递归到任意深度。(他是如何找出来的我不知道。)

36
我认为如果要搜索任意深度的话,$(wildcard **/*.cpp) 可以使用。 - Jack Kelly
4
@Jack Kelly:我刚试了一下,但没成功(使用 GNU Make 3.81)。这在你的版本中可行吗? - Beta
12
“**/*”对我没用。操作系统为OS X 10.8.2,GNU Make版本为3.81。实际上它的行为有些奇怪... - RyanM
19
SOURCES := $(shell find . -name "*.cpp") 也可以使用。 - Chnossos
6
**/*.cpp 对我来说是有效的,但它不包括当前目录中的任何内容(例如,为了获得类似于 *.cpp */*.cpp */*/*.cpp ... 的行为,您需要使用 *.cpp **/*.cpp),这在我看来相当不幸。 - Ponkadoodle
显示剩余7条评论

33

递归通配符可以在Make中纯粹实现,无需调用shell或find命令。只使用Make进行搜索意味着此解决方案也适用于Windows,而不仅仅是*nix。

# Make does not offer a recursive wildcard function, so here's one:
rwildcard=$(wildcard $1$2) $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2))

# How to recursively find all files with the same name in a given folder
ALL_INDEX_HTMLS := $(call rwildcard,foo/,index.html)

# How to recursively find all files that match a pattern
ALL_HTMLS := $(call rwildcard,foo/,*.html)

需要在文件夹名称中包含尾部斜杠“/”。该rwildcard函数不支持类似于Make内置通配符函数的多个通配符,但是使用foreach再添加一些支持将变得简单。


这对我不起作用。它返回所有子目录和文件,而不管提供的模式如何。 - Antimony
1
@Antimony:(如果你这样做了...)在递归的$(call ...)中,不要在分隔参数的逗号周围添加额外的空格。添加这样的空格会改变结果。 - Petr Vepřek

20

如果您不想使用递归的Makefile,这可能会给您一些想法:

subdirs := $(wildcard */)
sources := $(wildcard $(addsuffix *.cpp,$(subdirs)))
objects := $(patsubst %.cpp,%.o,$(sources))

$(objects) : %.o : %.cpp

1
这是一个有用的命令列表。subdirs := $(wildcard */) 就是我在寻找的! - Mike

17

您可以在wildcard中使用多个规则:

SOURCES := $(wildcard *.cpp */*.cpp)

如果您需要更深入的了解:

SOURCES := $(wildcard *.cpp */*.cpp */*/*.cpp */*/*/*.cpp)

不幸的是,与我们有时所读到的不同,glob (**) 在Makefile中不受支持,并且将被解释为普通的通配符 (*)。

例如,**/*.cpp 匹配 dir/file.cpp 但不匹配 file.cppdir/sub/file.cpp

如果需要无限深度,请使用 shellfind

SOURCES := $(shell find . -name "*.cpp")

12

常见做法 是在每个子目录下放置一个带有源文件的 Makefile 文件,然后

all: recursive
    $(MAKE) -C componentX
    # stuff for current dir
或者
all: recursive
    cd componentX && $(MAKE)
    # stuff for current dir

recursive: true

在源代码根目录中,为每个Makefile设置一个Makefile.inc可能是明智的选择。 recursive目标强制make进入子目录。确保它不会在需要recursive的目标中重新编译任何内容。


当然可以,但我仍然必须手写“componentX”,这正是我想避免的。 - ggambetta
2
请按照以下方式操作。简单地编译和链接每个子目录中的每个源/对象文件将会在您想要构建库、使用特殊编译器设置构建一个文件、编写测试程序等时出现问题。我总是在我的Makefile中列出每个单独的对象文件,有时甚至列出每个单独的源文件。列出几个要循环遍历的目录并不麻烦。 - Fred Foo
5
不要直接调用 make -C,而是需要使用 $(MAKE) -C。运行的 make 版本可能与系统的不同。另外,通过运行 $(MAKE) $@ 会不会引发无限循环?最后,有些人认为递归 make 是有害的。请参阅 http://miller.emu.id.au/pmiller/books/rmch/。 - Jack Kelly
4
注意:使用递归makefile的方法很难正确获取依赖关系。阅读建议:《递归式Make考虑有害》(http://aegis.sourceforge.net/auug97.pdf)。 - harmv

2
如果您可以使用find shell命令,您可以定义一个函数来使用它。
recurfind = $(shell find $(1) -name '$(2)')
SRCS := $(call recurfind,subdir1,*.c) $(call recurfind,subdir2,*.cc) $(call recurfind,subdir2,*.cu) \
        ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接