在 shell 命令中定位通配符



我正在尝试创建依赖于具有目标名称的目录中的文件列表的目标:

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.outall的依赖项(。

所以对于src中的每个子目录,所有源文件都被编译,目标文件被移动到out/projname最终将链接到bin/projname.out

  • 来源
    • 项目0
      • CPP/HPP 文件
    • 项目1
      • CPP/HPP 文件
    • 来源
      • 项目0
        • 目标文件
      • 项目1
        • 目标文件
  • .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(。

笔记:

  1. 我假设项目的每个对象文件都依赖于同一项目的所有头文件。如果不是这种情况,您将不得不对此进行一些返工。

  2. 重要的是要了解:

    • PROJECT_rule变量首先由call处理,以将$(1)替换为项目名称。
    • 然后递归且完全地扩展它(包括配方(由eval.递归意味着如果我们有:

      FOO = foo
      BAR = FOO
      BAZ = $($(BAR))
      QUX = $$(BAZ)
      

      $($(BAR))扩展为foo,而$$(BAZ)扩展为$(BAZ)。完全意味着PROJET_rule变量的所有部分都被扩展,甚至最终用作配方的部分(而当make解析Makefile时,它通常会将配方的扩展推迟到稍后阶段(。

    • 结果将被实例化为常规 make 构造,也就是说,在进行评估之前,它将再次扩展为常规 make 构造(而不是配方(。

  3. 因此,由于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
    

    请注意,除非CPPCPPFLAGSINCLUDES具有特定的目标相关定义,否则您还可以编写:

    $(CPP) $(CPPFLAGS) $(INCLUDES) -c $$< -o $$@
    

    因为第一个扩展会将其转换为以下内容:

    g++ -O3 -Iincludes/foobar -c $< -o $@
    

    这也是正确的。

  4. 更详细的解释:对于每个项目(例如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 并执行之前(如果是的话(。

  5. 编译规则是静态模式规则。如果你还不知道这一点,你可以阅读GNU make文档的这一部分。

  6. 练习:在PROJECT_rule的定义中,你可以用$(shell...代替$$(shell...,但不能用$(patsubst...代替$$(patsubst...。为什么?

最新更新