Makefile 基础教程及示例

Makefile

本文介绍 Makefile 的基础知识,包含了 Makefile 中最关键的内容。主体来自于Makefile教程和示例指南,并根据其英文原博文(https://makefiletutorial.com/)以及 GNU make 的官方文档(https://www.gnu.org/software/make/manual/html_node/index.html)调整、补充了部分内容。

基础

Makefile 意义

Makefile 用于帮助决定大型程序的哪些部分需要重新编译。在绝大多数情况下,都会编译 C 或 C++ 文件。其他语言通常有自己的与 Make 功能类似的工具。Make 也可以在编译之外使用,即当需要根据已更改的文件运行一系列指令时。本教程将重点介绍 C/C++ 编译用例。

下面是可以使用 Make 构建的依赖关系图示例。如果任何文件的依赖项发生更改,则该文件将被重新编译:

one.cpp
one.h
main.cpp
two.cpp
two.h
libm
libc

Make 的替代

流行的 C/C++ 替代构建系统有 SCons、CMake、Bazel 和 Ninja。一些代码编辑器(例如 Microsoft Visual Studio)有自己的内置构建工具。对于 Java,有 Ant、Maven 和 Gradle。其他语言(例如 Go、Rust 和 TypeScript)都有自己的构建工具。

Python、Ruby 和 raw Javascript 等解释性语言不需要与 Makefile 类似的东西。 Makefile 的目标是根据已更改的文件来编译需要编译的任何文件。但是,当解释语言中的文件发生更改时,不需要重新编译任何内容。程序运行时,将使用该文件的最新版本。

Make 的版本和类型

Make 有多种实现,但本指南的大部分内容都适用于任一版本。然而,它是专门为 GNU Make 编写的,GNU Make 是 Linux 和 MacOS 上的标准实现。所有示例都适用于 Make 版本 3 和 4,除了一些深奥的差异之外,它们几乎相同。

Make 基本运行

运行首先需要一个终端,并安装 make。对每一个项目,需包含在一个名为Makefile的文件(无后缀)中,并在同级目录下运行命令make

最简单的 Makefile:

hello:
	echo "Hello, World"

Makefile 必须使用 TAB 键进行缩进,其中不能有空格。

运行如下:

$ make
echo "Hello, World"
Hello, World

Makefile 语法

Makefile 的主要部分由规则组成,可有多条规则。

一条规则的基本语法:

targets: prerequisites
	command
	command
	...
  • 目标(target),每条规则可有多个,以空格分隔,但一般只有一个,且为文件名(make将其视为文件名);
  • 命令(command),一系列步骤,一般用于创建目标文件,开始的缩进必须是tab而非空格;
  • 先决条件(prerequisite),又称依赖(dependency),为文件名,可有多个,以空格分隔,在运行命令前需要存在的文件。在执行规则时,prerequisites中的每个prerequisite均需满足以下至少一点:
    • 当前目录下存在同名文件;
    • Makefile中存在以此名为target的规则。

Makefile 精髓

  • 运行时,在终端输入make <target>,然后make会执行该target对应的规则(如果同一目标有多条规则,则会警告,并执行最后一条规则);若在终端输入make,则默认执行第一条规则;

  • 当在终端输入make [<target>]时,make对该target对应规则中的每一个prerequisite进行判断:Makefile中是否存在以其为名的target,若有,则先对对应规则进行递归执行;

    如:

    test1: test2
    	echo "test1" > test1
    test2: 
    	echo "test2" > test2 
    

    在该例子中,test1的内容其实与test2无关,所以这个依赖关系只是对make而言的。

    当执行make test1make时,make实质上会先执行make test2

  • 执行规则时,make会先判断是否要执行命令,需满足以下条件的至少一点才会执行:

    1. target为名的文件不存在;
    2. 存在比target更新的prerequisite;(prerequisitetarget更新,即prerequisite文件内容的修改时间晚于target文件make会记录并更新该目录下每个文件被更新的时间戳)

    根据第一点,可以构造一种特殊规则,该规则不会创建以target为名的文件,因而一般情况下在输入make <target>时总是会被执行。常见的有cleanall等。(显然,若该目录下手动创建了对应名称的文件,则该构造就失效了,对应解决方案见后文)

    例如:若Makefile内容如下:

    test1: test2
    	echo "test1"
    test2: test3
    	echo "test2"
    
    1. 若开始时目录下只有该Makefile文件,然后手动依次创建test3test2test1文件(创建文件也是一种手动更新),再执行make,则会显示:

      $ make
      make: 'test1' is up to date.
      

      即没有规则被执行。

      分析如下:

      1. 执行make时,由于未指定target,故默认选择第一个规则,即执行make test1

      2. 执行make test1时,先递归执行其prerequisite,即先执行make test2

      3. make test2,进行判断,发现:

        1. 该目录下存在test2文件;
        2. 其先决条件test3的更新时间早于test2(这是因为创建时间test3 早于test2);

        故不执行对应规则;

      4. 递归回到make test1,进行判断,发现:

        1. 该目录下存在test1文件;
        2. 其先决条件test2的更新时间早于test1(这是因为创建时间test2 早于test1);

        故不执行对应规则;

    2. 换一种情况,若开始时目录下只有该Makefile文件,然后手动依次创建test2test3test1文件(即调换test2test3的创建顺序),再执行make,则会显示:

      $ make
      echo "test2"
      test2
      

      test2对应规则被执行了,test1对应规则未被执行。分析略。

make clean

clean通常用作删除其他目标的输出的目标,但它在make中并不是一个专有的词。

注:

  • 一般而言,clean不是第一个目标 (默认目标),也不是先决条件。 这意味着除非显式调用make clean,否则它永远不会运行;
  • 如“Makefile 精髓”中所述,clean不是一个文件名。 如果你碰巧有一个名为 “clean” 的文件,则此目标将无法运行,这不是我们想要的。 请参阅本教程后面的 .PHONY,了解如何解决此问题。

例:

some_file: 
	touch some_file

clean:
	rm -f some_file

变量

变量只能是字符串,一般通过:=赋值(使用= 也可以,具体见后续部分)。

使用$()${}引用变量(单字母名称变量也可以不用括号,但不推荐)。

例:

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

特殊目标

all

当需要制作多个目标并让所有目标都运行时,惯例是制作一个all,并将其置为第一个规则,以使在调用make且未指定目标时默认执行。同cleanall也不是一个专有词,故在目录下存在all文件时其可能无法执行。例:

all: one two three

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

clean:
	rm -f one two three

多目标

当一个规则有多个目标时,将分别为每个目标运行命令。一般会用到自动变量$@,其分别匹配目标名称,例如:

all: f1.o f2.o

f1.o f2.o:
	echo $@
# 相当于:
# f1.o:
#	 echo f1.o
# f2.o:
#	 echo f2.o

通配符与自动变量

通配符

Makefile中可用的通配符有*%,其中*其实是Unix shell 的通配符,而**%** 是 Makefile 中的模式匹配通配符。

* 通配符

*在目标,先决条件或 wildcard 函数中使用,会在文件系统中搜索匹配的文件名,建议始终将其包装在wildcard 函数中(若未匹配到任何文件,且未在wildcard函数中运行时,*将成为其本身)。

例:

# 打印出每个.c文件的文件信息,其中$?是一个自动变量,见后文
print: $(wildcard *.c)
	ls -la  $?

注意:

  • *不能在变量定义中使用。
  • * 不匹配任何文件时,它将维持原样 (除非在wildcard 函数中运行)

例:

thing_wrong := *.o # 不要这样做!‘*’不会展开
thing_right := $(wildcard *.o)

all: one two three four

# 失败,因为$(thing_wrong)是字符串“*.o”
one: $(thing_wrong)

# 如果没有与此模式匹配的文件,则保持为 *.o
two: *.o 

# 在这种情况下,它什么也不做。
three: $(thing_right)

# 与规则three相同
four: $(wildcard *.o)
%通配符

%通配符有多种使用场景:

  • 在“匹配”模式下使用时,匹配字符串中的一个或多个字符。 这种匹配被称为词干(stem)。
  • 在“替换”模式下使用时,它会获取匹配的词干,并替换字符串中的词干。
  • % 最常用于规则定义和某些特定函数中。

用于:

  • 静态模式规则
  • 模式规则
  • 字符串替换
  • vpath指令

自动变量

Make中存在很多自动变量,详见Automatic Variables (GNU make)

常用的有:

  • $@:目标名称;
  • $?:比目标更新的所有先决条件;
  • $^:所有先决条件;
  • $<:第一个先决条件;若目标从一个隐式规则中获得其配方,则为隐式规则添加的第一个先决条件;

其他规则

隐式规则

当未指定配方或直接未使用规则时,Make 会在c编译中调用一些隐式规则,包括:

  • 编译C程序: 从 n.c 自动生成 n.o,命令形式为 $(CC) -c $(CPPFLAGS) $(CFLAGS)
  • 编译C++程序:从n.ccn.cpp自动生成n.o,命令形式为$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
  • 链接单个目标文件: 从n.o自动构造n, 通过运行命令$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)

