简体   繁体   English

makefile 子外壳中的变量扩展失败

[英]variable expansion in subshell in makefile fails

I have a makefile function inside a makefile (myfunction.mk):我在 makefile(myfunction.mk)中有一个 makefile function:

.ONESHELL:

define call_script
set +x
mkdir -p $$(dirname $(2))
if [ ! -f $(2) ]; then
echo "" > $(2)
fi
REDIRECT='| tee -a'
echo '>> $(1)'
($(1) ???????? $(2))
RET_CODE=$$?
echo "exit_code is: $$RET_CODE"
if [ ! $$RET_CODE = 0 ]; then 
echo "$(3) terminated with error $$RET_CODE"
exit $$RET_CODE
else 
if [ ! -z "$(strip $(3))" ]; then
echo "$(3) done"
fi
fi
endef

this function call a script and append result to a log (which is created with its folder if non existing), the result of the script is append only if the makefile variable given as the 4th ($(4)) argument is equal to 'yes'. this function call a script and append result to a log (which is created with its folder if non existing), the result of the script is append only if the makefile variable given as the 4th ($(4)) argument is equal to '是的'。

you call it like this:你这样称呼它:

include myfunction.mk

OUTPUT_ENABLED ?= yes

target:
  $(call call_script, echo "test", reports/mylog.log, "doing test", OUTPUT_ENABLED)  

This works for the most part:这在大多数情况下都有效:

  • if i replace '????????'如果我替换'????????' by '|通过'| tee -a', it works. tee -a',它有效。
  • if i replace '????????'如果我替换'????????' by $(REDIRECT), it fails.通过 $(REDIRECT),它失败了。
  • if i replace '????????'如果我替换'????????' by $$REDIRECT, it fails.通过 $$REDIRECT,它失败了。

why?为什么?

note: running it from a shell /bin/sh: symbolic link to dash注意:从 shell /bin/sh: symbolic link to dash

note: of course i want to add a ifeq that allows me to check for $(4) and replace | tee -a注意:当然我想添加一个ifeq允许我检查$(4)并替换| tee -a | tee -a by &>> | tee -a by &>>

I'll assume that you use call in a recipe, not flat in your Makefile.我假设您在配方中使用call ,而不是在 Makefile 中使用。 There are few problems with your shell script.您的 shell 脚本几乎没有问题。 First, if you try the following on the command line:首先,如果您在命令行上尝试以下操作:

mkdir -p reports
REDIRECT='| tee -a'
echo '>> echo "test"'
(echo "test" $REDIRECT reports/mylog.log)

you'll see that echo considers:你会看到echo认为:

"test" $REDIRECT reports/mylog.log

as its arguments.作为其 arguments。 They are expanded and echoed, which prints:它们被扩展和呼应,打印:

test | tee -a reports/mylog.log

on the standard output, not the effect you expected, I guess.在标准 output 上,我猜不是你预期的效果。 You could, for instance, use eval .例如,您可以使用eval On the command line:在命令行上:

eval "echo "test" $REDIRECT reports/mylog.log"

Which, in your Makefile, would become:其中,在您的 Makefile 中,将变为:

eval "$(1) $$REDIRECT $(2)"

Next you should not quote the third parameter of call because the quotes will be passed unmodified and your script will be expanded by make as:接下来,您不应引用call的第三个参数,因为引号将不加修改地传递,并且您的脚本将通过 make 扩展为:

echo " "doing test" terminated with error $RET_CODE"

Again probably not what you want.再次可能不是你想要的。

Third, you should avoid useless spaces in the parameters of call because they are preserved too (as you can see above between the first 2 double quotes):第三,你应该避免call参数中无用的空格,因为它们也被保留了(正如你在上面的前两个双引号之间看到的那样):

.PHONY: foo
foo:
    $(call call_script,echo "test",reports/mylog.log,doing test,OUTPUT_ENABLED)

And for your last desired feature, it would be slightly easier to pass the value of OUTPUT_ENABLED to call instead of its name, but let's go this way:对于您最后想要的功能,传递OUTPUT_ENABLED的值而不是其名称来call会稍微容易一些,但让我们这样 go :

$ cat myfunction.mk
define call_script
set +x
mkdir -p $$(dirname $(2))
if [ ! -f $(2) ]; then
echo "" > $(2)
fi
if [ "$($(4))" = "yes" ]; then
REDIRECT='| tee -a'
else
REDIRECT='&>>'
fi
echo '>> $(1)'
eval "$(1) $$REDIRECT $(2)"
RET_CODE=$$?
echo "exit_code is: $$RET_CODE"
if [ ! $$RET_CODE = 0 ]; then 
echo "$(3) terminated with error $$RET_CODE"
exit $$RET_CODE
else 
if [ ! -z "$(strip $(3))" ]; then
echo "$(3) done"
fi
fi
endef
$ cat Makefile
.ONESHELL:

include myfunction.mk

OUTPUT_ENABLED ?= yes

target:
    $(call call_script,echo "test",reports/mylog.log,doing test,OUTPUT_ENABLED)

Note that I moved the .ONESHELL: in the main Makefile because it is probably better to not hide it inside an included file.请注意,我将.ONESHELL:移动到主 Makefile 中,因为最好不要将其隐藏在包含的文件中。 Up to you.由你决定。

