关于makefile:翻译最好的makefile入门教程

12次阅读

共计 16867 个字符,预计需要花费 43 分钟才能阅读完成。

入门

为什么会存在 Makefile?

Makefile 用于帮忙决定大型程序的哪些局部须要从新编译。在绝大多数状况下,编译 C 或 C++ 文件。其余语言通常有本人的工具,其用处与 Make 类似。当你须要一系列指令来运行取决于哪些文件已更改时,Make 也能够在编译之外应用。本教程将重点介绍 C/C++ 编译用例。

这是你能够应用 Make 构建的示例依赖关系图。如果任何文件的依赖项产生更改,则该文件将被从新编译:

Make 有哪些代替计划?

风行的 C/C++ 代替构建零碎是 SCons、CMake、Bazel 和 Ninja。一些代码编辑器(如 Microsoft Visual Studio)有本人的内置构建工具。对于 Java,有 Ant、Maven 和 Gradle。Go 和 Rust 等其余语言有本人的构建工具。

Python、Ruby 和 Javascript 等解释型语言不须要相似于 Makefile。Makefiles 的指标是依据已更改的文件来编译须要编译的任何文件。然而当解释语言中的文件发生变化时,不须要从新编译任何货色。当程序运行时,将应用该文件的最新版本。

Make 的版本和类型

Make 有多种实现形式,但本指南的大部分内容都实用于你应用的任何版本。然而,它是专门为 GNU Make 编写的,它是 Linux 和 MacOS 上的规范实现。所有示例都实用于 Make 版本 3 和 4,除了一些深奥的差别之外,它们简直雷同。

运行示例

要运行这些示例,你须要一个终端并装置“make”。对于每个示例,将内容放在一个名为 的文件 Makefile 中,而后在该目录中运行命令 make。让咱们从最简略的 Makefile 开始:

hello:
    echo "Hello, World"

留神:Makefile 必须应用 TAB 而不是空格缩进,否则 make 会失败。

这是运行上述示例的输入:

$ make
echo "Hello, World"
Hello, World

而已!如果你有点困惑,这里有一个视频,介绍了这些步骤,并形容了 Makefile 的根本构造。

<iframe width=”1131″ height=”703″ src=”https://www.youtube.com/embed/zeEMISsjO38″ title=”Beginner Makefile Tutorial” frameborder=”0″ allow=”accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture” allowfullscreen></iframe>

生成文件语法

一个 Makefile 由一组规定组成。规定通常如下所示:

targets: prerequisites
    command
    command
    command
  • 指标是文件名,以空格分隔。通常,每条规定只有一个。
  • 这些命令是通常用于制作指标的一系列步骤。这些须要以制表符结尾,而不是空格。
  • 先决条件也是文件名,以空格分隔。这些文件须要在运行指标命令之前存在。这些也称为依赖项

Make 的精华

让咱们从一个 hello world 示例开始:

hello:
    echo "Hello, World"
    echo "This line will always print, because the file hello does not exist."

这里曾经有很多货色能够排汇了。让咱们合成一下:

  • 咱们有一个指标叫做 hello
  • 这个指标有两个命令
  • 此指标没有先决条件

而后咱们将运行 make hello. 只有hello 文件不存在,命令就会运行。如果 hello 的确存在,则不会运行任何命令。

重要的是要意识到我说 hello 的是 targetfile。那是因为两者是间接分割在一起的。通常,当运行指标时(也就是运行指标的命令时),这些命令将创立一个与指标同名的文件。在这种状况下,hello 指标不会创立hello 文件。

让咱们创立一个更典型的 Makefile – 一个编译单个 C 文件的文件。但在咱们这样做之前,请创立一个名为的文件,该文件 blah.c 具备以下内容:

// blah.c
int main() { return 0;}

而后创立 Makefile(Makefile 判若两人地称为):

blah:
    cc blah.c -o blah