隐式规则使用的重要变量包括:

  • CC: 编译C程序的程序;默认cc
  • CXX: 编译C++程序的程序; 默认 “g++”
  • CFLAGS: 要提供给C编译器的额外标志
  • CXXFLAGS: 给C++编译器的额外标志
  • CPPFLAGS: 要提供给C预处理器的额外标志
  • LDFLAGS: 当编译器应该调用链接器时,会给他们额外的标志

例:

CC = gcc # 隐式规则标志
CFLAGS = -g # 隐式规则的标志。打开调试信息

# 隐式规则 #1:blah是通过C链接器隐式规则构建的
# 隐式规则 #2: blah.o是通过C编译隐式规则构建的,因为blah.c存在
blah:

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

clean:
	rm -f blah*

静态模式规则

静态模式规则在Makefile中用于减少编写量,语法如下:

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

本质是给定的 targettarget-pattern 匹配 (通过 % 通配符)。 任何匹配的东西都被称为词干。 然后将词干替换为 “prereq-pattern”,以生成目标的prereq。

例:

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

# 这些文件通过隐式规则进行编译
# 语法 - targets ...: target-pattern: prereq-patterns ...
# 在第一个目标foo.o的情况下,目标模式与foo.o匹配,并将“词干”设置为“foo”。
# 然后用该词干替换prereq模式中的 “%”
$(objects): %.o: %.c

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

