Makefile工程实践

目录

makefile简介

程序的编译与链接

makefile基本语法

makefile基本单元:规则

makefile中的变量

makefile中的条件判断

 makefile中的函数

makefile的执行过程

makefile隐含规则

 使用makefile构建MP3项目工程

自动添加目标对头文件的依赖

使用目录管理源文件

 使用目录管理目标文件

使用目录管理依赖文件

 使用目录管理头文件

 支持静态库的生成和使用

 支持动态库的生成和使用

 支持使用第三方的静态库,动态库

 支持软件的安装与卸载

 重构我们的makefile


makefile简介

一个工程中的源文件不计其数,其按类型,功能,模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个shell脚本一样,也可以执行操作系统的命令。(摘抄百度百科)

程序的编译与链接

程序的编译与链接:

可执行程序的生成经过两个过程:编译过程和链接过程

以生成helloworld.exe可执行程序为列:

 程序文件的分类:

程序文件分为两大类:BIN文件和ELF文件

BIN文件的全称是Binary  file,该文件为可执行的二进制文件

ELF文件的全称Executable and Linkable Format,该文件为可执行与可链接文件

ELF文件又分为:

1.可执行文件

2.可重定位文件(目标文件)

3.共享库文件

使用指令 readelf   -h   “elf-文件名”   查看elf文件的表头

使用指令  file   文件名    查看文件的类型

静态库和动态库 :

静态库是事先被加载到可执行文件中的; 

当可执行文件运行时,才会动态地去加载动态库,这个过程有点像函数调用。动态库的好处之一可以节约内存空间。

makefile基本语法

makefile文件的主要内容:

规则:

-----构成makefile的基本单元,构成依赖关系的核心部件

-----其他内容可以看作为规则服务

变量:

-----类似于c语言中的宏,引用:$(VAR)或者${VAR}

-----可以让makefile更加灵活

条件执行:

-----根据某一变量的值来控制make执行或者忽略makefile的某一部分

函数:

-----文本处理函数:字符串替换,查找,过滤,排序,统计等

-----文本名处理函数:取目录/文件名,前后缀,加前缀/后缀,单词连接等函数

-----其它常用函数:if函数,shell函数,foreach函数(for函数)

文件包含:

-----类似于c语言的#include,使用include命令

注释:

-----使用#开头,表示注释

依赖关系树:

makefile的目的:

------构建依赖关系树

如何表示依赖关系树:

------可以自己画图表示依赖关系树

依赖关系树的生命周期:

------解析阶段载入内存

------运行阶段根据其进行编译,根据时间戳生成文件

------有新文件添加,减少会动态改变依赖关系树

makefile基本单元:规则

规则基本构成:

目标:  目标依赖

             命令

注意事项:

-------命令必须使用tab键开头,一般是shell命令

-------一个规则中可以无目标依赖,仅仅实现某种操作

-------一个规则中可以没有命令,仅仅描述依赖关系

-------一个规则中必须有一个目标

目标:

 默认目标:

-------一个makefile里面可以有多个目标

-------一般会选择第一个作为默认目标(使用make指令编译的就是默认目标)

多目标:

-------一个规则中可以有多个目标

-------多个目标具有相同的生成命令和依赖文件

多规则目标:

-------多个规则可以是同一个目标

-------make在解析时,会将多个规则的依赖文件合并

伪目标:

-------并不是一个真正的文件名,可以看做是一个标签

-------无依赖,相比一般文件不会去重新生成,执行

-------伪目标,可以无条件执行

目标依赖:

文件时间戳:

-------根据时间戳来判断目标依赖文件是否更新

-------所有文件编译过,则对所有文件编译,  生成可执行程序

-------在上次make之后修改过的c文件,会被重新编译

-------在上次make之后修改过的头文件,依赖此头文件的会被重新编译

自动产生依赖:

gcc  -M 命令生成该文件要依赖的文件

gcc  -MM命令生成自己写的文件要依赖的文件

