一:Make基本介绍
(1)什么是Makefile
(2)什么是cmake
(3)重点
二:基本语法和常用命令
(1)基本语法:
-------------------------------------------------------------------------------
目标 : 依赖
命令
...
-------------------------------------------------------------------------------
默认只执行第一个目标,再寻找依赖(这个依赖可以是其他的目标),最后执行命令。
例如:下面的makefile文件,命令行输入make,就会自动寻找makefile这个文件,执行第一个目标a,输出hello world和ls的命令和结果。如果不想在控制台看到命令,可以用@ls ./这种方法。
在命令行输入make clean,执行指定目标,这种方法以后是为了方便清除已有的输出文件。
a:
echo "hello world"
ls ./
clean:
echo "hello clean"
(2)编译流程详解
编译流程写在了上一章。注意以下代码,这种分开写的模式,可以只编译对应有修改的部分。
# 这样好吗 ?
# 这样不好,这样不讲武德
# 第一次编译两小时
# 第二次编译五分钟
# 这样分开来写,保证只编译有改动的代码
#calc:
# gcc add.cpp sub.cpp multi.cpp calc.cpp -o calc
calc:add.o sub.o multi.o
gcc add.o sub.o multi.o calc.cpp -o calc
add.o:add.cpp
gcc -c add.cpp -o add.o
sub.o:sub.cpp
gcc -c sub.cpp -o sub.o
multi.o:multi.cpp
gcc -c multi.cpp -o multi.o
(3)变量详解
TARGET=calc
OBJ=add.o sub.o multi.o calc.o
$(calc) 表示变量的替换操作
OBJ=add.o sub.o multi.o calc.o
TARGET=calc
$(TARGET):$(OBJ)
gcc $(OBJ) -o $(TARGET)
add.o:add.cpp
gcc -c add.cpp -o add.o
sub.o:sub.cpp
gcc -c sub.cpp -o sub.o
multi.o:multi.cpp
gcc -c multi.cpp -o multi.o
calc.o:calc.cpp
gcc -c calc.cpp -o calc.o
clean:
rm -rf *.o calc
(4)几种特殊变量
一般常用的包括$^表示当前的依赖,$@当前目标文件的完整名称
$*当前不包括扩展名的目标文件,和$@的意思相反。
linux下的删除命令 rm -rf 强制删除文件包括递归目录。
-r 就是向下递归,不管有多少级目录,一并删除
-f 就是直接强行删除,不作任何提示的意思
命令行显示中,有波浪号:CLi@PC-CLI-1 ~/develop/docbook
其中,此处的波浪号~,含义是:
表示用户根目录,等价于$HOME
OBJ=add.o sub.o multi.o calc.o
TARGET=calc
$(TARGET):$(OBJ)
gcc $^ -o $@
add.o:add.cpp
gcc -c $^ -o $@
sub.o:sub.cpp
gcc -c $^ -o $@ // $^就表示这里不重复的目标文件sub.cpp,$@就表示目标文件的完整名称sub.o
multi.o:multi.cpp
gcc -c $^ -o $@
calc.o:calc.cpp
gcc -c $^ -o $@
clean:
rm -rf *.o $(TARGET)
OBJ=add.o sub.o multi.o calc.o
TARGET=calc
$(TARGET):$(OBJ)
$(CXX) $^ -o $@
add.o:add.cpp
$(CXX) -c $^ -o $@
sub.o:sub.cpp
$(CXX) -c $^ -o $@
multi.o:multi.cpp
$(CXX) -c $^ -o $@
calc.o:calc.cpp
$(CXX) -c $^ -o $@
clean:
$(RM) *.o $(TARGET)
show:
echo $(AS)
echo $(CC)
echo $(CPP)
echo $(CXX)
echo $(RM)
三:伪目标和模式匹配
(1)伪目标
为什么要声明伪目标?
答:如果当前文件夹里有个clean文件,那么使用make clean命令就会自动寻找当前文件夹的clean文件,不执行当前makefile的目标了。
(2)模式匹配
关于文件查找:
这两个可以一起用,表示先获取当前目录下的所有的.cpp文件,再将.cpp文件替换成.o的文件名,例如如下显示:
关于模板替换:
%目标:%依赖:目标和依赖相同的部分可用%来匹配,例如下面代码中
OBJ=$(patsubst %.cpp,%.o,$(wildcard ./*.cpp)) // 找到当前目录下的cpp文件,并替换成.o,显示为add.o sub.o multi.o
$(TARGET):$(OBJ)
...
表示自动寻找OBJ的依赖add.o sub.o multi.o,它是下面的目标,根据%目标:%依赖,作出替换即可,替换的结果就是我们这页第一个代码框的代码。
#伪目标 .PHONY:clean
#声明目标为伪目标之后,makefile将不会判断目标是否存在或该目标是否需要更新
.PHONY:clean show
#模式匹配 %目标:%依赖
#目标和依赖相同部份,可用%来通配
#%.o:%.cpp
#wildcard $(wildcard ./*.cpp) 获取当前目录下所有的.cpp文件
#patsubst $(patsubst %.cpp,%.o,./*.cpp) 将对应的cpp 文件名替换成 .o 文件名
#OBJ= sub.o multi.o calc.o add.o
OBJ=$(patsubst %.cpp,%.o,$(wildcard ./*.cpp))
TARGET=calc
$(TARGET):$(OBJ)
$(CXX) $^ -o $@
%.o:%.cpp
$(CXX) -c $^ -o $@
clean:
$(RM) *.o $(TARGET)
show:
#echo $(AS)
#echo $(CC)
#echo $(CPP)
#echo $(CXX)
#echo $(RM)
#echo $(wildcard ./*.cpp)
#echo $(patsubst %.cpp,%.o,$(wildcard ./*.cpp))
echo $(OBJ)
四:MakeFile编译动态链接库
如何编译出动态链接库呢?
源文件:SoTest.cpp,SoTest.h 使用如下命令编译完又生成了libSoTest.so
SoTest.cpp
//
//
include<iostream>
include "SoTest.h"
void SoTest::func1(){
printf("func1\n");
};
void SoTest::func2(){
printf("func2\n");
};
SoTest.h
ifndef INC_0303_SOTEST_H
define INC_0303_SOTEST_H
class SoTest {
public:
void func1();
virtual void func2();
virtual void func3()=0;
};
endif //INC_0303_SOTEST_H
g++ -shared -fPIC SoTest.cpp -o libSoTest.so
---------------------------------------------------------------------------------------------------------------------------------
测试代码Test.c
#include <iostream>
#include "SoTest.h"
class Test:public SoTest{
public:
void func2(){
printf("test-func2\n");
}
void func3(){
printf("test-func3\n");
}
};
int main() {
Test t1;
t1.func1();
t1.func2();
t1.func3();
return 0;
}
使用如下命令编译(将libSoTest.so加载到test.cpp中),其中lSoTest表示指定动态库libSoTest.so,... 发布只需要.so和.h文件
g++ -lSoTest -L./ test.cpp -o test
// 输出
./test
输出结果是 ,可以看到func1函数结果也被加载进去了。
注:以上都是在同一个文件夹里。
为了发布会编译出.so到单独的文件夹,这时候可以编译完成,但是运行的时候会找不到。
这是因为运行的时候会在当前文件夹,系统文件夹里找,因为.so被我们放到自定义的文件夹里了,所以运行会找不到。
现在有两种解决方案:
(1)将.so文件放到系统库里
(2)将.so文件目录设置到环境中去,在命令行里输入具体路径如下所示:
DYLD_LIBRARY_PATH=./001 // .so文件所在的路径,这里用的是相对路径,即当前运行文件的相对路径
export DYLD_LIBRARY_PATH
接下来,就可以写makefile一步完成了(用的第一种方法---将.so文件放到系统库里):
test:libSoTest.so
$(CXX) -lSoTest -L./ test.cpp -o test
cp libSoTest.so /usr/local/lib/
libSoTest.so:
$(CXX) -fPIC -shared SoTest.cpp -o libSoTest.so
clean:
$(RM) *.so test
五:MakeFile编译静态链接库
Windows的静态链接库是.lib文件,Linux下是.a文件。静态库中保存着代码中所需要的函数执行过程等,只不过保存的形式是一种二进制文件。(PS:也可以自己制作静态库,例如在Linux中静态库的制作和使用_零下10度C_zjw的博客-CSDN博客_linux静态库的生成与使用)
可以使用如下代码编译用来生成libaTest.a:(aTest.app--->libaTest.a),我们手动放到./002文件夹中。
g++ -c aTest.cpp -o aTest.o
ar -r libaTest.a aTest.o
然后我们使用./001和./002文件夹里的libaTest.a和libSoTest.so重新编译进入main.cpp中.
以下代码会在这两个路径中分别找到对应的.so和.a文件,并加载到main.cpp里,然后编译成main的可执行文件。
g++ -lSoTest -L./001 -laTest -L./002 main.cpp -o main
// 测试
./main
我们可以写makefile来加载了:
TARGET=main
LDFLAGS=-L./001 -L./002
LIBS=-lSoTest -laTest
$(TARGET):
$(CXX) $(LDFLAGS) $(LIBS) main.cpp -o $(TARGET)
clean:
$(RM) $(TARGET)
注 -I./inc 之类的表示编译头文件
六:makefile中通用部份做公共头文件
如下的代码所示,为设置的makefile公共头文件。这里需要注意的是OBJ:=和OBJ=的区别;
因此如果需要二次调用变量,必须使用OBJ:=,不然就会null。
#公共
SOURCE=$(wildcard ./*.cpp ./*.c)
OBJ=$(patsubst %.cpp,%.o,$(SOURCE))
OBJ:=$(patsubst %.c,%.o,$(OBJ))
.PHONY:clean
$(TARGET):$(OBJ)
$(CXX) $^ -o $@
clean:
$(RM) $(TARGET) $(OBJ)
show:
echo $(SOURCE)
echo $(OBJ)
如果要在/001的代码中使用它编译c.cpp,只需要写如下的makefile,把上面的makefile引入进来就可以了。
TARGET=c
include ../makefile
七:makefile中调用shell
shell命令就是编辑器的相关命令,可以直接使用echo shell XXX方法调用。
FILE=abc
A:=$(shell ls ../)
B:=$(shell pwd)
C :=$(shell if [ ! -f $(FILE) ];then touch $(FILE);fi;)
a:
echo $(A)
echo $(B)
echo $(C)
clean:
$(RM) $(FILE)
八:makefile中的嵌套调用
如果有两个目录./001和./002都有makefile文件,我想再写一个makefile,让它编译这两个目录的makefile文件。可以用如下的写法。
make -C ./001 就会自动寻找./001下的makefile文件进行编译。
#-C 指定工作目录
#$$表示展开shell 中的变量
.PHONY:001 002 clean
DIR=001 002
all:$(DIR)
$(DIR):
make -C $@
clean:
echo $(shell for dir in $(DIR);do make -C $$dir clean;done)
all-v1:
make -C ./001
make -C ./002
clean-v1:
make -C ./001 clean
make -C ./002 clean
九:makefile中的条件判断和循环
(1)条件判断
示例代码如下:
A:=321123
RS1:=
RS2:=
RS3:=
RS4:=
ifeq ($(A),123)
RS1:=123
else
ifeq ($(A),321)
RS1:=321
else
RS1:=no-123-321
endif
endif
ifndef A
RS3:=yes
else
RS3:=no
endif
ifndef FLAG
FLAG:=default-flag
endif
all:
echo $(RS1)
echo $(RS3)
echo flag=$(FLAG)
(2)循环
#循环
#makefile 中只有一个循环 foreach,只支持 GNU Make ,其它平台的make ,可以用shell 中的循环来实现
#可以在循环中逐个的修改值
TARGET:=a b c d
FILE:=$(foreach v, $(TARGET),$v.txt)
all:
#echo $(TARGET)
#echo $(foreach v, $(TARGET),$v)
#touch $(TARGET)
#touch $(foreach v, $(TARGET),$v.txt)
#mkdir $(foreach v, $(TARGET),$v_txt)
#echo $(FILE)
for v in $(TARGET);\
do touch $$v.txt;\
done;\
$(shell for v in $(TARGET); do touch $$v-txt;done)
clean:
$(RM) -rf $(TARGET) *txt
十:自定义函数的实现
自定义函数没有返回值。
示例代码:
#自定义函数,不是真正的函数,本质上是多行命令,放在外面了
#这里的自定义函数,没有返回值
LS1=$(call FUNC3)
define FUNC3
echo $(shell ls)
$(RM) a b c d
endef
LS2:=$(call FUNC3)
define FUNC2
return 123
endef
default:
# echo $(call FUNC2)
# echo return 123
$(call FUNC3)
$(LS2)
$(LS1)
A:=123
B:=$(A)
define FUNC1
echo $(0)
# echo $(1) $(2)
# echo func1
# echo $(A) $(B)
endef
A:=456
all:
$(call FUNC1,abc,def,$(A))
# echo $(A) $(B)
A:=789
十一:Make install 此处省略
十二:常用命令:
(1).SUFFIXES:
.SUFFIXES: .g .o
LIBS=gao.o
all: $(LIBS)
.g.o:
@echo "in .g.o"
.SUFFIXES 和 .a.b 格式可以相互配合, 和 %b:%a 是无法配合的。结果是,in .g.o
.SUFFIXES: 会删除所有已知的后缀。这样做是为了: 默认后缀不会干扰您的特殊后缀。 不同的make程序具有不兼容的后缀列表和隐式规则