在shell命令中定位通配符

时间:2017-08-02 11:05:09

标签: makefile gnu-make

我尝试使用目标名称创建依赖于目录中文件列表的目标:

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中,因此,主要依赖项是.outbin个与项目名称相同的文件。 (如果src/abc存在,bin/abc.outall}的依赖关系。

因此,src中的每个子目录都会编译所有源文件,并将目标文件移动到out/projname,最终将链接到bin/projname.out

  • SRC
    • proj0
      • cpp / hpp files
    • proj1
      • cpp / hpp files
    • SRC
      • proj0
        • 目标文件
      • proj1
        • 目标文件
    • 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的原因。

1 个答案:

答案 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)。

注意:

  1. 我假设项目的每个目标文件都依赖于同一项目的所有头文件。如果不是这样的话,你将不得不重做这一点。

  2. 重要的是要理解:

    • PROJECT_rule变量首先由call处理,以$(1)替换项目名称。
    • 然后通过eval递归完整地扩展(包括食谱)。递归意味着如果我们有:

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

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

      < / LI>
    • 结果将被实例化为常规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会将$(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并执行之前,如果它们是。

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

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