make和Makefile

引入

拿之前文件拷贝的例子来说,它们的结构是这样的:

image-20240830225819892

现在要使用源文件io.ccp.c编译生成一个可执行文件,然后实现文件之间的拷贝,假设这是一个工程代码,cp.cio.c是由两个人来完成的,所以现在要将源文件编译生成目标文件来检查语法是否有错,然后将两个目标文件进行链接生成可执行文件。

gcc -c src/io.c -o obj/io.o -I include/
gcc -c src/cp.c -o obj/cp.c -I include/
gcc -o bin/cp obj/io.o obj/cp.o

可见只有两个文件也需要敲三条命令,如果现在工程里有成百上千的源文件和目标文件,那么岂不是还要敲这么多指令?而且有些文件是依赖于别的文件,所以它们的编译顺序也有讲究,有些文件可能是目标文件它就不需要编译了,有些文件是源文件需要编译,所以它们的编译规则也很重要。可见如果工程十分庞大的话,使用敲指令的方法十分麻烦。所以现在引入makeMakefile,将指令写入到Makefile文件中,然后直接执行make就可以完成对项目工程的编译。

Makefile文件

将终端执行的指令放到这个Makefile文件中去, 这个文件中就描述了编译规则和依赖关系等信息。通过Makefile就可以对整个软件工程进行自动编译,极大地提高了软件开发的效率。

make工具

当将这个Makefile文件编写完毕后就要执行这个文件,因为这个文件相当于对终端上输入的指令进行批量操作,不需要手敲指令,只要执行这个文件就等同于在终端上敲入指令。那么要执行这个文件就需要make工具。这个Makefile文件其实有点类似于shell脚本,在shell脚本里放置在终端的指令,然后给这个脚本赋予一个可执行的权限,执行这个脚本就相当于在终端上敲这些指令。

Makefile的编写原则和规则

  • make命令不带选项运行时,它从Makefile中读取制定规则
  • 当制定规则在不同于Makefile(或makefile)的其他文件中时,就要运行带有-f选项的make命令,比如文件名为test.Makefile,在读取规则时就要执行make -f test.Makefile

Makefile的编写规则一

目标列表:关联性列表
<TAB>命令列表
  • 目标列表:可以理解为一个最后编译链接后的一个可执行文件或者是一个编译出来的一个目标文件(目标列表是用一个或多个空格分开的目标文件的清单,意思就是可以生成一个可执行文件,也可以生成多个可执行文件)
  • 关联性列表(也称先决条件):同样是用一个或多个空格分开的目标文件,是目标文件所依赖的多个目标文件的清单。例如有一个源文件test.c,现在要将它编译成一个目标文件test.o,那么它的依赖文件就是test.c,或者使用指令gcc -o test test.c后生成一个可执行文件,那么这个文件就是依赖源文件test.c生成的。
  • 命令列表:用于创建目标文件的将要执行的命令清单,这些命令被换行符分开。命令列表中的每个命令必需以字符开始。就拿上边的例子来说,要从源文件test.c变成一个可执行文件test就需要通过一些命令例如gcc或者shell指令等,这里的命令就是命令列表。

Makefile的编写规则二(一般使用第一种编写规则)

