[X]关闭

关于Makefile

文档创建者:东吴
浏览次数:959
最后更新:2024-06-01
本帖最后由 东吴 于 2024-6-1 15:19 编辑


一、Makefile介绍
   一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的功能操作。
   1、基本组成
   Makefile 里主要包含了五个东西:显示规则、隐晦规则、变量定义、文件指示和注释。
   (1)显示规则。显示规则说明了,如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
   (2)隐晦规则。由于我们的 make 有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile,这是由 make 所支持的。
   (3)变量的定义。在 Makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像 C 语言中的宏,当 Makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。
   (4)文件指示。其包括了三个部分,一个是在一个 Makefile 中引用另一个Makefile,就像 C 语言中的 include 一样;另一个是指根据某些情况指定Makefile 中的有效部分,就像 C 语言中的预编译#if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
   (5)注释。Makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,
这个就像 C/C++中的“//”一样。如果你要在你的 Makefile 中使用“#”字符,可以用反斜框进行转义,如:“\#”。
   最后,还值得一提的是,在 Makefile 中的命令,必须要以[Tab]键开始。


   2、Makefile三要素
   语法:
   [目标]:[依赖]
   (tab制表符)[命令]
   目标:规则的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签.一般来说,我们的目标基本上是一个文件,但也有可能是多个文件;
   依赖:是是没有.如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生我们的依赖文件,要生成 targets 需要的文件或者是另一个目标。可以是多个,也可以成的;
   命令:make 需要执行的命令(任意的 shell 命令)。如果其不与“目标,依赖”在一行,那么,必须以[Tab键]开头,如果和“目标,依赖”在一行,那么可以用分号做为分隔。
   如下
   [目标]:[依赖];[命令]
   (tab制表符)[命令]

   如果命令太长,你可以使用反斜框(‘\’)作为换行符。make 对一行上有多少个字符没有限制。规则告诉 make 两件事,文件的依赖关系和如何成成目标文件。
    image.jpg

   3、Makefile工作原理
   输入make命令:会执行Makefile的第一个目标,如果该目标有依赖,那么也会自动执行依赖的目标
Makefile执行的具体过程如下图:
    image.jpg

   4、Makefile编译过程
    image.jpg



二、Makefile编写规则
   1.# Makefile

   2.SRCS = main.c
   3.SRCS += command.c
   4.SRCS += display.c
   5.SRCS += files.c
   6.SRCS += insert.c
   7.SRCS += kbd.c
   8.SRCS += search.c
   9.SRCS += utils.c
   10.# 语法替换
   11.OBJS = $(SRCS:.c=.o)
   12.DEPS = $(SRCS:.c=.d)

   13.# 第一个目标 all,终极目标
   14.all : edit

   15.edit : $(OBJS)
   16.  $(CC) -o edit $(OBJS)

   17.-include $(DEPS)
   18.# 包含触发下面的 DEPS 依赖
   19.$(DEPS) : %.d : %.c
   20.  @set -e; rm -f $@;\
   21.     $(CC) -MM $(CPPFLAGS) $< > $@.$$$$;\
   22.     sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;\
   23.     rm -f $@.$$$$

   24..PHONY : clean
   25.clean :
   26.  @-rm edit $(OBJS) $ (DEPS)


   1、在规则中使用通配符
   如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。
   make支持三个通配符:“*”,“?”,“%”和“~”。这是和Unix的B-Shell是相同的。
   波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而 “~hchen/test”则表示用户hchen的宿主目录下的test目录。
   通配符(“*”)用于查找系统目录下的所有匹配项。如“*.c”表示所有后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。
   通配符(“%”)用于匹配零个或多个字符。
   通配符(“?”)用于匹配一个字符。


   2、Make搜寻文件
   实际中,比较大的工程文件都会分类放在不同目录下,当 Make 需要寻找文件依赖关系的时候,需要告知去寻找的路径,否则 make 只会查找当前目录。 两种方法:
   VPATH (变量)
   1.VPATH = src:../inc
   如上,指定了 ./src 和 ../inc 两个目录,冒号分隔,当前目录 搜索不到依赖文件的情况下,Make 就会依顺序进行搜索。
   vpath (关键字)
   注意:这不是一个变量,按照使用方式可以多次调用设定文件的搜索模式。
   vpath 使用的三种方法
   (1)vpath <pattern> <directories>:为符合模式<pattern>的文件指定搜索目录<directories>。
   (2)vpath <pattern>:清除符合模式<pattern>的文件的搜索目录。
   (3)vpath:清除所有已被设置好了的文件搜索目录。
   vapth 使用方法中, <pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符。例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而 <directories>则指定了<pattern>的文件集的搜索的目录。
   1.vpath %.c   dir1 # 在 dir1 寻找 .c 文件
   2.vpath %   dir2 # 在 dir2 寻找 任何需要的文件
   3.vpaht %.c   dir3 # 同 1
   4.# 当前目录找不到的情况下i, 按照 dir1.2.3的顺序查找 .c 文件


   3、伪目标
   上面例子中,clean 就是一个伪目标。伪目标是一个标签,执行一些动作,比如清除文件,安装程序等。因为没有依赖关系,所以 make 无法直接决定是否需要执行。我们显示地用 .PHONY来告诉 make 这是一个伪目标, 避免与实际目标命名冲突。
