Makefile 将命令输出分配给变量
我有一个脚本可以压缩我的 css 文件并输出输出文件的文件名。
我正在尝试构建一个 makefile 来自动化该过程:
all:
@echo "Compiling CSS"
CSS_OUTPUT=$(shell php minify_css.php )
echo $(CSS_OUTPUT)
我正在尝试将输出文件名存储在 CSS_OUTPUT 变量中,但我做错了一些事情,因为整个 makefile 只是打印:
$ make
abcdefg.css
Compiling CSS
CSS_OUTPUT=
echo
所以输出未分配给 CSS_OUTPUT
。另外,为什么 php 输出在 @echo "Compiling CSS"
之前打印?
我已经尝试过这个:
all:
@echo "Compiling CSS"
CSS_OUTPUT=$(shell echo php minify_css.php )
echo $(CSS_OUTPUT)
但它只会变得更糟:
$ make
Compiling CSS
CSS_OUTPUT=php minify_css.php
./minify_css.php: line 1: ?php: No such file or directory
./minify_css.php: line 3: syntax error near unexpected token `dirname'
./minify_css.php: line 3: `require_once( dirname(__FILE__) . DIRECTORY_SEPARATOR . 'maintenance.php' );'
make: *** [css] Error 2
编辑 按照评论中提供的答案,建议使用 eval:
@echo "Compiling CSS"
$(eval CSS_OUTPUT:=$(shell php minify_css.php))
echo ${CSS_OUTPUT}
输出:
make: *** No targets specified and no makefile found. Stop.
I have a script that compresses my css files and outputs the filename of the output file.
I'm trying to build a makefile to automatize the process:
all:
@echo "Compiling CSS"
CSS_OUTPUT=$(shell php minify_css.php )
echo $(CSS_OUTPUT)
I'm trying to store the output filename in CSS_OUTPUT variable, but I'm doing something wrong, as this whole makefile just prints:
$ make
abcdefg.css
Compiling CSS
CSS_OUTPUT=
echo
So output is not assigned to CSS_OUTPUT
. Also, why is php output printed before the @echo "Compiling CSS"
?
I've tried with this:
all:
@echo "Compiling CSS"
CSS_OUTPUT=$(shell echo php minify_css.php )
echo $(CSS_OUTPUT)
But it only gets worse:
$ make
Compiling CSS
CSS_OUTPUT=php minify_css.php
./minify_css.php: line 1: ?php: No such file or directory
./minify_css.php: line 3: syntax error near unexpected token `dirname'
./minify_css.php: line 3: `require_once( dirname(__FILE__) . DIRECTORY_SEPARATOR . 'maintenance.php' );'
make: *** [css] Error 2
Edit
Following the answer provided in comments, which suggests using eval:
@echo "Compiling CSS"
$(eval CSS_OUTPUT:=$(shell php minify_css.php))
echo ${CSS_OUTPUT}
Outputs:
make: *** No targets specified and no makefile found. Stop.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
作品分两个阶段进行。首先,它读取 makefile、评估变量并构建依赖关系图。 Make 函数也会在此阶段进行评估。在第二阶段,它执行必要的配方来更新主要目标。这意味着
$(shell ...)
函数调用在第一阶段、任何配方运行之前得到扩展。该命令的输出替换了 shell 函数调用,但我怀疑 php 命令的输出不会发送到 STDOUT,因此不会以CSS_OUTPUT=abcdefg.css
结束(这会成为您想要的),abcdefg.css
会回显到屏幕上,并且 shell 函数的结果为空。接下来执行配方,但完成后,每一行都在单独的 shell 实例中运行,因此配方中不同行上的命令无法访问另一行上设置的 shell 变量(无论如何在这种情况下这都是无用的) ,因为上一个问题)。解决这个问题的一种方法是以分号和反斜杠结尾,将行粘合在一起。现在它们在单个 shell 实例中作为一行执行。
另一个问题是,在配方的最后一行中,您没有引用 shell 变量(这就是 CSS_OUTPUT),而是引用了从未设置过的 makefile 变量。
你有什么理由不这样做:
Make works in two phases. First, it reads in the makefile, evaluates variables and builds a dependency graph. Make functions also get evaluated during this phase. In the second phase it executes the necessary recipes to update the main target. This means the
$(shell ...)
function call gets expanded during the first phase, before any recipe is run. The output of the command is substituted for the shell function call, but I suspect the output of the php command doesn't go to STDOUT, so instead of ending up withCSS_OUTPUT=abcdefg.css
(which would be what you want),abcdefg.css
is echoed to the screen and the result of the shell function is empty.Next the recipe is executed, but when this is done, each line is run in a separate shell instance, so the commands on different lines in a recipe have no access to shell variables set on another line (which would be useless in this case anyway, because of the previous issue). One way to counter this is to glue the lines together by ending them with a semicolon and a backslash. Now they get executed as one line in a single shell instance.
Another problem is that in the last line of the recipe, you don't refer to a shell variable (which is what CSS_OUTPUT is), but to a makefile variable that has never been set.
Is there any reason why you don't just do it this way:
问题与解决方案
我自己也遇到了同样的问题。我已经找到了解决这个问题的方法,但它绝对不太理想。
如上所述,问题归结于执行
$(shell)
命令的时间。它在评估 makefile 变量时执行,即在解析或处理任何目标之前。要解决此问题,您可以将$(shell)
命令分解为单独调用 makefile。为了变得棘手,我只是在同一个文件中为所有文件创建后工作创建了一个“隐藏”目标,并对同一个 makefile 进行了递归 make 调用。结果看起来像这样:解释
覆盖命令
该命令的第一行强制设置一个不能从命令行覆盖的变量来收集 makefile 的名称。第一行必须被称为 makefile 中的绝对第一行,因为包含其他 makefile 可以改变这一点,并且它在包含的 makefile 中不起作用。值得注意的是,如果您没有在命令行上指定 makefile,则该文件将为空,这意味着从 makefile 中调用时可以以相同的方式调用它。
第二行只是通过设置一些默认选项来构造对 makefile 的调用,例如更改为最初调用 makefile 命令行的当前目录,并且仅包含要使用的 makefile 的规范(如果它包含在原始调用中)。
请注意,前两行当前已在我的构建系统中实现,因此它们实际上适用于对同一 makefile 的多个嵌套递归调用。
递归调用
这两行后面是回答原始查询的重要内容。为了演示文件创建而不是仅使用 shell 命令的返回,我假设您的文件还需要创建
minify_css.php
文件。当我对目标all_hid
的 makefile 进行递归调用时,我知道目标all
正在被评估。这意味着minify_css.php
依赖项已经被处理,所以我可以保证它已经存在,这个假设允许$(shell php minify_css.php)
命令在对 makefile 的第一次递归调用中进行评估并对生成的文件进行操作。下一步是收集CSS_OUTPUT
值并将其放入另一个递归调用中。为此,我再次从已经递归的调用中递归调用 makefile,并将其传递到命令行。请记住,在调用 makefile 期间,只有显式导出的那些变量在子 make 中可用,因此我们不能只是设置它,然后在下一行进行递归调用,它实际上必须作为命令的一部分传递-线。在下一级递归 makefile 中,当我们调用print_css_hid
目标时,该变量似乎已从命令行设置并且可以使用。子 make 注意事项
在这种情况下,您实际上是在递归地调用自己的 makefile,因此必须担心所有其他变量都没有正确设置。幸运的是这不会发生。传递到原始命令行的选项的任何命令行值都会自动提供给从文件内调用的 make 的每个子调用。由于可以影响 makefile 中变量构造的唯一输入源是正在操作的目录的内容、命令行设置、调用 makefile 的目录以及 makefile 本身内的设置,因此您只需确保您不会在嵌套调用 make 期间意外地将新生成的文件包含在“源”列表中,并且可以保证环境设置相同。
Problem & Solution
I've run into the same issue myself. I've found a way to work around it, but it's definitely less than ideal.
As described, the issue comes down to when the
$(shell)
command is executed. It's executed when the makefile variables are evaluated, which is before any of the targets have been resolved or processed. To work around this, you can break the$(shell)
command out to occur in a separate invocation of the makefile. To get tricky, I just created a "hidden" target for all my post-file-creation work within the same file, and made a recursive make call to my same makefile. The result looks something like this:Explanation
The override commands
The first line of this handles forcibly setting a variable that cannot be overridden from the command-line to collect the name of the makefile. The first line MUST be called as the absolute first line in your makefile since including other makefiles can change this and it doesn't work from within included makefiles. Of note is the fact that this will be empty if you didn't specify a makefile on the command-line, which should mean it can be called the same way when invoked from within the makefile.
The second line just constructs the call to the makefile by setting some default options like changing to the current directory the makefile command-line was originally invoked from and only including the specification of the makefile to use if it was included in the original call.
Note that these first two lines are currently implemented in my build system so they do in fact work for multiple nested recursive calls to the same makefile.
Recursive Call
Following these two lines are the important ones that answer your original query. To demonstrate file creation as opposed to just using a shell command's return, I pretended your file also needs to create the
minify_css.php
file. When I make the recursive call to the makefile for targetall_hid
, I know that the targetall
is being evaluated. This means that theminify_css.php
dependency has already been processed, so I can guarantee it already exists, an assumption that allows the$(shell php minify_css.php)
command to be evaluated in the first recursive call to the makefile and operate on the generated file. The next step was to collect theCSS_OUTPUT
value and get it into another recursive call. To do that I once again invoked the makefile recursively, from within an already recursive call, and pass it on the command-line. Remember that during a call to a makefile only those variables you explicitly export are available in sub-makes, so we can't just set it and then make the recursive call on the next line, it must actually be passed as part of the command-line. In the next level recursive makefile, when we call theprint_css_hid
target, the variable will appear to have been set from the command-line and can be used.Sub-make Considerations
In this case you're actually calling your own makefile recursively, so a concern has to be that all your other variables aren't set up correctly. Luckily this doesn't happen. Any command-line values for options passed to the original command-line are automatically provided to every sub-invocation of make called from within the file. Since the only sources of input that can affect construction of your variables within a makefile are the contents of directories you're operating on, command-line settings, the directory the makefile was called from, and settings within the makefile itself, you just have to make sure you aren't doing something like accidentally including your newly generated file in a "sources" list during nested calls to make and you can guarantee the environment will be set up the same.