2. Makefile 基础规则

2. Makefile 基础规则Makefile的基础规则与命名方式 更新目标文件的依据 时间戳检验测试 依赖执行顺序 变量的使用 「.PHONY」与「-」的作用与使用方法 – 编写一段程序的`makefile`文件。

上次实验介绍了对不同源代码文件进行编译、链接生成可执行文件的基本过程,有了这些前导知识作为基础之后, 就可以开始学习makefile的基础规则了。

首先,我们已经知道makefile作为工程管理文件可以提供工程下各个源代码的编译、链接规则。 GNU make 工具可以读入makefile并解析其中的规则,并自动对工程进行编译链接,提高项目开发的效率。 那么,makefile到底如何实现对工程编译、链接的管理呢?本实验将通过介绍makefile的基础规则来回答这个问题。

知识点

  • Makefile 的基础规则与命名方式
  • Makefile 更新目标文件的依据
  • makefile时间戳检验测试
  • Makefile 依赖执行顺序
  • Makefile 变量的使用
  • 「.PHONY」与「-」的作用与使用方法
  • 编写一段程序的makefile文件。

代码获取

通过在 Terminal 中输入以下命令可以将本课程所涉及到的所有源代码下载到在线环境中,作为参照对比进行学习。

wget http://labfile.oss.aliyuncs.com/courses/849/make_example-master.zip && unzip make_example-master.zip && rm make_example-master.zip

本章节的源代码位于 /home/project/make_example-master/chapter1 目录下,请在 Terminal 中通过 cd 命令切换至该目录后再进行实验学习。

项目涉及到的代码文件:

  • main.c:C 语言主程序代码
  • makefile:Makefile 源代码

1️⃣ Makefile 的基础规则与命名方式

❗ 1.1 makefile 的基础规则

编写 main.c 文件,在本次实验中将用简单的 hello world! 程序来验证 makefile 的基本规则。

#include <stdio.h>

int main(void) {
    printf("hello world!\n");
    return 0;
}

因为 makefile 是为了自动管理编译、链接流程而存在的,因此 makefile 的书写需要遵照一定的书写规则。

TARGET... : PREREQUISITES...
COMMAND
  • TARGET:规则目标,可以是一个 object file (目标文件),也可以是一个执行文件,还可以是一个标签(label)。
  • PREREQUISITES:要生成那个 target 所需要的文件或是目标,即规则依赖。
  • COMMAND:也就是 make 需要执行的命令,必须以 [TAB] 开始,由 shell 执行。

1.2 编写makefile文件管理main.c的编译

编写一个简单的 makefile 文件用来管理 main.c 文件的编译。

#this is a makefile example

main: main.o
    gcc -o main main.o

main.o: main.c
    gcc -c main.c

line1:“#”为注释符号,后面接注释文本。

line3 – line4:声明目标 main 的依赖文件 main.o 及链接 command。

line6 – line7:声明目标 main.o 的编译command。

1.3 测试make命令

make工具的基本使用方法为:make TARGET。 在终端输入命令:

make main.o

Terminal 的输出结果如图所示: 图片.png

说明 shell 执行了命令 gcc -c main.c

接下来输入以下命令:

make main

Terminal 的输出结果如图所示:

图片.png

说明 shell 执行了命令 gcc -o main main.o

最后在 Terminal 中输入以下命令:

./main

图片.png

说明程序正常执行。

1.4 自动化编译目标

清除掉main.omain文件:

rm main.o main

由于我们的最终的目标是 main 文件,实际上我们并不关心中间目标文件 main.o。现在尝试只运行一次 make 编译出我们需要的最终目标。

make main

图片.png

可以看出 make 还是先生成 makefile 中 main 的依赖文件 main.o,再链接生成 main 文件。


1.5 make 自动寻找目标

make 自动寻找目标,即在 make 命令后不添加任何参数直接运行。

首先删除之前产生的 mainmain.o 文件。

rm main main.o

然后在 Terminal 中输入命令:

make

Terminal 的输出内容跟之前的输出内容相同:

图片.png

❗❗❗ 这是因为默认情况下,
m a k e 会以第一条规则作为其终极目标。 \color{red}{make 会以第一条规则作为其终极目标。}

现在我们尝试修改 makefile,在目标 main 之前再增加一条规则:

dft_test:middle_file
    mv middle_file dft_test
middle_file:
    touch middle_file

图片.png

注意:在将这段代码复制到 WebIDE 中时需要观察一下此时 tab 的默认格式是 space:4 表示使用 4 个空格代替 制表符。

这样的话在执行 make 命令会出现以下错误:

Makefile:2 *** missing separator 停止

图片.png

解决办法是依次点击 Space:4->Indent Using Tabs->4 Configured Tab Size 然后删除 Command 那一行的缩进,重新使用 tab 缩进,保存之后重新执行 make 命令,问题将得以解决。

图片.png

执行命令:make

图片.png

说明规则得到了正确的执行,同时在当前文件夹下会产生一个新的文件 dft_test。

创建文件middle_file,改名dft_test

❗ 1.6 makefile 的命名规则

