Makefile 规则与语法学习

Tue 20 June 2023

Makefile是啥 一种用于自动化构建和管理软件项目的文件。和make命令搭配使用 📝写一个简单的Makefile 编辑 Makefile,写入一个最简单的 hello: echo "hello world" JavaScript 然后运行 make hello,可以发现输出了 ➜ make hello echo "hello world" hello world 当然,我们可以在其中插入更多的过程(但是这个过程是不严谨的,因为hello此时应该是要被构建的目标,只有echo是不足以构建的,也就是make必须要产生点东西,在这里就是要产生hello,最差的做法都必须要 echo “hello world” > hello hello: echo "hello world" create: touch test delete: rm -f test write: echo "hhh" > test JavaScript 尝试将makefile修改成这样,然后写入一些简单的东西 guoguo in Program/C/HelloWorld at arch-playground … ➜ make create touch test

guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile test

guoguo in Program/C/HelloWorld at arch-playground … ➜ make write echo "hhh" > test

guoguo in Program/C/HelloWorld at arch-playground … ➜ cat test hhh

guoguo in Program/C/HelloWorld at arch-playground … ➜ make delete rm -f test

guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile JavaScript 很神奇 📓配合C语言进行编译与使用 首先写一个简单的 main.c

include

int main() { printf("hello world!"); } C 然后仿照我们日常使用的make命令,写一个简单的makefile build: gcc -o hello main.c install: cp ./hello /usr/local/bin/hello uninstall: rm /usr/local/bin/hello clean: rm ./hello Makefile 尝试 make build, make install 后直接在控制台执行 hello 试试 guoguo in Program/C/HelloWorld at arch-playground … ➜ hello hello world!% Makefile 再试试 make uninstall, make clean 等操作 guoguo in Program/C/HelloWorld at arch-playground … ➜ make uninstall rm /usr/local/bin/hello

guoguo in Program/C/HelloWorld at arch-playground … ➜ make clean rm ./hello

guoguo in Program/C/HelloWorld at arch-playground … ➜ hello zsh: command not found: hello Makefile 工作很正常 插入变量 自定义变量 Makefile中也可以使用变量,我们可以这样尝试,在makefile的开头插入 target_name = hello,并重新尝试 make build, make install等操作 target_name = hello

build: gcc -o $(target_name) main.c install: cp ./$(target_name) /usr/local/bin/$(target_name) uninstall: rm /usr/local/bin/$(target_name) clean: rm ./$(target_name) Makefile 预定义变量 除此之外,makefile还会有一些预定义变量,我们可以这么编写一个来查看 hello: echo "AR = $(AR)" echo "AS = $(AS)" echo "CC = $(CC)" echo "CXX = $(CXX)" echo "ARFLAGS = $(ARFLAGS)" echo "ASFLAGS = $(ASFLAGS)" echo "CFLAGS = $(CFLAGS)" echo "CXXFLAGS = $(CXXFLAGS)" Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make hello echo "AR = ar" AR = ar echo "AS = as" AS = as echo "CC = cc" CC = cc echo "CXX = g++" CXX = g++ echo "ARFLAGS = -rv" ARFLAGS = -rv echo "ASFLAGS = " ASFLAGS = echo "CFLAGS = " CFLAGS = echo "CXXFLAGS = " CXXFLAGS = Makefile 环境变量 还可以查看一些环境变量,比如 PATH 之类的 hello2: echo $(PATH) Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make hello2 echo /usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_per Makefile 在参考文章内,还可以发现有一些其他的环境变量 Makefile 常用的环境变量有下面这些: $:不包含扩展名的目标文件名称 $<:第一个依赖文件名称 $?:所有时间戳比目标文件晚的依赖文件 $@:目标文件完整名称 $^:所有不重复的依赖文件 hello2: echo $ echo $< echo $? echo $@ echo $^ Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make hello2 echo

echo

echo

echo hello2 hello2 echo Makefile Makefile 切换工作目录 需要注意的是,makefile每一行的命令是独立的,不会说上一条cd了以后工作目录就变了。如果需要跨目录执行,则需要在同一行使用 && 拼接,以下是拼接后的结果 test2: pwd cd build pwd cd build && pwd Makefile ➜ make test2 pwd /home/guoguo/Documents/Program/C/HelloWorld cd build pwd /home/guoguo/Documents/Program/C/HelloWorld cd build && pwd /home/guoguo/Documents/Program/C/HelloWorld/build Makefile Makefile 伪目标 伪目标(Phony Target)是一种特殊类型的目标,其名称与实际文件名或目标不相关。伪目标通常用于定义一些不生成对应输出文件的操作或命令。它们用于表示一些常见的动作或规则,并提供一种方便的方式来执行这些操作。 伪目标在Makefile中以特殊的方式声明,使用伪目标可以避免与实际文件名冲突,并且即使存在同名的文件,也能确保这些规则被执行。伪目标在Makefile中没有依赖关系,即使依赖项的文件存在,伪目标的规则也会被执行。 罗里吧嗦的讲了一大堆,结果我自己都没看懂,那就实际写个东西看看吧 对于单个构建 尝试写一个这样的makefile test1: touch test1 echo "我是test1喵喵喵" Makefile 然后再创建一个test1,尝试make test1 guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 touch test1 echo "我是test1喵喵喵" 我是test1喵喵喵

guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 make: 'test1' is up to date. Makefile 可以发现,test1所在的构建没法正常执行了,这是因为make判断当前目录下已经有test1这个文件了,那你已经构建完成了,我就没必要给你重复执行这个过程了,而.PHONY: test1就是告诉make,你这个构建过程不产生一个叫test1的文件,如果没有.PHONY,那么我一看到你有test1这个文件我就不会执行了。而如果有.PHONY,我不管你有没有test1,我都去执行test1这个过程。 加上 .PHONY: test1 ,我们再来试试 .PHONY: test1

test1: touch test1 echo "我是test1喵喵喵" Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile build main.c test1 test3

guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 touch test1 echo "我是test1喵喵喵" 我是test1喵喵喵 Makefile 加上了,可以发现,这时候make test1已经不管你目录下是否有test1了,我都照样执行。 对于依赖关系 尝试写一个这样的makefile,注意这里 test1: test2 test3 是放在同一行的,目的是为了让它执行test2和test3的构建,而不是将test2和test3当成命令。 test1: test2 test3 echo "我是test1喵喵喵" test2: echo "我是test2喵喵喵" test3: echo "我是test3喵喵喵" Makefile 在文件目录没有 test* 的时候,它执行的结果是这样的 guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo "我是test2喵喵喵" 我是test2喵喵喵 echo "我是test3喵喵喵" 我是test3喵喵喵 echo "我是test1喵喵喵" 我是test1喵喵喵

guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile build main.c Makefile 我们尝试创建一个test2,再重新执行一遍 guoguo in Program/C/HelloWorld at arch-playground … ➜ touch test2

guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo "我是test3喵喵喵" 我是test3喵喵喵 echo "我是test1喵喵喵" 我是test1喵喵喵 Makefile 发现了吗,少了一个test2。 我们再尝试创建一个test3 guoguo in Program/C/HelloWorld at arch-playground … ➜ touch test3

guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo "我是test1喵喵喵" 我是test1喵喵喵 Makefile 又少了一个。 然后我们尝试加入这个传说中的伪目标 .PHONY: test2

test1: test2 test3 echo "我是test1喵喵喵" test2: echo "我是test2喵喵喵" test3: echo "我是test3喵喵喵" Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo "我是test2喵喵喵" 我是test2喵喵喵 echo "我是test1喵喵喵" 我是test1喵喵喵

guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile build main.c test2 test3 Makefile 发现了吗,明明test2这个文件是存在的,但它还是执行了test2,我们再把test3加上。 .PHONY: test2 test3

test1: test2 test3 echo "我是test1喵喵喵" test2: echo "我是test2喵喵喵" test3: echo "我是test3喵喵喵"e build main.c test2 test3 Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo "我是test2喵喵喵" 我是test2喵喵喵 echo "我是test3喵喵喵" 我是test3喵喵喵 echo "我是test1喵喵喵" 我是test1喵喵喵

guoguo in Program/C/HelloWorld at arch-playground … ➜ ls Makefile build main.c test2 test3 Makefile test3也回来了,删掉test2和test3也是同样的结果。 至此,就可以发现,.PHONY的加入,可以让 Makefile 不再去看对应构建是否存在该文件再去判断是否执行。 Makefile 包含 如果在不同目录下有不同的 Makefile,可以用下面的方法来嵌套 ./Makefile: include build/Makefile

./build/Makefile: build2: gcc -o ./build/hello main.c Makefile 注意,这里include了build文件夹的Makefile以后,使用的是哪个目录下的makefile,那么工作目录就在那个目录,不会说工作目录变成了build 尝试在工作目录 make build2,成功执行,且文件生成到了 ./build/hello。

Makefile 条件判断 makefile也可以进行条件判断,但是只能进行一些简单的判断,如 ifeq, ifneq, ifdef, ifndef VAR = aaa

ifeq ($(VAR),aaa) test1: echo VAR = $(VAR) endif Makefile guoguo in Program/C/HelloWorld at arch-playground … ➜ make test1 echo VAR = aaa VAR = aaa Makefile 需要注意的是,条件判断语句不能直接放在规则的命令部分,因为这些语句需要在 Makefile 的解析阶段进行处理,而不是在命令执行阶段。 即以下的写法是错误的 VAR = aaa test1: ifeq ($(VAR),aaa) echo VAR = $(VAR) endif Makefile 它会抛出以下异常 ➜ make test1 ifeq (aaa,aaa) /bin/sh: -c: line 1: syntax error near unexpected token aaa,aaa' /bin/sh: -c: line 1:ifeq (aaa,aaa)' make: *** [Makefile:3: test1] Error 2 Makefile Makefile 管理命令 makefile还有许多管理命令,用--help就能看到,这里就不赘述了 ⁉️自问自答 像make xxx这样的指令,xxx就是在makefile中定义好过程的吗? 确实是。我们可以尝试新建一个Makefile,输入 hello: echo “Hello World” 然后输入 make hello ➜ make hello echo "hello world" hello world 只有C/C++能用makefile吗 从上面就能看得出来,并不是这样的,只不过比较常用的是这个而已?它就是描述一个过程,使用make xxx会执行一系列的命令,所以,当然我们可以在其中放一些恶意代码。makefile会像执行shell一样执行下来。 什么是 Makefile 伪目标(?) 伪目标(Phony Target)是一种特殊类型的目标,其名称与实际文件名或目标不相关。伪目标通常用于定义一些不生成对应输出文件的操作或命令。它们用于表示一些常见的动作或规则,并提供一种方便的方式来执行这些操作。 伪目标在Makefile中以特殊的方式声明,使用伪目标可以避免与实际文件名冲突,并且即使存在同名的文件,也能确保这些规则被执行。伪目标在Makefile中没有依赖关系,即使依赖项的文件存在,伪目标的规则也会被执行。 解决方案看上面 Makefile 怎么切换目录执行 不能直接在一行cd后在下一行执行,而是需要在需要切换目录命令同一行前面加上cd && 例如:cd build && pwd,否则下一行目录就会变回去了 make 的多线程编译?

Category: 编程