这一次,尝试简略地运行 make. 因为没有提供指标作为 make 命令的参数,因而运行第一个指标。在这种状况下,只有一个指标 (blah)。第一次运行时,blah将创立。第二次,你会看到 make: 'blah' is up to date。那是因为该blah 文件曾经存在。然而有一个问题:如果咱们批改 blah.c 而后运行make,则不会从新编译。

咱们通过增加一个先决条件来解决这个问题:

blah: blah.c
    cc blah.c -o blah

当咱们 make 再次运行时,会产生以下一组步骤:

  • 抉择第一个指标,因为第一个指标是默认指标
  • 这有一个先决条件blah.c
  • Make 决定它是否应该运行 blah 指标。它只会在 blah 不存在或更新 blah.c 时运行 blah

最初一步很要害,也是 make 的精华。它试图做的是确定blahblah上次编译以来是否产生了先决条件。即如果 blah.c 被批改,运行 make 应该从新编译文件。反之,如果 blah.c 没有扭转,则不应从新编译。

为了实现这一点,它应用文件系统工夫戳作为代理来确定是否产生了变动。这是一个正当的启发式办法,因为文件工夫戳通常只有在文件被批改时才会扭转。但重要的是要意识到状况并非总是如此。例如,你能够批改一个文件,而后将该文件的批改工夫戳更改为旧的。如果你这样做了,Make 会谬误地猜想文件没有更改,因而可能会被疏忽。

多嘴一句: 确保你了解这一点。它是 Makefile 的要害,可能须要你几分钟能力正确理解.如果事件依然令人困惑,请尝试上述示例或观看下面的视频。

更多疾速示例

以下 Makefile 最终运行所有三个指标。当你 make 在终端中运行时,它将构建一个程序 blah,通过一系列步骤调用:

  • Make 抉择指标 blah,因为第一个指标是默认指标
  • blah 须要 blah.o,所以搜寻 blah.o 指标
  • blah.o 须要 blah.c,所以搜寻 blah.c 指标
  • blah.c 没有依赖关系,所以 echo 命令运行
  • 而后运行该 cc - c 命令,因为所有 blah.o 依赖项都已实现
  • 运行 topcc 命令,因为所有 blah 依赖都实现了
  • 就是这样:blah 是一个编译的 c 程序
blah: blah.o
    cc blah.o -o blah # Runs third

blah.o: blah.c
    cc -c blah.c -o blah.o # Runs second

# Typically blah.c would already exist, but I want to limit any additional required files
blah.c:
    echo "int main() { return 0;}" > blah.c # Runs first

如果你 delete blah.c,所有三个指标都将从新运行。如果你编辑它(从而将工夫戳更改为比 blah.o 更新),前两个指标将运行。如果你运行 touch blah.o(因而将工夫戳更改为 blah 更新),那么只有第一个指标会运行。如果你什么都不做,那么指标都不会运行。试试看!

下一个示例没有做任何新的事件,但依然是一个很好的附加示例。它将始终运行两个指标,因为 some_file 依赖于 other_file,它永远不会被创立。

some_file: other_file
    echo "This will always run, and runs second"
    touch some_file

other_file:
    echo "This will always run, and runs first"

Make clean

clean常被用作去除其余指标输入的指标,但在 Make 中并不是一个非凡的词。你能够运行 makemake clean在此创立和删除 some_file.

请留神,clean 这里做了两个新的事件:

  • 它不是第一个指标(默认),也不是先决条件。这意味着它永远不会运行,除非你明确调用make clean
  • 它不是一个文件名。如果你碰巧有一个名为 的文件 clean,这个指标将不会运行,这不是咱们想要的。请参阅.PHONY 本教程前面的无关如何解决此问题的信息
some_file: 
    touch some_file

clean:
    rm -f some_file

变量

变量只能是字符串。你通常会想要应用 :=,但= 也能够应用。见变量 Pt 2。

上面是一个应用变量的例子:

files := file1 file2
some_file: $(files)
    echo "Look at this variable:" $(files)
    touch some_file

