首先创建一个 complicated 项目:
complicated / foo.h
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif
complicated / foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("This is foo ()!\n");
}
complicated / main.c
#include "foo.h"
int main()
{
foo();
return 0;
}
1.有序的编译环境:
(1)将所有的目标文件放入 objs 子目录下
(2)将最终生成的可执行程序放入 exes 子目录下
1.1自动创建
.PHONY : all
MKDIR = mkdir
DIRS = objs exes
all : $(DIRS)
$(DIRS) :
$(MKDIR) $@
运行结果
$make
mkdir objs
mkdir exes
$ ls
Makefile exes objs
1.2通过目录管理文件:
实例:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS := $(DIR_OBJS) $(DIR_EXES)
EXE = complicated.exe
SRCS := $(wildcard *.c)
OBJS = $(SRCS : .c = .o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
EXES := $(addprefix $(DIR_EXES)/, $(EXE))
all : $(DIRS) $(EXE)
$(DIRS) :
$(MKDIR) $(DIRS)
$(EXE) : $(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o : %.c
$(CC) -o $@ -c $^
clean :
$(RM) $(RMFLAG) $(OBJS) $(EXE)
运行结果:
$make
mkdir objs
mkdir exes
cc -o objs/foo.o -c foo.c
cc -o objs/main.o -c main.c
cc -o exes/complicated.exe objs/foo.o objs/main.o
从运行结果:
- 通过使用 “addprefix” 函数,为每一个生成的目标文件加上 “objs” 前缀,使其被放入 objs 目录下
- 在构建目标文件的规则中为目标名加上 “objs/” 前缀,即增加 “$(DIR_OBJS) /” 前缀
- 在 clean 规则中增加对 $(EXE) 目标的删除
2.提升依赖关系:
现只对 foo.h 进行修改, 其他不变:
complicated / foo.h
#ifndef __FOO_H
#define __FOO_H
void foo(int _value)
#endif
但是,我们发现,当我们对 foo.h 进行修改后 make ,并没有重新编译,它无法检测到 foo.h 已被修改。
这是因为,在我们的 Makefile 中并没有出现 foo.h 这个先决条件,所以,我们对 Makefile 作如下修改:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIRS := $(DIR_OBJS) $(DIR_EXES)
EXE = complicated.exe
SRCS := $(wildcard *.c)
OBJS = $(SRCS : .c = .o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
EXES := $(addprefix $(DIR_EXES)/, $(EXE))
all : $(DIRS) $(EXE)
$(DIRS) :
$(MKDIR) $(DIRS)
$(EXE) : $(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o : %.c foo.h
$(CC) -o $@ -c $<
clean :
$(RM) $(RMFLAG) $(OBJS) $(EXE)
如上程序,我们只作了一点修改:
- 在生成 foo.o 这个目标文件时,加了 foo.h 先决条件
- “$<” 的目的是只将 .c 文件作为 gcc 的输入内容
2.1自动生成文件依赖关系:
如何通过 gcc 获得一个源文件对其他依赖文件的列表?
$ gcc -MM foo.c | sed 's , \( .*\) \.o[ :]*, objs/\1.o: ,g'
objs/foo.o : foo.c foo.h
gcc 还有一个非常有用的选项 -E ,这个选项告诉 gcc 只做预处理而不进行程序编译。
实例:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = comlicated.exe
EXE := $(addprefix $(DIR_EXES), $(EXE))
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 $@ ..."
@echo -e; \
$(RM) $(RMFLAG) $@.tmp; \
$(CC) -E -MM $^ > $@.tmp; \
sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(DIRS)
相比之下,作了如下更改:
- 增加了 DIR_DEPS 变量用于保存需要创建的 deps 目录名,以及将这个变量的值加入到 DIR 变量中,(存放依赖文件列表)
- 删除了目标文件创建归责中对于 foo.h 文件的依赖,并将这个规则中的自动变量从 $< 变回了 $^
- 增加了 DEPS 变量用于存放依赖文件
- 为 all 目标增加了对 $(DEPS) 的依赖
- 增加了一个用于创建依赖关系文件的规则,在这个规则中,使用了 gcc 的 -E 和 -MM 选项来获取依赖关系,在生成最终的依赖关系之前,使用了一个由 $@.tmp 表示的临时文件,且在依赖关系文件生成以后将其删除。在这个规则中,“set -e” 的作用是告诉 Shell ,再生成依赖关系文件的过程中如果出现任何错误就直接退出,从而停止后续的工作。
对于一下几点需要掌握:
- 对于规则中的每一条命令, make 都是在一个新的 Shell 下运行
- 如果希望多个命令在同一个 Shell 中运行,可以用 “;” 将这些命令连起来
- 当命令很长时,可以用 “ \ ” 将命令分成多行书写
运行结果:
$make
mkdir objs
mkdir exes
mkdir deps
Creating deps / foo.dep ...
Creating deps / main.dep ...
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe objs/foo.o objs/main.o
cat deps / foo.dep
objs / foo.o : foo.c foo.h
cat deps / main.dep
objs / main.o : main.c foo.h
2.2使用依赖关系文件
Makefile 中存在一个 include 指令,通过使用它,将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为 Makefile 的一部分。
实例:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = comlicated.exe
EXE := $(addprefix $(DIR_EXES), $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS :.c = .o)
OBJS := $(addprefix $(DIR_OBJS), $(OBJS))
DEPS = $(SRCS :.c = .dep)
DEPS := $(addprefix $(DIR_DEPS), $(DEPS))
all : $(EXE)
include $(DEPS)
$(DIRS) :
$(MKDIR) $@
$(EXE) : $(DIR_EXES) $(OBJS)
$(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS) / %.o : $(DIR_OBJS) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS) / %.dep : $(DIR_DEPS) %.c
@echo "Creating $@ ..."
@echo -e; \
$(RM) $(RMFLAG) $@.tmp; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(DIRS)
当 make 遇到 include 指令时会试图去构建所需包含进来的依赖文件,如此一来,我们就不需要显式地让 all 目标依赖于它们了。
但是,在有些 LINUX 操作系统上,这个 Makefile 无法正常工作,出现死循环,原因:
有些文件系统当目录中的文件被修改时,目录的时间戳也会随之更新,在创建依赖关系文件规则中,指定了 deps 目录是其第一个先决条件,于是, deps 目录时间戳的更改使得 make 又一次使用规则再次创建 foo.dep 和 main.dep ,如此导致循环。
具体解决如下:
- 如果 deps 目录不存在,则让 deps 目录成为规则的第一个先决条件
- 如果 deps 目录已经存在,则不要让 deps 目录出现在规则的先决条件中(使用 Makefile 中的条件语法)
2.3运用条件语法
——ifdef 、ifeq 、ifndef 、ifneq
形式一:
conditional - directive
text - if - true
endif
形式二:
conditional - directive
text - if - true
else
text - if - false
endif
形式三:
conditional - directive
text - if - one - is - true
else conditional - directive
text - if - true
else
text - if - false
endif
实例:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = comlicated.exe
EXE := $(addprefix $(DIR_EXES), $(EXE))
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 $@ ..."
@echo -e; \
$(RM) $(RMFLAG) $@.tmp; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(DIRS)
运行结果:
$make
mkdir deps
Creating deps / foo.dep ...
Creating deps / main.dep ...
mkdir objs
mkdir exes
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe objs/foo.o objs/main.o
从运行结果上:
- 增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值
- 对于每个变量,如果对应的目录不存在,则将目录名赋值给它,否则为空
- 增加的三个变量分别放到相应的规则中作为第一个先决条件
2.4为依赖关系文件建立依赖关系
现在我们增加对 complicated 源程序的修改,以便增加程序文件间依赖关系的复杂度
comlicated / define.h
#ifndef __DEFINE_H
#define __DEFINE_H
#define HELLO "hello"
#endif
complicated / foo.h
#ifndef __FOO_H
#define __FOO_H
#include "define.h"
#endif
complicated / foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("%s, this is foo()!\n", HELLO);
}
complicated / main.c
#include "foo.h"
int main()
{
foo();
return 0;
}
其中的改动包括:
- 增加 define.h 文件并在其中定义一个 HELLO 宏
- 在 foo.h 中包含 define.h 文件
- 在 foo.h 中增加对 HELLO 宏的引用
运行结果:
$make
mkdir deps
Creating deps / foo.dep ...
Creating deps / main.dep ...
mkdir objs
mkdir exes
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe objs/foo.o objs/main.o
cat deps / foo.dep
objs / foo.o : foo.c foo.h define.h
在此基础上,我们在做一些改动,在改动之前,不用运行 “make clean”
complicated / define.h
#ifndef __DEFINE_H
#define __DEFINE_H
#include "other.h"
#endif
complicated / other.h
#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "hello"
#endif
运行结果:
$make
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe objs/foo.o objs/main.o
$ ./exes/complicated.exe
hello, this is foo()!
从运行结果:
尽管 foo.c 和 main.c 文件被重新编译了,但依赖关系文件却没有被重新构建
现对 other.h 在再进行更改:
complicated / other.h
#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "Hi"
#endif
运行结果:
$make
make : Nothing to be done for 'all'.
cat deps / foo.dep
objs / foo.o : foo.c foo.h define.h
cat deps / main.dep
objs / main.o : main.c foo.h define.h
从运行结果:
当前面的 define.h 文件被更改为包含 other.h 文件后, foo.dep 和 main.dep 文件也应当重新被生成以反映出 foo.o 和 main.o 文件对 other.h 文件的依赖。正是由于依赖关系中没有正确地反映出对 other.h 文件的依赖,所有当对 other.h 文件进行更改时, make 不能发现需要对项目进行重新编译
解决方法:
$gcc -MM foo.c | sed 's , \(.*)\.o[ :]*, objs/\1.o deps / foo.dep : ,g'
objs / foo.o deps / foo.dep : foo.c foo.h define.h
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated.exe
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
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)", "")
DEPS_DIR_OBJS := $(DIR_OBJS)
endif
ifeq("$(wildcard) $(DIR_EXES)", "")
DEPS_DIR_EXES := $(DIR_EXES)
endif
ifeq("$(wildcard) $(DIR_DEPS)", "")
DEPS_DIR_DEPS := $(DIR_DEPS)
endif
all : $(EXES)
include $(DEPS)
$(DIRS) :
$(MKDIR) $(DIRS)
$(EXES) : $(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) $(RMFLAG) $@.tmp; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(DIRS)
从运行结果:
- 在 Makefile 中只需在构建依赖关系文件的规则中增加自动变量 $@ 就行了,因为它表示的是依赖关系文件名。
- 我们更改项目中的任意一个文件, make 都能发现并作出正确的重构响应。
- 当我们连续运行两次 make clean ,发现两次的运行结果不同
运行结果:
$make clean
rm -rf objs exes deps
$make clean
mkdir deps
Creating deps / main.dep
Creating deps / foo.dep
rm -rf objs exes deps
原因:
- 第一次运行 make clean ,make 直接调用 rm 命令删除相应的目录
- 第二次运行 make clean ,make 先会构建依赖文件,紧接着又将所有的目录删除
为了去除在 make clean 时不必要的依赖关系文件构建动作,可以运用条件语句,如:
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAG = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS := $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated.exe
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
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 : $(EXES)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
$(DIRS) :
$(MKDIR) $@
$(EXES) : $(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) $(RMFLAG) $@.tmp;\
$(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@;\
$(RM) $(RMFLAG) $@.tmp
clean :
$(RM) $(RMFLAG) $(DIRS)
存在一个困惑:
在生成的依赖关系文件中, 其中的规则只描述了依赖关系,而没有任何的命令。 make 是如何知道使用哪些命令进行目标构建呢?
当一个 Makefile 中存在构建同一目标的不同规则时, make 会将这些规则合并在一起,合并的内容包括先决条件和命令。
参考文献:《专业嵌入式软件开发》李云·著
2016年7月5日,星期二