%.c:
	touch $@

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

可以在静态模式规则中使用 filter过滤器函数,以匹配正确的文件。

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

# 原教程中无 .PHONY: all,在我的环境中这会导致 make 将 all 视为文件名,又由于未给定命令,make 会应用隐含规则,导致报错。
.PHONY: all
all: $(obj_files)

$(filter %.o,$(obj_files)): %.o: %.c
	echo "target: $@ prereq: $^"
$(filter %.result,$(obj_files)): %.result: %.raw
	echo "target: $@ prereq: $^" 

%.c %.raw:
	touch $@

clean:
	rm -f $(src_files)

模式规则

模式规则可大致理解为:

  • 定义自己的隐含规则的方法
  • 静态模式规则的更简单形式

模式规则在目标中包含‘%’。 此 ‘%’ 匹配任何非空字符串,其他字符匹配自己。 模式规则先决条件中的‘%’代表与目标中的‘%’匹配的同一词干。

例:

# 定义将每个.c文件编译为.o文件的模式规则
%.o : %.c
		$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

双冒号规则

很少使用双冒号规则,但用它允许为同一目标定义多个规则。 如果这些规则是单冒号,则会打印一条警告,并且只运行这里的最后一组命令。当使用双冒号时,每条规则都需要使用双冒号,否则会报错。

