我正在尝试创建依赖于具有目标名称的目录中的文件列表的目标:
bin/%.out: src/%/ $(shell find src/%/* -type f -iname '*.cpp' -o -iname '*.hpp')
# Build stuff here
然而,shell find src/%/* ...
最终扩展到shell find src//* ...
。起初我认为这是因为我只能有 1 个目标通配符,但即使在删除了src/%/
依赖项之后,它也最终遇到了同样的问题。
更多上下文:我的目录包含一个包含目录的"src"目录。"src"的每个子目录我都视为一个"项目"。当我构建时,所有目标文件都应该进入out/src/projname
。我正在尝试使用find
递归获取每个项目的所有源文件和头文件。所有二进制文件都将进入bin/projname.out
,因此,主要依赖项是bin
中与其项目名称同名的.out
文件。(如果src/abc
存在,则bin/abc.out
是all
的依赖项(。
所以对于src
中的每个子目录,所有源文件都被编译,目标文件被移动到out/projname
最终将链接到bin/projname.out
。
- 来源
- 项目0
- CPP/HPP 文件
- 项目1
- CPP/HPP 文件
- 项目0
- 外
- 来源
- 项目0
- 目标文件
- 项目1
- 目标文件
- 项目0
- 来源
- .bin
- proj0.out
- proj1.out
当前获取项目列表和输出文件列表,如下所示:
SRCS := $(shell find src/* -maxdepth 0 -type d)
SRCS_OUT := $(patsubst src/%,bin/%.out,$(SRCS))
...
all: out/ bin/ $(SRCS_OUT)
项目可以包含子目录等,这就是我在最顶部的代码中使用find
的原因。
我不明白如何使用标准的、可移植的、make 功能来做你想做的事情。但是它可以通过高级GNU make功能来实现:
define PROJECT_rule
CPPS_$(1) := $$(shell find src/$(1) -type f -iname '*.cpp')
HPPS_$(1) := $$(shell find src/$(1) -type f -iname '*.hpp')
OBJS_$(1) := $$(patsubst src/$(1)/%.cpp,out/src/$(1)/%.o,$$(CPPS_$(1)))
$$(OBJS_$(1)): out/src/$(1)/%.o: src/$(1)/%.cpp $$(HPPS_$(1))
<compile recipe>
bin/$(1).out: $$(OBJS_$(1))
<link recipe>
endef
PRJS := $(patsubst src/%,%,$(shell find src/* -maxdepth 0 -type d))
$(foreach p,$(PRJS),$(eval $(call PROJECT_rule,$(p))))
我们首先定义一个make变量(PROJECT_rule
(,它将用作任何项目的模板;在此模板中,$(1)
将表示项目的名称。然后,我们计算项目列表(PRJS
(,最后,我们迭代项目并为每个项目处理我们的模板(foreach-eval-call
(。
笔记:
我假设项目的每个对象文件都依赖于同一项目的所有头文件。如果不是这种情况,您将不得不对此进行一些返工。
重要的是要了解:
PROJECT_rule
变量首先由call
处理,以将$(1)
替换为项目名称。然后递归且完全地扩展它(包括配方(由
eval
.递归意味着如果我们有:FOO = foo BAR = FOO BAZ = $($(BAR)) QUX = $$(BAZ)
$($(BAR))
扩展为foo
,而$$(BAZ)
扩展为$(BAZ)
。完全意味着PROJET_rule
变量的所有部分都被扩展,甚至最终用作配方的部分(而当make解析Makefile时,它通常会将配方的扩展推迟到稍后阶段(。结果将被实例化为常规 make 构造,也就是说,在进行评估之前,它将再次扩展为常规 make 构造(而不是配方(。
因此,由于
PROJECT_rule
将有两个扩展,一些$
符号必须被转义($$
(。在编写自己的<compile recipe>
和<link recipe>
时请记住这一点。例如,如果要使用$@
或$^
自动变量,请不要忘记编写$$@
。示例:如果您的编译配方是:$(CPP) $(CPPFLAGS) $(INCLUDES) -c $< -o $@
写:
$$(CPP) $$(CPPFLAGS) $$(INCLUDES) -c $$< -o $$@
在
PROJECT_rule
的定义中.第一个扩展将把它转换为:$(CPP) $(CPPFLAGS) $(INCLUDES) -c $< -o $@
虽然,如果你写:
$(CPP) $(CPPFLAGS) $(INCLUDES) -c $< -o $@
第一个扩展将把它转换为如下:
g++ -O3 -Iincludes/foobar -c -o
请注意,除非
CPP
、CPPFLAGS
或INCLUDES
具有特定的目标相关定义,否则您还可以编写:$(CPP) $(CPPFLAGS) $(INCLUDES) -c $$< -o $$@
因为第一个扩展会将其转换为以下内容:
g++ -O3 -Iincludes/foobar -c $< -o $@
这也是正确的。
更详细的解释:对于每个项目(例如
proj0
(,foreach
将产生:$(eval $(call PROJECT_rule,proj0))
call
将在PROJECT_rule
定义中用proj0
替换$(1)
,这样eval
的参数将为:CPPS_proj0 := $$(shell find src/proj0 -type f -iname '*.cpp') HPPS_proj0 := $$(shell find src/proj0 -type f -iname '*.hpp') OBJS_proj0 := $$(patsubst src/proj0/%.cpp,out/src/proj0/%.o,$$(CPPS_proj0)) $$(OBJS_proj0): out/src/proj0/%.o: src/proj0/%.cpp $$(HPPS_proj0) <compile recipe> bin/proj0.out: $$(OBJS_proj0) <link recipe>
eval
将递归且完全地扩展它,导致:CPPS_proj0 := $(shell find src/proj0 -type f -iname '*.cpp') HPPS_proj0 := $(shell find src/proj0 -type f -iname '*.hpp') OBJS_proj0 := $(patsubst src/proj0/%.cpp,out/src/proj0/%.o,$(CPPS_proj0)) $(OBJS_proj0): out/src/proj0/%.o: src/proj0/%.cpp $(HPPS_proj0) <expanded compile recipe> bin/proj0.out: $(OBJS_proj0) <expanded link recipe>
(每个
$$
都变得$
,即使在食谱中也是如此(。 然后,eval
会将结果实例化为常规 make 构造,并且它们将被解析为常规 make 语法。也就是说,(简单(变量将立即扩展,目标和先决条件也将扩展,但不会扩展配方。配方将在第二阶段进行扩展,就在传递到 shell 并执行之前(如果是的话(。编译规则是静态模式规则。如果你还不知道这一点,你可以阅读GNU make文档的这一部分。
练习:在
PROJECT_rule
的定义中,你可以用$(shell...
代替$$(shell...
,但不能用$(patsubst...
代替$$(patsubst...
。为什么?