file1:
    touch file1
file2:
    touch file2

clean:
    rm -f file1 file2 some_file

单引号或双引号对 Make 没有意义。它们只是调配给变量的字符。然而,引号对 shell/bash 很有用,你须要在 printf. 在此示例中,两个命令的行为雷同:

a := one two # a is assigned to the string "one two"
b := 'one two' # Not recommended. b is assigned to the string "'one two'"
all:
    printf '$a'
    printf $b

应用 ${} 或援用变量$()

x := dude

all:
    echo $(x)
    echo ${x}

    # Bad practice, but works
    echo $x 

指标

所有指标

制作多个指标并且你心愿所有指标都运行?做一个 all 指标。make 因为这是列出的第一条规定,如果在没有指定指标的状况下调用它,它将默认运行。

all: one two three

one:
    touch one
two:
    touch two
three:
    touch three

clean:
    rm -f one two three

多个指标

当一个规定有多个指标时,将为每个指标运行命令。[email protected]是一个蕴含指标名称的主动变量。

all: f1.o f2.o

f1.o f2.o:
    echo [email protected]

# Equivalent to:
# f1.o:
#     echo f1.o
# f2.o:
#     echo f2.o

主动变量和通配符

* 通配符

*% 在 Make 中都称为通配符,但它们的含意齐全不同。*在你的文件系统中搜寻匹配的文件名。我倡议你始终将其包装在 wildcard 函数中,否则你可能会陷入上面形容的常见陷阱。

# Print out file information about every .c file
print: $(wildcard *.c)
    ls -la  $?

*能够在指标、先决条件或 wildcard 函数中应用。

危险:*不能在变量定义中间接应用
危险:当 * 没有匹配到文件时,放弃原样(除非在 wildcard 函数中运行)

thing_wrong := *.o # Don't do this!'*' will not get expanded
thing_right := $(wildcard *.o)

all: one two three four

# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)

# Stays as *.o if there are no files that match this pattern :(
two: *.o 

# Works as you would expect! In this case, it does nothing.
three: $(thing_right)

# Same as rule three
four: $(wildcard *.o)

%通配符

%的确很有用,然而因为能够应用的状况多种多样,因而有些凌乱。

  • 在“匹配”模式下应用时,它匹配字符串中的一个或多个字符。这种匹配称为茎。
  • 在“替换”模式下应用时,它采纳匹配的词干并替换字符串中的词干。
  • %最罕用于规定定义和某些特定性能中。

无关应用它的示例,请参阅以下局部:

  • 动态模式规定
  • 模式规定
  • 字符串替换
  • vpath 指令

主动变量

有许多主动变量,但通常只显示几个:

hey: one two
    # Outputs "hey", since this is the target name
    echo [email protected]

    # Outputs all prerequisites newer than the target
    echo $?

    # Outputs all prerequisites
    echo $^

    touch hey

one:
    touch one

two:
    touch two

clean:
    rm -f hey one two

花式规定

隐式规定

make 喜爱 c 编译。每次它表白爱意时,事件都会变得凌乱。Make 中最令人困惑的局部可能是制订的魔法 / 主动规定。调用这些“隐式”规定。我集体不批准这个设计决定,也不举荐应用它们,但它们常常被应用,因而理解它们很有用。以下是隐式规定列表:

  • 编译 C 程序:应用以下模式的命令 n.o 主动生成 n.c $(CC) -c $(CPPFLAGS) $(CFLAGS)
  • 编译 C++ 程序:n.o 由 n.cc 或 n.cpp 应用以下模式的命令主动生成 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
  • 链接单个指标文件:通过运行命令 n 主动生成 n.o $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)

隐式规定应用的重要变量是:

  • CC:用于编译 C 程序的程序;默认 cc
  • CXX: 用于编译 C++ 程序的程序;默认 g ++
  • CFLAGS: 提供给 C 编译器的额定标记
  • CXXFLAGS: 提供给 C++ 编译器的额定标记
  • CPPFLAGS: 给 C 预处理器的额定标记
  • LDFLAGS: 当编译器应该调用链接器时提供额定的标记