例:

all: blah

blah::
	echo "hello"

blah::
	echo "hello again"

命令和执行

命令回显/静默

make会将其执行的命令打印在终端中,在某条命令之前添加@可阻止打印该命令;

例:

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

在执行make时使用-s标志,可阻止命令回显;

命令执行

每条命令在效果上都是在一个新的shell中运行的。若需要命令在同一shell中运行,则可使用;将多个命令连接在同一行。

例:

all: 
	cd ..
	# 上面的cd不会影响该行,因为每个命令都有效地在新的shell中运行
	echo `pwd`

	# 此cd命令会影响下一个命令,因为它们在同一行上
	cd ..;echo `pwd`

	# 此cd命令会影响下一个命令,因为它们在同一行上
	cd ..; \
	echo `pwd`

默认Shell

默认shell是/bin/sh,可通过改变变量SHELL来更改此设置:

SHELL=/bin/bash

cool:
	echo "Hello from bash"

错误处理

默认情况下,make执行命令出错时,会终止运行。

在某条命令前加-,可以在其出错时忽视之,继续执行。

在运行make时使用-i参数,相当于在每条命令前加-

在运行make时使用-k参数,则在某个命令失败时,make将继续执行后续的目标,如果目标本身有依赖关系,make 会尽量执行不受影响的部分,仍然会报告错误。

中断或杀死 Make

若在执行make时按下ctrl+c,则将删除make刚创建的较新的目标。

Make 的递归使用

make 的递归使用是指在一个 Makefile 中调用其他 Makefile 来处理子目录中的构建任务。这通常用于大型项目,其中不同模块或子项目的构建逻辑被分割到多个子目录中。递归调用 make 的方式有助于模块化构建,提高可维护性和扩展性。

在递归使用 make 时,父级 Makefile 会调用子目录中的 Makefile。这种递归是通过在父级 Makefile 中使用 $(MAKE) 变量(不要直接使用make命令)来实现的。$(MAKE) 是一个特殊的变量,它表示调用当前 make 命令自身。使用 $(MAKE) 变量可以确保调用的 make 使用与当前父级 make 相同的选项和环境。

new_contents = "hello:\n\ttouch inside_file"
all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	# -C参数表示在指定目录下寻找Makefile执行
	$(MAKE) -C subdir

clean:
	rm -rf subdir

使用$(MAKE)变量相当于在该行前面添加了一个++用在配方前,表示强制执行该命令,即使该命令已经被标记为”无效“或未被更改,也不会被跳过。这一特性主要会影响make-t(‘–touch’),-n(‘–just-print’)或-q(‘–question‘)参数(详见Instead of Execution (GNU make))。

递归调用中的变量传递

make会传递以下变量:

  • 显示要求的变量;
  • 初始定义在环境中的变量;
  • 在子make调用的命令行设定且变量名中只有数字、字母和下划线。

显示要求变量传递需要使用export,即:

export variable ...

阻止变量传递则需使用unexport,即:

unexport variable ...

如果想要默认传递所有变量,只需单独使用export

export

也可使用.EXPORT_ALL_VARIABLES 来实现。且若考虑与旧版本make的兼容性的话使用.EXPORT_ALL_VARIABLES更好,其在新版本中生效,而在旧版本中被忽略。
例:

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

cooly = "The subdirectory can see me!"

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时使用的参数会记录在MAKEFLAGS变量中,而MAKEFLAGS变量是默认传递给子make的,即使用$(MAKE)时,其自动采用和上层make同样的选项。

make的参数

详见GNU make

变量

类型和修饰

类型有两种:

  • 递归(使用=声明):在使用命令时才会查找变量,而不是在定义命令时查找变量;
  • 简单展开(使用:=声明):只有在目前为止定义的变量才会被展开。

