我尝试使用目标名称创建依赖于目录中文件列表的目标:
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
中,因此,主要依赖项是.out
中bin
个与项目名称相同的文件。 (如果src/abc
存在,bin/abc.out
是all
}的依赖关系。
因此,src
中的每个子目录都会编译所有源文件,并将目标文件移动到out/projname
,最终将链接到bin/projname.out
。
目前获取项目列表和输出文件列表如下:
SRCS := $(shell find src/* -maxdepth 0 -type d)
SRCS_OUT := $(patsubst src/%,bin/%.out,$(SRCS))
...
all: out/ bin/ $(SRCS_OUT)
项目可以包含子目录等,这就是我在最顶层的代码中使用find
的原因。
答案 0 :(得分:2)
我看不到如何用标准,便携,制作功能做你想做的事。但它可以通过高级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
会将$(1)
替换为proj0
定义中的PROJECT_rule
,以便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文档的this section。
练习:在PROJECT_rule
的定义中,您可以$$(shell...
替换$(shell...
,$$(patsubst...
替换$(patsubst...
。为什么呢?