可以试想一下,有一个上百个文件的代码构成的项目,如果其中只有一个或少数几个文件进行了修改,如果再从头到尾将每一个文件都重新编译是个比较繁琐的过程。
为此,引入了 Make 工程管理器的概念,工程管理器指管理较多的文件,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入Makefile 文件来执行大量的编译工作。
target: dependency_files //目标项:依赖项
< TAB >command //必须以 tab 开头, command 编译命令
注意点:在写command 命令行的时候,必须要在前面按 TAB 键。
例如,有Makefile 文件,内容如下:
使用make 编译:
对于该Makefile 文件,程序 make 处理过程如下:
·make 程序首先读到第 1 行的目标文件 main.exe 和它的两个依赖文件 main.o 和 func.o;然后比较文件main.exe 和 main.o/func.o 的产生时间,如果 main.exe 比 main.o/func.o 旧的话,则执行第 2 条命令,以产生目标文件 main.exe。
·在执行第2 行的命令前,它首先会查看 makefile 中的其他定义,看有没有以第 1 行 main.o 和 func.o 为目标文件的依赖文件,如果有的话,继续按照(1)、 (2)的方式匹配下去。
·根据(2)的匹配过程, make 程序发现第 3 行有目标文件 main.o 依赖于 main.cpp,则比较目 main.o 与它的依赖文件 main.cpp 的文件新旧,如果 main.o 比 main.cpp 旧,则执行第 4 行的命令以产生目标文件 main.o。
·在执行第4 条命令时,main.cpp 在文件 makefile 不再有依赖文件的定义,make 程序不再继续往下匹配,而是执行第 4 条命令,产生目标文件 main.o
·目标文件func.o 按照上面的同样方式判断产生。
·执行(3)、 (4)产生完 main.o 和 func.o 以后,则第 2 行的命令可以顺利地执行了,最终产生了第 1 行的目标文件 main.exe。
归结:
1)目标文件不存在,执行命令
2)文件已经更新了,执行命令
.PHONY 是makefile 文件的关键字,表示它后面列表中的目标均为伪目标。
.PHONY:b
b:
echo ‘b’ #通常用@echo “hello”
伪目标通常用在清理文件、强制重新编译等情况下。一般伪目标没有时间的检查,一旦指明了就会执行。
示例1:main.c 函数,func.c 函数为前面计算+,-,*,/运算的程序。
#vim Makefile #系统默认的文件名为 Makefile
在VIM编辑器Makefile文件内容如下:
#表示要想生成 main.exe 文件,要依赖于 main.o 和 func.o 文件
#如果 main.o,func.o 已经存在了,就链接成 main.exe
main.exe:main.o func.o
gcc -o main.exe main.o func.o
#表示 main.o 文件依赖于 main.c 文件
#编译 main.c,默认生成 main.o。可写为: gcc –c main.c –o main.o
main.o:main.c
gcc -c main.c
#表示 func.o 文件依赖于 func.c 文件
#如果 func.c 存在,则编译 func.c ,生成 func.o
func.o:func.c
gcc -c func.c
#表示后面的是伪目标, 通常用在清理文件、强制重新编译等情况下
#先执行clean字段,再执行main.exe字段
.PHONY:rebuild clean
rebuild:clean main.exe
#最后删除.o 和.exe 的文件
clean:
rm -rf main.o func.o main.exe
1)按ESC 键之后, :wq 保存退出(vim退出方式)。
注意:和shell 编程相同,在 Makefile 中,也用“ #”号表示注释。
2)再执行下面的命令:
#make //直接 make,即从默认文件名( Makefile)的第一行开始执行
#make clean //表示执行 clean: 开始的命令段
#make func.o //表示执行 func.o: 开始的命令段
#make rebuild //则先执行清除,再重新编译连接
如果不用系统默认的文件名Makefile,而是用户随便起的一个名字。
如:
#vi Makefile11
则make 后面必须要加上 -f Makefile11。
如:
#make –f Makefile11 clean //表示执行 clean: 开始的命令段
#make –f Makefile11 main.exe //表示执行 main.exe: 开始的命令段
1)变量
随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写makefile 文件,将会使 makefile也变得复杂而难于维护。
通过make 支持的变量定义、规则和内置函数,可以写出通用性较强的 makefile 文件,使得同一个 makefile 文件能够适应不能的项目。
变量:用来代替一个文本字符串
定义变量的2 种方法:
变量名=变量值递规变量展开(几个变量共享一个值) //不常用
变量名:=变量值简单变量展开(类似于 C++的赋值) //通常采用这种形式
使用变量的一般方法:
$(变量名)=??? //赋值
???=$(变量名) //引用
例:将以前的那个可以写为:
OBJS:=main.o func.o //相当于 main.o func.o
EXE:=main.exe
$(EXE):$(OBJS)
g++ -o $(EXE) $(OBJS)
main.o:main.cpp
g++ -c main.cpp –o main.o
func.o:func.cpp
g++ -c func.cpp –o func.o
clean:
rm –rf $(EXE) $(OBJS)
变量分为:用户自定义变量,自动变量,预定义变量,环境变量。
上面的例子就是用户自定义变量。
(1)自动变量
指在使用的时候,自动用特定的值替换。
常用的如表2.1:
变量 |
说明 |
$@ |
当前规则的目标文件 |
$< |
当前规则的第一个依赖文件 |
$^ |
当前规则的所有依赖文件,以逗号分隔 |
$? |
规则中日期新于目标文件的所有相关文件列表,逗号分隔 |
$(@D) |
目标文件的目录名部分 |
$(@F) |
目标文件的文件名部分 |
示例1:用自动变量。
OBJS:= main.o func.o //$(OBJS)相当于 main.o func.o (原样替换)
EXE:= main.exe
CFLAGS:= -Wall -O2 -fpic #显示所有警告信息,优化级别为 2
LIBFUNCSO:= libfunc.so #动态库
LIBFUNCA:= libfunc.a #静态库
$(EXE):$(OBJS) $(LIBFUNCSO) $(LIBFUNCA)
gcc -o $@ $< -L./ -lfunc
main.o: main.c
gcc -c $(CFLAGS) $< -o $@
func.o: func.c
gcc -c $(CFLAGS) $< -o $@
libfunc.a: func.o
ar rcsv $@ $<
libfunc.so: func.o
gcc -shared -o $@ $<
cp -f $@ /lib
.PHNOY:rebuild clean
rebuild:clean $(EXE)
clean:
rm -rf $(EXE) $(OBJS) $(LIBFUNCSO) $(LIBFUNCA)
(2)预定义变量
内部事先定义好的变量,但是它的值是固定的,并且有些的值是为空的。
变量 |
描述 |
AS |
汇编程序,默认为as |
CC |
c 编译器默认为 cc |
CPP |
c 预编译器,默认为$(CC) -E |
CXX |
c++编译器,默认为 g++ |
RM |
删除,默认为rm -f |
ARFLAGS |
库选项,无默认 |
ASFLAGS |
汇编选项,无默认 |
CFLAGS |
c 编译器选项,无默认 |
CPPFLAGS |
c 预编译器选项,无默认 |
CXXFLAGS |
c++编译器选项,无默认 |
根据内部变量,可以将makefile 改写为:
OBJS:=main.o func.o
#CC:=g++
main.exe:$(OBJS)
$(CC) -o $@ $^
main.o:main.cpp
$(CC) -o $@ -c $^
func.o:func.cpp
$(CC) -o $@ -c $^
2)规则
分为:普通规则,隐含规则,模式规则。
(1)隐含规则:
例如:*.o 文件自动依赖*.c 或*.cc 文件,所以可以省略main.o:main.cpp 等。
OBJS := main.o fun.o
CFLAGS := -Wall –O2 -g
main.exe: $(OBJS)
gcc $^ -o $@
(2)模式规则
通过匹配模式找字符串,%匹配 1 或多个任意字符串。
%.o: %.cpp 任何目标文件的依赖文件是与目标文件同名的并且扩展名为.cpp 的文件。
OBJS := main.o fun.o
CFLAGS := -Wall –O2 –g
main.exe : $(OBJS)
gcc $^ -o $@
%.o: %.cpp #模式通配
gcc -o $@ -c $^
另外还可以指定将*.o、 *.exe、 *.a、 *.so 等编译到指定的目录中:
DIR:=./Debug/
EXE:=main.exe
OBJS:=main.o
LIBFUNCSO:=libfunc.so
CFLAGS:= -fpic
$(DIR)$(EXE):$(DIR)$(OBJS) $(DIR)$(LIBFUNCSO)
gcc -o $@ $< -L./ -lfunc
$(DIR)$(LIBFUNCSO):$(DIR)func.o
gcc -shared -o $@ $^
$(DIR)main.o:main.c
gcc -o $@ -c $^
$(DIR)func.o:func.c
gcc $(CFLAGS) -c $^ -o $@
.PHONY:rebuild clean
rebuild:clean $(DIR)$(EXE)
clean:
rm -rf $(DIR)*.o $(DIR)*.exe $(DIR)*.so
注意:当OBJS 里面有多项的时候,此时$(DIR)$(OBJS)只能影响到 OBJS 中第一个,后面的全部无效,因此需要全部列出来。
3)函数
(1)搜索文件函数wildcard
搜索当前目录下的文件名,展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。
SOURCES = $(wildcard *.cpp)
//把当前目录下所有'.cpp'文件存入变量 SOURCES 里。
(2)字符串替换函数patsubst
格式:$(patsubst 要查找的子串,替换后的目标子串,源字符串)。
将源字符串(以空格分隔)中的所有要查找的子串替换成目标子串。
如:
OBJS = $(patsubst %.cpp,%.o,$(SOURCES))
把SOURCES 中'.cpp' 替换为'.o' ,然后把替换后的字符串存入变量 OBJS。
(3)加前缀函数addprefix
格式:$(addprefix 前缀,源字符串)
该函数把第二个参数列表的每一项前缀上第一个参数值。
下一小节:linux静态库和动态库