例:

# 递归变量。这将能在下面打印出 “later”
one = one ${later_variable}
# 简单展开变量。这无法在下面打印出 “later”
two := two ${later_variable}

later_variable = later

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

简单展开允许追加变量,而递归定义可能触发无线循环错误。、

例:

one = hello
# one被定义为一个简单展开变量(:=),因此可以处理追加
one := ${one} there

all: 
	echo $(one)

?=仅设置尚未被设置的变量:

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

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

变量定义时其值前面的空格会被忽略,而其后的空格则会被保留。需要在值前有空格则需使用$(with_space),单空格变量则需使用$(nullstring)

例:

with_spaces = hello   # with_spaces在 "hello" 之后有很多空格
after = $(with_spaces)there

nullstring =
space = $(nullstring) # 构造一个单个空格变量。

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

当变量未被定义时,其实际上是空字符串。

可使用+=进行追加。

字符串替换也是一种真正常见且有用的修改变量的方法。 另请查看 Text FunctionsFilename Functions

命令行参数和覆盖

在调用make时,可在命令行指定变量。而在 Makefile 中,则可使用override覆盖之。

例:

使用make option_one=hi运行以下 Makefile:

# 覆盖命令行参数
override option_one = did_override
# 未覆盖的命令行参数
option_two = not_override
all: 
	echo $(option_one)
	echo $(option_two)

命令列表与define

可通过 define 指令定义一组命令(配方),称为罐装配方(canned recipes), 以便在多个目标中复用。其定义的每个命令依然是分别在单独的shell里运行。罐装配方事实上为一个变量,故其名字不能与其他变量名冲突。

例:

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

define two
export blah=set
echo $$blah
endef

# one和two是不同的。

all: 
	@echo "这会打印 'I was set'"
	@$(one)
	@echo "这不会打印 'I was set' 因为每个command都在单独的shell中运行"
	@$(two)

目标特定变量

目标特定变量允许基于make正在构建的目标来为同一个变量赋不同的值。和自动变量一样,该值旨在该目标的配方上下文中有效(也包括该目标的其他目标特定变量赋值语句)。

设定一个目标特定变量如下:

target … : variable-assignment

例:

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)

条件语句

一个无else的简单条件语句的语法为:

conditional-directive
text-if-true
endif

其中的text-if-true可以是任意行文本,当条件为真时,其会被视为makefile的一部分,而条件为假时,其中文本不会被采用。

使用了else的条件语句语法如下:

conditional-directive
text-if-true
else
text-if-false
endif

或:

conditional-directive-one
text-if-one-is-true
else conditional-directive-two
text-if-two-is-true
else
text-if-one-and-two-are-false
endif

其中else conditional-directive的数量可以有多个。一旦其中一个条件为真,则 text-if-true 会被使用,而其他的语句就不会被采用。

conditional-directive 的语法有四种:

  1. ifeq (arg1, arg2)
    ifeq 'arg1' 'arg2'
    ifeq "arg1" "arg2"
    ifeq "arg1" 'arg2'
    ifeq 'arg1' "arg2"
    

    其扩展 arg1 和 arg2 中的所有变量引用,并比较之,若相同,则 text-if-true 被采用。

    若想测试一个变量是否为非空值,则可使用strip函数来避免将空格视为非空值。

    例:

    ifeq ($(strip $(foo)),)
    text-if-empty
    endif
    
  2. ifneq (arg1, arg2)
    ifneq 'arg1' 'arg2'
    ifneq "arg1" "arg2"
    ifneq "arg1" 'arg2'
    ifneq 'arg1' "arg2"
    

    类似ifeq,只是相反,在两参数不同时条件为真。

  3. ifdef variable-name
    

    以变量名为参数,而不展开变量引用。若该变量的值非空,则条件为真(故在赋值时赋空值也会被视为未定义)。

  4. ifndef variable-name
    

    若变量值为空,则条件为真。

    例:

    bar =
    foo = $(bar)
    
    all:
    ifdef foo
    	echo "foo is defined"
    endif
    ifndef bar
    	echo "but bar is not"
    endif
    

