-------------------------------------------------------------------------------
目标 : 依赖
命令
...
-------------------------------------------------------------------------------
默认只执行第一个目标,再寻找依赖(这个依赖可以是其他的目标),最后执行命令。
例如:下面的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 就是直接强行删除,不作任何提示的意思
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
// // includeinclude "SoTest.h" void SoTest::func1(){ printf("func1n"); }; void SoTest::func2(){ printf("func2n"); };
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#include "SoTest.h" class Test:public SoTest{ public: void func2(){ printf("test-func2n"); } void func3(){ printf("test-func3n"); } }; 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)六: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 此处省略