The most problematic issue here is that if you pipe your commands, the exit code is the exit code of the last command in a pipe, eg false | tee foo.log这里最成问题的问题是,如果您的命令是 pipe,则退出代码是 pipe 中最后一个命令的退出代码,例如false | tee foo.log false | tee foo.log will exit with 0 as tee will most probably succeed. false | tee foo.log将以 0 退出,因为tee很可能会成功。 Note also that pipe only redirects stdout, so your log will not contain any stderr messages unless explicitly redirected.另请注意,pipe 仅重定向标准输出,因此您的日志将不包含任何标准错误消息,除非明确重定向。

Considering that piping commands influence exit code and lack of portability of $PIPESTATUS (most specifically not being supported in dash ), I would try to avoid piping commands and use a temporary file for gathering output, ie:考虑到管道命令会影响$PIPESTATUS的退出代码和缺乏可移植性(特别是在dash中不支持),我会尽量避免管道命令并使用临时文件来收集 output,即:

$ cat Makefile
# $(1) - script to execute
# $(2) - log file
# $(3) - description
define call_script
echo '>> $(1)'
$(if $(OUTPUT_ENABLED), \
  $(1) > $@.log 2>&1; RET_CODE=$$?; mkdir -p $(dir $(2)); cat $@.log >> $(2); cat $@.log; rm -f $@.log, \
  $(1); RET_CODE=$$? \
); \
echo "EXIT_CODE is: $${RET_CODE}"; \
if [ $${RET_CODE} -ne 0 ]; then $(if $(3),echo "$(3) terminated with error $${RET_CODE}";) exit $${RET_CODE}; fi; \
$(if $(3), echo "$(3) done.")
endef

good:
        $(call call_script,echo "test",reports/mylog.log,doing test)

bad:
        $(call call_script,mkdir /root/foo,reports/mylog.log,intentional fail)

ugly:
        $(call call_script,bad_command,reports/mylog.log)

Regular call will not create the logs and will stop on errors:常规调用不会创建日志,并且会在出现错误时停止:

$ make good bad ugly
echo '>> echo "test"'
>> echo "test"
echo "test"; RET_CODE=$? ; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "doing test terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "doing test done."
test
EXIT_CODE is: 0
doing test done.
echo '>> mkdir /root/foo'
>> mkdir /root/foo
mkdir /root/foo; RET_CODE=$? ; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "intentional fail terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "intentional fail done."
mkdir: cannot create directory ‘/root/foo’: Permission denied
EXIT_CODE is: 1
intentional fail terminated with error 1
make: *** [Makefile:19: bad] Error 1

Note that ugly was not built due to failure on bad .请注意, ugly的不是由于bad上的失败而构建的。 Now the same with the log:现在与日志相同:

$ make good bad ugly OUTPUT_ENABLED=1
echo '>> echo "test"'
>> echo "test"
echo "test" > good.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat good.log >> reports/mylog.log; cat good.log; rm -f good.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "doing test terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "doing test done."
test
EXIT_CODE is: 0
doing test done.
echo '>> mkdir /root/foo'
>> mkdir /root/foo
mkdir /root/foo > bad.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat bad.log >> reports/mylog.log; cat bad.log; rm -f bad.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then echo "intentional fail terminated with error ${RET_CODE}"; exit ${RET_CODE}; fi;  echo "intentional fail done."
mkdir: cannot create directory ‘/root/foo’: Permission denied
EXIT_CODE is: 1
intentional fail terminated with error 1
make: *** [Makefile:19: bad] Error 1

$ cat reports/mylog.log
test
mkdir: cannot create directory ‘/root/foo’: Permission denied

Note that this time ugly was also not run.请注意,这次ugly也没有运行。 But if run later, it will correctly append to the log:但是如果稍后运行,它会正确 append 到日志中:

$ make ugly OUTPUT_ENABLED=1
echo '>> bad_command'
>> bad_command
bad_command > ugly.log 2>&1; RET_CODE=$?; mkdir -p reports/; cat ugly.log >> reports/mylog.log; cat ugly.log; rm -f ugly.log; echo "EXIT_CODE is: ${RET_CODE}"; if [ ${RET_CODE} -ne 0 ]; then  exit ${RET_CODE}; fi;
/bin/sh: 1: bad_command: not found
EXIT_CODE is: 127
make: *** [Makefile:22: ugly] Error 127

$ cat reports/mylog.log
test
mkdir: cannot create directory ‘/root/foo’: Permission denied
/bin/sh: 1: bad_command: not found

Personally I am not fan of implementing logging in this way.就我个人而言,我不喜欢以这种方式实现日志记录。 It is complicated and it only logs output of commands, not make output itself, and only of those commands which are explicitly called to do so.它很复杂,它只记录 output 的命令,而不是 output 本身,并且只记录那些被明确调用的命令。 I'd rather keep Makefile clean and simple and just run make 2>&1 | tee log我宁愿保持Makefile干净简单,只运行make 2>&1 | tee log make 2>&1 | tee log instead to have the output logged. make 2>&1 | tee log改为记录 output。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM