[英]How do I change a makefile target into a function?
我正在寻找一种干净的方法来将我在 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 行出现语法错误:syntax error near unexpected token `do'
通常,在 Makefile 中维护 shell 函数并不容易。 主要原因:
考虑几个选项:编写 shell helper 脚本(推荐),使用 make 用户定义的函数或将函数内联到 Make 中。 对于“回声”的具体情况,可能可以使用其他方法之一,请参阅“替代解决方案”
从个人经验来看,对于任何重要的函数,最好使用 shell 帮助脚本。 它们更容易开发和测试。
使用 Shell 帮助脚本
编写一个小助手脚本“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)
请注意,该函数可以编写为一个 liner,或者使用 '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"
首先,除非您指定.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)
另一个双彭北。
首先,我们要扩大$FLIST_01
的目标RUN1和FLIST_02
为目标RUN2。 一个干净的方法可能是:
FLIST_run1 := a b c
FLIST_run2 := 1 2 3
.PHONY: run1 run2
run1 run2:
for entity in ${FLIST_$@}; do echo $entity; done
对于稍微模糊的解决方案,我们注意到您尝试做的事情可以用make的 noddy 模式匹配来描述:
.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
在这里,当你的实例运行,请首先进行了RUN1的配方,那么几招RUN2,终于为运行的配方。
这有什么好处吗? 当然。 如果你运行make -j2运行则RUN1和RUN2命令将并行运行。 make的全部意义。
最后一个问题是摆脱 shell 循环,并用单独的命令替换它。 这样, make将为您检查echo的退出代码,如果合适,甚至可以并行运行它们。 当然,在这个 noddy 示例中没有帮助,但它们可以是例如编译命令。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.