跟我一起写Makefile细节总结学习笔记

文章目录

跟我一起写Makefile细节总结学习笔记

第一,二章

此篇仅为方便查阅记忆,详细的请看seisman/how-to-write-makefile: 跟我一起写Makefile重制版 (github.com)

Makefile介绍

,,Tab键,使用变量,-减号,

规则

shell 复制代码
target ... : prerequisites ...
	recipe
	...
	...

原始Makefile

shell 复制代码
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o \
		insert.o search.o files.o utils.o

稍微简化后的

shell 复制代码
objects = main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
	-rm edit $(objects)

注意

命令的Tab键位不可用空个代替。

在我们的 makefile 中以 $(objects) 的方式来使用这个变量

反斜杠(\ )是换行符的意思。

.PHONY 表示 clean 是一个"伪目标"。

一个小减号的意思就是,也 许某些文件出现问题,但不要管,继续做后面的事

不成文的规矩是------"clean 从来都是放在文件的最 后"。

Makefile 里主要包含了五个东西:

显式规则、隐式规则、变量定义、指令和注释。

  1. 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由 Makefile 的书写者明显指出要 生成的文件、文件的依赖文件和生成的命令。
  2. 隐式规则。由于我们的 make 有自动推导的功能,所以隐式规则可以让我们比较简略地书写 Makefile,这是由 make 所支持的。 8 Chapter 2. makefile 介绍 , 发行版本 1.0
  3. 变量的定义。在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像你 C 语 言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
  4. 指令。其包括了三个部分,一个是在一个 Makefile 中引用另一个 Makefile,就像 C 语言中的 include 一样;另一个是指根据某些情况指定 Makefile 中的有效部分,就像 C 语言中的预编译 #if 一样; 还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
  5. 注释。Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用 # 字符,这个就像 C/C++ 中的 // 一样。如果你要在你的 Makefile 中使用 # 字符,可以用反斜杠进行转义,如:# 。 最后,还值得一提的是,在 Makefile 中的命令,必须要以 Tab 键开始。

书写规则

在规则中使用通配符

另给一个变量使用通配符的例子:

  1. 列出一确定文件夹中的所有 .c 文件。

    c++ 复制代码
    objects := $(wildcard *.c) 
  2. 列出 (1) 中所有文件对应的 .o 文件,在(3)中我们可以看到它是由 make 自动编译出的:

    c++ 复制代码
     $(patsubst %.c,%.o,$(wildcard *.c)) 
  3. 由 (1)(2) 两步,可写出编译并链接所有 .c 和 .o 文件

    c++ 复制代码
    objects := $(patsubst %.c,%.o,$(wildcard *.c)) 
    foo : $(objects) 
    	cc -o foo $(objects)文件搜寻
        

文件搜寻

vpath <pattern> <directories>

​ 为符合模式<pattern>的文件指定搜索目录<directories>

vpath <pattern>

​ 清除符合模式 <pattern> 的文件的搜索目录。

vpath

​ 清除所有已被设置好了的文件搜索目录。

vpath 使用方法中的 <pattern> 需要包含 % 字符。% 的意思是匹配零或若干字符,(需引用 % ,使 用 \ )例如,%.h 表示所有以 .h 结尾的文件。<pattern>指定了要搜索的文件集,而<directories> 则 指定了 < pattern> 的文件集的搜索的目录。例如:

c++ 复制代码
vpath %.h ../headers

伪目标

当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记".PHONY"来显式地指明 一个目标是"伪目标",向 make 说明,不管是否有这个文件,这个目标就是"伪目标"。 .PHONY : clean 只要有这个声明,不管是否有"clean"文件,要运行"clean"这个目标,只有"make clean"这样。 于是整个过程可以这样写:

c++ 复制代码
.PHONY : clean
clean :
		rm *.o temp

多目标(自动变量)

c++ 复制代码
bigoutput littleoutput : text.g
	generate text.g -$(subst output,,$@) > $@

