ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

一文了解Makefile

2022-09-04 08:00:25  阅读:341  来源: 互联网

标签:一文 make Makefile echo blah 了解 clean foo


本篇翻译自 《Learn Makefiles With the tastiest examples》,翻译主要是意译,加入了一些个人理解。 熟练英文的朋友请直接阅读原文。链接见: https://makefiletutorial.com/#getting-started

以下是翻译:

写这篇文档的原因是因为我发现我从来没有完全的理解Makefile,Makefile中存在许多隐藏规则和一些奇怪的符号,问简单的问题并不能得到简单的答案,为了解决这个问题,我花了数周时间去了解关于Makefile的一切知识,并把Makefile关键的知识浓缩在了这篇文档中,分了几个章节,每个主题都有一个简短的描述和一个示例。

如果你比较了解Makefile,可以直接翻到文末 Makefile Cookbook,提拱了一个中型项目的模板,包含详细的注释。

Good luck,希望这篇文档能消除你对Makefile的疑惑。

1 开始

为什么需要Makefile?

大型项目里Makefile比较有用,当仅有较小改动时,不需要重新完全的编译整个工程,由于Makefile指明了文件的依赖关系,这样重编译时可以只编译在依赖路径上的文件,最后对整个工程进行打包,这就是所谓的增量编译,这样可以大大减少重新编译的时间。

下图是一个简单工程的依赖关系图示例。Makefile正是用来描述这些文件的依赖关系,make 调用Makefile,来按照描述的步骤编译链接相关文件,并得到最终目标产物。

依赖关系图

总结Makefile的作用:

  • 自动化编译,make按照Makefile的描述,进行自动化编译

  • 增量编译, 第一次编译可能较慢,重新编译时会节约很多时间。

不同编程语言的构建工具

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

像Python、Ruby和Javascript这样的解释型语言不需要类似于Makefile的文件,Makefile的目标是根据已经更改的文件来编译任何需要编译的文件,但是当解释语言中的文件发生变化时,当程序运行时,将使用文件的最新版本,不需要重新编译任何东西。

Make的版本

Make有不同的版本,本文中的例子适用于Make的versions 3 和 4,它们基本差不多,除了某些特别的特性。

Running简单的用例

在跑用例之前,你需要安装Make

本文使用的环境是(仅仅作为参考,环境不是太老就行):

  • 系统ubuntu-20.4

  • GNU Make 4.2.1

新建一个目录: Makefile_test
Makefile中的内容如下所示:

hello:
	echo "Hello, World"

注意: Makefiles 规则(命名行)只能用 TAB开头,而非空格,否则会失败。

在当前文件夹下运行make:

$ make
echo "Hello, World"
Hello, World

Makefile语法

Makefile包含一系列规则,通用规则如下:

targets: dependencies
	command
	command
	command

targets: 目标(如果是多个目标用空格隔开,典型的是只有一个目标)

dependencies: 依赖,需要的文件,下面命令会用到,用来加工成目标

command: 命令序列,一步一步将依赖文件加工成目标。(注意:command是以Tab键开头)

Make的本质

还是hello world的用例:

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

目标: hello

依赖: 无

命令序列: 有两条echo命令

当我们执行make hello时,如果当前文件夹没有hello文件,将会执行后续指令。但是如果当前文件夹存在hello文件,则不会执行后续指令。

需要注意的是,hello既是目标也是文件,这是因为两者是直接联系在一起的。通常当一个目标运行时,命令将创建一个与目标同名的文件(不过在本例中hello目标没有创建hello文件)

如果文件名与目标名重名,make将无法分辨,如:上例中当前文件夹有hello文件,目标名也为hello时,当执行make hello指令,我们本来的意思可能是为了执行Makefile的目标,但是make分辨不了,以为是是要touch文件。(Makefile中目标可以用.PHONY修饰来明确指定目标名,后续会讲到)。

以下是一个单独c文件的编译,名字是blah.c

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

Makefile中将会创建一个blah文件:

blah:
	cc blah.c -o blah
  • 第一次make blah,由于当前文件夹没有blah文件,所以命令会执行,在当前文件夹下生成blah文件,如下所示。
Makefile_test$ ls
blah  blah.c  Makefile
  • 再次make blah 时,make: 'blah' is up to date. 这是因为blah文件已经存在
  • 如果我们修改blah.c,然后make blah 时,也不会重新编译。