目标列表:关联性列表;命令列表
  • 命令列表是一系列被分号隔开的命令。一个很长的命令可以分隔符隔开
  • 例如:cd /home/dx/my_project/system_programming;rm file1 rm file2这里的命令可以使用反斜杠\隔开
  • Makefile中可使用shell中的一些通配符
    • 注释(#)、连接符(\)、通配符(? *)

make的使用语法

常用选项特性
-d显示调试信息
-f <文件>指定从哪个文件中读取依赖关系信息。默认是从Makefile或者makefile中读取 “-”表示从标准输入
-h显示所有选项的简要说明
-n仅输出要执行的命令,而不实际执行它们
安静的方式运行,不显示任何信息
示例–Makefile和make的使用
  1. 首先在目录下新建四个目录

    image-20240901105524424

    源文件存放在src目录下,头文件存放到include下,可执行文件存档到bin目录下,目标文件存放到obj目录下

  2. 然后编写源代码(一个简单的加减乘除程序)

    image-20240901105728253

  3. 然后编写一个Makefile文件来管理工程代码

    #书写规则:将最后生成的可执行文件放在最上边,然后指定它所依赖的文件
    bin/calc:obj/math.o obj/mycalc.o
        gcc -o bin/calc obj/math.o obj/mycalc.o
    obj/math.o:src/math.c
        gcc -o obj/math.o -c src/math.c
    obj/mycalc.o:src/mycalc.c
        gcc -o obj/mycalc.o -c src/mycalc.c -I include/
    #这里的mycalc.c文件里用到include目录下的头文件,所以要指定头文件的目录
    

    在编写Makefile文件的时候一定要注意文件名和目录名,如果编写错误,最后的make会报错。Makefile文件编写完成以后,通过make工具来执行Makefile文件中的规则和依赖关系。

    image-20240901111330241

    通过查看可以发现通过make工具生成了目标文件和可执行文件,并且最后生成的可执行文件也能够执行。

依赖树

依赖树.drawio

make的一些注意事项

  • 首先在编写Makefile文件的时候,每一条命令列表前边都要加一个tab键,如果不加tab键会报错;

  • Makefile中一般将可执行文件放到最上边,然后整体呈现一种倒推的方式进行编写。通过观察make的执行顺序和Makefile的编写顺序,发现虽然说可执行文件写在第一行但是它会去找到它依赖的文件,然后要先生成依赖文件后再回来链接成可执行文件。

    image-20240901112626100

    image-20240901112643613

​ 这里可执行文件先写的依赖是obj/math.o文件,所以在make的时候就会先生成这个目标文件,如果将它的依赖调换一下就会先生成obj/mycalc.o的目标文件。

image-20240901113120310

  • 当对同一个工程代码进行make两次的时候就提示/bin/calc已是最新,不会再进行编译。这是因为make工具会根据编写的规则去解析这些文件,根据这些文件的时间戳去判断是否需要对源文件进行编译链接等操作。这里为了验证这一现象,可以对源文件敲一个回车再删掉,给make工具一个错觉让它以为源文件发生了修改从而重新编译链接生成可执行文件。

    image-20240901114245376

    如图所示,正常的话如果里边有生成对应的文件,然后再次进行make肯定会显示xxx已是最新,但是由于现在对源文件进行编辑,所以它的时间戳更新为当前的时间,而make去解析源文件后,发现它的时间戳更新了,所以认为它是一个已被更改的文件,所以它会重新编辑链接生成可执行文件。通过make这个工具,可以对文件进行选择性的编译,只编译更改的文件,大大地简化了编译过程,提高了编译效率。就像keil工具里边的build按钮一样只编译改动过的文件。

Makefile的变量使用

简单变量

  • 定义

    image-20240901165246622

    这里的文本内容实际上就是字符串

  • 添加(如果已经定义了一个变量还想要追加内容的话可以使用添加的方式)

    image-20240901165710168

    image-20240901165547821

    上述两种方法等价,第二种方法在进行编写的时候中间要加空格隔开

  • 引用变量

    • $(变量名),例如CC:=gcc $(CC) -o test test.c
    • $单字符变量,例如C:=gcc $C -o test test.c
示例–对上边的Makefile进行修改,利用变量定义的方式简化内容

为了防止将原来的Makefile文件进行覆盖,所以将此文件拷贝为Makefile2。由于文件名不是标准的Makefile或者makefile,所以这里在运行Makefile2的时候要加-f参数来指定文件

CC:=gcc
TARGET:=bin/calc
DEPEND:=obj/mycalc.o
DEPEND+=obj/math.o
CFLAGS:=-Iinclude
CFLAGS+=-c


#书写规则:将最后生成的可执行文件放在最上边,然后指定它所依赖的文件
$(TARGET):$(DEPEND)
    $(CC) -o bin/calc obj/math.o obj/mycalc.o
obj/math.o:src/math.c
    $(CC) -o obj/math.o $(CFLAGS) src/math.c
obj/mycalc.o:src/mycalc.c
    $(CC) -o obj/mycalc.o $(CFLAGS) src/mycalc.c
#这里的mycalc.c文件里用到include目录下的头文件,所以要指定头文件的目录

这里需要注意几点:

  • Makefile中使用变量替换原字符串以后,后边使用变量名要加$符号,要不然就会报错;
  • 依据上边的代码,在进行追加写入的时候一定要使用+=而不是:=,如果使用:=就会将之前的覆盖掉,所以它真正有效的应该是写在后边的变量定义;
  • 在进行定义变量的时候一般不需要在:=两边敲空格,因为空格也会被当作字符作为输入;
  • 变量名一般使用大写来定义

最后的执行结果如下:

image-20240901173230463

内置变量

通过上边的变量定义简化了一些操作,但是后边编译用到的文件看着还是比较繁琐

image-20240901174739276

那么在Makefile中有几个内部变量来帮助简化这些操作,常用的内边变量和定义如下:

变量名意义
$@当前目标的名称
$?比当前目标更新的已修改的依赖性列表
$<依赖性列表中的第一个文件
$^用空格分开的所有依赖性列表

image-20240901175749756

示例–使用Makefile的内置变量对上边的Makefile进行修改
CC:=gcc
TARGET:=bin/calc
DEPEND:=obj/mycalc.o
DEPEND+=obj/math.o
CFLAGS:=-Iinclude
CFLAGS+=-c


#书写规则:将最后生成的可执行文件放在最上边,然后指定它所依赖的文件
$(TARGET):$(DEPEND)
    $(CC) -o $@ $^
obj/math.o:src/math.c
    $(CC) -o $@ $(CFLAGS) $<
obj/mycalc.o:src/mycalc.c
    $(CC) -o $@ $(CFLAGS) $<
#这里的mycalc.c文件里用到include目录下的头文件,所以要指定头文件的目录

image-20240901180314551

虚目标

  • 不存在的文件,而且也无需创建它们;

  • 允许强制执行某些事件,而这些事件在正常的规则中是不会发生的;

  • 虚目标不是真正的文件,make命令可以使用针对它们的任意规则

  • 常见的虚目标列表

    目标意义
    all生成工程中所有可以执行者,通常是Makefile的第一个生成目标
    test运行程序的自动测试套件
    clean删除make all生成的所有文件
    install在系统目录中安装工程项目生成的可执行文件和文档
    uninstall删除make install安装的所有文件

我的理解就是虚目标是make工具用来执行一些指令的,比如之前在生成目标文件和可执行文件后,再次make就会提示已是最新并不会编译,这时候就需要把文件删除后再执行make,通过虚目标就可以使用make指令执行相关的操作来调用shell指令来将相关的文件删除。

应用场景1:删除目标文件和可执行文件

clean:
    rm -rf $(TARGET) $(DEPEND)
#再上边文件最后添加这一句话

运行的时候指令为make clean -f Makefile2就可以清除目标文件和可执行文件

image-20240901182117435

需要注意的是如果在当前目录下如果有这样的一个同名的文件,那么执行make clean -f Makefile2就会提示XXX已是最新,就不会去执行Makefile里边的虚目标了。这个解决方法可以通过后边的特殊目标来解决这个问题。

image-20240901182609116

应用场景2:调试信息

在编写Makefile的过程中其实还是比较容易出错的,因为它不像在命令行那样子可以使用tab键补齐,需要手动的一个一个敲。一个不小心可能就会敲错,然后导致最终编译不过的问题,所以这里可以使用虚目标来进行调试信息的打印,将变量名引用的字符串打印出来以方便进行排错。

CC:=gcc
TARGET:=bin/calc
DEPEND:=obj/mycalc.o
DEPEND+=obj/math.o
CFLAGS:=-Iinclude
CFLAGS+=-c


#书写规则:将最后生成的可执行文件放在最上边,然后指定它所依赖的文件
$(TARGET):$(DEPEND)
    $(CC) -o $@ $^
obj/math.o:src/math.c
    $(CC) -o $@ $(CFLAGS) $<
obj/mycalc.o:src/mycalc.c
    $(CC) -o $@ $(CFLAGS) $<
#这里的mycalc.c文件里用到include目录下的头文件,所以要指定头文件的目录

debug:
    echo $(CFLAGS)
    echo $(TARGET)
    echo $(DEPEND)

.PHONY:clean

clean:
    rm -rf $(TARGET) $(DEPEND)

image-20240901195904484

它的用法和上边的clean类似,在执行的时候指定debug选项,然后将调试信息打印出来方便排错。如果不想要打印命令只打印字符串可以在前边加一个@,如@echo $(CFLAGS)就可以避免将指令打印出来。

特殊目标

make中有一些预定义的目标,这些预定义目标被make以一种特殊的方式进行处理,这些目标被称为特殊目标

image-20240901191703133

这里将使用.PHONYclean声明成一个虚目标后,就不会去关联clean这个文件了,也能够正常的将目标文件和可执行文件删除。

image-20240901191916111

image-20240901192045890

默认模式规则

在上面的Makefile文件中有两行指令是将.c源文件编译成.o目标文件,由于这里只有两个源文件所以对应两条指令,如果这里的源文件数量比较多,那么写那么多条指令显然不现实。那么此时就要用到make的默认模式规则。

image-20240901202152419

常用的默认的模式规则(GNU make)

%.o:%.c:
	$(CC) $(CFLAGS) -c $<	
#这里的%号是通配符的意思,表示所有的.o目标文件都依赖于所有的.c源文件,make工具会自动根据依赖关系进行查找,例如test.o:test.c calc.o:calc.c
#$(CC)是编译工具,$(CFLAGS)是编译指定的选项,-c参数是只进行编译不进行链接,$<表示依赖文件列表里边的第一个依赖项

示例–将上边由源文件编译生成目标文件的方法改为使用模式规则的方法
CC:=gcc
TARGET:=bin/calc
DEPEND:=obj/mycalc.o
DEPEND+=obj/math.o
CFLAGS:=-Iinclude
CFLAGS+=-c


#书写规则:将最后生成的可执行文件放在最上边,然后指定它所依赖的文件
$(TARGET):$(DEPEND)
    $(CC) -o $@ $^
obj/%.o:src/%.c
    $(CC) -o $@ $(CFLAGS) $<
#obj/目录下的所有.o目标文件依赖于src/下的所有源文件,make工具根据依赖规则生成对应的目标文件

#obj/math.o:src/math.c
#   $(CC) -o $@ $(CFLAGS) $<
#obj/mycalc.o:src/mycalc.c
#   $(CC) -o $@ $(CFLAGS) $<
#这里的mycalc.c文件里用到include目录下的头文件,所以要指定头文件的目录

debug:
    echo $(CFLAGS)
    echo $(TARGET)
    echo $(DEPEND)

.PHONY:clean

clean:
    rm -rf $(TARGET) $(DEPEND)

image-20240901200535525

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日落星野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值