一个Makefile中的Heredoc?

36

这到底可能吗?如果可以,应该如何实现?

更新:我需要这样做是因为我要从动态和静态数据创建一个文件。

用例:我有一个测试目录。每个C文件都产生一个测试可执行文件。使用

SRCS = $(wildcard [a-z]*.c)

我可以根据需要添加新的测试,然后使用 make 命令找到新的测试并编译、运行和进行内存泄漏检测。我还使用 git。我想让 .gitignore 包括这些可执行文件。

那么问题来了,如何创建 .gitignore 并且包含静态数据,即我希望被忽略的文件(*.odepend),以及动态生成的可执行文件?

8个回答

38

另一种GNU Make的解决方案。

您可以使用以下defineexport命令来实现:

define GITIGNOREDS
*.o
depend
endef

SRCS = $(wildcard [a-z]*.c)
EXES = $(SRCS:.c=)


export GITIGNOREDS
.gitignore: $(SRCS)
    echo $(EXES) | sed 's/ /\n/g' > $@
    echo "$$GITIGNOREDS" >> $@

在定义块内部进行扩展(如$(x))时,你必须小心。


30
是的,你可以这样做。正如其他人所指出的那样,你可能不应该这样做,但你可以。Ash's answer提供了一种使用定义命令的解决方案,但这很麻烦,可能会使变量扩展到正确值变得棘手。另一个技巧是使用.ONESHELL:特殊目标

有时,您希望将配方中的所有行都传递给单个shell调用。通常有两种情况下这是有用的:首先,在由许多命令行组成的makefile中,这可以通过避免额外的进程来提高性能。其次,您可能希望在配方命令中包括换行符(例如,也许您正在使用与SHELL非常不同的解释器)。如果在makefile中出现.ONESHELL特殊目标,则每个目标的所有配方行将提供给单个shell调用。配方行之间的换行符将被保留。

几个警告词:

  • 这将影响Makefile中所有配方的工作方式。
  • 行前缀运算符(例如-@)用于抑制输出或忽略错误仅适用于所有配方的第一行,并对其后的所有行生效。
  • 某些旧版本或GNU Make(例如即使在新容器中默认运行的Travis的CI系统上运行的版本)可能会忽略此指令,导致奇怪的错误和陷阱。确保您了解目标环境。

了解以上内容后,下面是如何生成markdown文件的示例:

SHELL = bash
.ONESHELL:
MYVAR = "Some Title"

file.md:
    cat <<- EOF > $@
        $(MYVAR)
        ========

        This stuff will all be written to the target file. Be sure
        to escape dollar-signs and backslashes as Make will be scanning
        this text for variable replacements before bash scans it for its
        own strings.

        Otherwise formatting is just as in any other bash heredoc. Note
        I used the <<- operator which allows for indentation. This markdown
        file will not have whitespace at the start of lines.

        Here is a programmatic way to generate a markdwon list all PDF files
        in the current directory:

        `find -maxdepth 1 -name '*.pdf' -exec echo " + {}" \;`
    EOF

注意一个额外的问题是Make会跳过空白行。如果在你的heredoc内容中有一个空白行很重要,你需要确保缩进该行与heredoc相匹配的适当级别,否则Make将吃掉它甚至不传递给cat!

只是添加一条注释。oneshell并不总是有效的。bash 3.2是默认的系统之一,它会忽略oneshell。 - mirageglobe
@JimmyMGLim Bash不会忽略ONESHELL。也许一些旧版本的make可以(或者你根本没有GNU make,而是使用了替代品),但作为shell的bash始终处理多行输入。 - Caleb

7

GNU Makefile可以做以下类似的事情。它看起来很丑,我不会说你应该这样做,但在某些情况下我会这样做。

.profile = \
\#!/bin/sh.exe\n\
\#\n\
\# A MinGW equivalent for .bash_profile on Linux.  In MinGW/MSYS, the file\n\
\# is actually named .profile, not .bash_profile.\n\
\#\n\
\# Get the aliases and functions\n\
\#\n\
if [ -f \$${HOME}/.bashrc ]\n\
then\n\
  . \$${HOME}/.bashrc\n\
fi\n\
\n\
export CVS_RSH="ssh"\n  
#
.profile:
        echo -e "$($(@))" | sed -e 's/^[ ]//' >$(@)