等价于

c++ 复制代码
bigoutput : text.g
	generate text.g -big > bigoutput
littleoutput : text.g
	generate text.g -little > littleoutput

静态模式

c++ 复制代码
objects = foo.o bar.o
	all: $(objects)
$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

上面的例子中,指明了我们的目标从 $object 中获取,%.o 表明要所有以 .o 结尾的目标,也就是 foo.o bar.o ,也就是变量 $object 集合的模式,而依赖模式 %.c 则取模式 %.o 的 % ,也就是 foo bar ,并为其加下 .c 的后缀,于是,我们的依赖目标就是 foo.c bar.c 。而命令中的 \< 和 @ 则是自动化变量, @ 则是自动化 变量, @则是自动化变量,\< 表示第一个依赖文件,@ 表示目标集(也就是"foo.o bar.o")。于是,上面的规则展开后等价 于下面的规则:

c++ 复制代码
foo.o : foo.c
	$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
	$(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的 %.o 有几百个,那么我们只要用这种很简单的"静态模式规则"就可以写完一堆 规则,实在是太有效率了。"静态模式规则"的用法很灵活,如果用得好,那会是一个很强大的功能。再 看一个例子:

c++ 复制代码
files = foo.elc bar.o lose.o
    
$(filter %.o,$(files)): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
	emacs -f batch-byte-compile $<

( f i l t e r (filter %.o, (filter(files)) 表示调用 Makefile 的 filter 函数,过滤"$files"集,只要其中模式为"%.o"的 内容。其它的内容,我就不用多说了吧。这个例子展示了 Makefile 中更大的弹性。

自动生成依赖性

在 Makefile 中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的 main.c 中有一 句 #include "defs.h" ,那么我们的依赖关系应该是:

c++ 复制代码
main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些 C 文件包含了哪些头文件,并且,你在加入或 删除头文件时,也需要小心地修改 Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容 易出错的事情,我们可以使用 C/C++ 编译的一个功能。大多数的 C/C++ 编译器都支持一个"-M"的 选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:

c++ 复制代码
cc -M main.c

其输出是:

c++ 复制代码
main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编 译器自动生成了。需要提醒一句的是,如果你使用 GNU 的 C/C++ 编译器,你得用 -MM 参数,不然,-M 参数会把一些标准库的头文件也包含进来。

gcc -M main.c 的输出是

c++ 复制代码
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
	/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
	/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
	/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
	/usr/include/bits/sched.h /usr/include/libio.h \
	/usr/include/_G_config.h /usr/include/wchar.h \
	/usr/include/bits/wchar.h /usr/include/gconv.h \
	/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
	/usr/include/bits/stdio_lim.h

gcc -MM main.c 的输出则是:

c++ 复制代码
main.o: main.c defs.h
相关推荐
数据与后端架构提升之路30 分钟前
从神经元到神经网络:深度学习的进化之旅
人工智能·神经网络·学习
一行139 分钟前
电脑蓝屏debug学习
学习·电脑
咔叽布吉1 小时前
【论文阅读笔记】CamoFormer: Masked Separable Attention for Camouflaged Object Detection
论文阅读·笔记·目标检测
johnny2331 小时前
《大模型应用开发极简入门》笔记
笔记·chatgpt
亦枫Leonlew1 小时前
微积分复习笔记 Calculus Volume 1 - 4.7 Applied Optimization Problems
笔记·数学·微积分·1024程序员节
小肥象不是小飞象1 小时前
(六千字心得笔记)零基础C语言入门第八课——函数(上)
c语言·开发语言·笔记·1024程序员节
星LZX1 小时前
WireShark入门学习笔记
笔记·学习·wireshark
阑梦清川1 小时前
在鱼皮的模拟面试里面学习有感
学习·面试·职场和发展
qq_433099401 小时前
Isaac Gym学习笔记——概述
学习
努力变厉害的小超超3 小时前
ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
笔记·鸿蒙