同运行程序的时候我们给个参数让程序执行特定动作一样,运行 make 时指定伪目标标签,指定执行对应的命令。就如上述例子,执行 make clean 时进行清理工作。
  
   4、静态模式
   对应多个目标对象,构建每个对象对应名称的依赖关系的规则。
   如下例子
   1.OBJS = aa.o bb.o cc.o
   2.$(OBJS) : %.o : %.c
   3.  cc -c $< -o $@
   4.# 等同:
   5.aa.o : aa.c
   6.cc -c aa.c -o aa.o
   7.bb.o : bb.c
   8.cc -c bb.c -o bb.o
   9.cc.o : cc.c
   10.cc -c cc.c -o cc.o
   上述例子,Make 从 OBJS 集合中获取符合 目标模式 %.o 的文件作为目标,依赖模式 %.c 取前面获取的“%.o”的“%” 部分作为自己的前缀。在文件很多的情况下,可以大大提高了书写效率。


   5、自动生成依赖关系
   如果在 main.c 中包含了 defs.h 文件,那么依赖关系上我们需要写上 defs.h,这样,当 defs.h 文件修改了(比如新定义了一个宏..),Make 才会重新执行依赖关系。但是对于一个文件包含什么头文件,对应修改 Makefile,这样是很难维护的。
   C/C++ 编译器 -MM 功能可以自动找寻文件的包含 ,生成依赖关系。
   执行:
   $ gcc -MM mian.c
   输出:
   main.o : main.c defs.h
   因此,我们借助编译器帮我们自动生成依赖关系,并包含到 Makefile 中。
   上述的 -include 把每个源文件对应的依赖 [.d] 文件(gcc -MM生成的依赖关系)包含进来,把 [.d] 文件的更新也纳入 Makefile 中,修改了某个文件的依赖关系,对应命令执行生成新的依赖文件。

   6、命令回响
   1.-include $(DEPS)
   2.$(DEPS) : %.d : %.c
   3.@set -e; rm -f $@;\
   4.  $(CC) -MM $(CPPFLAGS) $< > $@.$$$$;\
   5.  sed ‘s,\($*\)\.o[ :]*,\1.o $@ : ,g’ < $@.$$$$ > $@;\
   6.  rm -f $@.$$$$
   7.  rm -f $@.$$$$



   在 Makefile 中执行如下命令,
   1.echo 命令执行
   终端会输出如下
   1.echo 命令执行
   2.命令执行
   第一行是执行的命令完整打印(回响),第二行才是我们需要的输出的,关闭命令回响的方法是在该行命令前添加 @
   1.@echo 命令执行
   如果 Make 执行时,带参数“-n”或“--just-print”,那么其只是显示命令,不会执行命令,这个功能有利于我们调试我们的 Makefile,看看我们书写的命令执行起来是什么样子的或是什么顺序的。 而 Make 带参数“-s”或“--slient”则是全面禁止命令的显示。

   7、命令的依赖
   shell 按顺序一条条执行规则指定的命令。但是如果需要让上一条命令的结果应用到下一条,需要用分号分隔命令并保证命令处于同一行。
假设在目录 /home/lcd/mf/ 下执行 Makefile
   1.exec1 :
   2.  @cd /home/lcd/kk
   3.  @pwd
   4.# show : /home/lcd/mf

   5.exec2 :
   6.  @cd /home/lcd/kk; pwd
   7.# show : /home/lcd/kk

   exec1 执行的所在目录,因为上一条命令的结果没有应用到下一条。
   
   8、忽略出错命令
   一般情况,Make 会一条一条执行命令,当某条命令执行后出错, Make 会终止当前规则,这可能导致整个任务终止。