隐式申明:

依赖文件为.c文件,隐式申明会直接生成目标文件.o文件

生成命令:

 命令的组成:

-------shell命令组成,tab键开头

命令的执行:

-------每条命令,make会开一个进程

-------每条命令执行完,make会检测每个命令的返回码

-------若命令返回成功,make继续执行下个命令

-------若命令执行出错,make会终止执行当前规则,退出

并发执行命令:

make  -j4   表示有4个进程并发执行命令

命令同一进程执行

makefile中的变量

变量基础:

变量定义:

------ CC = gcc

变量赋值:

------  追加赋值:+=

------  条件赋值: ?=

变量引用:

------      $(CC)   ${CC}

变量分类:

立即展开变量:

------使用:=操作符赋值

------在解析阶段直接赋值常量字符串

延迟展开变量:

------使用=操作符赋值

------在运行阶段,实际使用变量时再进行求值

注意事项:

------一般在目标,目标依赖中使用立即展开变量

------在命令中一般使用延迟展开变量

目标变量:

一般变量:

------默认为全局变量

目标变量:

------该目标所依赖的规则中都可以使用

使用目标变量用途:

------做到文件级的编译选项

模式变量:

目标变量:

------变量可以定义在某个目标上

模式变量:

------变量可以定义在符合某种模式的目标上,例如: %.o : N = 3 

自动变量:

------自动变量是局部变量

目标:

------$@

所有目标依赖:

------$^

第一个依赖:

------$<

使用举例:

------gcc  -o $@ $^

系统环境变量:

作用范围:

------变量在make开始运行时被载入到makefile文件中

------对所有的makefile都有效

------若makefile中定义同名变量,系统环境变量将被覆盖

------命令行中传递同名变量,系统环境变量将被覆盖

常见的系统环境变量:

------CFLAGS

------SHELL

------MAKE

 变量的传递:

makefile在多目录下递归执行

$(MAKE) -C subdir

cd subdir && $(MAKE)

通过export传递变量

通过命令行传递变量

makefile中的条件判断

条件执行:

关键字:ifeq ,else ,endif

使用:条件语句从ifeq开始,括号与关键字用空格隔开

例如:有时候可以根据条件判断到底生成debug版本还是release版本

 makefile中的函数

 一些函数的使用:

foreach函数的使用:

$(foreach var text command)

var:局部变量

text:文件列表,空格隔开,每一次取一个值赋值为变量var

command:对var变量进行操作,每次操作结果都会以空格隔开

 shell函数的使用:

makefile的执行过程

执行过程:

 进入编译目录,执行make命令

依赖关系解析阶段

命令运行阶段 

依赖关系解析阶段:

1.解析makefile,建立依赖关系图

2.控制解析过程:引入makefile,变量展开,条件执行

3.生成依赖关系树

命令执行阶段:

 1.把解析生成的依赖关系树加载到内存

2.按照依赖关系,按顺序生成这些文件

3.再次编译make会检查文件时间戳,判断是否过期

----若无过期,不再编译

----若文件有更新,则依赖该文件的所有依赖关系上的目标重新更新,编译生成

makefile隐含规则

隐含规则:

make默认将.c文件编译成对应的.o目标文件

对没有命令行的规则:寻找一个隐含的规则来执行

取消隐含规则:使用 -r或-R参数取(在命令行输入  make  -r)

隐含变量:

命令变量:

CC: 编译程序,默认是cc

AS:汇编程序,默认是as

CXX:C++编译程序,默认g++

AR:函数库打包程序,默认是ar

命令参数变量:

CFLAGS:执行CC编译器的命令行参数

CXXFLAGS:执行g++编译器命令行参数

ASFLAGS:执行汇编器AS命令行参数

ARFLAGS:执行AR命令行参数

更多参数:LDFLAGS,CPPFLAGS

模式规则:

使用模式规则来定义一个隐式规则:

1.模式规则,至少在规则的目标定义中包含%

2.%任意长度的非空字符串,表示对文件名的匹配

3.在目标文件名中,%匹配的部分称为“茎”

目标和目标依赖同时含有%:

1.依赖目标的茎会传给目标

多目标模式规则:

1.同一个模式规则可以存在多个目标

2.普通多目标规则:每个目标作为一个独立规则处理;多个目标对应多个独立规则

3.多目标模式规则:所有规则目标共同拥有依赖文件和规则的命令行,当文件符合多个目标模式中的任何一个时,规则定义的命令执行

 使用makefile构建MP3项目工程

自动添加目标对头文件的依赖

 从关系依赖树中可以看出makefile生成的可执行文件并不依赖头文件,所以当我们修改头文件,可执行文件不会重新生成。

将usb.o:usb.c  mp3.h规则加入到makefile中,makefile对有相同目标文件的规则会将依赖文件合并起来处理。

 如下:

usb.o:usb.c

     gcc -o usb.o  -c usb.c

usb.o:usb.c  mp3.h

  使用make编译时发生了下面的错误:

使用gcc编译的时候,如果加了-c参数时后面不能跟多文件。因为编译是按文件编译的。

因此使用filter函数将.c文件过滤出来

 

   使用gcc -MM  usb.c可以直接将依赖关系打印出来,并重定向到usb.d文件

在makefile中使用include  usb.d将文件包含进来

也可以将gcc  -MM命令加入到makfile中

使用目录管理源文件

使用指令mkdir创建源目录,使不同的源文件存放在不同的目录下,最后使用命令tree查看,如下图:

 将makefile拷贝至源文件模块目录下,同时清除BIN变量,并在伪目标all中添加变量OBJS

 对于player.c文件目录下的makefile需要将BIN变量等于mp3最终目标文件

 执行该makefile会显示 错误,找不到该头文件。

 出现该错误的原因是该头文件已经不再当前目录了,头文件被放在模块文件目录下。在头文件中加入该头文件的相对目录,如下图:

 再次使用make编译出现如下错误,提示找到不到模块函数:

 打开makefile检查发现,在当前目录下只有player.c文件,只能生成player.o。手动添加目标文件,如下图:

 模块文件的makefile生成对应的目标文件,和头文件依赖文件。

而程序应用的makefile生成对应的目标文件,和头文件依赖文件,以及最终的应用程序文件。

因此可以写一个包含他们相同内容的common.mk。

模块文件的makefile只需要包含该改commom.mk即可

使用tree查看目录分级

 同时,如果去每个模块文件下编译比较麻烦。所以可以再主目录下编译一个makefile自动编译。

 使用目录管理目标文件

由于目标文件放在不同的目录下,所有在生成app程序时,需要手动将目标文件添加到规则中。不是很方便。在app目录下新建一个link_obj目录,将生成的所有目标文件都放在该目录下。

 修改目标变量OBJS,同时修改生成该目标的规则。

使用make执行,出现如下错误:

找不到对应的函数,链接的时候模块目标文件没有被链接进去。因为目标文件都被放在了/home/xyz/makefile/mp3/app/link_obj/目录下。通过定义一个变量将目标文件加入到规则当中去。

 使用make再次编译发现,player.o文件未生成,同时没有该文件也无法链接。

 这是因为主makefile执行顺序是

make  -C   lcd

make  -C   usb

make  -C   media

make  -C   app 

执行完前三个模块文件的makefile后会生成三个目标文件,lcd.o   usb.o  media.o。进入app

目录执行makefile的时候,在解析阶段会把如下规则

$(BIN):$(LINK_OBJ)

         gcc -o  $@   $^

中的$(LINK_OBJ)展开成lcd.o   usb.o   meida.o。在makefile执行的时候因为没有player.o目标依赖,最终的生成的程序没有main函数报错。 解决办法将player.o添加到目标依赖中。

