Makefile工具的使用

一、相关资料推荐

1、来自网络:Linux工具入门:make工具与Makefile文件 - melonstreet - 博客园

2、配套详细的手册:Make工具《一起写Mkakefile》-Linux文档类资源-CSDN下载MAKE工具,一本书更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/weixin_40639467/75976402

二、基础部分

1、何为Makefile文件

Makefile诞生的目的:

1、如果工程没有编译过,那么工程中的所有.c 文件都要被编译并且链接成可执行程序。

2、如果工程中只有个别 C 文件被修改了,那么只编译这些被修改的 C 文件即可。

3、如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的 C 文件,并且

链接成可执行文件。

/

一个小例子的解析:

上述代码中一共有 5 条规则,1~2 行为第一条规则,3~4 行为第二条规则,5~6 行为第三条

规则,7~8 行为第四条规则,10~12 为第五条规则,make 命令在执行这个 Makefile 的时候其执

行步骤如下:

        首先更新第一条规则中的 main,第一条规则的目标成为默认目标,只要默认目标更新了那

么就认为 Makefile 的工作。在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,

第一条规则依赖于文件 main.o、input.o 和 calcu.o 这个三个.o 文件,这三个.o 文件目前还都没

有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行。以 main.o

为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc

–c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。

        最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main,因此 clean 的功能就是完成工程的清理,“make clean”

/

2、Makefile的基本语法
Makefile的规则格式

 

例子:

 

         这条规则的目标是 main,main.o、input.o 和 calcu.o 是生成 main 的依赖文件,如果要更新
