【Linux + Makefile】十分钟教你学会Makefile的FORCE

https://blog.csdn.net/szullc/article/details/85036984

相信大家在使用Linux环境编程的时候,一定接触过Makefile这个玩意。Makefile在搭建自定义的编译环境,尤其是自动化编译、多功能一键编译等功能上,还是发挥了很大的作用。如果接触过Linux内核编译的童鞋,一定会看到编译内核中的各级Makefile中,有很多地方都会有 FORCE 这样的字段出现,那么这个 FORCE 究竟是何方神圣呢?本文将给你答案,通过阅读本文,你讲了解到以下内容:

  • Makefile的基本规则

  • FORCE 在Makefile的含义

  • FORCE在实际工程中的应用


Makefile的基本规则


Makefile的基本形式如下所示:

TARGET : DEPENDENCESCMD# TARGET:生成的目标,可以是一个文件,也可以是一个虚拟符号(非真实文件)# DEPENDENCES: 生成目标的所有依赖,它是一个集合,可以只有一个文件,或者很多文件;也可以是虚拟符号# CMD:把所有依赖生成目标的执行命令,其中CMD前面是一个TAB键,而不能是空格

Makefile的基本规则,归纳起来就是一句话:【当一个TARGET (欲生成的目标)比它的任何一个DEPENDENCES(依赖的文件)旧时,这个TARGET就要重新生成】。

这句话读起来很简单,就是Makefile在执行编译的时候,会先去判断TARGET和其依赖文件的时间,如果TARGET的时间比较旧,意味着依赖文件有更新了,所以TARGET要重新生成;反之,如果TARGET的时间比较新,意味着在TARGET生成后,依赖文件是没有改变过的,自然就不用重新去生成TARGET。


FORCE 在Makefile的含义


有了上一小节中介绍的Makefile基本概念后,我们来进一步分析下Makefile中的FORCE,以下是FORCE在Makefile中出现的最简化版本:

file = test.txtall: generate-a-filegenerate-a-file: $(file)$(file):@echo "Force to generate a test file for every make ..."rm -rf $(file) && echo `date "+%Y-%m-%d %H:%M:%S"` > $(file)FORCE:.PHONY: FORCE

这个Makefile不是用于真正的编译工程,而是提供一个很简单的功能,生成一个test.txt,并且这个test.txt的内容是记录每次编译的时间。但是使用这个makefile执行make时,发现只有第一次make的时候,才会生成test.txt,而其他时候只要test.txt还存在都不会重新生成,如下所示:

很明显,它没有达到我们期望的“每次编译都重新生成test.txt”。这个时候 FORCE 就发挥作用了,我们在 test.txt目标的后面添加 FORCE作为它的依赖试试看,即如下所示:

file = test.txtall: generate-a-filegenerate-a-file: $(file)$(file): FORCE  #FORCE表示每次这段都要执行@echo "Force to generate a test file for every make ..."rm -rf $(file) && echo `date "+%Y-%m-%d %H:%M:%S"` > $(file)FORCE:.PHONY: FORCE

执行输出如下所示: 我们可以看到,这达到了我们的目的,每次test.txt都是重新生成了,它记录了每次make的时间。

这个就是要归功于 FORCE 的功劳了。我们来分析下,为何加了 FORCE 就能实现这样的功能。

我们可以注意到FORCE这个目标,它的DEPENDENCES是空的,CMD部分也是空的;这个比较特殊了,在Makefile里,像这样依赖为空、执行命令也为空的TARGET,则需要每次都重新生成,而这个TARGET不一定是一个文件,可以是任意的符号,而 FORCE 只是我们最常用的符号,理论上它可以换成任意符号,比如NO-FORCE、SOMETHING等等。


FORCE在实际工程中的应用


上一小节,我们讲到可以用Makefile配合shell命令来自动生成一些文件,自然我们很容易想到,在我们实际的编译工程中,往往需要动态生成一些配置项,然后嵌入到代码中,比如编译版本号、编译时间等。

假设我们有以下一个main.c:

#include <stdio.h>#include "build_info.h"  #这个头文件需要每次编译时自动生成int main(int argc, const char *argv[]){printf("%s >>> APP_TIME=%s\n", __func__, APP_TIME);return 0;}

示例代码很简单,就是再main函数中打印一个 build_info.h中的一个宏定义APP_TIME,这个build_info.h要求每次编译的时候都重新生成。我们给出的Makefile示例如下:

##拷贝时,注意tab键SHELL           = /bin/bash #指定shell使用/bin/bash,否则echo -e可能会出问题ECHO            = echoBIN             = testBUILG_INFO_H    = build_info.hSRC-C-y         = main.cSRC-O           = $(patsubst %.c, $(O)%.o, $(SRC-C-y))all: gen_build_info $(BIN)$(BIN) : $(SRC-O)gcc -o $(O)"$@" $(SRC-O)%.o : %.cgcc -c "$<" -o "$@"gen_build_info: $(BUILG_INFO_H)$(BUILG_INFO_H): FORCE     #强制生成build_info.h@$(RM) $@@$(ECHO) '  GEN     $@'@$(ECHO) -e   " #ifndef __BUILD_INFO_H__\n"\"#define __BUILD_INFO_H__\n"\"#define APP_TIME        \"`date "+%Y-%m-%d %H:%M:%S"`\"\n"\"#endif"  > $@FORCE:.PHONY: FORCE

make执行输出测试如下:

从输出的测试,我们可以看出,make的每次执行都触发了生成build_info.h,但是运行编译出来的test可执行程序,我们发现并不是每次生成的build_info.h的内容都传递到了test里面;也就是当build_info.h改变的时候,test没有被重新编译。这里先留下点疑问,为何会产生这样的问题。博主将会在后续的文章中解决这个问题。


不管怎么样,经过对上文的学习,我们至少掌握了 FORCE的基本用法,而在实际项目工程中,我们也见证了它的威力;那么,你学会了吗?如果还有疑问,欢迎在评论席提出你的问题和看法,博主定会尽力解决你的困惑。

版权申明:本文为博主原创文章,转载请注明出处!https://blog.csdn.net/szullc/article/details/85036984

原创作者:李路昌

电子邮箱:recan.szu@foxmail.com

(0)

相关推荐