大家好,又见面了,我是你们的朋友全栈君。
相信大家在使用Linux环境编程的时候,一定接触过Makefile这个玩意。Makefile在搭建自定义的编译环境,尤其是自动化编译、多功能一键编译等功能上,还是发挥了很大的作用。如果接触过Linux内核编译的童鞋,一定会看到编译内核中的各级Makefile中,有很多地方都会有 FORCE 这样的字段出现,那么这个 FORCE 究竟是何方神圣呢?本文将给你答案,通过阅读本文,你讲了解到以下内容:
- Makefile的基本规则
- FORCE 在Makefile的含义
- FORCE在实际工程中的应用
Makefile的基本规则
Makefile的基本形式如下所示:
TARGET : DEPENDENCES
CMD
# 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.txt
all: generate-a-file
generate-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.txt
all: generate-a-file
generate-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 = echo
BIN = test
BUILG_INFO_H = build_info.h
SRC-C-y = main.c
SRC-O = $(patsubst %.c, $(O)%.o, $(SRC-C-y))
all: gen_build_info $(BIN)
$(BIN) : $(SRC-O)
gcc -o $(O)"$@" $(SRC-O)
%.o : %.c
gcc -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的基本用法,而在实际项目工程中,我们也见证了它的威力;那么,你学会了吗?如果还有疑问,欢迎在评论席提出你的问题和看法,博主定会尽力解决你的困惑。
版权申明:本文为博主原创文章,转载请注明出处!【Linux + Makefile】十分钟教你学会Makefile的FORCE_架构师李肯-CSDN博客
原创作者:recan
电子邮箱:recan.szu@foxmail.com
延伸阅读:
【Linux + Makefile】Makefile的高阶用法:解决C文件包含的头文件修改了,但C文件不重新编译的问题
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/131032.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...