让咱们看看咱们当初如何构建一个 C 程序,而无需明确通知 Make 如何进行编译:

CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info

# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
blah: blah.o

blah.c:
    echo "int main() { return 0;}" > blah.c

clean:
    rm -f blah*

动态模式规定

动态模式规定是另一种在 Makefile 中少写的办法,但我想说它更有用,也少了一点 ” 魔力 ”。这是他们的语法:

targets...: target-pattern: prereq-patterns ...
   commands

实质是 target_parttern 来匹配给定的 target。匹配的内容称为stem。而后将stem 依据 prereq-patter 进行组合以生成指标的先决条件。

一个典型的用例是将文件编译.c 成.o 文件。这是手动形式:

objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c

all.c:
    echo "int main() { return 0;}" > all.c

%.c:
    touch [email protected]

clean:
    rm -f *.c *.o all

这是更无效的办法,应用动态模式规定:

objects = foo.o bar.o all.o
all: $(objects)

# These files compile via implicit rules
# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c

all.c:
    echo "int main() { return 0;}" > all.c

%.c:
    touch [email protected]

clean:
    rm -f *.c *.o all

动态模式规定和过滤器

当我稍后介绍函数时,我将预示你能够用它们做什么。该 filter 函数可用于动态模式规定以匹配正确的文件。在这个例子中,我组成了.raw 和.result 扩大。

obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c

all: $(obj_files)

$(filter %.o,$(obj_files)): %.o: %.c
    echo "target: [email protected] prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
    echo "target: [email protected] prereq: $<" 

%.c %.raw:
    touch [email protected]

clean:
    rm -f $(src_files)

模式规定

模式规定常常被应用但相当凌乱。你能够将它们视为两种形式:

  • 一种定义本人的隐式规定的办法
  • 一种更简略的动态模式规定

让咱们先从一个例子开始:

# Define a pattern rule that compiles every .c file into a .o file
%.o : %.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]

模式规定在指标中蕴含一个 %。这个% 匹配任何非空字符串,其余字符匹配它们本人。模式规定的先决条件中的% 代表与指标中的 % 匹配的雷同词干。

这是另一个例子:

# Define a pattern rule that has no pattern in the prerequisites.
# This just creates empty .c files when needed.
%.c:
   touch [email protected]

双冒号规定

双冒号规定很少应用,但容许为同一个指标定义多个规定。如果这些是单冒号,则会打印一条正告,并且只会运行第二组命令。

all: blah

blah::
    echo "hello"

blah::
    echo "hello again"

命令和执行

命令回显 / 静音

在命令之前增加一个 @ 以阻止它被打印
你也能够用 make -s 运行,这样相当于在每行之前增加一个 @

all: 
    @echo "This make line will not be printed"
    echo "But this will"

命令执行

每个命令都在一个新的 shell 中运行(或者至多成果是这样的)

all: 
    cd ..
    # The cd above does not affect this line, because each command is effectively run in a new shell
    echo `pwd`

    # This cd command affects the next because they are on the same line
    cd ..;echo `pwd`

    # Same as above
    cd ..; \
    echo `pwd`

默认 SHELL

默认 SHELL 是 /bin/sh. 你能够通过更改变量SHELL 来更改它:

SHELL=/bin/bash

cool:
    echo "Hello from bash"

$$

如果你想让一个字符串有一个美元符号,你能够应用$$. 这是如何在 bash 或 sh 中应用 shell 变量的办法。

请留神下一个示例中 Makefile 变量和 Shell 变量之间的区别。

make_var = I am a make variable
all:
    # Same as running "sh_var='I am a shell variable'; echo $sh_var" in the shell
    sh_var='I am a shell variable'; echo $$sh_var

    # Same as running "echo I am a amke variable" in the shell
    echo $(make_var)