目标 main,就必须先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也
必须更新,“更新”就是执行一遍规则中的命令列表。(命令列表中的每条命令必须以 TAB 键开始,不能使用空格
/
Makefile的变量
注意:Makefile 中的变量都是字符串!类似 C 语言中的宏。
        我们来分析一下“示例代码 3.4.2.1”,第 1 行是注释,Makefile 中可以写注释,注释开头要
用符号“#”,不能用 C 语言中的“//”或者“/**/”!第 2 行我们定义了一个变量 objects,并且给这个变量进行了赋值,其值为字符串“main.o input.o calcu.o”,第3和4行使用到了变量objects,Makefile 中变量的引用方法是“$(变量名)”,比如本例中的“$(objects)”就是使用变量 objects。
        在“示例代码 3.4.2.1”中我们在定义变量 objects 的时候使用“=”对其进行了赋值,Makefile
变量的赋值符还有其它两个“:=”和“?=”,我们来看一下这三种赋值符的区别
1、赋值符“=
使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值,比如如下代码

        我们来分析一下上述代码,第 1 行定义了一个变量 name,变量值为“zzk”,第 2 行也定义
了一个变量curname,curname的变量值引用了变量name,按照我们C写语言的经验此时curname
的值就是“zzk”。第 3 行将变量 name 的值改为了“zuozhongkai”,第 5、6 行是输出变量 curname的值。在 Makefile 要输出一串字符的话使用“echo”,就和 C 语言中的“printf”一样,第 6 行中的“echo”前面加了个“@”符号,因为 Make 在执行的过程中会自动输出命令执行过程,在命令前面加上“@”的话就不会输出命令执行过程,大家可以测试一下不加“@”的效果。使用命令“make print”来执行上述代码,结果如图 3.4.2.1

 

      在图3.4.2.1中可以看到curname的值不是“zzk”,竟然是“zuozhongkai”,也就是变量“name”最后一次赋值的结果,这就是赋值符“=”的神奇之处!借助另外一个变量,可以将变量的真实值推到后面去定义。也就是变量的真实值取决于它所引用的变量的最后一次有效值
2、赋值符“:=”
        在“示例代码 3.4.2.1”上来测试赋值符“:=”,修改“示例代码 3.4.2.1”中的第 2 行,将其
中的“=”改为“:=”,修改完成以后的代码如下

 

执行结果:

 

从图 3.4.2.2 中可以看到此时的 curname 是 zzk,不是 zuozhongkai 了。这是因为赋值符“:=”不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。
3、赋值符“?=”
“?=”是一个很有用的赋值符,比如下面这行代码:

 

       上述代码的意思就是,如果变量 curname 前面没有被赋值,那么此变量是“zuozhongkai”,如果前面已经赋过值了,那么就使用前面赋的值。
4、变量追加"+="
         Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进
去,此时就要使用到符号“+=”,比如如下所示代码:

 

        一开始变量 objects 的值为“main.o input.o”,后面我们给他追加了一个“calcu.o”,因此变
量 objects 变成了“main.o input.o calcu.o”,这个就是变量的追加。
/
Makefile的模式规则
        在 3.3.2 小节中我们编写了一个 Makefile 文件用来编译工程,这个 Makefile 的内容如下:

 

        上述 Makefile 中第 3~8 行是将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对
应的规则,如果工程中 C 文件很多的话显然不能这么做。为此,我们可以使用 Makefile 中的模
式规则,通过模式规则我们就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。
        模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件。
        当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方
法如下:

 

        因此“示例代码 3.4.3.1”中的 Makefile 可以改为如下形式:

 

“示例代码 3.4.3.2”中第 5、6 这两行代码替代了“示例代码 3.4.3.1”中的 3~8 行代码,
修改以后的 Makefile 还不能运行,因为第 6 行的命令我们还没写呢,第 6 行的命令我们需要借
助另外一种强大的变量—自动化变量。
/
Makefile的自动化变量
        上面讲的模式规则中,目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候
都会是不同的目标和依赖文件,而命令只有一行,如何通过一行命令来从不同的依赖文件中生
成对应的目标?自动化变量就是完成这个功能的!所谓自动化变量就是这种变量会把模式中所
定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该
出现在规则的命令中,常用的自动化变量如表 3.4.4.1:

 

        表 3.4.4.1 中的 7 个自动化变量中,常用的三种:$@、$<和$^,我们使用自动化变量来完
成“示例代码 3.4.3.2”中的 Makefile,最终的完整代码如下所示:

 

这里的表格每个符号并没有完全理解!!
/
Makefile的伪目标
        Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代
表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命
        使用伪目标主要是为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出
现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文
件的,比如在前面的“示例代码 3.4.4.1”中有如下代码用来完成清理工程的功能:

 

        上述规则中并没有创建文件 clean 的命令,因此工作目录下永远都不会存在文件 clean,当
我们输入“make clean”以后,后面的“rm *.o”和“rm main”总是会执行。可是如果我们“手
贱”,在工作目录下创建一个名为“clean”的文件,那就不一样了,当执行“make clean”的时
候,规则因为没有依赖文件,所以目标被认为是最新的,因此后面的 rm 命令也就不会执行,我
们预先设想的清理工程的功能也就无法完成。为了避免这个问题,我们可以将 clean 声明为伪
目标,声明方式如下:

 

我们使用伪目标来更改“示例代码 3.4.4.1”,修改完成以后如下:

 

       上述代码第 5 行声明 clean 为伪目标,声明 clean 为伪目标以后不管当前目录下是否存在名
为“clean”的文件,输入“make clean”的话规则后面的 rm 命令都会执行。
/
Makefile的条件判断
        在 C 语言中我们通过条件判断语句来根据不同的情况来执行不同的分支,Makefile 也支持条件判断,语法有两种如下:

 

以及

 

        其中条件关键字有 4 个:ifeq、ifneq、ifdef 和 ifndef,这四个关键字其实分为两对、ifeq 与
ifneq、ifdef 与 ifndef,先来看一下 ifeq 和 ifneq,ifeq 用来判断是否相等,ifneq 就是判断是否不
相等,ifeq 用法如下:

 

          上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值。ifneq 的用法类似,只不过 ifneq 是用来了比较“参数 1”和“参数 2”是否不相等,如果不相等的话就为真
ifdef 和 ifndef 的用法如下:

 

        如果“变量名”的值非空,那么表示表达式为真,否则表达式为假。“变量名”同样可以是
一个函数的返回值。ifndef 用法类似,但是含义用户 ifdef 相反。
具体使用例子

 

/
Makefile的函数使用
        Makefile 支持函数,类似 C 语言一样,Makefile 中的函数是已经定义好的,我们直接使用,
不支持我们自定义函数。make 所支持的函数不多,但是绝对够我们使用了,函数的用法如下

 

        可以看出,调用函数和调用普通变量一样,使用符号“$”来标识。参数集合是函数的多个
参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“$”开
头。接下来我们介绍几个常用的函数,其它的函数大家可以参考《跟我一起写 Makefile》这份
文档。

 

 

 

7、函数 origin
基本语法:$(origin <variable>)   //获取到变量variable的来源
解释:variable 是变量名,origin 函数的返回值就是变量来源,因此$(origin V)就是变量 V 的来源。
8、函数 filter
基本语法:$(filter <pattern...>,<text>)  //根据语法规则pattern过滤文本text
解释:ilter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,
可以有多个模式。函数返回值就是符合 pattern 的字符串。
9、函数 firstword
基本语法:$(firstword <text>)   //获取文本text的第一个单词
解释:firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。
10、函数 include
基本语法:include <FILE_PATH>  //在这里调用目标文件
解释:<FILE_PATH>就是需要调用的文件的路径,可以是相对路径。
11、函数 words
基本语法:$(words <text>)    //统计文本text单词个数
解释
/
make文件的递归调用(子目录make文件调用)
基本语句: make -C subdir    //调用当前目录下子目录subdir下的Makefile
向子目录的Makefile传递变量:export VARIABLE ……    //导出变量VARIABLE .....给子目录 make 
                                                     unexport VARIABLE…… //不导出变量给子 make。
特殊的内置变量:SHELL 和 MAKEFLAGS。这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。这两个变量的值,可以自己去添加。
/
make的一些内部变量
CURDIR:是makefile的内嵌变量,显示当前路径
CC:C语言编译器的名称
CPP:C语言预处理器的名称 $(CC) -E
CXX:C++语言的编译器名称
RM:删除文件程序的名称
CFLAGS:C语言编译器的编译选项,无默认值
CPPFLAGS:C语言预处理器的编译选项,无默认值
CXXFLAGS:C++语言编译器的编译选项, 无默认值
MAKEFLAGS:传递给子目录make的变量,默认值rR
SHELL:传递给子目录make的变量
MAKE_VERSION:当前make工具的版本号,保存着当前使用的make工具的版本号IDxx.xx
/

 

三、Makefile的实战经验

基于mx6ull使用C语言裸机程序控制LED

Makefile文件:

LDS链接脚本文件:

基于imx6ull单片机的使用官方SDK定义的寄存器实验

这里为了方便后期的裸机开发,使用NXP官方提供的SDK中一些头文件,可以直接模仿STM32操作配置寄存器,后期可以自己去构建功能库函数文件。

引入类似网络上和KEIL5那样的工程文件管理和编译技术(很重要):

从图片中来分析:

CROSS_COMPILE ?= arm-linux-gnueabihf-                 #定义了使用的交叉编译器

TARGET        ?= ledc_bsp                             #定义了之后编译出来的文件的文件名

CC            := $(CROSS_COMPILE)gcc                  #将 CC变量展开到 arm-linux-gnueabihf-gcc

LD            := $(CROSS_COMPILE)ld                   #LD = arm-linux-gnueabihf-ld

OBJCOPY       := $(CROSS_COMPILE)objcopy              #OBJCOPY = arm-linux-gnueabihf-objcopy

OBJDUMP       := $(CROSS_COMPILE)objdump              #OBJDUMP = arm-linux-gnueabihf-objdump

INCUDIRS      := user \

                 system \

                 library/BASE \

                 library/CCM \

                 library/GPIO                         #指定列举出工程中所有头文件所在路径,交叉编译器编译.c文件时,需要指定头文件路径

SRCDIRS       := core \

                 user \

                 library/BASE \

                 library/CCM \

                 library/GPIO                         #指定列举出所有需要被编译的.s .c文件所在路径,后面会从里面找出所有.s和.c文件

INCLUDES      := $(patsubst %, -I %, $(INCUDIRS))                      #这里使用make工具函数里的替换函数patsubst,输出-I XXX -I XXX,

                                                                        #后面GCC编译时,-I xxx指定了被编译的源文件使用的.h文件的路径

SFILES        := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.s))    #这里使用make工具函数里的循环查找函数foreach,查询出 变量 SRCDIRS 里所有的.s目录,

                                                                        #并使用函数wildcard,在目录里查找到 .s 后缀的文件目录输出

