什么是makefile?或许很多Windows的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。特别在Unix下的软件编译,你就不能不自己写makefile了。
我们用一个示例来说明Makefile的书写规则。在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成
(1)Makefile的规则。还是让我们先来粗略地看一看Makefile的规则。
target ... : prerequisites ...
command
target也就是一个目标文件,可以是Object File,也可以是执行文件,还可以是一个标签(Label)。 prerequisites就是要生成那个target所需要的文件或是目标。command也就是make需要执行的命令。这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。
Linux中对Makefile的使用,内核的Makefile分为5个组成部分:
A,Makefile 最顶层的Makefile
B,.config 内核的当前配置文档,由Kconfig在menu config时生成,编译时成为顶层Makefile的一部分
C,arch/$(ARCH)/Makefile 和体系结构相关的Makefile
D,scripts/Makefile.xxx 一些特定Makefile的规则
E,kbuild级别Makefile 各级目录下的大概约500个文档,编译时根据上层Makefile传下来的宏定义和其他编译规则,将源代码编译成模块或编入内核。顶层的Makefile文档读取.config文档的内容,并总体上负责build内核和模块。Arch Makefile则提供补充体系结构相关的信息。
(2)示例:
edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个.o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
(3) 现就最常接触到的就是linux各层目录下基于kbuild架构的kbuild Makefile文件规则做下说明和使用,核心内容主要包括:
A,目标定义。
就是用来定义哪些内容要做为模块编译,哪些要编译链接进内核。例如:obj-y += foo.o,表示要由foo.c或者foo.s文件编译得到foo.o并链接进内核,而obj-m则表示该文件要作为模块编译成ko,可以手动insmod加进去。而更常见的做法是根据.config文件的CONFIG_ 变量($变量标识变量的值)来决定文件的编译方式,如:
obj-$(CONFIG_ISDN) += isdn.o
除了obj-形式的目标以外,还有lib-y library库,hostprogs-y 主机程序等目标,但是基本都应用在特定的目录和场合下。
B,多文件模块的定义。
最简单的kbuild Makefile如上一节一句话的形式就够了,如果一个模块由多个文件组成,那么稍微复杂一些,采用模块名加 –objs后缀或者–y后缀(就是表示加载)的形式来定义模块的组成文件。如以下例子:
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o bitmap.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
模块的名字为ext2,由balloc.o和bitmap.o两个目标文件最终链接生成ext2.o 直至ext2.ko文件,是否包括xattr.o取决于内核配置文件的配置情况。
C,目录层次的迭代。
如:obj-$(CONFIG_EXT2_FS) += ext2/
如果CONFIG_EXT2_FS 的值为y或m,kbuild将会将ext2目录列入向下迭代的目标中,但是其作用也仅限于此,具体ext2目录下的文件是要作为模块编译还是链入内核,还是由ext2目录下的Makefile文件的内容来决定的。
(4)关于出现编译错误:makefile:1: *** 遗漏分隔符(或者*** missing separator. Stop)
简单的说,Makefile中的语句主要有2类,“规则”和“动作”,动作命令前需要有一个TAB字符,不能顶格,也不能多空格! 另外,make 的时候出现错误:commands commence before first target ,也是由于这个原因。另一个出错的原因是用\做换行符时,后面不能跟任何字符包括空格。
(5)交叉编译器的路径
由于makefile不是shell脚本,所以交叉编译器不能设置成带变量的路径。例如设置成:CROSS_COMPILE = $HOME/gcc/arm-eabi-4.4.3/bin/arm-eabi-,那么在调用时会以$来约束变量H,而不是把它当作一个整体路径。所以得用CROSS_COMPILE = /home/zxtcukk/gcc/arm-eabi-4.4.3/bin/arm-eabi-等绝对路径。
(6)编译错误:make: Nothing to be done for
原因是Makefile书写格式非常严格,大概如下:
all:
<TAB缩进>make -C $(KDIR) M=$(PWD) $(EXTRA_CFLAGS) modules
default:
<TAB缩进>make -C $(KDIR) M=$(PWD) $(EXTRA_CFLAGS) modules
clean:
<TAB缩进>make -C $(KDIR) M=$(PWD) clean
在拷贝网络代码的过程中,很可能原有的TAB被若干空格键所替代,就会出现Nothing to be done for...的错误了。
(7)常用语法和函数:
“ifeq strip”:ifeq 用来判断参数是否相等,如果相同则(条件为真)将“TEXT-IF-TRUE”作为make要执行的一部分,否则将“TEXT-IF-FALSE”作为make要执行的一部分。由于参数值可能是通过引用变量或者函数得到的,因而在展开过程中可能造成参数值中包含空字符(空格等)。一般在这种情况时我们使用“strip”函数来对它变量的值进行处理,去掉其中的空字符。格式为:
ifeq ($(strip $(foo)),yes)
TEXT-IF-EMPTY
endif
如果以不等于作为判断条件,用ifneq
过滤函数:filter,以模式过滤字符串中的单词,保留符合模式的单词。
GSL_TOUCH_ID := $(if $(filter gslX680,$(CUSTOM_KERNEL_TOUCHPANEL)),yes,no)
ifeq ($(GSL_TOUCH_ID),yes)
........
endif
filter还可以做逻辑或,因为在makefile中不可能用||来实现逻辑或,如下例,filter 的功能是将MAINLCD_SIZE分別和320X480及480X800比较,如果一样就满足
ifeq ($(strip $(MAINLCD_SIZE)), $(filter $(MAINLCD_SIZE), 240X400 320X480))
...执行前往
endif
call函数:call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:
$(call
,,,...)
当make执行这个函数时,参数中的变量,如$(1),$(2),$(3)等,会被参数,,依次取代。返回值就是call函数的返回值。例如:
reverse = $(1) $(2)
foo = $(call reverse,a,b)
那么,foo的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:
reverse = $(2) $(1)
foo = $(call reverse,a,b)
(8)关于o_shipped的说明,如何在kernel模块中使用已经编译好的obj文件
在Document/kbuild/modules.txt中看到这么一段:--- 3.3 Binary Blobs,Some external modules need to include an object file as a blob.kbuild has support for this, but requires the blob file to be named <filename>_shipped. When the kbuild rules kick in, a copy of <filename>_shipped is created with _shipped stripped off,giving us <filename>. This shortened filename can be used in the assignment to the module.
A,最开始使用 gcc -c ex_obj.c -o ex_obj.o_shipped 来做的,结果链接后不能正常工作。原来是一定要放在编译kernel的环境中编译这个obj。编译文件成功后,将这个.o拷贝到需要链接的模块目录。记住要添加_shipped后缀
B,然后在需要这个obj的模块中添加scull-y := main.o pipe.o access.o ex_obj.o,然后在源代码中调用相应的函数,ok,可以了。作用就是可以用来加密某些文件。
C,也就是,没有源码的二进制.o文件必须以原文件名加_shipped结尾,例如8123_bin.o_shipped,KBuild会把8123_bin.o_shipped复制为8123_bin.o,然后一起编译。
(9)关于.PHONY的用法
先看一个样例:
main1.c内容:
#include<stdio.h>
int main(void)
{
printf("main1\n");
}
main2.c内容:
#include<stdio.h>
int main(void)
{
printf("main2\n");
}
makefile的内容:
all:main1 main2
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o:main2.c
@gcc -c main2.c
clean:
@rm -f main2.o
执行完make all后再去执行make clean,发现main2.o还在,也即clean并没有执行到。什么原因呢?原来这里的目标clean没有任何依赖,make执行时认为这已经到“根上”了,将其忽略。关键字.PHONY可以解决这问题,告诉make该目标是“假的”,这时make为生成这个目标就会将其规则执行一次。.PHONY修饰的目标就是只有规则没有依赖。
改成:
all:main1 main2
main1: main1.c
@gcc main1.c -o main1
main2: main2.o
@gcc main2.o -o main2
main2.o:main2.c
@gcc -c main2.c
.PHONY:clean
clean:
@rm -f main2.o
再执行make clean就可以了。
(10)ccflags编译链接选项,类似宏
ifneq ($(CONFIG_MTK_CCCI_DEVICES),y)
ccflags-y += -D__USING_DUMMY_CCCI_API__
endif
如果CONFIG_MTK_CCCI_DEVICES定义成y,则下面的语句起作用。去掉-D,类似定义了__USING_DUMMY_CCCI_API__这个宏,在C代码中,用#ifdef __USING_DUMMY_CCCI_API__来使用。
(11)include -include
Makefile 中包含其它文件的关键字是“include”,它和 C 语言对头文件的包含方式一致。 “include”指示符告诉 make 暂停读取当前的 Makefile,而转去读取“include”指定的一个或者多个文件,完成以后再继续当前 Makefile 的读取。其形式如下include FILENAMES。切忌不能以 [Tab] 字符开始(如果一行以 [Tab] 字符开始 make 程序将此行作为一个命令行来处理)。
如果指示符“include”指定的文件不是以斜线开始(绝对路径,如/usr/src/Makefile...),而且当前目录下也不存在此文件;make将根据文件名试图在以下几个目录下查找:首先,查找使用命令行选项“-I”或者“--include-dir”指定的目录,如果找到指定的文件,则使用这个文件;否则继续依此搜索以下几个目录(如果其存在/usr/gnu/include”、“/usr/local/include”和“/usr/include”。当在这些目录下都没有找到“include”指定的文件时,make将会提示一个包含文件未找到的告警提示,但是不会立刻退出。而是继续处理Makefile的后续内容。
当完成读取整个Makefile后,make将试图使用规则来创建通过指示符“include”指定的但未找到的文件,当不能创建它时(没有创建这个文件的规则),make将提示致命错误并退出。会输出类似如下错误提示:
Makefile:错误的行数:未找到文件名:提示信息(No such file or directory)
Make: *** No rule to make target ‘’. Stop
-include FILENAMES...使用这种方式时,当所要包含的文件不存在时不会有错误提示、make也不会退出;除此之外,和第一种方式效果相同。
(12) = := ?= +=这四个符号的差别
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是追加等号后面的值
至于makefile中“=”和“:=”的区别到底有什么区别,因为给变量赋值时,两个符号都在使用。举一个例子清晰说明
1、“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
2、“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := xyz
(13)Makefile中跑shell命令
比如一个拷贝命令
CUR_PATH := device/rockchip/rkpx2/goc
$(shell cp -af $(CUR_PATH)/goc $(ANDROID_PRODUCT_OUT)/system/)
(14)内核的Makefile中加打印,确认编译时编到没有。Android.mk中同样可用
比如$(warning this is log1),就会在编译时显示这句this is log1,以确认编译路径。
也可以输出变量值
$(warning path is $(shell pwd)),编译时提示当前编译到的路径。回显/home/sw01/workspace/atc8317M/out/target/product/ac83xx_evb/obj/KERNEL_OBJ
(15)patsubst 与 subst 区别
格式:$(subst <from>,<to>,<text>),名称:字符串替换函数——subst。功能:把字串<text>中的<from>字符串替换成<to>。返回:函数返回被替换过后的字符串。
示例:
$(subst a,the,There is a big tree),
把“There is a big tree”中的“a”替换成“the”,返回结果是“There is the big tree”。
而对于patsubst,模式字符 “%”,只会把最末尾的替换。example:
SRC = a.c b.c m.c.c
all:
echo $(patsubst %.c,%.s,${SRC})
echo $(subst .c,.s, ${SRC})
a.s b.s m.c.s
a.s b.s m.s.s
(16)makefile中实现或操作
ifeq ($(strip $(PRODUCT_CUSTOMER)),$(filter $(PRODUCT_CUSTOMER),SEIWA BLAUPUNKT AVT))
可以巧妙地混用filter去达到以上的效果,filter 的功能是将PRODUCT_CUSTOMER分別和SEIWA及BLAUPUNKT或者AVT比较,如果一样就会返回其中的某个值。
================make menuconfig报错=================
重装系统后,执行make menuconfig报错,如下:
menubox.c:(.text+0x1002): undefined reference to `delwin'
menubox.c:(.text+0x100a): undefined reference to `delwin'
menubox.c:(.text+0x10a4): undefined reference to `wbkgdset'
menubox.c:(.text+0x10ab): undefined reference to `acs_map'
menubox.c:(.text+0x10b2): undefined reference to `waddch'
scripts/kconfig/lxdialog/menubox.o: In function `do_scroll':
menubox.c:(.text+0x55): undefined reference to `wrefresh'
scripts/kconfig/lxdialog/menubox.o: In function `print_arrows':
menubox.c:(.text+0x1a4): undefined reference to `wrefresh'
scripts/kconfig/lxdialog/menubox.o: In function `do_print_item':
menubox.c:(.text+0x3a9): undefined reference to `wrefresh'
scripts/kconfig/lxdialog/menubox.o: In function `print_buttons':
menubox.c:(.text+0x4b1): undefined reference to `wrefresh'
collect2: ld returned 1 exit status
make[1]: *** [scripts/kconfig/mconf] Error 1
make: *** [menuconfig] Error 2
解决方法:应该是安装工具链时安装的一个工具是libncurses5-dev:i386,应该装64位版本,用sudo apt-get install libncurses5-dev即可。