Makefile构建多级目录项目

1)MakeFile是什么

根据百度:

        MakeFile是一个文件。

        MakeFile是一个文本文件。

        Makefile是make工具执行自动化编译流程的依据文本文件。

就像编译一个可执行程序一样:

        xxx.c是一个文件。

        xxx.c是一个文本文件。

        xxx.c是gcc工具执行编译的依据文本文件。

xxx.c文件需要遵守C语言语法规则去编写;同样的,Makefile文件需要遵守Makefile语法规则编写。

2)编写一个简单的Makefile

1  #1 设定生成的目标名称(可执行文件名称)
2  TARGET := helloWorld.out
3
4  #2 声明伪目标
5  .PHONY : clean $(TARGET)
6 
7  #3 目标TARGET执行流程
8  $(TARGET) : helloWorld.c
9      gcc -o $@ $<

10 #4 clean执行流程
11 clean:
12     rm *.out

第二行,定义一个值为"helloWorld.out"的变量,将这个变量作为生成目标。

第五行,声明一些伪目标,防止与伪目标同名文件冲突。声明伪目标的作用是:告诉make工具当执行这些目标的时候,即使当前目录下存在与伪目标同名的文件,也会忽略这些文件,去执行MakeFile文件中对应的目标流程。

第8、9行,生成工程。$(TARGET)依赖于 helloWorld.c,根据此依赖关系执行第9行的编译命令。

第11、12行,清理工程。

命令行下:

执行命令 make:生成helloWorld.out

执行命令 make clean : 清除所有后缀为.out的文件

这是最简单的一个Makefile,工程非常简单的时候使用这样的Makefile没有问题,但是当工程有多个依赖的动态库、静态库;有多个目录下的依赖.h头文件;有多个目录下的.c源文件;而且头文件包含关系复杂的情况下,这样写是无法满足编译需求的。

3)编写一个适应所有工程的MakeFile

编写mk-rules.mk文件,此文件是配置文件,语法规则等同于MakeFile,MakeFile将会通过包含这个文件来获取一些变量的值。

#头文件相对路径,以MakeFile所在文件为根目录
#这个变量留给用户去配置,后面会将INC_DIRS转换成绝对路径,因为编译器只能识别到绝对路径,
#之所以填入相对路径,是因为这样对用户来说更加简洁明了。
#谨记:将丑陋留给自己,把复杂藏在内部
INC_DIRS := dir1 \
            dir2 \
            dir3

#源文件相对路径,以MakeFile所在文件为根目录
#同上
SRC_DIRS := dir1 \
            dir2 \
            dir3 

#.o文件输出目录,以MakeFile所在文件为根目录
#同上,以根目录下的output为.o文件输出目录
OUTPUT_COBJS := output

CFLAGS += -Wall -Os -g
CLIBS += -lpthread

#将INC_DIRS、SRC_DIRS、OUTPUT_COBJS 转换为绝对路径
CUR_DIRS = $(shell pwd)
INC_DIRS := $(patsubst %,$(CUR_DIRS)/%,$(INC_DIRS))
SRC_DIRS := $(patsubst %,$(CUR_DIRS)/%,$(SRC_DIRS))
OUTPUT_COBJS := $(patsubst %,$(CUR_DIRS)/%,$(OUTPUT_COBJS))

#VPATH是Makefile的内置变量,依赖项如果在根目录找不到的话就从VPATH目录中查找.
VPATH := $(SRC_DIRS)

#指定编译器工具名称前缀
#CROSS_COMPILE ?= arm-linux-gnueabihf-
CROSS_COMPILE ?= 
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
AS = $(CROSS_COMPILE)as
NM = $(CROSS_COMPILE)nm
STRIP = strip



编写MakeFile文件:

#设定生成的目标名称(可执行文件名称)
TARGET := helloWorld.out

#配置文件
include mk-rules.mk

#CFLAGS用于C编译器的编译选项。
CFLAGS += $(patsubst %,-I%,$(INC_DIRS))

#将SRC_DIRS内全部目录下的.c文件展开并返回到CFILES(包含绝对路径)
#SRC_DIRS在mk-rules.mk文件中由用户指定
CFILES := $(foreach dirs,$(SRC_DIRS),$(wildcard $(dirs)/*.c) )

#CFILES_NO_DIR包含了所有指定目录下的.c文件(去除掉了绝对路径)
CFILES_NO_DIR := $(notdir $(CFILES))

#COBJS包含了所有.c文件对应的.o文件
COBJS := $(patsubst %.c,%.o,$(CFILES_NO_DIR))
COBJS := $(patsubst %,$(OUTPUT_COBJS)/%,$(COBJS))

#声明伪目标
.PHONY : all clean $(TARGET)

#声明可忽略错误的目标。
#在命令行执行 make all ,如果没有这一行,当不存在*.o文件或者TARGET文件的时候
#会导致目标 clean 会出错导致make停止运行。
.IGNORE : clean

all: clean $(TARGET)

#TARGET生成依赖于COBJS中的所有.o
$(TARGET) : $(COBJS)
    $(CC) -o $@ $^ $(CLIBS)

#COBJS内所有.o文件的生成依赖于对应的.c.
#以下是MakeFile的一种循环目标匹配规则:
#每次从COBJS中拿出一项进行 $(OUTPUT_COBJS)/%.o:%.c 这样的字符串匹配。
#$(OUTPUT_COBJS)/%.o为目标,%.c为依赖
$(COBJS) : $(OUTPUT_COBJS)/%.o:%.c
    $(CC) -o $@ -c $^ $(CFLAGS)


#4 clean执行流程
clean:
    rm $(OUTPUT_COBJS)/*.o
    rm $(TARGET)

两个文件的编写完成。

需要新加头文件路径的时候:在mk-rules.mk文件的INC_DIRS下新加一个目录

需要新加源文件路径的时候:在mk-rules.mk文件的SRC_DIRS下新加一个目录

需要更换.o文件生成路径的时候:对mk-rules.mk文件的OUTPUT_COBJS变量进行更改

需要更换编译器的时候:对mk-rules.mk文件的CROSS_COMPILE变量进行更改

两个文件中使用了大量的MakeFile内置字符串处理函数,包括 :

patsubst、notdir、foreach、wildcard.

这些函数的用法在我的另一篇文章【MakeFile常用函数】中有非常详细的解释。

如果你是一个初学者,不建议你立刻去深究其中的细节。

当不了解一门技术的时候,深究细节是一件绝对忌讳的事情,因为这时候缺乏足够的基础知识,深究起细节来,一行代码可能会让你的大脑对它有两三种甚至五六种模棱两可的解释,而这样混杂的错误的认知,只会让你体会到一种久违的亲切的感觉————《MAKEFILE:从入门到入土》。

希望看了这篇文章能让你对MakeFile有更深入的了解。

上一篇:阿里云时序数据库TSDB的优势讲解,数据写入效率提升百倍


下一篇:聊一聊负载均衡SLB的DDoS防护