目前为止,我们写的自动编译规则都放在 makefile 中,通过实验也可证明了 make 工具会自动调用 makefile 文件。 是否文件名必须命名为「makefile」呢? 然而并不是,GNU make 会按默认的优先级查找当前文件夹的文件,查找的优先级为: 「GNUmakefile」> 「makefile」> 「Makefile」

接下来对 makefile 名称的优先级进行测试。

新建 GNUmakefile 文件,添加以下内容:

#this is a GNUMakefile

.PHONY: all

all:
    @echo "this is GNUMakefile"

新建 Makefile 文件,添加以下内容:

#this is a Makefile

.PHONY: all

all:
    @echo "this is Makefile"

查看一下当前目录文件,现在应该有三个 make 能够识别到的文件。

ls *file* -hl
  • ls 显示文件信息 -h, –human-readable 以容易理解的格式列出文件大小 (例如 1K 234M 2G)

-l 除了文件名之外,还将文件的权限、所有者、文件大小等信息详细列出来。

ls *file* -hl 表示 输出* file * (中间有file的文件)详细信息

linux ls命令

Terminal 输出结果如图:

图片.png

执行 make 命令,查看 Makefile 的执行情况。

make

Terminal 的输出结果如图:

图片.png

说明 make 调用的是 GNUmakefile。 删除 GNUmakefile 再执行一次 make 命令。

输出结果如图所示:

图片.png

说明 make 调用的是 makefile。 删除 makefile 再执行一次 make 命令。

输出结果如图所示:

图片.png

说明 Makefile 是三者中优先级最低。

然而在创建 makefile 文件时推荐以 makefile 或者 Makefile 进行命名,而不使用 GNUmakefile ,因为 GNUmakefile 只能被 GNU 的 make 工具识别到。

2️⃣ Makefile 之时间戳

make 命令在执行时会自动检测依赖文件的时间戳,具体规则如下:

  1. 若依赖文件不存在或者依赖文件的时间戳比目标文件新,则执行依赖文件对应的命令。
  2. 若依赖文件的时间戳比目标文件老,则忽略依赖文件对应的命令。

接下来对 makefile 时间戳规则进行验证。

将 makefile 文件内容还原,还原后的内容应该与下面给出的代码一致:

main: main.o
    gcc -o main main.o

main.o: main.c
    gcc -c main.c

为 makefile 打上 v1.0.patch 补丁,增加一些新的内容。

patch -p2 < v1.0.patch

此时 makefile 的内容如下:

#this is a makefile example

main:main.o testa testb
    gcc -o main main.o

main.o:main.c
    gcc -c main.c

testa:
    touch testa

testb:
    touch testb

清除可能存在的中间文件。

rm main.o testa testb

执行 make 命令。

make

Terminal 输出内容如下:

图片.png

make 命令执行后分别生成 main.otestatestb 这三个中间文件。这验证了时间戳规则中的第一条。

现在删除 testb 文件,再观察 make 会如何执行。

make

Terminal 输出结果如图:

图片.png

可见 make 分别执行了 testbmain 两条规则,main.otesta 规则对应的命令没有被执行。这验证了 makefile 时间戳规则中的第二条。

3️⃣ Makefile 依赖执行顺序

从上述实验可以看出make目标文件的依赖文件是按照需要从左到右的顺序生成的。 对应规则“main”:

main:main.o testa testb
    gcc -o main main.o

make 按照顺序分别执行 main.otestatestb 所对应的规则。

现在我们调换 main.otestatestb 的顺序。 修改 makefile 文件的 main 规则的依赖顺序:

main: testb testa main.o

输出结果如图:

图片.png

可见 make 的确是按照从左到右的规则分别执行依赖文件对应的命令。

4️⃣ 变量、「.PHONY」与「-」

makefile 也可以使用变量,它类似于 C 语言中的宏定义。 变量可以直接使用··「vari=string」的写法来定义,并以「$(vari)」格式来使用。 我们用变量来定义目标的依赖项,使 makefile 保持良好的扩展性。

4.1 在 makefile 中定义并使用变量

将 makefile 还原至以下内容:

main: main.o
    gcc -o main main.o

main.o: main.c
    gcc -c main.c

为 makefile 打上v1.0.patch补丁,并移除旧的中间文件。

patch -p2 < v1.0.patch
rm main.o testa testb

在目标 main 之前定义一个变 depen

#this is a makefile example

depen=main.o testa testb

main:main.o testa testb
    gcc -o main main.o

main.o:main.c
    gcc -c main.c

testa:
    touch testa

testb:
    touch testb

修改 main 的依赖声明。

main:$(depen)

图片.png

执行 make 命令,并观察输出结果。

输出结果如图:

图片.png

可以看出 makefile 依然能够正常执行。 就算之后 main 目标的依赖项有变化时,也只需修改 depen 变量的值即可。

4.2 为makefile添加clean规则

每次测试makefile的时候我们都要清除中间文件,为了使得编译工程更加自动化,我们在makefile中添加规则让其自动清除。

makefile中修改depen变量,增加clean依赖:

