先导问题
在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的问题
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 <iostream>
#include "test1.hpp"
#include "test2.hpp"
using namespace std;
int main()
{
cout << "testfunc1: " << testfunc(6) << endl;
testfunc2();
return 0;
}
文件名:test1.cpp
#include <iostream>
#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 <iostream>
#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语法
文件名: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
本文探讨了在Linux中使用bash编译大型工程时的不便,介绍了通过bash脚本和Makefile进行编译的解决方案。Makefile通过依赖管理和时间戳判断,实现高效编译更新,尤其是在内核编译等场景中。
2299

被折叠的 条评论
为什么被折叠?