下面通过添加依赖来解决上述问题。

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

当执行make blah时:

  • 第一次make blah,由于当前文件夹没有blah文件,所以命令会执行

  • 第二次make blah,make: 'blah' is up to date

  • 修改blah.c,然后make blah 时,会重新编译。

当blah.c不存在或者blah.c 比目标blah新,便会触发重新编译,这便是Make的关键,可以在再次编译时节省很多时间。

make是怎么做到的呢?

它使用文件的时间来进行比较然后确定是否需要重新编译,因为文件时间戳通常只在文件被修改时才会更改。有种情况:你可以修改一个文件,然后将该文件的时间戳更改为旧的内容,这样Make将不会重新编译。

更多的例子

下面的Makefile最终生成所有三个目标。当你在终端运行make时,它会按照一系列步骤构建一个名为blah的程序。

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

当执行make blah时,有如下输出:

$ make
echo "int main() { return 0; }" > blah.c # Runs first
cc -c blah.c -o blah.o # Runs second
cc blah.o -o blah      # Runs third

执行make blah时,其执行步骤如下:

  • Make构建目标blah,因为第一个目标是默认目标

  • blah依赖blah.o,所以make寻找blah.o

  • blah.o依赖blah.c,所以make寻找blah.c

  • blah.c没有依赖,直接执行echo命令生成blah.c

  • blah.c生成后,cc -c 将会运行,生成blah.o

  • blah.o生成后,cc命令将会运行,生成最终目标blah

  • blah就是最终的目标

当删除掉blah.c,这三个目标将会重新编译。

下面一个例子没有任何新的内容,它将始终运行两个目标,因为some_file依赖于other_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用来清理编译产生的中间产物(但是clean不是Makefile的关键字),可以运行make和make clean命令来创建和删除some_file。

如下例子:

1 clean不是第一个目标(默认值),也不是依赖项,这意味着除非显式地调用make clean,否则它永远不会运行;

2 clean并不是一个文件名,如果当前文件夹下恰好有一个名为clean的文件,这个目标将不会运行,这不是我们想要的,可以用.PHONY来解决这个问题。

some_file: 
	touch some_file

clean:
	rm -f some_file

变量

Makefile中的变量只能为字符串,通常使用:=,用= 也可以。

如下例子:

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

引用变量使用${} 或者 $(),如下:

x := dude

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

	# Bad practice, but works
	echo $x 

2 目标

所有的目标

如果你想让多个目标都跑起来,可以制作一个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 $@
# Equivalent to:
# f1.o:
#	 echo f1.o
# f2.o:
#	 echo f2.o

3 自动变量和通配符

通配符 *

#  % 和 * 作为Make中的统配符,但它们含义完全不同。
# * 在文件系统中搜索匹配的文件名,如 *.c 表示当前目录下所有以.c结尾的文件
# 建议 * 需要配合Wildcard一起使用
# Print out file information about every .c file
print: $(wildcard *.c)
	ls -la  $?

注意: 变量定义中不要使用通配符*,否则会产生非期望的结果。用通配符 * 定义变量,一定要配合wildcard函数使用。

下面例子中,包含这两种情况:

  • *.o经过变量定义,然后在规则中引用变量,通配符得不到正确的展开
  • 直接在规则中引用*通配符,如果.o文件存在,则是期望的,否则将会退化,这是非期望的
thing_wrong := *.o             # 不要这么定义, *.o不会被正确的展开,仅仅表示字符串
thing_right := $(wildcard *.o) # 正确方式

all: one two three four

# 失败, 因为$(thing_wrong) 为*.o
one: $(thing_wrong)
# 分两种不同情况:
# 如果工作目录下已经存在必需的.o文件,那么这些.o文件将成为目标的依赖文件,这是期望的
# 如果工作目录下所有的.o文件删除,那么会得到“没有创建*.o文件的规则”,这是非预期的
two: *.o 

# 正确,和你期望一致
three: $(thing_right)

# 和three一样
four: $(wildcard *.o)

# 常见情况
clean:
	rm -f *.o

通配符 %

% 是Makefile中非常有用的统配符,用法多样,容易让人迷惑。当用作匹配模式时,% 用来匹配字符串中一个或者多个字符,匹配部分称之为“茎”。%最常用于规则定义和一些特定函数中。

自动变量

Makefile中有许多自动变量,但是常用的这三个。

$@ ----------- 当前规则的目标

