茁壮成长篇
通过一个复杂的项目来实践更多关于 Makefile 的知识点,我们暂且将该项目命名为 playground(游乐场);该项目对 Makefile 的基本需求有:
将所有的目标文件放入源程序所在目录的 objs 子目录中;
将所有最终生成的可执行程序放入源程序所在目录的 exes 子目录中;
将引入用户头文件来模拟复杂项目的情形。
实例1:创建目录
编辑 Makefile
.PHONY: all
MKDIR = mkdir
DIRS = objs exes
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
编译执行
$ make
$ ll
$ ls
结果输出
语法说明
- 需要注意的是,OBJS 变量既是一个依赖目标,也是一个目录,不同场合其意义是不同。上述 Makefile 第一次make时, objs 和 exes 都不存在,所以 all 目标将它们视作是一个先决条件/依赖目标,接着 Makefile 先根据目录构建规则构建 objs 和 exes 目录,构建目录时,第二条规则中的命令被执行,创建 objs 和 exes 目录。
实例2:清除目录
编辑 Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
DIRS = objs exes
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
clean:
$(RM) $(RMFLAGS) $(DIRS)
编译执行
$ make
$ ls
$ make clean
$ ls
结果输出
语法说明
- 创建一个 clean 目标删除生成的目标文件和可执行文件。
实例3:增加头文件
foo.h 源码
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif
foo.c 源码
#include <stdio.h>
#include "foo.h"
void foo()
{
printf("This is foo()!\n");
}
main.c 源码
#include "foo.h"
int main()
{
foo();
return 0;
}
编辑 Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
EXE = playground
DIRS = objs exes
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
all: $(DIRS)
$(DIRS):
$(MKDIR) $@
$(EXE): $(OBJS)
$(CC) -o $@ $^
%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)
编译执行
$ make
$ ls
$ ./playground
$ make clean
结果输出
语法说明
- 在 all 后面增加了对 EXE 目标的依赖。当一个规则中出现多个先决条件时(如这里的 all 规则),make 会以从左到右的顺序来一个一个构建目标。
实例4:将生成的文件放入目录
编辑 Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES)
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
all: $(DIRS) $(EXE)
$(DIRS):
$(MKDIR) $@
$(EXE): $(OBJS)
$(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
$(CC) -o $@ -c $^
clean:
$(RM) $(RMFLAGS) $(DIRS) $(EXE)
编译执行
$ make
$ ls
$ cd exes
$ ls
$ cd ../objs/
$ ls
结果输出
语法说明
- 增加对于addprefix 函数运用为每一个目标文件加上"objs/" 前缀;
- 在构建目标文件的模式规则中的目标前也加上"objs/"前缀;
- 加上前缀原因:规则命令中 -o 选项需要以它作为目标文件的最终生成位置,同时为了是的Makefile中的目标创建规则被运用,也需要采用相类似的格式。
实例5:更复杂的依赖关系
编辑 Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
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 "Making $@ ..."
@set -e; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $^ > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)
编译执行
$ make
$ ls exes/
$ ls objs/
$ ls deps/
结果输出
语法说明
- 本例相对于 实例 4,完善更加全面的依赖关系;
- 实例 4 存在的问题:在已经成功编译过一次程序的背景下,对 foo.h 文件进行修改(由 void foo(); 改为 void foo(int)),然后重新编译,发现程序并不会报错,原因是因为实例23的Makefile并未对 foo 进行依赖,导致无法准确识别存在的问题。
关键技术:- gcc中的 -M 选项和 -MM 选项;两者的区别是:-MM选项不列出对于系统头文件的依赖关系,比如stdio.h;
- sed :可以对字符串进行查找和替换;
- set -e :作用是告诉 BASH Shell 当生成依赖文件的过程中出现任何错误时,就直接退出。最强终的表现就是make 会告诉我们出错了,从而停止后面的 make工作。如果不进行这一设置,当构建依赖文件出现错误时,make 还会继续后面的工作,这是我们所不希望的。
实例6:Makefile条件语句
编辑 Makefile
.PHONY: all
sharp = square
desk = square
table = circle
foo = defined
ifeq ($(sharp), $(desk))
result1 = "desk == sharp"
endif
ifneq "$(table)" 'square'
result2 = "table != square"
endif
ifdef foo
result3 = "foo is defined"
endif
ifdef zoo
resunlt4 = "zoo is not defined"
endif
all:
@echo $(result1)
@echo $(result2)
@echo $(result3)
@echo $(result4)
编译执行
$ make
结果输出
语法说明
- make 看到条件语法时将立即对其进行分析,这包括 ifdef、ifeq、ifndef 和 ifneq 四种语句形式。
小结:本项目 Makefile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
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))
all: $(EXE)
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif
$(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 "Making $@ ..."
@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)