【问题标题】:How do I change a makefile target into a function?如何将 makefile 目标更改为函数?
【发布时间】:2020-01-03 01:25:45
【问题描述】:

我正在寻找一种干净的方法来将我在 makefile 中创建的一些目标声明更改为更具功能性的类型声明,在该声明中我能够传递变量并且输出将保持一致。

例如:

default: clean run1 run2

run1:
    for entity in $(FLIST_01); do \
        echo $entity ; \
    done

run2:
    for entity in $(FLIST_02); do \
         echo $entity ; \
    done

理想情况下,我想删除重复的运行目标声明,并且只有 1 个。

FLIST_01 = my_flist.txt
FLIST_02 = other.txt

default: clean run

run:
    $(run_func $(FLIST_01))
    $(run_func $(FLIST_02))

如何在make 中创建自定义函数来执行 run_func 应该执行的操作(传递给它的文件列表变量的 for 循环读取?

更新:

到目前为止我的尝试是这样的:

run:
    runfunc() { \
        echo "test" \
        for entity in $1; do \
            echo $(entity); \
        done \
    }

    runfunc $(FLIST_01)
    runfunc $(FLIST_02)

但我在 do 行出现语法错误:意外标记 `do' 附近出现语法错误

【问题讨论】:

    标签: shell makefile sh gnu-make


    【解决方案1】:

    首先,除非您指定.ONESHELL,否则目标中的命令将在单独的子shell 中运行。因此,您的 runfunc() 声明将在声明函数的 shell 中运行,然后退出。下一条语句将在一个新的 shell 中运行,该 shell 对已声明然后基本上被遗忘的函数一无所知。

    其次,

    echo "test" \
    for entity in $1;
    

    将由make扩展成

    echo "test" for entity in ;
    

    显然包含多个错误。你的意思是

    echo "test"; \
    for entity in $$1;
    

    正确地将美元符号传递给外壳。但总的来说,我会说你的方法是有缺陷的。您可以将其重构为 make 函数,就像您最初希望的那样;

    define run
       for entity in $(1); do \
           echo $$entity; \
       done
    endef
    

    现在你可以这样称呼了

    run:
        $(call run,$(FLIST_01))
        $(call run,$(FLIST_02))
    

    但是这个特殊的循环可以很容易地用一个 shell 语句替换。

    run:
        printf '%s\n' $(FLIST_01)
        printf '%s\n' $(FLIST_02)
    

    【讨论】:

      【解决方案2】:

      又一个双彭。

      首先,我们要为目标 run1 扩展 $FLIST_01,为目标 run2 扩展 FLIST_02。 一个干净的方法可能是:

      FLIST_run1 := a b c
      FLIST_run2 := 1 2 3
      
      .PHONY: run1 run2
      run1 run2:
          for entity in ${FLIST_$@}; do echo $entity; done
      

      对于一个稍微模糊的解决方案,我们注意到您正在尝试做的事情可以用 make 的点头模式匹配来描述:

      .PHONY: run1 run2
      run%: run1 run2:
          for entity in ${FLIST_$*}; do echo $entity; done
      

      这里我们使用静态模式规则。 在配方中,$* 扩展为与模式中的 % 匹配的任何内容。 这个解决方案对我有黄疸的眼睛来说看起来很干净。

      我敦促您寻找惯用的 make 而不是惯用的外壳。 您的 makefile 几乎总是会感谢您。

      首先,保留两个运行目标,而不是将它们合并到同一个配方中。

      .PHONY: run1 run2
      run%: run1 run2:
          for entity in ${FLIST_$*}; do echo $entity; done
      
      .PHONY: run
      run: run1
      run: run2
      run: ; echo $@ Success
      

      这里,例如,当你 make run 时,make 首先执行 run1 的配方,然后执行 run2 的配方,最后是 run 的秘诀。

      这样做有什么好处吗?当然。如果您运行 make -j2 run,那么 run1run2 的命令将并行运行。 make 的全部意义。

      最后一个问题是摆脱 shell 循环,并用单独的命令替换它。这样 make 将为您检查 echo 的退出代码,如果合适的话,甚至可以并行运行它们。当然,在这个点头示例中没有帮助,但它们可以是例如编译命令。

      【讨论】:

        【解决方案3】:

        通常,在 Makefile 中维护 shell 函数并不容易。主要原因:

        • 每个 shell '$' 都需要转义为 '$$'
        • $(...) 构造在 shell 和 Make 中的含义不同。
        • Makefile 中的每一行都由新的 shell 实例执行,这意味着变量赋值、函数定义等不会在行之间共享。

        考虑几个选项:编写 shell 帮助脚本(推荐)、使用 make 用户定义函数或将函数内联到 Make。对于“回声”的具体情况,可能可以使用其他方法之一,请参阅“替代解决方案”

        根据个人经验,对于任何重要的功能,最好使用 shell 辅助脚本。它们更容易开发和测试。

        使用 Shell Helper 脚本

        编写一个小帮助脚本“print-list.sh”

        #! /bin/bash
        echo "test" \
        for entity ; do
            echo $entity
        done
        

        然后从目标调用它

        FLIST_01 = a.txt b.txt c.txt
        
        t1:
                print-list.sh ${FLIST_01)
        

        内联 shell 函数

        如上所述,将 shell 函数嵌入到 Makefile 中需要匹配有关引用、转义等的 make 规则。调试脚本也需要付出很多努力,因为在此设置中错误消息非常神秘。

        请注意,RUNFUNC 必须包含在使用该命令的每个操作行之前。

        RUNFUNC = runfunc() { \
                echo "test" ; \
                for entity in $$*; do \
                    echo $$entity; \
                done \
            } ;
        
        FLIST_01 = a.txt b.txt c.txt
        
        t2:
                $(RUNFUNC) runfunc ${FLIST_01)
        

        请注意,该函数可以写成一行,或者使用'define'/'endef'来简化行尾转义。

        使用 make 用户定义函数

        可以使用 make 创建简单的函数。此选项需要经验和时间。

        UFUNC = \
                echo "test" ; \
                for entity in $1; do \
                    echo $$entity ; \
                done
        
        
        FLIST_01 = a.txt b.txt c.txt
        
        t3:
                $(call UFUNC, ${FLIST_01))
        
        

        替代解决方案

        最后一个选项是使用现有构造(假设该函数的唯一目标是将空格分隔的文件列表转换为换行符分隔

        t4:
                echo ${FLIST_01} | tr " " "\n"
        

        【讨论】:

        • 你可能想要for i in "$@",它可以缩写为for i
        • 另外你想要$entity${entity},而不是Bash 脚本中的$(entity)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-02-07
        • 2018-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多