$? ------------ 模式规则中所有比所在规则中的目标更新文件组成的列表

$^ ------------ 依赖列表(所有依赖)

$< ------------ 第一个依赖?

hey: one two
	# Outputs "hey", since this is the target name
	echo $@
	# Outputs all dependencies newer than the target
	echo $?
	# Outputs all dependencies
	echo $^
	touch hey
one:
	touch one
two:
	touch two
clean:
	rm -f hey one two

4 花哨的规则

隐式规则

隐式规则是Makefile中比较令人困惑的事情,比如明明我没有写这条规则,但是也能正常编译,这可能是Make在背后“捣鬼”,这就是Makefile的自动/隐式规则。不建议使用,但是了解它们是有益的。

如:

编译C代码:

n.o是由n.c自动生成的,使用这种命令$(CC) -c $(CPPFLAGS) $(CFLAGS)

编译c++代码:n.o是由n.c或n.cpp自动生成,使用这种命令 $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)

链接单个对象文件:n 是通过运行命令$(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)由n.o自动生成的

隐式规则使用了一些重要的变量:

变量 含义
CC C编译程序。默认是"cc"
CXX C++编译程序。默认是"g++"
CFLAGS C编译程序的命令行参数
CXXFLAGS C++编译程序的命令行参数
CPPFLAGS C/C++预处理器的命令行参数
LDFLAGS 链接器的命令行参数

如下例子用到了隐式规则,不需要告诉编译器blah.o怎么生成,make会自动推导,由blah.c生成blah.o,由blah.o链接生成最终目标文件blah。

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: dependencies ...
   commands

其本质是:给定的目标,由目标模式匹配(%通配符),然后将茎代入依赖通配式,来生成依赖。

一个典型的用例是将.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 $@

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 $@

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

可见:

# 方法1, 手动版
foo.o: foo.c
bar.o: bar.c
all.o: all.c

# 方法2,静态模式
objects = foo.o bar.o all.o
$(objects): %.o: %.c

方法1和方法2是等价的,当一个系统参与编译的.c特别多时,静态模式便表现出它的效率。

静态模式和filter

可以在静态模式规则中使用filter函数来匹配正确的文件。filter函数后面会加以介绍。

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: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
	echo "target: $@ prereq: $<" 

%.c %.raw:
	touch $@

clean:
	rm -f $(src_files)

模式规则

模式规则经常被使用,但是非常混乱。可以从如下两方面来看:

  • 一种自定义的隐式规则

  • 静态模式的简化版

如下是一个模式规则的例子:

# 模式规则,指明所有的.o文件依赖于对应的.c文件
%.o : %.c
		$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

模式规则类似于普通规则。只是在模式规则中,目标名中需要包含有模式字符“%”(一个),包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。例如上述代码表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。

如果只有目标,没有依赖项,则只会创建空的.c文件

# Define a pattern rule that has no pattern in the prerequisites.
# This just creates empty .c files when needed.
%.c:
   touch $@

双冒号规则

双冒号规则很少使用,双冒号规则就是使用“::”代替普通规则的“:”得到的规则,Makefile 中,一个目标可以出现在多个规则中。但是这些规则必须是同一类型的规则,要么都是普通规则,要么都是双冒号规则。而不允许一个目标同时出现在两种不同类型的规则中。

双冒号规则允许为同一个目标定义多个规则。

如果这些是单个冒号,则会打印一个警告,只有第二组命令会运行。

all: blah

blah::
	echo "hello"

blah::
	echo "hello again"

输出:

echo "hello"
hello
echo "hello again"
hello again

使用双冒号,两条规则都被执行。

5 命令和执行

命令回显

echo: 会在shell中显示echo这条命令和后面要输出的内容
@echo: 不会显示echo这条命令,只会显示后面要输出的内容

也可以使用 make -s指令,为每行命令添加@

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

命令执行

每个命令都在一个新的shell中运行(至少效果是这样的),比如下个例子中,

all: 
	# 写到不同行,效果是在不同shell运行
	cd ..
	# The cd above does not affect this line, because each command is effectively run in a new shell
	echo `pwd`
	# 写到同行,效果是在一个shell里运行
	# 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"

错误处理参数 -k -i 与-

make -k:(keep-going)即使出现错误也可以继续运行。如果想一次看到make的所有错误,可以添加-k参数;

在命令行前添加-可以忽略错误,见例子;

make -i:(ignore-errors),每一条命令都忽略错误。

