Make 快速入门
Makefiles
是一种方便的组织代码编译方式。这篇文章不会深入的讲述如何使用make
,只会作为一篇入门教程来让大家快速地为小中型项目创建自己的makefiles
.
一个简单的例子
首先举一个简单的例子,在项目文件夹中有三个文件:main.c
,func.c
,func.h
,分别代表着主程序,函数实现和对应的头文件。
main.c
// main.c
#include <stdio.h>
#include "func.h"
int main() {
sayHello();
return 0;
}
func.h
// func.h
void sayHello();
func.c
// func.c
#include <stdio.h>
#include "func.h"
void sayHello() {
printf("Hello makefiles!\n");
}
一般情况下,我们将使用以下命令来编译这个项目:
gcc -o hello main.c func.c -I. # 注意最后的点代表当前目录
这条命令编译了两个.c
文件并将可执行文件命名为hello
. -I.
代表gcc
将在当前目录(.)寻找包含的头文件. 当只有轻微的改动时适当修改一下这条命令就已经足够了,但当添加更多文件时,这就很麻烦了。
另外,如果你只改动了其中某一个.c
文件,这条命令每次都将重新编译所有文件,对于大项目来说效率很低,所以这才需要makefile
.
Makefile 1
hello: main.c func.c
gcc -o hello main.c func.c -I.
将这条规则写进一个名为makefile
或者Makefile
(一般两者等同,但有些IDE只能识别其中一种)的文件,然后在命令行中键入make
就可以执行文件中的第一条规则了。其中冒号:
后面放置的是规则hello
所依赖的文件,当这些文件改动时,规则hello
会被重新执行。这里没有放置头文件,所以即使头文件改变,命令行中键入make
,这条规则也不会被执行。当然,就目前来说,这条规则效率很低。
需要注意的是,在gcc
命令前有一个tab
制表符,在任何命令前都必须有个tab
制表符。
接下来对这条规则进行适当改进。
Makefile 2
CC=gcc
CFLAGS=-I.
hello: main.o func.o
$(CC) -o hello main.o func.o
这里我们定义的宏CC
和CFLAGS
,将告诉make
如何编译.c
文件,这里CC
代表着C
编译器,CFLAGS
代表着命令可选参数列表。通过把目标文件main.o
和func.o
放置在规则依赖列表中,make
将自动使用我们已经定义的宏来分别编译对应的.c
文件,以生成目标文件,接下来的命令便是链接目标文件生成可执行文件hello
。
这样的makefile
足够应对小型项目,但是像之前提到的:依赖文件中没有包含头文件,所以头文件改变后make
将不会重新编译.c
文件。为了修补这个问题,我们需要将头文件加入到依赖列表中。
Makefile 3
CC=gcc
CFLAGS=-I.
DEPS=func.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hello: main.o func.o
$(CC) -o hello main.o func.o
这次更改主要创建了宏DEPS
,它代表着.c
文件依赖的头文件.h
。接着我们定义了一个作用于所有.o
文件的规则,它依赖于所有的.c
文件和宏DEPS
。具体的命令指出make
使用宏CC
代表的编译器去编译.c
文件,且-c
指出仅生成目标文件,-o $@
指出生成的目标文件命名为:
左侧部分,%
可以理解为匹配符,即.o
和.c
文件一一对应,前缀相同,$<
是依赖列表中的第一个对象。
接下来使用$@
和$^
来进一步简化,这两个宏分别代表着:
的左侧和右侧。为了使得这些规则更加通用,所有的目标文件用宏OBJ
来表示。
Makefile 4
CC=gcc
CFLAGS=-I.
DEPS=func.h
OBJ = main.o func.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hello: $(OBJ)
$(CC) -o $@ $^
Makefile 5
但实际项目通常会把.h
文件放在include
目录中,源文件放在src
目录中,链接库放在lib
中,生成的目标文件放在obj
目录中(可能也不需要)下面就是一个实际的例子(点击下载):
.
├── include
│ ├── dylib.h
│ ├── fun1.h
│ └── fun2.h
├── lib
│ └── libdy.so
├── Makefile
├── obj
└── src
├── fun1.c
├── fun2.c
└── main.c
对应的Makefile
文件如下所示。其中.PHONY: clean
是避免有名称为clean
的文件而造成错误。
CC=gcc
IDIR =include
CFLAGS =-I$(IDIR)
ODIR=obj
LDIR=lib
LIBS=$(LDIR)/libdy.so
SRC =src
_DEPS = dylib.h fun1.h fun2.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = main.o fun1.o fun2.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: $(SRC)/%.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
main: $(OBJ)
$(CC) -o $@ $^ $(LIBS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o
现在我们有了一个比较完美的makefile
,大家可以自己修改以适应自己的需求。更多的参考如下: