先导问题
在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