makefile 主要是编译就是用来编译源文件的
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。(百度百科)
makefile的文件命名:makefile 或 Makefile
makefile的规则或者说写法就三个部分:目标、依赖、命令
具体怎么写呢
目标:依赖
(tab缩进)命令
看一个小例子
例一:
app:main.c add.c sub.c mul.c gcc main.c add.c sub.c mul.c -o app
app就是最终要生成的目标,所有需要的c文件就是依赖
执行gcc命令
这就是一个最简单的makefile,但是它最大问题是,如果你修改了中一个.c文件,在gcc编译的时候会把所有的c文件都编译一遍,这样效率就很低,所以可以把他们分开写,先编译成.o二进制文件。这样就是第二个例子
例二:
app:main.c add.c sub.c mul.c gcc main.o add.o sub.o mul.o -o app main.o :main.c gcc main.c -c add.o :add.c gcc add.c -c sub.o :sub.c gcc sub.c -c mul.o :mul.c gcc mul.c -c
只有app是要生成的终极目标,下面是为它服务的小目标
这样当你更改某一个.c文件后,只会编译你修改的那个c文件,编译成.o二进制文件,而不用把所有的都编译一遍,节省很多时间。
那makefile是怎么知道要重新编译的是哪一个文件呢。
原理是:
首先向下搜索下面的规则,检测下面的规则里是否有终极目标app需要的依赖,
如果依赖存在就判断是否需要更新也就是小目标是否要执行,
判断的依据是目标和依赖的时间,如果目标的时间大于依赖的时间,不更新,反之需要更新。说白了就是它会根据文件时间的变化检测你有没有更改源文件,更改了就要更新。因为一定是现有依赖才有的目标,目标.o文件时间要晚于依赖的时间也说成大于。也就是说一旦更改了依赖文件,这个文件最终时间就在目标之后了,依赖时间大于了目标时间,说明你修改了依赖文件,这时就需要更新。
但是上面这个makefile写的太冗余了。下面看下面一个例子改进一下:
例三:
前面说makefile就像一个脚本,那它就可以有变量和函数。所以先看一下变量,变量你可以自己定义,makefile本身也提供了几个固定的变量。
自定义变量:makefile里面自定义变量没有类型 直接赋值就行,例如:obj = a.o b.o c.o 或者 obj = 10 ,不讲道理的使用,
但是变量不能直接使用要取出变量的值,用$符号,例如$(obj)
makfile提供的变量:CC 、CPPLAGS、LDFLAGS、LIBS等
这些参数都和gcc命令相关,例如让:
CC=”gcc -march=k8 -O2 -s”
LDFLAGS = -L/var/xxx/lib -L/opt/mysql/lib
LIBS = -lmysqlclient -liconv
CPPFLAGS='-I/usr/local/libjpeg/include -I/usr/local/libpng/include'
就是在gcc编译的时候需要指定一下头文件和库文件目录,写起来太长了,你就讲路径赋值给相应的变量,这是一种规范,你随便给别的变量也能用。甚至很多makefile文件中编译都没有gcc的字样就是把gcc赋值给了CC。当然也可以把这些值设置到环境变量中去export LDFLAGS="-L/var/xxx/lib -L/opt/mysql/lib -Wl,R/var/xxx/lib -Wl,R/opt/mysql/lib" ;
自动变量:$@、$<、$^,makefile提供了几个变量表示着固定的意思,不能对他们赋值且只能在规则命令中使用。
$@:规则中的目标
$<:规则中的第一个依赖
$^:规则中的所有的依赖
改写例二
obi = main.o add.o sub.o mul.o target = app $(target):$(obj) gcc $(obj) -o $(target) 这一行就可以替换成 gcc $^ -o $@ %.o:%.c %模式匹配代表所有的.o文件和所有的.c文件 gcc -c $< -o $@
现在这个makefile的可移植性很差
例四:
这里看一下makefile的函数
makefile中所有的函数都有返回值,下面介绍两个makefile的函数
(1)wildcard 查找指定文件夹下指定类型的文件
src = $(wildcard ./*.c) 当前目录下所有的.c文件并赋值给src
(2)patsubst匹配替换
obj = $(patsubst %.c, %.o,$(src)) 把src变量中所有.c文件替换为.o文件
src = $(wildcard ./*.c) obj = $(patsubst %.c,%.c,$(src)) trarget = app $(target):$(obj) gcc $^ -o $@ %.o:%.c gcc -c $< -o $@
例五:
前面说的makefile最后生成的是只有第一目标,也就终极目标。其实makefile也可以生成不是终极目标的目标,
调用使用 :make 目标名 ,例如一个makefile最后会执行make clean清理项目
src = $(wildcard ./*.c) obj = $(patsubst %.c,%.c,$(src)) trarget = app $(target):$(obj) gcc $^ -o $@ %.o:%.c gcc -c $< -o $@ hello: echo "hello,makefile"
执行make hello 会打印hello,makefile
如果我们加一个,清理项目的操作,表示如果执行失败删除前面生成的文件。
clean:
-mkdir /abc
-rm $(obj)&(target) -f #-f表示强制执行 ,命令前的'-'号表示当前命令失败向下执行
这里有会有个问题就是,如果当前目录下有同名的clean文件,make clean就不会执行。前面例二就说了,makefile根据依赖的时间来判断会不会更新,但是这里clean没有依赖。
解决这个问题的方法是写一个伪目标 .PHONY:clean ,注意PHONY前面有个'.'符号。
完整的就是
src = $(wildcard ./*.c) obj = $(patsubst %.c,%.c,$(src)) trarget = app $(target):$(obj) gcc $^ -o $@ %.o:%.c gcc -c $< -o $@ .PHONY:clean clean: -mkdir /abc -rm $(obj)&(target) -f
好了makefile就是这么简单。
最后写一个小例子
在plus目录下有两个目录include和src,现在在plus目录下编写makefile,编译src下的源文件,需要include中的头文件。
trarget = app CPPFLAS = ./include src = $(wildcard ./src/*.c) obj = $(patsubst %.c,%.c,$(src)) $(target):$(obj) gcc $^ -o $@ %.o:%.c echo $< gcc -c $< -I $(CPPFLAGS) -o $@
上面可以甚至让 CPPFLAS = '-I./include',这样gcc中就不用再加参数 -I了