ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[原文地址](https://www.jianshu.com/p/442e71755643) ## 简介 ` `makefile文件用于管理和组织代码工程的编译和链接,其不是可执行文件,其被make工具解析并完成相关动作。 ` `Makefile文件里描述的是编译的时候依赖关系,宏定义,编译参数,链接生成的程序名字等等。 ` `等Makefile文件写好后,需要用make程序来执行Makefile,所以需要先安装make程序。 ``` sudo apt install make -y ``` ## 基本语法 ### 文件包含 ` `语法:include 文件名 ` `作用:将其它makefile文件包含进来,组成一个更大的makefile文件,这样有利于makefile模块化编程。通常我们将一些配置选项分开成一个独立的makefile文件,这样有利于makefile文件的管理,或将模块代码的依赖关系和需要编译的文件信息独自写到一个 makefile文件中,最终通过include命令形成一个顶层makefile文件来完成整个工程代码的编译和链接。 ### 变量定义 ` `语法:变量名 := 变量值 ` `在makefile中,经常先定义一个变量,然后往该变量中追加新的值(通过+=符号),比如先定义一个C_SRCS变量(该值可以为空),然后将代码文件test1.c和test2.c添加到C_SRCS中,其代码如下所示: ``` C_SRCS := C_SRCS += test1.c test2.c ``` ` `在makefile中有一类特殊的变量,其名称为 **自动变量**,自动变量的值会依据规则中的target 和 prerequisites自动计算其值,自动变量一般以开头$为起始,下面将列出一些常见的自动变量: ``` $@ 为规则中的target名称。 $< 为规则中第一个prerequisite名称 ``` ### 内置命令 ` `Makefile中内置了一些常用的命令。 * 有字符串处理函数 ``` 1. subst 2. patsubst 3. strip 4. findstring 5. filter 6. filter-out 7. sort 8. word 9. wordlist 10. words 11. firstword 12. lastword ``` * 文件名处理函数 ``` 1. dir 2. notdir 3. suffix 4. basename 5. addsuffix 6. addprefix 7. join 8. wildcard 9. realpath 10. abspath ``` * 条件处理函数`if`; * 循环处理函数`foreach`等 **以下是一些常用的函数:** * [ ] **wildcard 函数**:其语法为$(wildcard pattern),pattern为匹配的模式,比如$(wildcard %.c) 为查找当前路径下面文件名以.c结尾的文件。 * [ ] foreach 函数:其语法为$(foreach var,list,text),每循环一次var从list中按顺序取值一个,然后执行一次text代码并记录结果,最终返回所用text代码运行的结果。比如 dirs := C_DIR S_DIR file := $(foreach dir,$(dirs),$(wildcard $(dir)/*)) 将C_DIR和S_DIR文件夹下面的所有文件添加到file变量中。 * [ ] **dir 函数**:其语法为$(dir names…),用于获取names中文件夹路径,比如 $(dir src/foo.c hacks) 将获得文件夹路径 src/ ./ * [ ] **notdir 函数**:其语法为$(notdir names…),用于获取names中除去路径的信息,比如 $(notdir src/foo.c hacks) 将获得文件信息 foo.c hacks * [ ] **basename 函数**:其语法为$(basename names…),用于获取names中除去后缀信息,比如 $(basename src/foo.c src-1.0/bar hacks) 将获得信息 src/foo src-1.0/bar hacks * [ ] **addsuffix 函数**:其语法为$(addsuffix suffix,names…),用于往names中添加后缀信息suffix,比如 $(addsuffix .c,foo bar) 将获得文件信息 foo.c bar.c * [ ] **addprefix 函数**:其语法为$(addprefix prefix,names…),用于往names中添加前缀信息prefix,比如 $(addprefix src/,foo bar) 将获得信息src/foo src/bar patsubst 函数:其语法为$(patsubst pattern,replacement,text),根据 pattern信息将text替换成replacement,比如 objects = foo.o bar.o baz.o files = $(patsubst %.o,%.c,$( objects)) 将获得信息 foo.c bar.c baz.c 其可以简单写成 objects = foo.o bar.o baz.o files = $(objects:.o=.c) ## 规则定义 ` `规则是makefile中最重要的概念,其告诉make 目标文件的依赖关系,以及如何生成及更新这些目标文件。在makefile文件规则有2种,一种是显式规则,另一种是隐式规则。 ` `显式规则用于说明 何时及如何重新生成目标,其列出了目标依赖的文件信息,并通过调用命令来创建或更新目标,其语法一般为: ``` targets : prerequisites         recipe         … ``` ` `targets为要生成或更新的目标,prerequisites为目标依赖的关系,recipe为生成目标的命令,一个规则可以有多条recipe,比如 ``` foo.o : foo.c defs.h         cc -c -g foo.c 其中foo.o为target,foo.c defs.h 为prerequisites,cc -c -g foo.c为recipe。 ``` ` `隐式规则用于说明 何时及如何来重新生成一类目标文件根据其名称,其描述了目标是如何依赖于名称相似的文件(一般来说除去后缀信息,其目标与依赖文件的名称是一样的),并调用命令来创建或更新目标,比如 ``` %.o : %.c         $(CC) -c $(CFLAGS) $< -o $@ ``` ` `  这个隐式规则说明了.o的目标文件依赖于同名的.c文件,其中$< 及 $@为自动变量,$<为第一个prerequisites条件,也就是 目标名称.c,$@为目标,也就是 目标名称.o。 ` `在makefile中,我们通常要编写3种隐式规则,第1种为代码链接规则,第2种为源代码编译规则,第3种为汇编代码编译规则。 ## 文件搜索路径设置 ` `  Make命令默认会在当前路径中搜索prerequisites中的文件,比如头文件,但我们在写程序时,经常将头文件和源文件隔开放在不同的文件夹下,这种该怎么处理呢?1、我们可以通过VPATH变量来解决,2、我们可以通过vpath指令来解决。 **VPATH变量** ` `VPATH变量为所有的prerequisites指定文件路径,路径之间可以通过 :或空格隔开,比如 ``` **VPATH变量** VPATH变量为所有的prerequisites指定文件路径,路径之间可以通过 :或空格隔开,比如 ``` **vpath指令** ` `vpath指令的作用与变量VPATH的作用差不多,但vpath有更多的灵活性,其语法为: vpath pattern directories ` `pattern为需要查找的文件匹配模式信息,directories为要查找的文件路径,比如 ``` vpath %.h ../headers ``` ` `其代表在上一层文件夹headers中查找 .h头文件信息。 ## 实例说明 ` `看个最简单的Makefile的例子,把下面的内容保存到文件Makefile里: ~~~ # 最简单的Makefile hello: @echo "hello makefile" ~~~ **注意**: 1. makefile文件中命令行的行首不能用空格,而要用Tab键 2. makefile文件中的字符格式有要求,必须是英文字符,不能有中文字符。 ` `然后make一下,如果make的时候当前目录有叫Makefile的文件,默认执行这个Makefile,如果需要指定其他文件名,需要用make -f。 ![](https://img.kancloud.cn/3c/1f/3c1fd36193b85d705900aa714def303e_599x206.png) ` `上面Makefile文件里的hello表示目标,这个目标的执行动作是打印一行字符串"hello Makefile"。 我们可以有多个目标,修改Makefile,加入目标hello2: ``` # 最简单的Makefile hello: @echo "hello makefile" hello2: @echo "hello linux" ``` ![](https://img.kancloud.cn/a1/94/a1949f24fba3de59fd20c64cf76ac794_569x93.png) ` `每个目标可以有相应的依赖项,看下面的例子 ``` # 最简单的Makefile hello: ready @echo "hello makefile" hello2: @echo "hello linux" ready: @echo "i am ready" ``` ![](https://img.kancloud.cn/fd/3d/fd3d167c6fb0c05c3d1c776b1bf25bba_551x130.png) ` `目标可以执行一个空的动作,修改Makefile为下面的内容: ``` # 最简单的Makefile all :hello hello2 hello: ready @echo "hello makefile" hello2: @echo "hello linux" ready: @echo "i am ready" ``` ![](https://img.kancloud.cn/0e/d7/0ed738cccebb2a29b8aa4f2eca5c73c9_495x94.png) ` `们可以看出Makefile实际上非常简单,就是一个个目标与依赖结合起来的有序的解析过程。 ` `了解了Makefile的执行过程,我们只要把目标的动作改为编译和链接就可以了,下面我们看执行一个编译动作的命令。 ` `linux下编译c++代码用g++,c++代码的文件后缀为.cpp或.cc,编译c代码用gcc,c代码的文件后缀为.c。 链接程序也用g++。 ** g++的常用参数说明** * -c 表示编译代码 * -o 表示指定生成的文件名 * -g 表示编译时候加入调试符号信息,debug时候需要这些信息 * -I (大写i)表示设置头文件路径,-I./表示头文件路径为./ * -Wall 表示输出所有警告信息 * -D 表示设置宏定义,-DDEBUG表示编译debug版本,-DNDEBUG表示编译release版本 * -O 表示编译时候的优化级别,有4个级别-O0,-O1,-O2 -O3,-O0表示不优化,-O3表示最高优化级别 * -shared 表示生成动态库 * -L 指定库路径,如-L.表示库路径为当前目录 * -l (小写L)指定库名,如-lc表示引用libc.so ` `新装的ubuntu可能没有g++,可以先在bash里输入g++ -v试一下,如果没有安装会提示先安装,这时候跟着提示apt install g++就可以了。 安装gcc也一样。 ` `看一下最简单的编译.cpp代码的Makefile怎么写 ~~~c 1 2 main : main.o 3 g++ -o $@ $^ 4 5 .cpp.o: 6 g++ -c -o $@ $< ~~~ ` `解释一下上面的Makefile: * 第2行main表示一个目标名为main,依赖为main.o,.o是代码编译后生成的obj文件 * 第3行表示目标main的执行动作, 就是执行链接程序的命令。 * 第5行.cpp.o表示这是一个目标,作用是把.cpp文件编译为.o文件 * 第6行是该目标的具体编译命令,-c表示编译, -o @表示指定生成文件名 * 看下面的测试: ![](https://img.kancloud.cn/94/a1/94a18d80ff4dd88f053eba858a12c144_536x131.png) > 小知识 Makefile里的echo和rm前面带了@表示不要打印执行该命令时候命令本身的输出,比如 rm -rf \*.o main在执行的时候会输出这句命令"rm -rf \*.o main" 如果把rm改为@rm, 再make clean的时候就不会输出"rm -rf \*.o main"命令本身了。 ` `看到这里基本上再回头看开完整Makefile就能看懂了,需要重点说的是编译的参数,头文件路径和库引用的写法,下面我们一步一步写上注释 ``` ~~~c # 这句是链接时候的命令,在g++前面加入了@echo linking $@ # 这样在链接时候就会先输出linking xxx, # 这行直接写g++也是没有任何问题的 LINK = @echo linking $@ && g++ # 编译c++代码时候用的,一样会显示compiling xxx GCC = @echo compiling $@ && g++ # 编译c代码时候用gcc GC = @echo compiling $@ && gcc # 生成静态库时候用ar命令 AR = @echo generating static library $@ && ar crv # 这是编译时候的参数设置,下面-g表示编译时候加入调试信息, # -DDEBUG表示编译debug版本 # -W -Wall表示输出所有警告 # -fPIC是生成dll时候用的 FLAGS = -g -DDEBUG -W -Wall -fPIC GCCFLAGS = DEFINES = # 这里指出头文件的目录为./ HEADER = -I./ # 需要引用的库文件 LIBS = LINKFLAGS = # 更多头文件可以用 += 加进来 #HEADER += -I./ # 如果需要librt.so,就把-lrt加进来 #LIBS += -lrt # 如果需要写多线程程序,就需要引用-pthread #LIBS += -pthread # 这里是主要需要修改的地方,每一个.c或.cpp对应于这里的一项, # 如main.cpp对应于main.o # 多个.o可以用空格分开,也可以像下面这样用"\"换行,然后写在新一行 OBJECT := main.o \ # 下面举个例子,这里编译表示两个代码文件 # OBJECT := main.o \ # other.o BIN_PATH = ./ TARGET = main # 链接用的,可以看到后面加了$(LIBS),因为只有链接时候才需要引用库文件 $(TARGET) : $(OBJECT) $(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS) # 编译cpp代码用这个目标 .cpp.o: $(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $< # 编译c代码用这个 .c.o: $(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $< # 把生成的$(TARGET)拷贝到$(BIN_PATH)下 install: $(TARGET) cp $(TARGET) $(BIN_PATH) clean: rm -rf $(TARGET) *.o *.so *.a ~~~ ``` ## 生成动态链接库 ` `linux动态链接库的后缀为 .so,生成动态链接库也比windows要方便的多,只要在link的时候加上-shared参数就可以了,下面我们看个例子。 先来看一下完整测试的目录结构,最上层目录是test_makefile_so ~~~c test_makefile_so ├── Makefile #这个是总的Makefile,管理所有子目录的Makefile ├── bin │ ├── libfun.so │ └── main └── src ├── Makefile ├── fun │ ├── Makefile │ ├── fun.cpp │ └── fun.h └── main.cpp 3 directories, 8 files ~~~ ` `main.cpp的内容 ~~~c #include <stdio.h> #include "fun.h" int main(int, char**){ printf("hello so\n"); int a = 1; int b = 2; int c = sum( a, b ); printf( "sum: %d + %d = %d\n", a, b, c ); return 0; } ~~~ ` `src/Makefile的内容 ~~~c LINK = @echo linking $@ && g++ GCC = @echo compiling $@ && g++ GC = @echo compiling $@ && gcc AR = @echo generating static library $@ && ar crv FLAGS = -g -DDEBUG -W -Wall -fPIC GCCFLAGS = DEFINES = HEADER = -I./ LIBS = LINKFLAGS = HEADER += -I./fun #LIBS += -lrt #LIBS += -pthread #这里表示链接的时候从bin目录下找libfun.so LIBS += -L../bin -lfun OBJECT := main.o \ #这里加了bin的相对路径,编完的main会install到bin目录下 BIN_PATH = ../bin/ TARGET = main $(TARGET) : $(OBJECT) $(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS) .cpp.o: $(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $< .c.o: $(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $< install: $(TARGET) cp $(TARGET) $(BIN_PATH) clean: rm -rf $(TARGET) *.o *.so ~~~ ` `fun.h的内容 ~~~c #ifndef __fun_h__ #define __fun_h__ int sum(int a, int b); #endif//__fun_h__ ~~~ ` `fun.cpp的内容 ~~~c #include "fun.h" int sum(int a, int b){ return a+b; } ~~~ ` `fun/Makefile的内容 ~~~c LINK = @echo linking $@ && g++ GCC = @echo compiling $@ && g++ GC = @echo compiling $@ && gcc FLAGS = -g -DDEBUG -W -Wall -fPIC GCCFLAGS = DEFINES = HEADER = -I./ LIBS = #修改的地方1: 这里加了-shared,表示生成动态库 LINKFLAGS = -shared #HEADER += -I./ #LIBS += -lrt #LIBS += -pthread OBJECT := fun.o \ #修改的地方2: 表示编译fun.cpp #修改的地方3: 指出了bin的相对路径 BIN_PATH = ../../bin/ #修改的地方3,生成的文件名叫libfun.so,动态库一般以lib为前缀 TARGET = libfun.so $(TARGET) : $(OBJECT) $(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS) .cpp.o: $(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $< .c.o: $(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $< install: $(TARGET) cp $(TARGET) $(BIN_PATH) clean: rm -rf $(TARGET) *.o *.so ~~~ 先编译动态库libfun.so,因为main程序要依赖libfun.so。 在fun目录下先make install一下,会生成libfun.so,并且自动拷贝到bin目录下 ~~~c bash$ cd fun bash$ make install compiling fun.o linking libfun.so cp libfun.so ../../bin/ ~~~ 切换到src目录下,再编译main程序 ~~~c bash$ make clean; make install rm -rf main *.o *.so compiling main.o linking main cp main ../bin/ ~~~ 然后切到bin目录下,运行main程序 ~~~c bash$ cd ../bin # bin目录下现在有两个文件,libfun.so和main bash$ ls libfun.so main # 运行main程序 bash$ ./main mac:bin tpf$ ./main hello so sum: 1 + 2 = 3 #这行是由libfun.so里的函数执行的 ~~~