欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Make 构建工具

程序员文章站 2022-07-14 15:24:50
...

Make 构建工具

简介

make 是 GNU 软件包中的一个构建工具,可用于构建并管理项目的目标文件和可执行文件,另外我们可以编写 Makefile 作为批处理命令的定义。由于属于 GNU 中的工具之一,所以 make + Makefile 的项目管理工具必须用于类 unix 环境,要想在 Windows 或其他环境使用就需要用到抽象程度更高的 cmake、qmake 等其他工具,这边就不做说明了。

参考

make makefile cmake qmake都是什么,有什么区别? https://www.zhihu.com/question/27455963
Make 命令教程-阮一峰 http://www.ruanyifeng.com/blog/2015/02/make.html
makefile内置变量 https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
makefile自动变量 https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html

正文

Install 安装

要使用一个工具当然要先安装

  • Linux 环境下安装
# Ubuntu
$ apt-get install make

# CenOS
$ yun install make
  • Mac 环境
$ xcode-select --install

make 初体验

make 顾名思义就是制作的意思,命令默认会查找的当前目录下的 Makefile 作为批处理脚本,我们也可以使用 -f 参数指定使用脚本,咱们马上就来试试看吧(详细脚本结构和输出后面会进行说明)

项目结构

/
|- Makefile
|- a.txt
  • Makefile
all:
	echo "make something...(default"
  • a.txt
all:
	echo "make something...(by a.txt"

使用默认脚本(文件名 Makefile,大小写无关)