depen=clean main.o testa testb

增加 clean 规则及其命令:

clean:
    rm main.o testa testb

此时 makefile 的内容如下:

#this is a makefile example

depen=clean main.o testa testb 

main:$(depen)
	gcc -o main main.o

main.o:main.c
	gcc -c main.c

testa:
	touch testa

testb:
	touch testb

clean:
	rm main.o testa testb

当前目录下是存在main.o testa testb三个中间文件的,执行make看看效果:

图片.png

说明现在make会先清除掉上次编译的中间文件并重建。

4.3 clean规则中也使用 depen变量。

makefile 中定义了depen变量来声明各个依赖项。但新增的clean规则没有使用这个变量,这会让makefile的维护产生麻烦:当依赖项变更的时候需要同时修改depen变量和clean规则。 因此,我们让clean规则的rm命令也使用depen变量。

修改clean规则下的rm命令行:

clean:
    rm $(depen)

然而当我们执行了 make 命令之后,发现问题产生了。

图片.png

原来是因为 depen 变量指明 clean 为依赖项,因此执行 rm 命令时也会试图删除 clean 文件,这时就会出现错误。 而 make 在执行命令行过程中出现错误后会退出执行。

4.4 「-」的使用

  • clean命令出错后make也能继续执行

rm 某个不存在的文件是很常见的错误,在大部分情况下我们也不将其真正作为错误来看待。 如何让make忽略这个错误呢? 我们需要用到“-”符号。 “-”:让make忽略该指令的错误。 修改makefile中的clean规则:

现在修改 makefile 中的 clean 规则:

clean:
    -rm $(depen)

执行 make 命令并观察输出结果:

图片.png

从输出结果来看,虽然 rm 指令报出错误,make 却依然可以生成 main 文件。

4.5 伪目标的使用

前面提到 makefile 依赖文件的时间戳若比目标文件旧,则对应规则的命令不会执行。 我们现在定义了一个 clean 规则,但如果文件夹下正好有一个 clean 文件会发生什么样的冲突呢? 我们先在当前目录下先新建一个 clean 文件:

touch clean

执行 make 命令,并观察输出结果。

图片.png

发现 clean 规则没有得到执行,因为 clean 文件已经存在。然而 clean 实际上是一个伪目标,我们并不期望它会与真正 clean 文件有任何关联。 因此需要使用 .PHONY 来声明伪目标。

接下来修改 makefile 在变量 depen 之前加入一条伪目标声明。

.PHONY: clean

执行 make 命令并观察输出结果。

图片.png

可以看到 makefile 又能得到正常执行了,并且所有流程都符合我们的预期。

现在删除掉 main 依赖项 testatestb 因为在生成 main 文件过程中并不需要用到这两个文件。 修改 makefile 的 depen 变量:

depen=clean main.o

执行 make 命令并观察输出结果。

图片.png

5️⃣ 编写一段程序的 makefile 文件

现在我们已经掌握了makefile的基本规则,可以尝试自己写一个makefile进行工程管理。

make_example/chapter0目录下有一段简单的计算器示例程序,现在要为它建立一个makefile文件。 切换到chapter0目录,查看目录下的文件:

图片.png

简单介绍一下程序的需求:

  1. add_minus.c要求被编译成静态链接库libadd_minus.a
  2. multi_div.c要求被编译成动态链接库libmulti_div.so
  3. main.c是主要的源文件,会调用上述两个代码文件中的API,main.c要求被编译为main.o
  4. 将main.o libadd_minus.a libmulti_div.so链接成可执行文件 main。
  5. 每次编译前要清除上次编译时产生的文件。

打上补丁 v3.0 并增加库文件路径,export环境变量LD_LIBRARY_PATH为当前路径:

patch -p2 < v3.0.patch
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:当前文件路径

这时候的main.c文件如下;

 #include <stdio.h>
#include "add_minus.h"
#include "multi_div.h"
 
 int main(void) {
	int rst;

    printf("Hello Cacu!\n");
	
	rst = add(3,2);
	printf("3 + 2 = %d\n",rst);

	rst = minus(3,2);
	printf("3 - 2 = %d\n",rst);

	rst = multi(3,2);
	printf("3 * 2 = %d\n",rst);

	rst = div(6,2);
	printf("6 / 2 = %d\n",rst);

     return 0;
 }

makefile文件示例

makefile_for_chapter0的内容:

# this is a chapter0 makefile
.PHONY:main clean depen 

depen=clean main.o add_minus.o libadd_minus.a libmulti_div.so

main:$(depen)
	-gcc -o main main.o -L./ -ladd_minus -lmulti_div

main.o:main.c
	gcc -c main.c

add_minus.o:add_minus.c
	gcc -c add_minus.c

libadd_minus.a:add_minus.o
	ar rc libadd_minus.a add_minus.o

libmulti_div.so:
	gcc multi_div.c -fPIC -shared -o libmulti_div.so

clean:
	-rm $(depen)

最后运行结果如下图:

图片.png

今天的文章2. Makefile 基础规则分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/17515.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注