CFILES        := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

SFILESNODIR   := $(notdir $(SFILES))                                   #去除上一步查找到的.s文件的目录部分,只保留 xxx.s 这个东西

CFILESNODIR   := $(notdir $(CFILES))

SOBJS         := $(patsubst %, obj/%, $(SFILESNODIR:.s=.o))            #将 xxx.s 格式替换为 xxx.o,后面作为编译的目标输出文件名

COBJS         := $(patsubst %, obj/%, $(CFILESNODIR:.c=.o))            

OBJS          := $(SOBJS)$(COBJS)                                      #本次编译出的目标 .o文件的集合

VPATH         := $(SRCDIRS)                                            #make工具的内部变量VPATH,当make在 Makefile 所在目录 找不到源文件时,会去VPATH指定的目录查找

#vpath %.s $(SRCDIRS)

#vpath %.c $(SRCDIRS)                                                  #make内部变量VPATH的另一种表达方式

.PHONY :clean

#=====================================================================

#                这里开始写真正编译的动作.......

#=====================================================================

$(TARGET).bin : $(OBJS)

    $(LD) -T$(TARGET).lds -o $(TARGET).elf $^

    $(OBJCOPY) -O binary -S $(TARGET).elf $@

    $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis

$(SOBJS) : obj/%.o : %.s                                                #make的静态模式,表示:将所有的.s文件编译为.o并存放到obj目录下去

    %(CC) -Wall -nostdlib -c -O2 $(INCLUDES) -o $@

$(COBJS) : obj/%.o : %.c                                                #make的静态模式,表示:将所有的.c文件编译为.o并存放到obj目录下去

    %(CC) -Wall -nostdlib -c -O2 $(INCLUDES) -o $@

clean:

    rm -rf $(OBJS) $(TARGET).elf $(TARGET).bin $(TARGET).dis


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值