同时可以将mp3程序直接生成在主目录,生成在app目录下,每次都需要进入执行该程序,不是很方便。

 查看.d依赖文件可以发现,.d文件中的依赖关系与comom.mk里面的依赖关系已经不一致了。使用正则表达式匹配生成相应的.d依赖文件

 

使用目录管理依赖文件

1.在app目录下新建一个dep文件夹,用于存放.d依赖文件

2.定义一个变量DEP_DIR存放dep文件夹路径

3.将变量DEP_DIR添加到DEPS变量中

4.修改生成.d依赖文件的规则,将变量DEP_DIR添加到目标当中

在usb.h中添加一个头文件usb20.h,如果make之后修改usb20.h的内容,依赖文件.d并不会重新生成,所以需要增加依赖关系。

例如:

/home/xyz/makefile/mp3/app/link_obj/usb.o  /home/xyz/makefile/mp3/app/dep/usb.d:usb.c   usb.h

 同时gcc  -MM $^命令中只需要c文件,因此需要使用filter函数过滤

 结论:依赖文件跟位置无关,目标文件跟位置无关,依赖关系不变!

使用export定义一个BUILD_ROOT变量来存放根目录路径

 修改commom.mk将BUILD_ROOT写入进去

修改app目录下的makefile

 使用目录管理头文件

1.在主目录下新建一个inc目录用来存放头文件

2.将各个模块目录下的头文件移动到inc目录中,移动之后的软件层次架构如下图:

修改主makefile文件,这样gcc就能找到头文件。修改如下图:

 同时commom.mk也要修改,修改如下:

将player.c中头文件的相对路径删除

 正则表达式不匹配,修改如下:

 支持静态库的生成和使用

1.在app目录下新建一个lib_obj文件夹,在主目录下新建一个lib文件夹

2.创建变量用来存放lib_obj文件夹的路径,创建变量用来存放lib文件夹的路径

1. 使用条件编译,默认生成模块目标文件,当要生成库文件时就生成库目标文件

 2.定义一个生成库的规则

 make之后出现如下错误,找到到对应的meida_init函数。这因为目标文件被生了库文件,而没有生成对应的目标文件链接到主程序中。

 静态库的使用,例如:

gcc -o hello  -L 库的路径    -l库的名字

修改common.mk文件,让其使用静态库生成目标程序

 模式替换函数patsubst的使用

 

 取文件名函数notdir

$(notdir    $(var))  

取文件名前缀部分函数basename

 支持动态库的生成和使用

1.新建一个数学模块math,创建math目录,math.c文件,math.h文件,已经该模块文件中的makefile。

2.修改common.mk文件,已经修改主makefile,添加命令make  -C math

 动态库的使用

 将math_dll_init函数添加到player.c文件当中,同时将math.h移动到目录inc下面。

修改生成BIN文件的规则如下:

 支持使用第三方的静态库,动态库

 1.在lib中新建ext_lib文件夹用于存放第三方的库文件。

2.在mp3目录下新建temp目录,在里面创建jpg.c jpg.h rmvb.c rmvb.h文件,将他们分别编译成静态库和动态库,当作第三方库使用。

3.将libjpg.a 和librmvb.so 拷贝到 ext_lib目录下

4. 将头文件rmvb.h和jpg.h拷贝至inc目录下

5.修改common.mk文件

 6.在player.c文件中调用rmvb_init函数,jpg_init函数,并且添加头文件。

 支持软件的安装与卸载

 安装就是将可执行程序放在系统环境变量里面,使用echo $PATH查看环境变量。如果使用了动态库,动态库也需要拷贝到系统环境变量当中。

 重构我们的makefile

在主目录下面新建一个文件夹config.mk

 将一部分内容移动到config.mk文件当中,同时在makefile中添加shell脚本for循环语句

将config.mk包含到makefile中

有时为了程序的可读性,我们还会添加一些注释

 在模块目录下的makefile里面写入注释

 在common.mk中添加注释

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值