- 先导问题
- 说明
- 解决方案
- bash文件
- bash的问题
- Makefile
- 构建场景
- 依赖
- 时间戳
- Makefile应用举例
- 构建场景
- Makefile语法
- 执行Makefile
- 答案
在Linux系统上,如果用bash编译程序,就需要手动敲入命令,并且每次编译都需要重新敲一遍命令,对于大型工程,比如内核编译,就显得非常麻烦。如何解决这个问题?
说明以下代码均在Linux环境下执行,涉及文件都在同一个文件夹下。
解决方案有2种解决方案,第一种是把需要编写的命令行写入一个文件中,也就是bash文件,后缀是.sh;第二种是Makefile文件。
bash文件将要编写的命令放在一个文件中,之后在命令行运行该文件。
比如下面这些命令,操作是编译一个动态库并软链接到/usr/lib/中:
[fish@localhost 文档]$ ls CPP_program java_program [fish@localhost 文档]$ cd CPP_program [fish@localhost CPP_program]$ ls libtest.so main.c test.c test.h [fish@localhost CPP_program]$ gcc test.c -fPIC -shared -o libtest.so [fish@localhost CPP_program]$ sudo link libtest.so /usr/lib/libtest.so
写入一个文件,名为showtime.sh:
ls cd CPP_program ls gcc test.c -fPIC -shared -o libtest.so link libtest.so /usr/lib/libtest.so
文件截图如下:
在命令行中输入bash showtime.sh即可,因为要进行软链接的那个目录需要管理员权限,所以在前面加上sudo。执行时相当于后台重新打开一个bash窗口,并把输出打印在当前窗口。
bash用于指令是没有问题的,但是用于编译却有一个很严重的问题,假设一种场景,一个大工程,比如编译Linux内核,这个内核经过2个小时的编译,编译结束之后,突然发现有一个小错误,及时修改了这一处错误,接下来怎么做?重新编译吗?再经过2个小时?这种效率太低了,这就是bash的问题。于是Makefile登场了。
Makefile 构建场景假设一个工程中,文件F依赖于文件C和文件E,文件C依赖于文件A和文件B,文件E依赖于文件D,可以得到下面这个关系图。
假设这个工程写好了,编译结束。然后开始代码审查和测试,发现文件E出现问题,里面有一个小BUG,开发人员修改了这处BUG,也就是修改文件E,接下来要重新编译,这个时候只用编译文件E和文件F即可,因为只有这两个文件受修改的影响。如何做到呢?
Makefile有2个机制,一个是依赖,另一个是时间戳。
在写Makefile文件的时候,会写如下的语句:
target : source
冒号前是目标,冒号后是源,目标依赖于源,这样就可以实现只编译修改的对象和依赖于修改的对象。
那么如何确定哪个文件被修改了?
通过对比时间戳可以判断那些文件是在上次做Makefile时改变的还是没有改变,以此来判断是否重新编译。
Makefile应用举例 构建场景文件名:mytest.cpp
#include#include "test1.hpp" #include "test2.hpp" using namespace std; int main() { cout << "testfunc1: " << testfunc(6) << endl; testfunc2(); return 0; }
文件名:test1.cpp
#include#include "test1.hpp" using namespace std; int testfunc1(int n) { int savenum = 5 + n; cout << "test1" << endl; return savenum; }
文件名:test1.hpp
#ifndef TEST1 #define TEST1 int testfunc1(int n); #endif
文件名:test2.cpp
#include#include "test2.hpp" using namespace std; void testfunc2(void) { cout << "test2" << endl; }
文件名:test2.hpp
#ifndef TEST2 #define TEST2 void testfunc2(void); #endif
文件依赖图如下:
我想要实现制作两个动态库,分别为test1和test2,mytest依赖于这两个动态库。
有关动态库和静态库可以参考头文件+源文件、动态库和静态库、宏定义
文件名:Makefile
target=mytest source=mytest.cpp lib=-L./lib/ -ltest1 -L./lib/ -ltest2 OBJ1=test1.cpp OBJ2=test2.cpp FUNCLIB1=./lib/libtest1.so FUNCLIB2=./lib/libtest2.so $(target): $(source) $(FUNCLIB1) $(FUNCLIB2) g++ $(source) -o $@ $(lib) $(FUNCLIB1):$(OBJ1) g++ -fPIC -shared $^ -o $@ $(FUNCLIB2):$(OBJ2) g++ -fPIC -shared $^ -o $@ clean: rm -rf $(target) $(FUNCLIB1) $(FUNCLIB2)
在Makefile中同样有变量的概念,就相当于将一长串字符串用一个变量代替,在运行Makefile文件时,会将变量所表示的字符串直接替换变量的位置。
target=mytest source=mytest.cpp lib=-L./lib/ -ltest1 -L./lib/ -ltest2 OBJ1=test1.cpp OBJ2=test2.cpp FUNCLIB1=./lib/libtest1.so FUNCLIB2=./lib/libtest2.so
上面这一段的意思如下表所示:
| 变量 | 赋值 |
|---|---|
| target | mytest |
| source | mytest.cpp |
| lib | -L./lib/ -ltest1 -L./lib/ -ltest2 |
| OBJ1 | test1.cpp |
| OBJ2 | test2.cpp |
| FUNCLIB1 | ./lib/libtest1.so |
| FUNCLIB2 | ./lib/libtest2.so |
接下来两行正式开始执行命令:
$(target): $(source) $(FUNCLIB1) $(FUNCLIB2) g++ $(source) -o $@ $(lib)
第一行的意思是变量target依赖于三个变量:source,FUNCLIB1和FUNCLIB2,即可执行文件mytest依赖于三个文件:mytest.cpp,./lib/libtest1.so和./lib/libtest2.so。在语法上要注意依赖于依赖之间要用空格分隔,变量前面有$并且需要将变量放在括号中,所以这一行等同于下面这一条语句:
mytest : mytest.cpp ./lib/libtest1.so ./lib/libtest2.so
第二行是一个命令,在Makefile中只有前面有tab的才算命令,在Makefile中有三个非常有用的变量,分别为$@,$^和$<,代表的意思为:$@为目标文件,$^为所有的依赖文件,$<为第一个依赖文件。
从参数表查找各个参数,并结合规则,第二行等同于:
g++ mytest.cpp -o mytest -L./lib/ -ltest1 -L./lib/ -ltest2
如果通过依赖判断,需要执行命令,相当于在bash中执行了
g++ mytest.cpp -o mytest -L./lib/ -ltest1 -L./lib/ -ltest2
其余同理,读者可以自行尝试翻译,答案放在文末。
执行Makefile在bash窗口输入make即可执行以下代码:
target=mytest source=mytest.cpp lib=-L./lib/ -ltest1 -L./lib/ -ltest2 OBJ1=test1.cpp OBJ2=test2.cpp FUNCLIB1=./lib/libtest1.so FUNCLIB2=./lib/libtest2.so $(target): $(source) $(FUNCLIB1) $(FUNCLIB2) g++ $(source) -o $@ $(lib) $(FUNCLIB1):$(OBJ1) g++ -fPIC -shared $^ -o $@ $(FUNCLIB2):$(OBJ2) g++ -fPIC -shared $^ -o $@
在bash窗口输入clean即可执行以下代码:
clean: rm -rf $(target) $(FUNCLIB1) $(FUNCLIB2)
那么写一个简单的bash脚本来实现自动执行。
文件名:Testsolib.sh
make wait export LD_LIBRARY_PATH=./lib/ wait ./mytest
在bash窗口输入bash Testsolib.sh即可执行bash文件。
答案./lib/libtest1.so:test1.cpp g++ -fPIC -shared test1.cpp -o ./lib/libtest1.so ./lib/libtest2.so:test2.cpp g++ -fPIC -shared test2.cpp -o ./lib/libtest2.so clean: rm -rf mytest ./lib/libtest1.so ./lib/libtest2.so



