Makefile
Makefile是什么
问题:在linux下开发应用程序的时候,如果项目的源文件有一个或者几个我们可以通过:gcc xxx.c -o xxxx
来进行编译。那么如果有成百上千个的时候,还怎么通过这样的命令去实现?
那么这时候就需要一定的规则去编译链接这些源文件了,Makefile就是定义这些规则的文件,你可以把它理解为一个脚本,make就是执行脚本的工具,就像bash和shell脚本的.sh文件一样。
所以要开发一个大型项目,Makefile就必须写的好。这样编译工作就会很简单,一个make就搞定了。
Makefile的语法
target:requirement
<tab> cmd
...
这就是最基本的语法格式:
- target就是要生成的目标,比如.o文件或者可执行文件,requirement,就是生成target所需要的源文件
- 就是tab键,这里只能按tab,不能按空格,如果正确语句就会高亮,然后就是命令,这个命令生成,requirement
比如我们来看一个实例:项目目录下有1.c,2.c,3.c…,那么应该怎么写Makefile
app:1.o 2.o 3.o
gcc -o app 1.o 2.o 3.o
1.o:1.c 1.h
gcc -c 1.c
2.o:2.c 2.h
gcc -c 2.c
3.o:3.c 3.h
gcc -c 3.c
clean:
rm -rf *.o
那么有多少个文件都这么写就可以了:
- 首先要生成app,需要链接这下.o文件
- 然后make会去找从哪生成这些.o文件就会继续向下执行
- clean 是执行make clean 的时候就会被执行,否则不执行,因为他现在像是一个标签一样的,帮助make找到后面的动作
Makefile使用变量
上面的makefile虽然已经可以帮助我们做一些编译工作了,但是我们想想,是不是可以让其好看一点呢,我们平时都是使用变量来组织代码的,那么这如何使用变量,使用变量就像shell一样要用$
改进版:
OBJECTS = 1.o 2.o 3.o
app : $(OBJECTS)
gcc -o app $(OBJECTS)
1.o:1.c 1.h
gcc -c 1.c
2.o:2.c 2.h
gcc -c 2.c
3.o:3.c 3.h
gcc -c 3.c
clean:
rm -rf $(OBJECTS)
这样的话如果再加入文件就只要修改OBJECTS就可以了,那么这样好像还是不好,因为每次都要写生成.o的操作,重复性太高了
Makefile自动推导
其实make是很强大的,在生成.o的时候,并不需要加上那么复杂的指令,我们可以这样写:
OBJECTS = 1.o 2.o 3.o
app : $(OBJECTS)
gcc -o app $(OBJECTS)
1.o:1.h
2.o:2.h
3.o:3.h
.PHONY : clean
clean:
rm -rf $(OBJECTS)
这里,要生成.o文件,make会自动的把.c加到依赖里面,然后后面的命令也会被推导出来。
.PHONY表示clean是一个伪依赖文件,在使用make clean的时候,如果目录下有clean,那么clean就不会被执行了,加上这个以后,就可以执行了。
到这里我么再来写一版:
OBJECTS = 1.o 2.o 3.o
app : $(OBJECTS)
gcc -o app $(OBJECTS)
$(OBJECTS) : 1.h
.PHONY : clean
clean:
rm -rf $(OBJECTS)
这样似乎更简单了,但是这样好像就看不清文件的依赖关系了,虽然我可以肯定的告诉你,make可以自动推导出来。
我们添加新的源码的时候,好像还是每一次都需要来修改OBJECTS,这时候就可以使用make的函数了。
make函数
先上代码:
SOURCE = $(wildcard *.c)
OBJECTS = $(patsubst %.c,%.o,$(SOURCE))
PROGRAM = app
CC = gcc
CFLAGS = -Wall
$(PROGRAM) : $(OBJECTS)
$(cc) -o $(PROGRAM) $(OBJECTS) $(CFLAGS)
$(OBJECTS) : $(SOURCE)
.PHONY : clean
clean:
rm -rf $(OBJECTS)
- wildcard 的作用是把符合规则的文件展开成文件名,也就是1.c 2.c 3.c,然后我们这里使用一个变量接一下结果,当然也可以不使用。
- patsubst,是pattern substitude匹配替换的缩写,它需要三个参数,第一个是需要匹配的式样,第二个是替换成什么样,第三个是一个以空格分开的列表,,我们这里替换完成后就是源文件对应的.o文件
到这里已经把makefile写了好几个版本了,好像已经很强大了,其实已经能解决很多项目的需要了,那么大型项目可以了吗?肯定还有更厉害的。
文件搜索
在一些很大型的工程中,源文件肯定是被放在不同的文件夹下的,因为为了分类,在这时候我们可以为文件加上路径,但是这样做太麻烦,可不可以让make帮我们呢,毕竟我们程序员是无所不能的,怎么会委屈了自己。
Makefile中有一个特殊的变量VPATH,如果没有设定这个变量,make就只会在当前路径下去找依赖的文件,现在我们可以这样写:
VPATH = src:../src
这时候就相当于定义了两个目录,一个是当前项目下的src一个是上一级目录下的src,当然了,可以指定更多,用:隔开即可,make会根据顺序去搜索。
还用一种方法就是使用vpath关键字,注意它不是变量是一个关键字,全是小写:
- vpath < pattern> < directories> 为符合模式< pattern>的文件指定搜索目录。
- vpath < pattern> 清除符合模式< pattern>的文件的搜索目录。
- vpath 清除所有已被设置好了的文件搜索目录。
比如:
vpath %.h ../src
这个就去上级目录的的src下搜索所有的.h文件,当然肯定是在当前目录下先找,因为当前目录永远是最先去找的。
好像Makefile到这就差不多了,其实还没有呢。在那些绝对顶级的大项目下,makefile长什么样呢?
超级大项目的Makefile
LIVEMEDIA_DIR = liveMedia
GROUPSOCK_DIR = groupsock
USAGE_ENVIRONMENT_DIR = UsageEnvironment
BASIC_USAGE_ENVIRONMENT_DIR = BasicUsageEnvironment
PULLER_MODULE_DIR = PullerModule
all:
cd $(LIVEMEDIA_DIR) ; $(MAKE)
cd $(GROUPSOCK_DIR) ; $(MAKE)
cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE)
cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE)
install:
mkdir -p $(PULLER_MODULE_DIR)/lib
cd $(LIVEMEDIA_DIR) ; $(MAKE) install
cd $(GROUPSOCK_DIR) ; $(MAKE) install
cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE) install
cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE) install
module:
cd $(PULLER_MODULE_DIR) ; $(MAKE)
cd $(PULLER_MODULE_DIR) ; $(MAKE) test
clean:
cd $(LIVEMEDIA_DIR) ; $(MAKE) clean
cd $(GROUPSOCK_DIR) ; $(MAKE) clean
cd $(USAGE_ENVIRONMENT_DIR) ; $(MAKE) clean
cd $(BASIC_USAGE_ENVIRONMENT_DIR) ; $(MAKE) clean
cd $(PULLER_MODULE_DIR) ; $(MAKE) clean
cleanall:
cd $(PULLER_MODULE_DIR) ; $(MAKE) cleanall
all:下面有这样的 cd $(LIVEMEDIA_DIR) ; $(MAKE)
,就是先进入这个目录下,然后执行目录下的make就会按照目录下的Makefile执行了,然后继续这样的操作,把目录下的都执行了,然后剩下的就是install,module这样的伪目标,也是一样的,就是为使用make xxx进行的划分。
还有就是一些操作命令了,其实就是一个脚本的语法:
$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件
后话:
Makefile是大型项目的基本技能,所以还是有必要掌握的,现在比较好用的还有cmake,它可以用来生成linux下的makefile,和windows下的.sln文件,这两个都是项目中文件的依赖关系,还有在linux下有automake和autoconf这样的工具,因为可能写Makefile实在是有点小难啊,这两个工具就是用来管理GNU程序三部曲:”./configure”,”make”,”make instal”的工具,这样就可以更加简单的安装程序到Linux系统中了,有兴趣可以去了解一下。