前言

在上篇文章中,目录结构是最终的形式,为了从最简单的Makefile开始,现在把Makefile文件当到src目录下方便演示,即

simple_encrypt # 项目根目录
|
|----- include # 头文件目录
|        |
|        |----- checksum.h
|        |----- encrypt.h
|
|----- src # 源码目录
|       |
|       |----- checksum.c
|       |----- encrypt.c
|       |----- test_code.c
|       |----- Makefile
|

Makefile version1

最简单的Makefile文件长这样:

test_code:checksum.c encrypt.c test_code.c
    gcc -I../include/ checksum.c encrypt.c test_code.c -o test_code

Makefile基本语法

目标:源码1 源码2 源码3 ...
    gcc -I头文件目录 源码1 源码2 源码3 ... -o 目标

有一点需要特别注意的是,在gcc命令之前有一个tab字符。事实上,在任何指令之前,都必须加一个tab字符,这是make的要求,不加tab, make会不开心的 :)

test_code目标文件依赖checksum.c encrypt.c test_code.c这三个源码

Makefile version2

我们更进一步,让makefile更加高效一点:

CC = gcc
IDIR = ../include/
CFLAGS = -I$(IDIR)
test_code:checksum.o encrypt.o test_code.o
    $(CC) checksum.o encrypt.o test_code.o -o test_code

现在,我们在makefile中定义了一些常量,CCCFLAG,这些常量告诉make如何编译checksum.c,test_code.c,encrypt.c文件。CC宏定义了要使用哪个编译器,CFLAG是一些编译标志。通过将目标文件checksum.o encrypt.o test_code.o放在依赖列表中,make就知道首先需要编译c文件得到目标文件,然后链接得到可执行文件test_code。对于大多数小型项目来说,这种makefile已经足够了。

Makefile version3

但是上个版本的makefile还忽略了一点:对头文件的依赖。如果你修改了头文件,然后重新执行make,这时候即使需要重新编译c文件,make也不会重编的。为了解决这个问题,我们得告诉makec文件依赖哪些h文件。

CC = gcc
IDIR = ../include/
DEPS += checksum.h
DEPS += encrypt.h
CFLAGS += -I$(IDIR)
CFLAGS += -Wall -Wextra -Werror

%.o:%.c $(DEPS)
        $(CC) -c -o $@ $< $(CFLAGS)

test_code:checksum.o encrypt.o test_code.o
        $(CC) checksum.o encrypt.o test_code.o -o test_code

.PHONY:clean
clean:
        rm -rf *.o test_code

在这个版本中,我们新加了一个常量DEPS,这是c文件依赖的头文件集合。然后,我们又定义了一个适用于所有*.o文件的规则,这个规则说明.o文件依赖于同名的.c文件和DEPS中包含的头文件。为了产生.o文件,make需要使用CC常量定义的编译器编译.c文件。

-c 标志表示产生目标文件
-o $@ 表示将输出文件命名为:左边的文件名
$< 表示依赖列表中的第一个项

makefile version4

最后使用特殊的宏$@ $^做最后一次简化,让编译规则更加通用。$@ $^分别表示:左边和右边。在下面的例子中,所有的头文件都应该作为DEPS宏的一部分,所有的目标文件*.o都应该作为OBJ宏的一部分。

CC = gcc
IDIR = ../include/
CFLAGS += -I$(IDIR)
CFLAGS += -Wall -Wextra -Werror
DEPS += encrypt.h
DEPS += checksum.h

OBJ = checksum.o encrypt.o test_code.o

%.o:%.c $(DEPS)
        $(CC) $(CFLAGS) -c -o $@ $< 

test_code:$(OBJ)
        $(CC) $(CFLAGS) -o $@ $^

.PHONY:clean
clean:
        rm -rf *.o test_code

makefile version5

如果我们想把头文件,源文件和其他库文件分别放在不同的文件夹,那么makefile该怎么写呢?
另外,我们可以隐藏那些烦人的中间文件(目标文件)吗?当然可以!

下面的makefile定义了include文件夹,lib文件夹路径,obj文件夹路径,并且把目标文件放到文件夹obj里面。这个makefile文件应该位于项目的根目录,注意,这个makefile还包含了一个规则用于清理sourceobj文件夹,只需要输入make clean即可。.PHONY规则可以让make不去改动任何名为clean的文件(如果有的话)。

目录结构

simple_encrypt # 项目根目录
|
|----- include # 头文件目录
|        |
|        |----- checksum.h
|        |----- encrypt.h
|
|----- lib # 静态库和动态库目录
|
|----- obj # 目标文件目录
|
|----- src # 源码目录
|       |
|       |----- checksum.c
|       |----- encrypt.c
|       |----- test_code.c
|
|----- Makefile
|
CC = gcc

IDIR = ./include
ODIR = ./obj
LDIR = ./lib
SDIR = ./src

CFLAGS += -I$(IDIR)
CFLAGS += -Wall -Wextra -Werror

_DEPS += encrypt.h
_DEPS += checksum.h
DEPS = $(patsubst %, $(IDIR)/%, $(_DEPS))

_OBJ = checksum.o encrypt.o test_code.o
OBJ = $(patsubst %, $(ODIR)/%, $(_OBJ))

$(ODIR)/%.o:$(SDIR)/%.c $(DEPS)
        $(CC) -c -o $@ $< $(CFLAGS)

test_code:$(OBJ)
        $(CC) -o $@ $^ $(CFLAGS)

.PHONY:clean
clean:
        rm -f $(ODIR)/*.o

现在,我们有了一个非常不错的makefile,你可以对其进行简单的修改来管理中小型软件项目了。你也可以将多个规则写到一个makefile里面,甚至可以在一个规则中调用其他规则。


参考资料:
Makefile傻瓜教程

A Simple Makefile Tutorial

makefile之patsubst函数

Makefile中echo和@echo的区别

标签: none

评论已关闭