-k-i- 的错误处理

  • 在运行 make 时增加-k,即便遇到谬误也能持续运行。如果你想一次查看 Make 的所有谬误,这很有帮忙。
  • 在命令前增加一个 - 以克制谬误
  • 增加 -i 以使每个命令都产生这种状况。
one:
    # This error will be printed but ignored, and make will continue to run
    -false
    touch one

打断或停止 make

仅留神:如果你 <keyboard>ctrl+c</keyboard>make,它将删除它刚刚删除你最新生成的指标。

递归应用 make

要递归调用 makefile,请应用 $(MAKE)而不是make,因为它会为你传递 make flags, 并且自身不会受到它们的影响。

new_contents = "hello:\n\ttouch inside_file"
all:
    mkdir -p subdir
    printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
    cd subdir && $(MAKE)

clean:
    rm -rf subdir

导出、环境和递归 make

当 Make 启动时,它会主动从执行时设置的所有环境变量中创立 Make 变量。

# Run this with "export shell_env_var='I am an environment variable'; make"
all:
    # Print out the Shell variable
    echo $$shell_env_var

    # Print out the Make variable
    echo $(shell_env_var)

该 export 指令承受一个变量并将其设置为所有配方中所有 shell 命令的环境:

shell_env_var=Shell env var, created inside of Make
export shell_env_var
all:
    echo $(shell_env_var)
    echo $$shell_env_var

因而,当你在 make 中运行 make 命令时,你能够应用该 export 指令使其可供子 make 命令拜访。在这个例子中,cooly 被导出以便 subdir 中的 makefile 能够应用它。

new_contents = "hello:\n\techo \$$(cooly)"

all:
    mkdir -p subdir
    printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
    @echo "---MAKEFILE CONTENTS---"
    @cd subdir && cat makefile
    @echo "---END MAKEFILE CONTENTS---"
    cd subdir && $(MAKE)

# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly

clean:
    rm -rf subdir

你还须要导出变量以使它们在 shell 中运行。

one=this will only work locally
export two=we can run subcommands with this

all: 
    @echo $(one)
    @echo $$one
    @echo $(two)
    @echo $$two
.EXPORT_ALL_VARIABLES 为你导出所有变量。.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"

cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly

all:
    mkdir -p subdir
    printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
    @echo "---MAKEFILE CONTENTS---"
    @cd subdir && cat makefile
    @echo "---END MAKEFILE CONTENTS---"
    cd subdir && $(MAKE)

clean:
    rm -rf subdir

make 选项

有一个很好的选项列表。看看 –dry-run, –touch, –old-file.

你能够一次运行多个指标,即make clean run test, 运行 clean 指标,而后 run,而后 test。

变量 2

风味和润饰

有两种类型的变量:

  • 递归 (=) – 仅在应用命令时查找变量,而不是在定义时查找变量。
  • 简略地扩大(:=)——就像一般的命令式编程, 只有那些到目前为止定义的变量被扩大
# Recursive variable. This will print "later" below
one = one ${later_variable}
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}

later_variable = later

all: 
    echo $(one)
    echo $(two)

简略扩大(应用:=)容许你附加到变量。递归定义将产生有限循环谬误。

one = hello
# one gets defined as a simply expanded variable (:=) and thus can handle appending
one := ${one} there

all: 
    echo $(one)

?=仅在尚未设置变量时设置变量

one = hello
one ?= will not be set
two ?= will be set

all: 
    echo $(one)
    echo $(two)

行尾的空格不会被删除,但结尾的空格会被删除。要应用单个空格创立变量,请应用$(nullstring)

with_spaces = hello   # with_spaces has many spaces after "hello"
after = $(with_spaces)there

nullstring =
space = $(nullstring) # Make a variable with a single space.

all: 
    echo "$(after)"
    echo start"$(space)"end

未定义的变量实际上是一个空字符串!

