makefile的结构
通常是3部分,目标,依赖,指令
目标:通常是可执行文件,或者.so,在行首输入
依赖:头文件,cpp,库,.o文件。。。用冒号和目标文件分割然后换行
指令:如何生成(在下一行按tab然后输入)
示例:
生成一个名为libxlog.so的动态链接库(目标),需要../xlog/xlog.h和../xlog/xlog.cpp(依赖),使用" g++ ../xlog/xlog.cpp -fpic -shared -o libxlog.so"(指令)生成
libxlog.so:../xlog/xlog.h ../xlog/xlog.cpp
g++ ../xlog/xlog.cpp -fpic -shared -o libxlog.so
g++的使用可参考我的另一篇博客 https://blog.csdn.net/weixin_37453450/article/details/102577169
指定使用哪个makefile
一个项目里可能有多个makefile,如果想指定用哪个文件作为makefile,可以写如下指令,默认会找到
make -f xxxmakefile
makefile的五个部分
1 显式规则(明显指出的生成什么样文件,依赖什么等)
2 隐式规则(自动推导,如果依赖一个.o但是没有显式的指明怎么生成,这时候make指令会自动推导出使用什么样的命令,例如如果文件夹里只有一个main.cpp,执行指令make main,连makefile都没有也可以编译,这就是自动推导出的,但有可能不可靠,还是自己写比较好)
3 变量定义(可以定义一些变量,也有一些自己配置的变量,一些cpp共通的编译选项等就可以通过使用变量避免重复写)
4 文件引用(有可能写多个makefile,因为可能要跨平台开发,不同平台makefile就不太一样,不能是全都都重写一遍,还是要避免重写的)
5 注释(加#)
实例1——基本案例
考虑如下目录结构
根目录
xlog
xlog.h(声明了类XLog,仅有构造函数)
xlog.cpp(定义XLog的构造函数,输出一行)
testxlog
testxlog.cpp(在main函数中创建一个全局XLog对象,并输出一行字符串)
希望将xlog中的内容编译为动态库供testxlog调用,最终生成一个名为testxlog的可执行文件
下面在testxlog文件夹中编写makefile
makefile应如下编写
最终生成的目标文件为testxlog,它的生成依赖testxlog.o和libxlog.so
testxlog.o的生成依赖于testxlog.cpp,是将cpp文件编译成.o文件,编译时需要引入头文件xlog.h所在的路径
libxlog.so的生成依赖于xlog.h和xlog.cpp,将其编译成动态链接库
testxlog:testxlog.o libxlog.so
g++ testxlog.o -o testxlog -l xlog -L ./
testxlog.o:testxlog.cpp
g++ testxlog.cpp -c -I ../xlog
libxlog.so:../xlog/xlog.h ../xlog/xlog.cpp
g++ ../xlog/xlog.cpp -fpic -shared -o libxlog.so
make后成功,但是如果运行生成的testxlog文件会出错,因为在运行的时候依然要告诉它动态链接库所在的路径,否则只会在系统默认的路径(一般是类似/lib或者/usr/lib的路径)寻找.so文件
解决方法:
1 将.so文件复制到系统的目录下面,这时候可以直接运行
2 写个脚本export一下路径,然后运行生成的testxlog,例如叫run,脚本内容如下,编写好之后./run即可运行
#!/bin/sh
LD_LIBRARY_PATH=./
export LD_LIBRARY_PATH
./testxlog
3 临时的方法是直接在命令行里写一行export LD_LIBRARY_PATH=对应的路径,不过这个关了命令行也就失效了
实例2——优化(变量及清理)
考虑如下情景:
1 我们有很多个文件需要编译,并且都引用到了某个头文件,那么此时在每一条指令中都指定头文件的路径是很不好的,如果头文件被改名/移动到新路径会非常糟糕,因此可以定义一个变量指明头文件的路径
2 考虑到跨平台因素,有时候可能会使用其他编译器进行编译,每行修改g++就很麻烦
3 一些其他配置也可简写,例如-L指明动态链接库的所在路径等
综上,可以将之前的makefile简写为以下形式
定义了CC,INCLUDE,CTAG这样的变量,在引用的时候用$(变量名)的方式,并且变量名之间可嵌套
进一步考虑一个情景:
由于make比较智能,只对更新后的文件进行编译,有时可能有想要一次性把之前编译好的东西都删除然后重新生成的需求,这时候手动删除会很麻烦,可以写一个命令,例如叫clean,后面加上所需要执行的指令,同样是冒号和tab,之后在这个目录下执行make clean即可按照代码执行
CC=g++
INCLUDE=-I ../xlog
CTAG=-L ./ $(INCLUDE) -g
testxlog:testxlog.o libxlog.so
$(CC) testxlog.o -o testxlog -l xlog $(CTAG)
testxlog.o:testxlog.cpp
$(CC) testxlog.cpp -c $(CTAG)
libxlog.so:../xlog/xlog.h ../xlog/xlog.cpp
$(CC) ../xlog/xlog.cpp -fpic -shared -o libxlog.so
clean:
rm *.o -f
rm *.so -f
rm testxlog -f
实例3——优化(自动变量)
在之前的例子中,还有一些可优化的部分,这时可以借助自动变量来帮助我们,例如看这一部分
testxlog:testxlog.o libxlog.so
$(CC) testxlog.o -o testxlog -l xlog $(CTAG)
1 目标文件是testxlog,指令中还带有testxlog,这其实有些繁琐,而且如果之后想要改名还需要改两次,有没有办法直接指定生成的文件名和目标文件名一致?写法是使用$@表示目标
2 已经指定了依赖项,那么在命令中再把依赖项写出来也是一件繁琐的事情,因此可以改写,写法是使用$+表示依赖项
另外其实还可以进一步总结指令中的变量(这些技巧就和实例2一样了)
CC=g++
INCLUDE=-I ../xlog
CTAG=-L ./ $(INCLUDE) -g
OUT=testxlog
LIBS=-l xlog
SOCC=$(CC) $+ -fpic -shared -o $@
OCC=$(CC) $+ -c $(CTAG)
$(OUT):testxlog.o libxlog.so
$(CC) testxlog.o -o $@ $(LIBS) $(CTAG)
testxlog.o:testxlog.cpp
$(OCC)
libxlog.so:../xlog/xlog.h ../xlog/xlog.cpp
$(SOCC)
clean:
rm *.o -f
rm *.so -f
rm testxlog -f
主要的变动有:
1 将可替换成$+和$@的部分进行替换,注意在生成.so文件的命令中,实际编译只会用到cpp文件,虽然我们在命令中写了$+会将命令替换成形如 “g++ ......h .....cpp .....“的样子,但是g++会智能地判断.h文件并没有在编译中使用,所以这样写是安全的
2 将生成.so和生成.o文件的命令进行总结,写成变量OCC和SOCC,以后生成.o和.so可直接使用这两个变量
3 进一步总结变量
再考虑一个场景,假如我们现在在这个文件夹中新增了两个文件person.h和person.cpp,这两个文件的内容类似xlog.h和xlog.cpp,都是定义一个类,只定义构造函数,并且在构造函数中打印一行。计划是将这两个文件编译生成.o文件,然后供生成testxlog使用
下面可以看到修改makefile是很方便的。
我们做的修改无非是新增了两个文件,并且希望通过这两个文件生成.o文件再供testxlog调用
于是可以作以下修改:
1 新增一项,目标文件名为person.o,依赖项是person.cpp和person.h
(注意虽然单独写编译指令的时候我们一般是写g++ person.cpp -c -o person.o,只用到了person.cpp,看起来不依赖person.h,但实际上不是,如果没有在依赖项中写上.h的话,造成的后果就是如果.h文件被修改,而.cpp文件没被修改,那么在make的时候是不会新生成文件的)
2 使用之前定义的生成.o文件的指令OCC
3 (非必须步骤,这里做一个小优化)把最终生成的可执行文件依赖的.o文件都归纳成变量OBJ,修改一下原来的指令
CC=g++
INCLUDE=-I ../xlog
CTAG=-L ./ $(INCLUDE) -g
OUT=testxlog
LIBS=-l xlog
SOCC=$(CC) $+ -fpic -shared -o $@
OCC=$(CC) $+ -c $(CTAG)
OBJ=testxlog.o person.o
$(OUT):$(OBJ) libxlog.so
$(CC) $(OBJ) -o $@ $(LIBS) $(CTAG)
testxlog.o:testxlog.cpp
$(OCC)
person.o:person.cpp person.h
$(OCC)
libxlog.so:../xlog/xlog.h ../xlog/xlog.cpp
$(SOCC)
clean:
rm *.o -f
rm *.so -f
rm testxlog -f
实例4——优化(安装与卸载)
考虑实例1基本案例中,make成功后./testxlog并不能直接运行,当时给出的解决方案之一是写一个脚本run,之后执行这个run就可以运行了。
但其实有时候我们希望这个可执行文件编译好了之后,用户输入testxlog就能直接运行,那么这就涉及到安装操作了,要把testxlog安装到系统中
故新版本的makefile可添加如下两项
1 install表示安装,它的依赖项是编译生成的目标文件,也就是testxlog,将这个文件复制到/usr/bin中就可以直接使用(我登陆的用户不是root所以我写了sudo)
为了能让系统自动找到它使用的.so,需要把我们生成的.so都复制到/usr/lib中
执行make install,这样再输入testxlog就可以直接运行了
2 uninstall表示卸载,我们只要把该删除的文件从刚才的目录里移除即可
install:$(OUT)
sudo cp *.so /usr/lib
sudo cp $(OUT) /usr/bin
uninstall:
sudo rm /usr/libxlog.so -f
sudo rm /usr/bin/$(OUT) -f
在我的虚拟机上运行结果如下