【译】一个简单的 make & makefile 教程
这篇教程假设你已经知道如何使用 GCC 编译文件,如果不清楚,请看此教程:
概述
makefile 文件帮助我们轻松地 组织代码编译 。这个教程用以帮助初学者快速掌握为中小项目编写 makefile 的能力。
一个简单的例子
首先我们需要以下三个文件:
hellomake.c 源文件:
// hellomake.c
#include <hellomake.h>
int main() {
// call a function in another file
myPrintHelloMake();
return(0);
hellofunc.c 源文件:
// hellofunc.c
#include <stdio.h>
#include <hellomake.h>
void myPrintHelloMake(void) {
printf("Hello makefiles!\n");
return;
}
hellomake.h 头文件:
/*
example include file
void myPrintHelloMake(void);
现在,你可以使用以下命令编译这些代码:
gcc -o hellomake hellomake.c hellofunc.c -I.
gcc 编译器会编译两个 C 源文件并把可执行程序命名为 hellomake。参数“ -I. ”用以指示 gcc 在当前目录“ . ”下寻找头文件 hellomake.h。如果没有 makefile 文件,每次当我们修改过源文件后,如果要重新编译代码,我们都需要重新输入编译命令(虽然可以使用 UP 箭头找到历史命令),当要编译的源文件很多时,这样做就很没有效率。
不幸的是,这样做还有另外两个问题 。首先,如果编译命令丢失了,你就需要重新输入。其次,如果你只对一部分源文件做了修改,每次都重新编译所有的文件耗时且低效。因此,我们最好花点时间看一下 makefile 可以为我们做什么。
makefile 由一系列的规则组成,规则的结构如下 :
目标文件:依赖文件
命令n
你能创建的最简单的 makefile 可能像这样:
hellomake: hellomake.c hellofunc.c
gcc -o hellomake hellomake.c hellofunc.c -I.
将此文件命名为 Makefile 或者 makefile,然后在命令行输入 make,它会执行你在 makefile 里所写的编译指令(如果报错请检查第二行的命令前是否是一个 tab)。注意,不带参数的 make 会执行 makefile 文件中的第一条规则。此外,通过将编译 hellomake 所依赖的源文件放在 " : " 之后,make 知道如果其中任何依赖文件发生更改,则需要执行 hellomake 规则,即需要重新编译此文件。现在 makefile 已经帮助我们解决了之前提出的问题一,但是现在系统依然不会只编译更改过的文件。
需要注意的是, makefile 中的命令必须以 tab 开始 ,不能使用空格。
为了使编译过程更加有效率,我们试一下以下的代码:
CC=gcc
CFLAGS=-I.
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
如你所见,我们现在定义了两个常量(也可称之为 macro,宏) CC 和 CFLAGS,这些特殊的常量用来告诉 make 我们要如何编译源文件。特别地,CC 指定了我们要使用的 C 编译器,而 CFLAGS 则是要传递给编译命令的标志的列表。通过把目标文件 hellomake.o 和 hellofunc.o 放在依赖项中,make 知道它首先需要编译出 .o 目标文件,然后才能编译可执行文件 hellomake。
这种形式的 makefile 已经适用于大多数小型项目了。然而,我们还少了一个东西:头文件。举个例子,如果你对头文件 hellomake.h 做了修改,make 不会重新编译 hellomake.c,尽管实际上需要重新编译。为了解决这个问题,我们需要告诉 make 所有的 .c 源文件依赖于特定的一些 .h 头文件。我们所要做的就是在 makefile 中添加一条简单的规则:
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: hellomake.o hellofunc.o
$(CC) -o hellomake hellomake.o hellofunc.o
我们添加了一个新的常量:DEPS,其指定了一些 .c 源文件所依赖的 .h 头文件。然后我们定义了一条新的规则,其应用于所有以 .o 结尾的目标文件。这条规则表明了 .o 文件依赖于相应的 .c 文件和 DEPS 中包含的头文件。之后此规则表示,要生成 .o 目标文件,make 需要使用 CC 中指定的 C 编译器编译 .c 源文件。参数 -c 指示编译器产生 .o 目标文件;参数 $@ 和 $^ 分别指代规则第一行中的冒号的左右两边的内容,参数 $< 表示第一个依赖文件。
接下来我们使用 OBJ 来表示我们需要的目标文件,这样可以简化我们之前的 makefile 文件:
CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS)
如果我们想把 .h 文件放在 include 目录下,把 .c 文件放在 src 目录下以及把一些本地的库文件放在 lib 目录下要怎么做呢?还有,我们可不可以把烦人的 .o 文件通过某种方法隐藏呢?下面的 makefile 文件可以解决这两个问题。注意此 makefile 应放在 src 目录下。
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
LIBS=-lm
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)