$ make
echo "make something...(default"
make something...(default

指定制作脚本

$ make -f a.txt
echo "make something...(by a.txt"
make something...(by a.txt

嗯输出了一些奇怪的东西,不过先不管,我们先来看看 makefile 里面都在写些啥呢。

Makefile 结构

通常我们直接使用 Makefile 也就是默认的脚本文件名,不论写在哪,作为 make 构建的脚本内容格式是有规范的:

<target>: [<dependency>, ...]
    <command>
    ...

前面说过 make 指令代表制作,也就是说我们进行构建时围绕的核心当然就是我们的构建目标(target)也就是我们的目标文件啦!而构建目标文件的时候可能会依赖一些其他已经存在的文件成为依赖(dependency);最后如何构建,也就是具体的构建操作就由下面的命令(command)序列来指定。

看着上面的定义可能看不太懂,那就举个栗子:

示例

  • Makefile
main: main.c
    gcc main.c -o main

这个脚本的意思就是:

  • main 作为构建目标
  • main.c 作为构建依赖
  • gcc main.c -o main 为实际构建时执行的指令

格式

由于 Makefile 作为批处理脚本,在写法上有些限制才能被工具正确解析

规则一:目标依赖间用空格隔开

target: a.txt b.txt c.txt
# a.txt, b.txt, c.txt 三个文件名用空格隔开

规则二:命令前使用 tab 缩进

init:
    echo "make"
# 注意!这里不可以使用四个空格代替 tab,必须要是真正的制表符 \t,否则会报错:*** missing separator.  Stop.

Rule 构建规则

规则一:默认目标

我们有时候写好 Makefile 就不想每次都指定构建目标(反正每次都一样)或是想指定一个默认的构建目标。

在 make 指令的规则中,如果没有指定构建目标则会默认构建第一个目标(特殊目标不算)

默认构建目标

  • Makefile
default:
	echo "make default"
a:
	echo "make a"
b:
	echo "make b"

执行结果:

$ make a
echo "make a"
make a
$ make b
echo "make b"
make b
$ make
echo "make default"
make default

说明:这边我们定义了是三个目标,分别是 defaultab,make 命令不指定目标是则会制作第一个目标也就是 default

规则二:构建条件

然而并不是所有时候 make 都会重新构建所有指定的目标的,只有在以下其中一项条件成立时才会进行重构建(否则保持原文件内容)

  • 构建目标不存在
  • 构建目标最后更新时间戳与依赖相同

在构建之前会检查目标(target)依赖(dependencies)最后更新时间戳(latest update time),只有当时间戳不相同时,make 才会认为有必要重新进行构建

另外,如果依赖的文件也作为构建目标存在,make 原目标时将会优先进行依赖的构建,而后再进行当前目标的构建,举个栗子:

依赖同时也作为构建目标

  • Makefile
main: main.c
    gcc main.c -o main

main.c:
    echo "int main() {}" > main.c

执行结果:

$ make main
echo "make main.c"
make main.c
echo "int main() {}" > main.c
gcc main.c -o main

我们就会看到 main.cmain 都被制作出来了

Phony Target 伪目标

有时候我们想要建立的目标不知一个或是我们只是单纯的想执行一个批处理操作,我们就可以使用伪目标(phony target)

其实伪目标和一般目标没什么区别,都是一个标识而已,而且也没规定说目标下的操作必须真的制作出目标文件,不过也有可能存在同名的文件的风险,所以我们需要借助特殊目标 .PHONY 的声明

使用 .PHONY 声明伪目标

  • Makefile
.PHONY: build clean

# 伪目标
build: main

clean:
	rm main.c main

# 目标文件
main: main.c
	gcc main.c -o main

# 源文件
main.c:
	echo "int main() {}" > main.c

执行结果:

$ make
echo "int main() {}" > main.c
gcc main.c -o main
$ make clean
rm main.c main

.PHONY 指定了 buildclean 作为伪目标,由于伪目标并不存在对应具体文件,所以每次都必定会被执行,相当于一段批处理脚本的命名。

执行流程:这里没有指定构建目标,所以默认找到的目标为 build 是一个伪目标,build 的依赖是 mainmain 的依赖是 main.c,所以整个制作流程为:main.c -> main

Makefile 语法

到此其实我们已经基本了解 make 的结构和规则了,最后介绍一些 Makefile 中的语法

回声 echoing:使用 @ 取消

如果细心一些我们会发想上面示例中的输出都怪怪的,其实是因为 make 命令默认会打印每条命令(command)(这个行为称为回声 echoing),而后才执行命令,我们可以在命令前加上 @ 来取消回声

示例

  • Makefile
all: with_echo without_echo

with_echo:
	echo "make with_echo"

without_echo:
	@echo "make without_echo"

执行结果:

$ make
echo "make with_echo"
make with_echo
make without_echo

我们看到 with_echo 伪目标的命令被打印出来后才执行,而 without_echo 的则没有出现回声直接执行

注释 Comment:使用 #

在 Makefile 中使用 # 表示注释,回声同样会打印注释,需要注意

示例

  • Makefile
init:
	# 这是一个注释
	@# 取消回声的注释
	echo "make"

执行结果:

$ make
# 这是一个注释
echo "make"
make

变量声明:使用 = 声明、使用 $() 访问

同样在 Makefile 中我们可以声明变量,来一次操作多个目标(本质上就是字符串直接替换

示例

  • Makefile
# 符号替换
CC = gcc

# 变量声明
SRC = main.c
BIN = main
ALL_OBJ = $(SRC) $(BIN)

context = "int main() {}"

# 目标
init: $(BIN)

clean:
	rm $(ALL_OBJ)

$(BIN): $(SRC)
	$(CC) $(SRC) -o $(BIN)

$(SRC):
	echo $(context) > $(SRC)

执行结果:

$ make
echo "int main() {}" > main.c
gcc main.c -o main
$ make clean
rm main.c main

Shell 变量:使用 $$ 转译

注意:使用 Shell 变量时需要进行字符转译

使用 $$ 转译使用 Shell 变量

  • Makefile
init:
	export STR="Hello World"; echo $$STR

执行结果:

$ make
export STR="Hello World"; echo $STR
Hello World

共用 Shell:使用 ;\.ONESHELL

由于每条命令(command)都是独立的 shell,所以要使用变量声明后引用必须使用 ; 将多条命令合并成一行,或是使用 \ 转译换行符,或是使用 .ONESHELL 指定共用 Shell

分别使用 ;\.ONESHELL 实现使用同一个 SHELL

  • Makefile
a:
	export STR="Hello World"; echo $$STR

b:
	export STR="Hello World";\
	echo $$STR

.ONESHELL:
c:
	export STR="Hello World";
	echo $$STR

执行结果:

$ make a
export STR="Hello World"; echo $STR
Hello World
$ make b
export STR="Hello World";\
	echo $STR
Hello World
$ make c
export STR="Hello World";
echo $STR;
Hello World

内置变量

make 命令还提供一些内置变量,以满足跨平台的兼容性,如 $(CC) 代表编译器,详细可以查看手册(参考链接3

示例

  • Makefile
init:
	$(CC) -v

执行结果:

$ make
cc -v
Apple clang version 12.0.0 (clang-1200.0.32.21)
Target: x86_64-apple-darwin19.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

说明:博主使用的环境为 mac 的 command-line-tools

自动变量:[email protected]$<$^、…

除了一般自定义变量和内置变量之外,我们还能够使用运行时自动替换的自动变量,常用下列几个,详细可以参考链接4

自动变量 含义
[email protected] 指向构建目标
$< 指向第一个依赖
$^ 指向所有依赖

示例

  • Makefile
A = a.txt
OBJ = a.txt b.txt c.txt

init: $(A)
	cat $^

clean:
	rm $(OBJ)

a.txt: b.txt c.txt
	echo "target: [email protected]\nfirst dependency: $<\nall dependencies: $^" > [email protected]
b.txt:
	echo "target: [email protected]" > [email protected]
c.txt:
	echo "target: [email protected]" > [email protected]

执行结果:

$ make
echo "target: b.txt" > b.txt
echo "target: c.txt" > c.txt
echo "target: a.txt\nfirst dependency: b.txt\nall dependencies: b.txt c.txt" > a.txt
cat a.txt
target: a.txt
first dependency: b.txt
all dependencies: b.txt c.txt
$ make clean
rm a.txt b.txt c.txt

其他

makefile 还支持函数定义,也存在内置函数等,还有一些其他比较少用的特性,由于篇幅原因这里就不展开说明

结语

make 实在是一个方便简单的工具,相当于一个面向项目构建的 shell 脚本一般,透过 sh 脚本和 make、Makefile 的使用可以实现简单的自动化编译、构建、打包的操作,供大家参考。