自动生成文件依赖关系
我们知道通过gcc能够获得一个源文件对其他依赖文件的列表,gcc的这个功能其实就是为make所准备的。使用gcc的-MM选项并且结合sed命令后输出结果如下:(使用sed命令进行替换的目录是为了在目标名前加上“objs/”前缀)
gcc -MM foo.c | sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g'
gcc还有另一个非常有用的-E选项,这个选项能够告诉gcc制作预处理而不就行程序编译。在生成依赖关系时,其实不需要gcc编译源文件,只有进行预处理获得依赖文件列表就行了。同使用-E选项,可以避免生成依赖关系时gcc发出编译警告以及提高依赖关系生成的效率。
接下来,我们就开始修改Makefile为每一个源文件通过采用gcc和sed生成一个依赖文件,这些文件采用“.dep”后缀结尾,在此,创建一个新的deps目录用于存放生成的依赖关系文件。如下:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIR_DEPS = deps DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS = $(SRCS:.c=.dep) DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) all: $(DIRS) $(DEPS) $(EXE) $(DIRS): $(MKDIR) $@ $(EXE): $(OBJS) $(CC) -o $@ $^ $(DIR_OBJS)/%.o: %.c $(CC) -o $@ -c $^ $(DIR_DEPS)/%.dep: %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $^ > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
这修改包含:
- 增加了DIR_DEPS变量用于保存需要创建的deps目录名,以及将这个变量的值加入到DIR变量中。
- 删除了目标文件创建规则中对于foo.h文件的依赖,并将这个规则中的自动变量从< 变 回 到 <变回到<变回到^
- 增加了DEPS变量用于存放依赖文件。
- 为all目标增加了对$(DEPS)的依赖。
- 增加了一个用于创建依赖关系文件的规则。在这个规则的命令中,使用了gcc的-E和-MM选项来获取依赖关系。
注意
- 对于规则中的每一条命令,make都是在一个新的Shell上运行它的。
- 如果希望多个命令在同一个Shell中运行,可以使用“;”将这些命令连起来。
- 当命令很长时,可以用“\”将一个命令分成多行书写。
使用依赖关系文件
Makefile中存在一个include指令,它的作用同C语言中的#include宏指令相同,在Makefile中,可以通过使用include指令将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为Makefile的一部分。如下:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIR_DEPS = deps DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS = $(SRCS:.c=.dep) DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif all: $(EXE) include $(DEPS) $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
改动主要增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值。对于每个变量,如果对应的目录不存在,则将目录名赋值给它,否则其值为空。增加的三个变量,分别被放到相应的规则中作为第一个先决条件。
为依赖关系文件建立依赖关系
现在,让我们再对complicated项目的源程序文件进行一定的修改,以便于增加程序文件之间依赖关系复杂度。
如下:
define.h #ifndef __DEFINE_H #define __DEFINE_H #define HELLO "hello" #endif
- foo.h
#ifndef __FOO_H #define __FOO_H #include "define.h" void foo(); #endif
- foo.c
#include <stdio.h> #include "foo.h" void foo() { printf("%s this is foo!\n", HELLO); }
- main.c
#include "foo.h" int main() { foo(); return 0; }
其中改动包括:
- 增加define.h文件并且在其中定义了一个HELLO宏。
- 在foo.h中包含define.h文件
- 在foo.c中增加对HELLO宏的引用。
- 增加了这些改动后,对项目进行make,结果如下:
这次成功编译项目基础上,(不执行make clean)增加一个other.h文件并将之前在define.h中定义的宏放在这个文件中,另外,让define.h包含other.h文件,如下:
- other.h
#ifndef __OTHER_H #define __OTHER_H #define HELLO "hello" #endif
- define.h
#ifndef __DEFINE_H #define __DEFINE_h #include "other.h" #endif
从结果看,已经对foo.c和main.c重新编译,并且执行结果也是最新的。
现在对other.h文件再进行修改。将宏改变,运行make发现:
从结果看发现项目并没有因为更改了other.h文件而重新编译。这是因为other.h的被包含文件是define.h、从.c文件中获取到的依赖关系中并没有关于other.h的依赖关系。
完善方法
为foo.dep和main.dep也引入依赖关系,有了这种依赖关系,make就能发现当对define.h进行修改后需要对依赖关系重新构建,这也就造成了other.h也能够出现在依赖关系树中。
修改Makefile如下:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIR_DEPS = deps DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS = $(SRCS:.c=.dep) DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif all: $(EXE) include $(DEPS) $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
在Makefile中只需要在构建依赖关系的规则中增加自动变量$@就行了,因为它表示的是依赖关系文件名。有了这样改动后,就需要执行make clean后重新make构建了。
在这里发现一个问题:当执行两次make clean时显示结果不同,第二次执行make clean会重新构建依赖关系然后清除。
为了去除这一现象,可以运用条件语法。更改后如下:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIR_DEPS = deps DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) DEPS = $(SRCS:.c=.dep) DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif all: $(EXE) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
打造更专业的编译环境
在上一小节中已经说明了,一个好的目录结构对于软件项目的维护至关重要,而Makefile的设计也应当迎合项目目录结构规划的需要。前面的几个示例项目使用的目录结构都很单一,但是大型项目旺旺是采用多个目录来存放不同的模块。接下来通过模拟的huge项目来实现一个更加专业的编译环境。
规划项目目录结构
如下图说明了huge项目采用的目录结构。从中可以看出,huge项目最上层有两个目录,其中一个是build目录,另一个是code目录。前者用于存放各Makefile文件间的共享文件make.rule以及编译整个项目的Makefile。在build目录中还会在编译期间自动生成libs和exes两个子目录。libs目录用于存放编译出来的目标文件,而exes目录用于存放编译出来的可执行文件。
code目录用于存放项目的源程序文件,在code目录下按照各个软件模块分成不同的子目录。huge项目中包括foo库和huge主程序,使用在code目录下分别创建了foo和huge两个子目录。
对于每个软件模块子目录,又分别为用于存放.c文件的src子目录和用于存放.h文件的Inc子目录。当进行项目编译时,我们希望make在src目录下创建deps和objs目录,作用同前面描述的这两个目录的作用一致。
在每一个src目录下都会有一个Makefile,用于构建所在目录中的源程序文件。由此可知,最上层的build目录下的Makefile将会调用每一个软件模块中的src下的子Makefile完成整个项目的构建。
首先需要按照这样的目录结构创建目录:
接下来,我们先创建code/foo/.src下的Makefile,这个可以将前一小节的Makefile进行一定修改得到。如下:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc AR = ar ARFLAGS = crs DIR_OBJS = objs DIR_EXES = ../../../build/exes DIR_DEPS = deps DIR_LIBS = ../../../build/libs DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS) RMS = $(DIR_OBJS) $(DIR_DEPS) EXE = ifneq ("$(EXE)", "") EXE := $(addprefix $(DIR_EXES)/, $(EXE)) RMS += $(EXE) endif LIB = libfoo.a ifneq ("$(LIB)", "") EXE := $(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)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif ifeq ("$(wildcard $(DIR_LIBS))", "") DEP_DIR_LIBS := $(DIR_LIBS) endif all: $(EXE) $(LIB) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(LIB): $(DEP_DIR_LIBS) $(OBJS) $(AR) $(ARFLAGS) $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(RMS)
以上修改:
- 增加了AR和ARFLAGS两个变量,它们被用于静态库的创建。ar工具的使用请参考bintuil编译工具链中的说明。
- 将exes目录的实际位置以相对路径的形式赋值给DIR_EXES变量。
- 增加了DIR_LIBS变量以记录libs目录的实际位置,同样采用相对路径的形式。
- 在DIRS变量中增加了DIR_LIBS变量的值,以便于创建build/libs目录。
- 新增了RMS变量用于表示需要删除的目录和文件。由于这个Makefile只是针对libfoo.a库的,使用当运行“make clean”时,不应将位于build目录下的exes和libs目录全部删除,这与之前项目实例不太一样。
- 清除了对EXE变量所赋的值,同时增加了ifneq条件语句用于判断EXE变量的值是否为空。只有当EXE不为空时才需要为EXE变量的值增加目录前缀并将$(EXE)加入到RMS变量中以便调用“make clean”时清除它。
- 新增了LIB变量,用于存放最终生成库的名字,目前这个值被设置为libfoo.a。同样采用处理EXE变量的方法,使用条件语法来决定是否需要为LIB变量中的值增加目录前缀。
- 为all目录增加$(LIB)先决条件。
- 增加了一条用于生成库的规则,在规则的命令体中使用ar工具来生成库。
- 在clean目标命令中,采用删除RMS变量中的内容而不是DIRS变量中的内容的方式。这一点前面说过了,因为我们不希望在foo模块中运行“make clean”时将build目录下的libs和exes目录也删除。
增进复用性
可以将公用部分放入一个独立的文件中——这就是build目录下make.rule文件的作业。那再foo模块的Makefile中,哪些是不能公用的呢?
- 变量EXE和LIB的定义对于每一个软件模块是不同的。比如在该项目中,需要将code/foo/src目录下Makefile中的LIB变量设置为“libfoo.a”,且EXE变量应当为空。但是,在code/huge/src目录中的Makefile内却要反过来,只定义EXE变量的值为“huge.exe”。
- DIR_EXES变量和DIR_LIBS变量由于运用了相对路径,所以也是每个模块特有的。但是可以采用绝对路径的方式解决这个文件。比如,可以定义一个ROOT环境变量,其值设置为huge项目的根目录,这样的话,DIR_EXES和DIR_LIBS就可以以ROOT为相对路径,从而使得其值对于所有的模块都相同。
在考虑复用情况下,foo模块的Makefile由两部分组成,分别是build目录中的make.rule和code/foo/src目录中的Makefile,内容如下:
- huge/build/make.rule
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc AR = ar ARFLAGS = crs DIR_OBJS = objs DIR_EXES = $(ROOT)/build/exes DIR_DEPS = deps DIR_LIBS = $(ROOT)/build/libs DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS) RMS = $(DIR_OBJS) $(DIR_DEPS) #EXE = 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)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif ifeq ("$(wildcard $(DIR_LIBS))", "") DEP_DIR_LIBS := $(DIR_LIBS) endif all: $(EXE) $(LIB) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -o $@ $(filter %.o, $^) $(LIB): $(DEP_DIR_LIBS) $(OBJS) $(AR) $(ARFLAGS) $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(RMS)
- huge/code/foo/src/Makefile
EXE = LIB = libfoo.a include $(ROOT)/build/make.rule
接下来在code/huge/src目录下创建新的Makefile及一个main.c文件来验证执行文件的编译。
- code/huge/src/Makefile
EXE = huge LIB = include $(ROOT)/build/make.rule
- main.c
#include <stdio.h> int main() { printf("hello world!\n"); return 0; }
验证:
支持头文件目录的指定
现在,是时候将项目文件放入各目录结构中,目前准备huge项目具有三个文件如下:
- huge/code/foo/inc/foo.h
1#ifndef __FOO_H #define __FOO_H void foo(); #endif
- huge/code/foo/src/foo.c
#include <stdio.h> #include "foo.h" void foo() { printf("this is foo!\n"); }
- huge/code/huge/src/main.c
#include "foo.h" int main() { foo(); return 0; }
修改Makefile添加INCLUDE_DIR变量设置
- huge/build/make.rule
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc AR = ar ARFLAGS = crs DIR_OBJS = objs DIR_EXES = $(ROOT)/build/exes DIR_DEPS = deps DIR_LIBS = $(ROOT)/build/libs DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS) RMS = $(DIR_OBJS) $(DIR_DEPS) #EXE = 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)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif ifeq ("$(wildcard $(DIR_LIBS))", "") DEP_DIR_LIBS := $(DIR_LIBS) endif all: $(EXE) $(LIB) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif ifneq ($(INCLUDE_DIRS), "") INCLUDE_DIRS := $(strip $(INCLUDE_DIRS)) INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS)) endif ifneq ($(LINK_LIBS), "") LINK_LIBS := $(strip $(LINK_LIBS)) LINK_LIBS := $(addprefix -l, $(LINK_LIBS)) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS) $(LIB): $(DEP_DIR_LIBS) $(OBJS) $(AR) $(ARFLAGS) $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(RMS)
- huge/code/huge/src/Makefile
EXE = huge LIB = INCLUDE_DIRS = $(ROOT)/code/foo/inc LINK_LIBS = foo include $(ROOT)/build/make.rule
- 执行
实现库链接
前一步已经完成了库的编译,但是还没有生成可执行文件。添加编译选项实现库链接。
- huge/build/make.rule
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc AR = ar ARFLAGS = crs DIR_OBJS = objs DIR_EXES = $(ROOT)/build/exes DIR_DEPS = deps DIR_LIBS = $(ROOT)/build/libs DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS) RMS = $(DIR_OBJS) $(DIR_DEPS) #EXE = 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)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif ifeq ("$(wildcard $(DIR_LIBS))", "") DEP_DIR_LIBS := $(DIR_LIBS) endif all: $(EXE) $(LIB) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif ifneq ($(INCLUDE_DIRS), "") INCLUDE_DIRS := $(strip $(INCLUDE_DIRS)) INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS)) endif ifneq ($(LINK_LIBS), "") LINK_LIBS := $(strip $(LINK_LIBS)) LINK_LIBS := $(addprefix -l, $(LINK_LIBS)) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS) $(LIB): $(DEP_DIR_LIBS) $(OBJS) $(AR) $(ARFLAGS) $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(RMS)
- huge/code/huge/src/Makefile
EXE = huge LIB = INCLUDE_DIRS = $(ROOT)/code/foo/inc LINK_LIBS = foo include $(ROOT)/build/make.rule
其中的改动有:
- 在make.rule文件中增加了对LINK_LIBS变量的引用,这个变量用来存放可执行程序在链接时所需要用到的所有库。
- 在make.rule中将( D I R L I B S ) 通 过 g c c 的 − L 选 项 加 入 到 了 编 译 器 的 库 搜 索 目 录 列 表 中 。 在 h u g e 项 目 中 , 采 用 将 所 有 的 库 文 件 都 放 入 (DIR_LIBS)通过gcc的-L选项加入到了编译器的库搜索目录列表中。在huge项目中,采用将所有的库文件都放入(DIR
- L
- IBS)通过gcc的−L选项加入到了编译器的库搜索目录列表中。在huge项目中,采用将所有的库文件都放入(DIR_LIBS)目录中这种方式简化了Makefile的实现。
- 在各模块的src目录下的Makefile中增加了LINK_LIBS变量的定义,并且在code/huge/src/makefile中对LINK_LIBS赋值为“foo”。在linux中,一个静态库的个数是libxxx.a,其中xxx就是采用gcc的-l选项时所需要用到的名。在这里用到的“foo”指的就是libfoo.a库。
- 执行结果
增加一个bar模块
现在,在huge项目中 增加一个bar模块来验证我们编译系统设计的情况。bar这个模块将生成libbar.a静态库,在code目录下新建bar目录以及内部的src和inc,在src中新建并编译好Makefile。具体如下:
- huge/code/bar/src/bar.c
#include #include "bar.h" void bar() { printf("this is bar()!\n"); }
- huge/code/bar/inc/bar.h
#ifndef __BAR_H #define __BAR_H void bar(); #endif
- huge/code/bar/src/Makefile
EXE = LIB = libbar.a INCLUDE_DIRS = $(ROOT)/code/bar/inc LINK_LIBS = include $(ROOT)/build/make.rule
* 执行:
- 修改主编译Makefile
EXE = huge LIB = INCLUDE_DIRS = $(ROOT)/code/foo/inc INCLUDE_DIRS += $(ROOT)/code/bar/inc LINK_LIBS = foo bar include $(ROOT)/build/make.rule
执行:
增强可使用性
从前面看,为了编译huge项目需要进入不同的目录运行make,这一步可以被简化,需要通过修改build目录下的Makefile来实现。
- huge/build/Makefile
.PHONY: all clean ROOT = $(realpath ..) DIRS = $(ROOT)/code/foo/src \ $(ROOT)/code/bar/src \ $(ROOT)/code/huge/src RM = rm RMFLAGS = -rf RMS = $(ROOT)/build/exes $(ROOT)/build/libs all clean: @set -e; \ for dir in $(DIRS);\ do \ cd $$dir && $(MAKE) ROOT=$(ROOT) $@; \ done @set -e; \ if [ "$(MAKECMDGOALS)" = "clean" ]; then $(RM) $(RMFLAGS) $(RMS); fi @echo "" @echo ":-) Completed" @echo ""
- 执行结果(make编译会执行目录下所有的Makefile文件)
管理对库的依赖关系
当我们修改了foo模块中的foo.c文件,重新make后发现libfoo.a库被重新编译了,这是对的,但是发现exes可执行文件目标并没有因此重新被编译。这是因为可执行文件的编译并没有标明对库的依赖,我们修改makefile文件来使得可执行文件依赖库文件。
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc AR = ar ARFLAGS = crs DIR_OBJS = objs DIR_EXES = $(ROOT)/build/exes DIR_DEPS = deps DIR_LIBS = $(ROOT)/build/libs DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS) RMS = $(DIR_OBJS) $(DIR_DEPS) #EXE = 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)) ifeq ("$(wildcard $(DIR_OBJS))", "") DEP_DIR_OBJS := $(DIR_OBJS) endif ifeq ("$(wildcard $(DIR_EXES))", "") DEP_DIR_EXES := $(DIR_EXES) endif ifeq ("$(wildcard $(DIR_DEPS))", "") DEP_DIR_DEPS := $(DIR_DEPS) endif ifeq ("$(wildcard $(DIR_LIBS))", "") DEP_DIR_LIBS := $(DIR_LIBS) endif all: $(EXE) $(LIB) ifneq ($(MAKECMDGOALS), clean) include $(DEPS) endif ifneq ($(INCLUDE_DIRS), "") INCLUDE_DIRS := $(strip $(INCLUDE_DIRS)) INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS)) endif ifneq ($(LINK_LIBS), "") LINK_LIBS := $(strip $(LINK_LIBS)) LIB_ALL := $(notdir $(wildcard $(DIR_LIBS)/*)) LIB_FILTERED := $(addprefix %, $(addprefix lib, $(LINK_LIBS))) $(eval DEP_LIBS = $(filter $(LIB_FILTERED), $(LIB_ALL))) DEP_LIBS := $(addprefix $(DIR_LIBS)/, $(DEP_LIBS)) LINK_LIBS := $(addprefix -l, $(LINK_LIBS)) endif $(DIRS): $(MKDIR) $@ $(EXE): $(DEP_DIR_EXES) $(OBJS) $(DEP_LIBS) $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS) $(LIB): $(DEP_DIR_LIBS) $(OBJS) $(AR) $(ARFLAGS) $@ $(filter %.o, $^) $(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^) $(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c @echo "Creating $@ ..." @set -e; \ $(RM) $(RMFLAGS) $@.tmp; \ $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \ sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\ $(RM) $(RMFLAGS) $@.tmp clean: $(RM) $(RMFLAGS) $(RMS)
总结
本文根据李云先生写的书籍《专业嵌入式软件开发—全面走向高质高效编程》中的第三章的内容总结了Makefile的编写方式及构建完整的编译工程。当然,在实际使用过程中,还有很多问题和情况,还需要我们继续去努力发现并改善。