有时候执行一些命令无需考虑出错,比如某文件存在删除,不存在就不管等。 这种情况下不希望出错终止,可以在任务前添加一个减号 -
   1.clean :
   2.  -rm -f *.o
   一个全局方法是, Make 运行加上“-i”或是“--ignore-errors”参数,那么,Makefile 中所有命令都会忽略错误。如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。
    Make 的参数的是“-k”或是“--keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。

   9、Makefile嵌套
   对于一个比较大的工程,不同模块分类在不同目录,分别用一个 Makefile 进行管理,模块化编译,方便工程维护和保证 Makefile 的简洁。
   例如,子目录 subdir 下有一个 Makefile 描述该目录模块的编译规则, 那么总控 Makefile 中调用子目录 Makefile 可以这么写:
   1.subsystem :
   2.  cd subdir && $(MAKE)# 等价
   3.subsystem :
   4.  $(MAKE) -C subdir
   使用 $(MAKE) 宏定义,在某些情况下调用嵌套我们可以直接修改添加参数。另外,当运行 Make 时候添加诸如 ‘-t’ (‘--touch’), ‘-n’ (‘--just-print’), or ‘-q’ (‘--question’) z这些特殊选线时,使用 $(MAKE) 可以保证语句相当于在前面添加了 + 号 的作用(特殊命令,继续执行)。很正常,希望测试的时候命令不是真的执行,但是包含其他 Makefile 这种命令是例外,必须执行,不然 Makefile 就不完整了, 我是这么理解的。
   上层 Makefile 中定义的变量是可以在被调用的下一层 Makefile 中使用的, 前提是该变量在上层中被显式暴露 export,同理,可以采用 unexport取消。
   1.export OBJS # 传递 变量 OBJS
   2.export # 不指定,全部传递
   如此,在下面的 makefile 就可以直接使用了。但是如果下层目录已经定义了该变量,那么下层默认使用的是它自己定义的变量值,除非上层 makefile 在调用下层 makefile 时给参数 -e,则会强行覆盖。
   两个变量,一个是 SHELL,一个是 MAKEFLAGS,这两个变量不管你是否 export,其总是要传递到下层 Makefile 中。
   Make 命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”
   嵌套执行中, “-w”或是“--print-directory”会在 Make 的过程中让你看到目前的工作目录。
   比如,如果我们的下级 Make 目录是/home/lcd/mf/subdir,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:
   make: Entering directory '/home/lcd/mf/subdir'
   而在完成下层 make 后离开目录时,我们会看到:
   make: Leaving directory `/home/lcd/mf/subdir'
   当你使用“-C”参数来指定 Make 下层 Makefile 时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。


三、常见的解析符
1、自动化变量
变量
解析
$0
当前脚本的文件名
$n(n≥1)
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2>
$#
传递给脚本或函数的参数个数
$*
传递给脚本或函数的所有参数
$@
表示目标文件
$?
表示比目标更新的所有依赖,每个依赖之间以空格隔开
$$
当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID
$^
表示所有的依赖文件
$<
表示第一个依赖文件


2、变量和赋值
变量
解析
=
执行时再赋值
:=
定义时就进行赋值
-d
检测文件是否是目录
?=
表示变量为空时再赋值
+=
表示将值追加到变量的尾部
always :=
总是需要被编译的模块
targets :=
编译目标
obj-y :=
编译进内核的文件(夹)列表
obj-m :=
编译成外部可加载模块的列表
lib-y := 和 lib-m :=
编译成库文件
Subdir-y := 和subdir-m :=
表示需要递归进入的子目录


3、内置函数
函数
含义
wildcard
cfiles := $(wildcard *.c),
作用是匹配当前目录(不包含子目录)下所有.c文件,每个文件以空格隔开,然后赋值给cfiles变量
patsubst
objs := $(patsubst %.c,%.o,$(wildcard *.c))
作用是将当前目录(不包含子目录)下所有的.c文件替换成对应的.o文件,即将后缀为.c的文件替换为后缀为.o的文件,每个文件以空格隔开,然后赋值给objs变量。
abspath
path := $(abspath main.c)
作用是获取当前目录下main.c文件的绝对路径(含文件名,结果比如:/work/main.c),然后赋值给path变量。

Makefile.pdf

227 KB, 下载次数: 3

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则