all: 
    # Undefined variables are just empty strings!
    echo $(nowhere)
用于 += 追加

foo := start
foo += more

all: 
    echo $(foo)

字符串替换也是一种十分常见且有用的批改变量的办法。另请查看 Text Functions 和 Filename Functions。

命令行参数和笼罩

你能够应用 override 笼罩来自命令行的变量。在这里,咱们运行make option_one=hi

# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all: 
    echo $(option_one)
    echo $(option_two)

命令列表和定义

define 批示符不是一个函数,只管它看起来可能是这样的。我见过它应用得很少,所以我不会具体介绍,但它次要用于定义[canned recipes](https://www.gnu.org/software/make/manual/html_node/Canned-Recipes.html#Canned-Recipes),并且与[eval](https://www.gnu.org/software/make/manual/html_node/Eval-Function.html#Eval-Function) 函数配合得很好。

define/endef简略只是创立一个代表一系列命令的变量。请留神,这与在命令之间应用分号有点不同,因为 define 里每个命令都在独自的 shell 中运行,正如预期的那样。

one = export blah="I was set!"; echo $$blah

define two
export blah="I was set!"
echo $$blah
endef

all: 
    @echo "This prints'I was set'"
    @$(one)
    @echo "This does not print'I was set'because each command runs in a separate shell"
    @$(two)

和指标绑定的特定变量

能够为特定指标调配变量

all: one = cool

all: 
    echo one is defined: $(one)

other:
    echo one is nothing: $(one)

和模式绑定的变量

你能够为特定指标模式调配变量

%.c: one = cool

blah.c: 
    echo one is defined: $(one)

other:
    echo one is nothing: $(one)

Makefile 的条件

if/else

foo = ok

all:
ifeq ($(foo), ok)
    echo "foo equals ok"
else
    echo "nope"
endif

查看变量是否为空

nullstring =
foo = $(nullstring) # end of line; there is a space here

all:
ifeq ($(strip $(foo)),)
    echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
    echo "nullstring doesn't even have spaces"
endif

查看是否定义了变量

ifdef不扩大变量援用;它只是查看是否定义了某些货色

bar =
foo = $(bar)

all:
ifdef foo
    echo "foo is defined"
endif
ifndef bar
    echo "but bar is not"
endif

$(makeflags)

此示例向你展现如何应用 findstring 和测试 make 标记 MAKEFLAGS。运行此示例 make - i 以查看它打印出 echo 语句。

bar =
foo = $(bar)

all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
    echo "i was passed to MAKEFLAGS"
endif

函数

第一个函数

函数次要只是用于文本处理。通过 $(fn, arguments)${fn, arguments}调用。你能够应用 call 调用本人创立的函数。Make 有相当数量的内置函数。

bar := ${subst not, totally, "I am not superman"}
all: 
    @echo $(bar)

如果要替换空格或逗号,应用变量

comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))

all: 
    @echo $(bar)

不要在第一个参数之后蕴含空格。这将被视为字符串的一部分。

comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))

all: 
    # Output is ", a , b , c". Notice the spaces introduced
    @echo $(bar)

字符串替换

$(patsubst pattern,replacement,text)执行以下操作:

“在文本中查找与 pattern 匹配的空格分隔的单词并用 replacement 替换它们。这里的 pattern 可能蕴含一个充当通配符的 ’%’,匹配一个单词中任意数量的任何字符。如果 replacement 还蕴含一个 ’%’,‘%’ 被替换为与模式中的 ‘%’ 匹配的文本。只有 patternreplacement中的第一个 ‘%’ 会以这种形式解决;任何后续的 ‘%’ 都不会扭转。(GNU 文档)

替换援用 $(text:pattern=replacement) 是对此的简写。

还有另一种只替换后缀的速记:$(text:suffix=replacement). 这里不容许 应用 % 通配符。

留神:不要为这个速记增加额定的空格。它将被视为搜索词或替换词。

foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)

