【问题标题】:Target wildcards in a shell commandshell 命令中的目标通配符
【发布时间】:2017-08-02 11:05:09
【问题描述】:

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

bin/%.out: src/%/ $(shell find src/%/* -type f -iname '*.cpp' -o -iname '*.hpp')
#   Build stuff here

但是shell find src/%/* ...最终扩展为shell find src//* ...。起初我以为是因为我只能有 1 个目标通配符,但即使在删除 src/%/dependency 之后,它也会遇到同样的问题。

更多上下文:我的目录包含一个“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
        • 目标文件
    • 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

【问题讨论】:

  • 我可以使用宏并将 % 传递给它,然后从宏中返回整个部分吗?
  • 这行不通。您可能会使用一些技巧来用您想要的所有源文件填充变量,但我认为如果您可以提供一些上下文,会有一个更好/更清洁的解决方案。
  • 好的,我已经更新了问题,希望能提供一些更有用的信息。

标签: makefile gnu-make


【解决方案1】:

我不知道如何使用标准、便携、制作功能来做你想做的事。但它可以通过高级 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 将有两个扩展,因此必须转义一些$ 符号($$)。在编写自己的&lt;compile recipe&gt;&lt;link recipe&gt; 时请记住这一点。例如,如果你想使用$@$^ 自动变量,不要忘记写$$@。示例:如果您的编译配方是:

    $(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 定义中将$(1) 替换为proj0,这样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 文档的this section

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

【讨论】:

  • 是的,它确实有效,但我最终可能只是为每个项目指定一个目标,以使其更便携(如果我在我的 Windows 机器上使用,我不确定 msys 的 make 是否可以使用它)。
  • Paul's rule number 1: 使用 GNU make。不要为编写可移植的 makefile 而烦恼,改用可移植的 make! GNU make 在 Windows 下移植。
  • 我对 GNU make 不是很熟悉(实际上我是昨天才开始的),但在这部分:$$(OBJS_$(1)): out/src/$(1)/%.o: src/$(1)/%.cpp $$(HPPS_$(1)) 这只是“链接”目标吗?我不完全理解那条线(除此之外其余的都很好)。比如,左侧依赖于中间,而中间又依赖于右侧?
  • 我提到的手册部分(注释#5)很好地解释了这一点,我想。我认为我不能做得更好。
猜你喜欢
  • 2016-08-08
  • 1970-01-01
  • 1970-01-01
  • 2015-01-01
  • 2012-08-25
  • 1970-01-01
  • 2017-08-21
  • 2016-12-30
  • 1970-01-01
相关资源
最近更新 更多