make .profile 如果不存在,则会创建一个 .profile 文件。

这个解决方案适用于应用程序只在 POSIX shell 环境中使用 GNU Makefile 的情况。该项目不是一个开源项目,平台兼容性也不是一个问题。

目标是创建一个 Makefile,以便更轻松地设置和使用特定类型的工作区。Makefile 会带来各种简单的资源,而无需像另一个特殊的存档文件等东西。可以通过一个过程来指定将此 Makefile 放入要工作的文件夹中。设置您的工作区并输入 make workspace,然后执行 blah,请输入 make blah 等等。

有时候可能会比较棘手的是如何确定需要引用哪些 shell。上述内容完成了任务,并且接近于在 Makefile 中指定 here 文档的想法。但是否适用于一般情况是另一个问题。


4
如果您正在使用Gnu Make,请使用“define”创建多行文本变量,并使用规则将其回显到文件中。请参见https://www.gnu.org/software/make/manual/make.html#Appending的6.8定义多行变量。示例如下:
  define myvar
  # line 1\nline 2\nline 3\n#etc\n
  endef

  myfile.txt:
         /bin/echo -e "$(myvar)) >myfile.txt

要创建此文件,建议使用编辑器创建文件,将"\n"附加到每行的末尾,然后将它们全部连接成一个字符串并粘贴到您的makefile中。在Linux上测试过GNU Make 3.81版本。


1
你有没有读过三年前Ash Berlin的回答?你的跟他的一样。 - nalply
1
我的更简单,不需要他使用的“export”。我的与他的不同。 - Joi Owen
在我更改了echo命令并使用bash包装器来执行echo后,这对我非常有效:bash -c 'echo -e "$(myvar)"' >myfile.txt - jerryb

4

这取决于你的决心。最好假设它不会起作用。编写一个 shell 脚本来替代 makefile 中的 here document。

失败1

heredoc:
    cat - <<!
    This is the heredoc.
    !

这将产生以下结果:
cat - <<!
This is the heredoc.
make: This: No such file or directory
make: *** [heredoc] Error 1

每行代码都是单独执行的 - 噢,出问题了。

失败2

heredoc:
    cat - <<! \
    This is the heredoc.\
    !

这将生成:
cat: This: No such file or directory
cat: is: No such file or directory
cat: the: No such file or directory
cat: heredoc.!: No such file or directory
make: *** [heredoc] Error 1

可能有使用特定版本的make工具的方法(例如GNU make可以在单个子shell中执行所有操作命令),但是这样你必须指定你的可移植性要求。对于常规(例如符合POSIX标准的)make,假设此处文档不起作用。


1
+1 感谢详细说明为什么在 Makefile 中使用 Heredocs 是一个坏主意。 - nalply

0

尽可能接近heredoc的方式:

Makefile:

switch-version:
    @printf "services:\n\
      myservice:\n\
        build:\n\
          args:\n\
            my_version: $(version)\n\
            some_other_arg: foo\n\
        image: my_image\n\
    " > docker-compose.override.yml

结果:

$ make switch-version version=1.2.3
$ cat docker-compose.override.yml 
services:
  myservice:
    build:
      args:
        my_version: 1.2.3
        some_other_arg: foo
    image: my_image
$ 

0
SRCS = (wildcard [a-z]*.c)
EXES = $(SRCS:.c=)
GITIGNOREDS = *.o depend

.gitignore: $(SRCS)
        echo $(EXES) | sed 's/ /\n/g' > $@
        echo $(GITIGNOREDS) | sed 's/ /\n/g' > $@

重要的是GITIGNOREDS那一行。我更喜欢像heredoc这样的东西

GITIGNOREDS = <<EOT
*.o
depend
EOT

但我也可以接受一个以空格分隔的文件列表,并使用sed脚本将空格转换为换行符。

编辑 正如Ryan V. Bissell所建议的那样:使用一个单独的测试子目录,你将其添加到gitignore中。然后一切都水到渠成。很简单。


2
我知道这是一个老问题,但是:你有没有考虑过只需将所有的.o和可执行文件发送到./test/out子目录,然后只需在.gitignore中添加一个静态的'./test/out'行? - Cognitive Hazard

-1

似乎在Makefile中没有办法使用Here-Document,但有一个可能的解决方法。可以使用echo命令发送Here-Document数据:

all:
    echo "some text" | myScript.sh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接