开花结果篇
通过一个相对较复杂的项目来实践加深 Makefile 的应用,我们暂且将该项目命名为 huge(大块头);
项目目录结构图如下:
实例1:source/foo/src下的Makefile
编辑 source/foo/src/Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = ../../../build/exes/
DIR_DEPS = deps
DIR_LIBS = ../../../build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
EXE = zoo
ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
LIB = libfoo.a
ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifneq ($(EXE), "")
all: $(EXE)
endif
ifneq ($(LIB), "")
all: $(LIB)
endif
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif
$(DIRS):
$(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
$(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
@set -e ; \
echo "Making $@ ..." ; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS) $(RMS)
编译执行
$ make
$ ls
$ ls ../../../bulild/libs/
$ ls ../../../build/exes/
$ ls deps/
$ ls objs/
$ make clean
结果输出
语法说明
- AR 和 ARFLAGS 两个变量用途是以生成静态库;
- IDS_EXES 表示 exes 目录的实际位置,现在采用的是相对路径;
- DIRS 变量增加DIR_LIBS变量中的内容,以便在生成库文件之前生成 build/libs 目录;
- RMS 变量用于表示要删除的目录和文件;
- 该 Makefile 只是针对构建libfoo.a 库,因此 make clean 不能将位于build 目录下的 exes 和 libs 目录全部删除;
- ifneq 条件语句用于判断 EXE 变量是否被定义,因为后面在设置 all 目标的依赖关系时,需要判断 EXE 变量是否有值,如果没有值,我们并不需要让 all 目标依赖 $(EXE) ,不需要为其调用addprefix函数增加前缀,否则会打破后面判断 EXE变量是否有值,从而决定是否让 all 目录依赖于它这一方法;
- 如果 EXE 有值,应当将其值加入到 RMS 变量中,以便我们在调用 make clean 时清除它;
- LIB 变量用于存放库名,比如这里的库名就是 libfoo.a;
- 增加一条用于构建的规则,采用 crs 参数调用 ar 命令以生成库。
- 在 clean 目标命令中,采用删除 RMS 变量中的内容,而不是DIRS变量中的内容。因为我们不希望在 foo 模块中 make clean 时将bulid目录下的 libs 和 exes 目录也删除。
实例2:利用make.rule提高复用性
将实例1 foo 模块中的 Makefile 分成两部分:build 目录下的 make.rule 和 source/foo/src 目录中的 Makefile,同时扩展 huge/src 目录下的Makefile。
1、编辑 build/make.rule
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifneq ($(EXE), "")
all: $(EXE)
endif
ifneq ($(LIB), "")
all: $(LIB)
endif
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif
$(DIRS):
$(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
$(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
@set -e ; \
echo "Making $@ ..." ; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS) $(RMS)
2、编辑 source/foo/src/Makefile
EXE =
LIB = libfoo.a
include $(ROOT)/build/make.rule
在 huge/ 目录下给 ROOT 变量赋值:
$ export ROOT=`pwd`
进入 huge/source/foo/src 编译执行
$ make
$ ls
$ ls ../../../bulild/
$ ls ../../../build/libs/
$ ls deps/
$ ls objs/
$ make clean
结果输出
3、在 source/huge/src/ 目录下创建 main.c
int main()
{
return 0;
}
编辑 source/huge/src/Makefile
EXE = huge.exe
LIB =
include $(ROOT)/build/make.rule
进入 source/huge/src 编译执行
$ make
$ ls
$ ls deps/
$ ls objs/
$ ls ../../../build/exes/
$ make clean
结果输出
语法说明
- 通过 build 目录下的 make.rule 文件,让所有位于各软件模块的 src 目录下面的 Makefile 提高复用性;
- 通过将 foo 模块的 Makefile 中的一部分内容放入到 make.rule 中;
- 变量 EXE 和 LIB 的定义对于每一个软件模块是不同的;比如本项目中,需要在source/foo.src 目录中的Makefile里面将LIB的变量值设置为libfoo.a, 且 EXE 变量为空;而在 source/huge/src 目录中的Makefile里,只定义 EXE 变量值为 huge.exe;
- 为了使得 DIR_EXES 变量和 DIR_LIBS 变量对于所有的模块都相同,可以采用绝对路径的方式来实现;本项目是通过定义 ROOT 环境变量实现(需要注意的是在 export 所需的 ROOT 变量时,除了先要进入 huge项目的根目录外,pwd 命令前后的字符是 “`” 而不是 “’”,这个字符是键盘上 “!” 键左边的那一个)
实例3:添加源程序文件
1、编辑 build/make.rule
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
ifneq ($(LIB), "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifneq ($(EXE), "")
all: $(EXE)
endif
ifneq ($(LIB), "")
all: $(LIB)
endif
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
ifneq ($(INC_DIRS), "")
INC_DIRS:=$(strip $(INC_DIRS))
INC_DIRS:=$(addprefix -I, $(INC_DIRS))
endif
ifneq ($(LINK_LIBS),"")
LINK_LIBS:=$(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif
$(DIRS):
$(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
$(CC)-L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DIR_LIBS) $(OBJS)
$(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
$(CC) $(INC_DIRS) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
set -e ; \
echo "Making $@ ..." ; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) $(INC_DIRS) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(RMS)
2、编辑 source/foo/src/Makefile
EXE =
LIB = libfoo.a
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule
3、编辑 source/huge/src/Makefile
EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule
4、源文件源码
// huge/source/foo/inc目录 foo.h
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif
// huge/source/foo/src 目录 foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("This is foo()!\n");
}
// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"
int main()
{
foo();
return 0;
}
5、编译 huge/source/foo/src 的 Makefile
6、编译 huge/source/huge/src 的 Makefile
7、执行 huge/build/exes 的 huge
语法说明
- 在 make.rule 文件中增加了 INC_DIRS 变量,用于存放个模块用到的全部头文件,在 make.rule 中增加了一个条件语句块,即当 INC_DIRS 中的值不为空时,先采用 strip 函数去除多余的空格,然后再用 addprefix 函数为所有的目录的前面加上“-I”(i 的大写)前缀。最后的改动就是,让目标文件生成规则和依赖文件生成规则中增加对INC_DIRS 变量的引用,以告诉 gcc 到哪去找头文件;
- 在 make.rule 文件中增加了 LINK_LIBS 变量,用来存放所有需要在连接时用到的库;
- 在 make.rule 文件中通过使用gcc的-L选项将 DIR_LIBS 变量加入到连接器的搜索目录中,由于我们采用将所有的库文件都放入$(DIR_LIBS)目录中,这种方式能简化 Makefile 的设计,因为我不需要指定多个目录;
- 在各个模块的src目录中的 Makefile 增加了 LINK_LIBS 变量,同时在source/huge/src/Makefile中对LINK_LIBS负值为foo(在linux中一个库名的格式为libxxxx.a或.so,其中的xxxx就是我们采用gcc的 -l (L小写) 选项时所需给的名)
实例4:添加模块检验编译设计兼容性
在huge项目中新增一个zoo模块,作为一个库 libzoo.a 用到项目中,zoo模块跟 foo 模块为同一个路径下,如上图。
1、源文件源码
// huge/source/zoo/inc目录 zoo.h
#ifndef __ZOO_H
#define __ZOO_H
void zoo();
#endif
// huge/source/zoo/src 目录 zoo.c
#include <stdio.h>
#include "zoo.h"
void zoo()
{
printf("This is zoo()!\n");
}
// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"
#include "zoo.h"
int main()
{
foo();
zoo();
return 0;
}
2、编辑 source/zoo/src/Makefile
EXE =
LIB = libzoo.a
INC_DIRS = $(ROOT)/source/zoo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule
3、编辑 source/huge/src/Makefile
EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc \
$(ROOT)/source/zoo/inc
LINK_LIBS = foo zoo
include $(ROOT)/build/make.rule
4、编译 source/foo/src/Makefile
5、编译 source/zoo/src/Makefile
6、编译 source/huge/src/Makefile
7、执行 build/exes/huge
语法说明
- zoo 模块下 Makefile 与 foo 模块的Makefile文件基本相同,除了做些额外的小改动;
- huge 模块下 Makefile 增加对 zoo 模块库的引用。
实例5:简化操作
从上面 实例4 我们看到,从库文件到执行文件,编译流程需要经过:“进入source/foo/src/目录执行 make 编译 – 进入source/zoo/src/目录执行 make 编译 – 进入source/huge/src/目录执行 make 编译”,总共3次手动编译;如果模块不断增多,编译步骤会更加繁杂,不易于开发、维护。由此,我们在 build/ 目录下引入 Makefile 文件,用于简化项目的编译工作。
编辑 build/ 目录下 Makefile
.PHONY: all clean
DIRS = $(ROOT)/source/foo/src \
$(ROOT)/source/bar/src \
$(ROOT)/source/huge/src
RM = rm
RMFLAGS = -fr
RMS = $(ROOT)/build/exes $(ROOT)/build/libs
all:
@set -e; \
for dir in $(DIRS); \
do \
cd $$dir && $(MAKE) ; \
done
@echo ""
@echo ":-) Completed"
@echo ""
clean:
@set -e; \
for dir in $(DIRS); \
do \
cd $$dir && $(MAKE) clean;\
done
$(RM) $(RMFLAGS) $(RMS)
@echo ""
@echo ":-) Completed"
@echo ""
编译 build/ 目录下 Makefile
$ make
$ make clean
语法说明
- 使用 Shell 的 for 语句,遍历变量 DIRS 中的每一个目录,并进入目录运行 make;
- 使用 MAKE 特殊变量,不直接使用 make 是为了更好的移植性;
- 库文件需要比可执行程序先构造出来,在 DIRS 变量中,需要将库目录放在可执行程序的目录之前,因为 Makefile 是根据目录的先后顺序来进行构建工作的。