all:
    echo $(one)
    echo $(two)
    echo $(three)

foreach 函数

foreach 函数如下所示 $(foreach var,list,text)
它将一个单词列表(由空格分隔)转换为另一个单词列表。var 设置为 list 中的每个单词,在 text 中顺次进行扩大。
上面的例子会在每个单词后附加一个感叹号:

foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
    # Output is "who! are! you!"
    @echo $(bar)

if 函数

if查看第一个参数是否为非空。如果是,则运行第二个参数,否则运行第三个。

foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

all:
    @echo $(foo)
    @echo $(bar)

call 函数

Make 反对创立根本函数。你只需通过 创立变量 来 ” 定义 ” 该函数,应用参数 $(0)$(1) 等。而后你能够应用非凡函数 call 调用该函数。语法是 $(call variable,param,param). $(0) 是变量,而 $(1),$(2) 等是参数。

sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

all:
    # Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
    @echo $(call sweet_new_fn, go, tigers)

shell 函数

shell – 它调用了 shell,但它用空格替换了换行符!

all: 
    @echo $(shell ls -la) # Very ugly because the newlines are gone!

其它性能

Include Makefile

include 指令通知 make 读取一个或多个其余 makefile。它是 makefile makefile 中的一行,如下所示:

include filenames...

当你应用 -M 编译器标记(如基于源代码创立 Makefile)时,这特地有用。例如,如果某些 c 文件蕴含头文件,则该头文件将被增加到由 gcc 编写的 Makefile 中。我在 Makefile Cookbook 中有更多的探讨

vpath 指令

应用 vpath 指定某些先决条件存在的地位。格局vpath <pattern> <directories, space/colon separated>

<pattern>能够有一个 `%,它匹配任何零个或多个字符。

你也能够应用全局变量 VPATH 执行此操作

vpath %.h ../headers ../other-directory

some_binary: ../headers blah.h
    touch some_binary

../headers:
    mkdir ../headers

blah.h:
    touch ../headers/blah.h

clean:
    rm -rf ../headers
    rm -f some_binary

多行

当命令太长时,反斜杠(“\”)字符使咱们可能应用多行

some_file: 
    echo This line is too long, so \
        it is broken up into multiple lines

.phony

增加.PHONY 到指标将避免 Make 将虚伪指标与文件名混同。
在此示例中,如果 clean 创立了文件,make clean 仍将运行。从技术上讲,我应该在每个示例中都应用 allclean,但我并没有放弃示例的简洁。此外,”phony” 指标通常都不是文件名的名称.

some_file:
    touch some_file
    touch clean

.PHONY: clean
clean:
    rm -f some_file
    rm -f clean

.delete_on_error

如果命令返回非零退出状态,make 工具将进行运行规定(并将流传回先决条件)。
如果规定以这种形式失败,DELETE_ON_ERROR将删除指标。这将产生在所有指标上,而不仅仅是 PHONY 指标。应用它始终是一个好主见,即便 make 出于历史起因没有这样做。

.DELETE_ON_ERROR:
all: one two

one:
    touch one
    false

two:
    touch two
    false

Makefile Cookbook

让咱们浏览一个十分丰盛的 Make 示例,它实用于中型我的项目。

这个 makefile 的奇妙之处在于它会主动为你确定依赖关系。你所要做的就是将你的 C/C++ 文件放入该 src/ 文件夹中。

# Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
TARGET_EXEC := final_program

BUILD_DIR := ./build
SRC_DIRS := ./src

# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')

# String substitution for every C/C++ file.
# As an example, hello.cpp turns into ./build/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)

# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)

# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP

# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
    $(CXX) $(OBJS) -o [email protected] $(LDFLAGS)

# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
    mkdir -p $(dir [email protected])
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o [email protected]

# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
    mkdir -p $(dir [email protected])
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o [email protected]


.PHONY: clean
clean:
    rm -r $(BUILD_DIR)

# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)

原文

正文完
 0