one:
	# This error will be printed but ignored, and make will continue to run
	-false
	touch one

中断make编译过程

注意:如果你按ctrl+c ,会终止make并删除它刚刚创建的新目标。

make的递归使用

要递归地调用makefile,请使用特殊的$(MAKE)而不是make,$(MAKE)可以传递make标志。

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

其中:

cd subdir && $(MAKE) 等价于 $(MAKE) -C subdir

使用export修饰变量

变量加上export修饰后,在子目录中也可以访问,相当于全局变量。在下面例子中,cooly被导出,以便subdir中的makefile可以使用它。

注意: export和sh有相同的语法,但它们不相关(尽管在函数上相似)。

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

变量加export修饰后,可以在shell中访问。

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

all: 
	@echo $(one) # OK
	@echo $$one  # 空,不能访问
	@echo $(two) # OK
	@echo $$two  # OK

.EXPORT_ALL_VARIABLES导出所有变量(不需要专门用export修饰):

.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 这样会先执行 make clean 然后make run 然后make test

6 变量(第二部分)

前面第一章提了一小点变量内容,这里继续谈一下变量。

变量赋值有两种类型:

递归 =,只在使用变量时查找变量,而不是在定义变量时。

简单地展开 :=,在定义变量时立即赋值。

# 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 later
two

7 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和MAKEFLAGS测试make标志,使用make -i运行这个例子,可以看到它打印出回显语句。

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

输出:

$make -i
i was passed to MAKEFLAGS

8 函数

Makefile中的函数主要用于文本处理中。调用方法类似于变量的引用,语法格式如下:

$(fn 参数1, 参数2...) 或

${fn 参数1, 参数2...}

返回值为字符串,fn与参数之间用空格或者Tab键隔开,多个参数之前用逗号隔开。

fn可以是内置函数,也可以通过call来自定义函数。

subst

# $(subst FROM,TO,TEXT)	字符串替换函数:把字串“TEXT”中的“FROM”字符替换为“TO”
# 输出: I am  totally superman
bar := ${subst not, totally, "I am not superman"}
all: 
	@echo $(bar)

因为函数与参数之间用空格隔开,而参数之间用逗号隔开,所以函数的参数不能出现逗号和空格。

当有逗号或者空格作,为函数的参数时,需要把它们赋值给一个变量,在函数的参数中引用这个变量

来实现。

如下面的例子,希望能将字符串 "a b c" 中的空格替换为逗号,得到“a, b, c", 可以如下操作:

comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
# bar := $(subst ,, ,$(foo)) 这样是不行的
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

语法:$(patsubst pattern,replacement,text)

支持通配符的字符串替换函数。查找text中由空格分隔的单词,将pattern替换为replacement,其中pattern与replacement支持通配符%。

还有一种简写的形式:$(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 var,list,text)

它将一个word列表(由空格分隔)转换为另一个word列表。var用来取list中的每个word,并代入text中获取新的word列表。

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

call用来自定义函数。

自定义函数与定义变量一样,不过使用$(0) $(1)等表明入参。

调用语法是:$(call variable,param,param)

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

Makefile中可以调用shell,字符串用空格隔开。

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

9 其它的特性

Include Makefiles

可以像c语言inclue头文件一样,Makefile可以include其它的Makefile,有时特别有用,见第10章。

include filenames...

vpath

vpath(全小写)用来设置文件搜索路径,是make的关键字,也可以使用变量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”就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个文件。我们输入“make clean”之后。“rm”命令都会被执行。而且当一个目标被声明为伪目标后,make 在执行此规则时不会去试图去查找隐含规则来创建它,这样也提高了 make 的执行效率。

some_file:
	touch some_file
	touch clean

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

.DELETE_ON_ERROR

如果在 Makefile中存在特殊目标.DELETE_ON_ERROR, make如果规则的命令执行错误,将删除已经被修改的目标文件。

.DELETE_ON_ERROR:
all: one two

one:
	touch one
	false

two:
	touch two
	false

10 Makefile Cookbook

最后展示一个真正有趣的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)
	$(CC) $(OBJS) -o $@ $(LDFLAGS)

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

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


.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)

参考:

1 https://makefiletutorial.com/#getting-started

2 《GNU make中文手册》

标签:一文,make,Makefile,echo,blah,了解,clean,foo
来源: https://www.cnblogs.com/sureZ-learning/p/16654198.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有