嵌入式之行(5):我的Makefile

说明:
1、文中多处出现Makefile,它可以认为是一个具体的文件——即文件名就是“Makefile”,也可以认为它是抽象的“Makefile”,比如下文说到的“两个Makefile”,它们的名称肯定是不同的,但它们都是“Makefile”。——不知这样说,阁下能不能明白,我也没有好的文字表达了。
2、本文以小笑自己从网络、书籍总结的Makefile模板来讲一下有关的Makefile知识、技巧,当然,不可能很完整,不过能正常使用。

本文不打算讲述Makefile的来源、好处以及其它一些理论的知识,有关Makefile的知识可以写成一本书。网络上的《跟我一起写 Makefile》是一篇很好的文章,建议看一下。此处给出小笑的一个Makefile例子,它能应付基本的项目管理。小笑的毕业设计程序就是在这个Makefile基础上修改而来的。闲话不说,进入主题。

先来看一下具体的Makefile模板文件。其中的红色为小笑写的注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
######################################
# my Makefile template
#
# Uage:
# compile:"make all" or just "make"
# clean:"make clean"
#
# ChangeLog:
# 2010-4-21:
# add some info
# 2010-4-20:
# new for DEBUG
# another way to change SRCS to OBJECTS
# another way to generate .o file
########################################

### 宏定义DEBUG,我没有找到好的办法,只好出此下策。
DEBUG = y

# 这些就是传说中的编译器了,比如CC是C编译器、CPP是C++编译器,其它们都是宏来着。
# 也可以定义其它一些编译器,比如交叉编译器CROSS_COMPILER=arm-linux-gcc等等。
CC = gcc
CPP = g++


CROSS_COMPILER = arm-linux-gcc

###=====================================


#####>>>>>!!!!! C编译的一些标志,如打开警告,调试标志等 !!!!!!<<<<<#####
### C
CFLAGS = -Wall

##! 这里就是添加调试或优化标志,当然,也可以在上述宏中使用,不必这样麻烦。
ifeq ($(DEBUG), y)
CFLAGS += -g
else
CFLAGS += -O2
endif


#####>>>>>!!!!! 这是C++语言编译的一些标志,同C !!!!!!<<<<<#####
### C++
CPPFLAGS = -Wall

##! 一样的
ifeq ($(DEBUG), y)
CPPFLAGS += -g
else
CPPFLAGS += -O2
endif


###=====================================


#####>>>>>!!!!! 这些是别的一些宏,如链接库位置、名称等(我不知如何移称呼这个宏,百度吧) !!!!!!<<<<<#####
### 如-lpthread or -lncurses or -lpanel or -lmenu or -lm,等。
### 注意:有些库不是Linux默认的,比如多线程的pthread,如果编译时不加上的话,编译是不会通过的,
### 此外,还有ncurses库、SDL库等等,要注意一下。
### 在此处添加
LDFLAGS =
LDFLAGS +=


# 这个是删除使用到的宏
RM = rm -rf

###=====================================

### 此处添加目标名称
#####>>>!!最好起一个有意义的名称,比如采集视频数据的,可以是capture,显示用的,可以是display,等等 !!
target =

### 此处添加目标文件(即.o文件)
OBJECTS = .o
OBJECTS += .o

### 这是生成.o文件的另外一种方法,有点麻烦,但也可以。
#此处添加源文件
#SRCS =
#SRCS +=


# 生成相应的.o文件
#OBJECTS = $(SRCS:.c=.o)
#OBJECTS = $(SRCS:.cpp=.o)
###=====================================

### 真正的编译开始,all是一个伪目标,编译时的“make all”中的“all”就是它。
all: $(target)