测试 make 标志

可使用findstring函数与MAKEFLAGS变量来测试make命令运行时使用的标志,如-t

MAKEFLAGS会将所有单字母标志(如-t)放在第一个词中,若未指定单字母标志,则该词为空。可以在变量前加一个字符来确保该词存在,如-$(MAKEFLAGS)(在变量前加了一个减号)。

例如:

bar =
foo = $(bar)

all:
# 搜索 “-i” 标志。 MAKEFLAGS只是一个单一字符的列表,每个标志一个字符。 所以在这种情况下寻找 “i”。
ifneq (,$(findstring i, $(fistword -$(MAKEFLAGS)))
	echo "i was passed to MAKEFLAGS"
endif

函数

函数主要用于文本处理,调用函数的形式为:$(fn, arguments)${fn, arguments}。make 有许多内置函数,详见Functions (GNU make)。其中 call 内置函数可用于创建函数。

注意:变量后不要跟空格,否则空格会被视为变量的一部分。

字符串替换

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

在text空格分隔的单词中与pattern匹配的,并将其替换为replacement。 在这里,模式可以包含充当通配符的‘%’,匹配单词中任意数量的任何字符。 如果替换还包含 ‘%’,则将 ‘%’ 替换为与模式中的 ‘%’ 匹配的文本。 只有模式和替换中的第一个‘%’会以这种方式处理;任何后续的‘%’都不会改变。

(GNU docs)

替换引用 $(text:pattern=replacement) 是这方面的简写。

还有一个只替换后缀的简写:$(text:suffix=replacement)。 这里不使用 % 通配符。

注意:不要为此简写添加额外的空格。 它将被视为搜索或替换术语。

例:

foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# 这是上述内容的简写
two := $(foo:%.o=%.c)
# 这是仅后缀的简写,也等同于上述。
three := $(foo:.o=.c)

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

foreach函数

foreach函数形式为:$(foreach var,list,text)

其中varlist会在完成任何其他事情之前进行展开,而text参数未同时进行展开。然后对于list展开后的值中的每个词,其会被设定一个变量名,为var展开之后的值,此时再展开text

结果是text展开次数和list中以空格分隔的词的数量一致。

例:

foo := who are you
# 对于foo中的每个“word”,输出相同的单词,并在后面加一个感叹号
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
	# 输出是 "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)

调用函数

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:
	# 输出 “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) # 很难看,因为换行不见了!

其他特性

包含其他 Makefile

include指令令make停止读取当前 Makefile,并在继续前读取一个或多个其他 Makefile。指令形式如下:

include filenames

其中filenames可包含 shell 文件名模式。

此行开始可以有额外的空格,其会被忽略,但不能有 tab 键,否则会被视为配方行。若文件名包含变量或函数引用,则会被展开。

当你使用诸如 -M 之类的编译器标志基于源创建Makefiles时,这特别有用。 例如,如果某些c文件包含头文件,则该头文件将被添加到由GCC编写的Makefile中。

vpath指令

vpath用于指定一些先决条件的存在位置。格式为vpath <pattern> <directories, space/colon seperated>,其中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

换行

Makefile 中一个命令一行,但命令太长时为可读性可使用\进行换行,其逻辑上依然为一行。

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

.PHONY

.PHONY用于使 make 将目标视为非文件,从而避免目标与实际存在的同名文件冲突。

make clean,其规则一般用于清除项目目录中的临时文件和中间文件,而非创建一个名为clean的文件,但若目录下存在一个clean文件,则可能导致其规则不执行,此时即需使用.PHONY

故严谨起见,应在每个带有allclean的 Makefile 中使用,但实际上一般“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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值