makefile基础及常用规则

      什么是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,是否包括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

去掉-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
      在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。具体可详见http://www.cnblogs.com/wanqieddy/archive/2011/09/21/2184257.html

(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比较,如果一样就会返回其中的某个值。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值