##############################
# 这是另外一种方法:
# foo.o:foo.c foo.h(可以不使用.h文件的!)
# (tab) $(CC) $(CPPFLAGS) -c $< -o $@
#
# thread.o: thread.cpp thread.h
# $(CC) $(CPPFLAGS) -c $< -o $@
# main.o: main.cpp thread.cpp
# $(CC) $(CPPFLAGS) -c $< -o $@
#
# 这种方法就是实打实的,需要什么,添加什么,我也用过,也不麻烦,不妨一试。
##############################
# 这种方法简便一些
# 上述列出了所有用到的.o文件,都是依赖条件。
# $^:所有的依赖文件,$<:第一个依赖文件,$@:目标文件(可执行的程序)
$(target): $(OBJECTS)
$(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@

# 清除,比如中间文件,目标文件。
clean:
@echo "Cleaning..."
$(RM) $(OBJECTS) $(target)
@echo "Done"

# 安装。其实这个命令除了显示一些信息外,什么事也没做。
# 因为一般程序编译安装都是:make;make install;这样做,以防万一。
# 也可试试删除这几行,执行一下make install,看看效果。
install:
@echo " Note:"
@echo "To install or not install,that is the question"
@echo

# 声明了三个伪目标
.PHONY:all clean install
### end of the Makefile

Makefile的格式如下所示:

1
2
target : prerequisites 
        command

下面是《跟我一起写 Makefile》中的介绍:

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
prerequisites就是要生成那个target所需要的文件。
command就是make需要执行的命令。(任意的Shell命令)

下文所讲的“目标文件”,可能是指生成的“可执行文件”,也可能是指这里的“target”,假设读者应当能区别出来。

在使用Makefile来make程序时,最常见的提示信息就是“Nothing to be done for XXX”,很多人看到这个信息,很不理解(试一下那个make install测试吧)。我认为在两种情况下会出现这个提示信息,第一种情况,这个程序已经编译过一次了,已经生成了.o和可执行文件了,所有目标均已是最新,不需要再做一次“无用功”了——make是何等讲究效率!如果你想每次make all都从编译一次,可以在Makefile的all后添加clean,这个clean必须放在第一位置才能在每次编译前都清除一些中间生成的文件(.o文件)和目标文件。如下:

1
all: clean $(target)

不要担心clean在后面才出现,Makefile不管先后的。

另一种情况是声明了一个“伪目标”,但又make它,就会出现上面的提示信息,比如上述的Makefile模板中,如果去掉与目标install相关的几行语句,但最后却声明它是“伪目标”的话,当执行“make install”后,就会出现“Nothing to be done for install”。

当make一个不存在的目标时,会提示:

1
Make: *** No rule to make target ‘XXX’. Stop.

因为make确实找不到XXX,当然也不会去执行了。

make程序时,也不一定是“make all”,只要是一个在Makefile文件中出现的目标文件(target)即可。如果你将“all”改为“love”的话,你输入“make love”,照样能顺利通过编译,阁下不妨一试。

此外,还有一些make的技巧,比如一个程序要应用于两个平台(我写的视频采集程序要在PC上执行,也要在ARM上执行),程序是不用改多少的。最关键的就是编译器,当应用于ARM平台时,只要修改Makefile中的编译器就可以了。所以我的工程目录下有两个Makefile,比如PC平台中的为Makefile,而ARM平台的为arm-Makefile。这样,在编译PC平台的程序时,可以直接“make all”,因为make首先找到的是Makefile,就不会执行到arm-Makefile了。那么,交叉编译怎么办呢?make有一个-f选项,可以选择自定义的Makefile。不过在编译过程中,出现了一些问题,我明明在Makefile中指明了编译器为arm-linux-gcc了,但编译过程中有些文件还是被gcc所编译,造成链接的失败,我实在没有办法,只能显式指定CC选项了。这样,在交叉编译时,简单的“make all”,就变成了“make –f arm-Makefile CC=arm-linux-gcc”,由于同时要处理两个平台,不得不出此下策了。(其实也没有多么麻烦。)

我还发现gcc的-M选项的好处。此选项是在编译时指定某一个宏,在条件编译中特别有用。在PC机上,摄像头的设备文件为/dev/video,但在ARM开发板上却为/dev/video1,我也不知是怎么搞的,在开发板中,video不是video1的链接文件,我试了几次,结果还是一样。没办法,只好在程序显式使用宏定义来指定设备文件名称了。不过,为了方便,我使用了条件编译。如下:

1
2
3
4
5
#ifdef __ARM__
#define device “/dev/video1”
#else
#define device “/dev/video”
#endif

即使用ARM来选择设备文件的名称。

这时,gcc的-D选项就派上用场了,在arm-Makefile文件中的CFLAGS中添加了-DARM,在Makefile文件中不添加,这样就很好解决了两个平台的设备文件名称问题了。当然,如果仅仅只是针对ARM平台的话,这些也用不着,但不失为一种方法。(更正:以前写成“-M”选项是错误的!正确的选项为“-D”,特此说明并致歉!)

本着“够用即可”的原则,不再详细介绍Makefile了。毕竟,小笑也不太懂。

上述的Makefile可以认为是一个Makefile的模板,可以在它的基础上修改,成为适合自己的工程的“Makefile”,再总结出一个自己的Makefile模板。

再:写完这篇文章后,我读了几次,感觉不像我写作风格,总是表达不出想要表达的意思。自己文笔水平是一个问题,二来对Makefile的确了解不太深入。望诸君见谅!