您的位置:首页 > 运维架构 > Linux

快速编写“专家级”makefile(3.提高编译环境的实用性)

2016-07-21 18:00 274 查看
首先创建一个 complicated 项目:

complicated / foo.h

#ifndef __FOO_H

#define __FOO_H


void foo();


#endif

[/code]

complicated / foo.c

#include <stdio.h>

#include "foo.h"


void foo()

{

printf("This is foo ()!\n");

}

[/code]

complicated / main.c

#include "foo.h"


int main()

{

foo();

return 0;

}

[/code]

1.有序的编译环境:

(1)将所有的目标文件放入 objs 子目录下

(2)将最终生成的可执行程序放入 exes 子目录下

1.1自动创建

.PHONY : all


MKDIR = mkdir

DIRS = objs exes


all : $(DIRS)


$(DIRS) :

$(MKDIR) $@

[/code]

运行结果

$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)

[/code]

运行结果:

$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

[/code]

但是,我们发现,当我们对 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)

[/code]

如上程序,我们只作了一点修改:

在生成 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)

[/code]

相比之下,作了如下更改:

增加了 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)

[/code]

当 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)

[/code]

运行结果:

$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

[/code]

complicated / foo.h

#ifndef __FOO_H

#define __FOO_H


#include "define.h"


#endif

[/code]

complicated / foo.c

#include <stdio.h>

#include "foo.h"


void foo()

{

printf("%s, this is foo()!\n", HELLO);

}

[/code]

complicated / main.c

#include "foo.h"


int main()

{

foo();

return 0;

}

[/code]

其中的改动包括:

增加 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

[/code]

complicated / other.h

#ifndef __OTHER_H

#define __OTHER_H


#define HELLO "hello"


#endif

[/code]

运行结果:

$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

[/code]

运行结果:

$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)

[/code]

从运行结果:

在 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) $@
<
e959
/li>

$(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)

[/code]

存在一个困惑:

在生成的依赖关系文件中, 其中的规则只描述了依赖关系,而没有任何的命令。 make 是如何知道使用哪些命令进行目标构建呢?

当一个 Makefile 中存在构建同一目标的不同规则时, make 会将这些规则合并在一起,合并的内容包括先决条件和命令。

参考文献:《专业嵌入式软件开发》李云·著

2016年7月5日,星期二
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息