关闭

Makefile 理解

260人阅读 评论(0) 收藏 举报

最近因为需要了解bootloader的一些内容所以看顺便看了一下Mafefile的编写规则。当然了解的只是皮毛了,作为一个学习记录与总结 还是写一篇小文章吧。
作为我在正式工作后的第一次的写博客 也把在百度博客上的文章copy过来了, 当然那是在校时写得, 现在看来写的一般了,不过还是有些参考价值吧, 呵呵 写的差大家多多批评,指教。
呵呵 又说废话了。
先介绍一下两篇中文 文章 《跟我一起写Makefile》 《GNU make中文手册》 
我看的是第一篇文章,第二篇还没有来得及看,看似写的更好!以后有时间再看。
我写的就是一个读后感吧,做一个总结 呵呵。
在Linux下编程,都有看到Makefile吧,一个人能不能看懂Makefile也就说明了是否是合格的linux程序员,当然了能写Makefile怎能展示一个人在linux上完成大型项目的能力。我是没达到了,我现撑死也就是能够看懂Makefile了。
Makefile最重要的功能就是进行自动编译功能,linux内核,包括Android系统都是靠着Makefile来编译。大家知道一个源程序要最后变为可执行的程序要经过编译(compile)生成目标文件、 链接(link)将目标文件合成一个可执行文件。
OK 大家都知道Makefile用三个基本的条件
target ... : prerequisites ...
        command (一定是tab来做的空格)
        command
        .
        .
也就是我们说的目标 依赖 命令 却是Makefile就是这些东西了,不过真正用起来还是比较困难的。
最简单的:
main : main.o
        gcc -o main main.o
main.o : main.c
        gcc -c main.c
clean:
        rm -f mian.o
保存为Makefile(也可以保存为其他文件名用-f 来制定)。执行make命令会去找当前目录下得Makefile的第一个目标,根据依赖文件来执行命令,如果依赖文件找不到 则会向下查找看是否在另一个目标中去生成依赖文件,或者根据make命令的隐含规则去生成依赖文件。
当执行make clean 时候会将生成的目标文件 main.o 删除
以上的make 执行后默认执行main边也就是最终的目标,次目标依赖与main.o文件,而main.o在一开始是不存在的,所以去超找有没有以main.o作为目标的段,如果有次目标,就会根据依赖来执行命令,生成main.o,然后在执行main目标的的命令来生成这个目标文件。
其实make命令是很智能的, 他会去检测目标是否已经生成,如果应生成且依赖修改的时间早与目标生成时间就不去执行命令了,当然当依赖也是又目标生成是还会去递归查找直到查找到真正的 C C++ 文件看他们的修改时间来确定时候执行目标的命令。好像写的有点含糊,反正就是make比较智能,只有在修改源程序后在执行make才会有选择的执行一些命令,对于没有修改的源程序就不在重复执行命令。这个先不了解也木有关系。
上边说到了隐含规则 比如C C++源程序在执行make生成一个可以行文件时候会依赖很多目标文件(.o)而我得Makefile中只说了一览这些.o文件但是没有给这些文件一个生成的命令。make会根据一个默认的命令来生成这些.o文件。
这就是Makefile的核心, 其实难点是不在这里,难点是一些伪目标, 变量使用 ,以及一些函数, 知道了这些就可以知道整个工程的架构与流程(只要肯研究的话)。
最难得地方其实是 编译器(gcc /ld ....)的一些选项 天啊这几天被搞得晕晕的。
几个难点 再研究一下 以后再写了 

 

 

先说说伪目标 与 变量: 函数不多但用法很灵活 换句话说就是 太复杂了 以后慢慢讨论
先看一个例子:

  1. OBJS = main.o a.o b.o  
  2.   
  3. main: $(OBJS)  
  4.     cc -o main $(OBJS)  
  5.   
  6. main.o: main.h  
  7.   
  8. a.o: a.h  
  9.   
  10. b.o: b.h  
  11.   
  12. <span style="color:#FF0000;">.PHONY: clean  
  13. clean:  
  14.     rm -f main $(OBJS)</span>  

  1. OBJS = main.o a.o b.o  
  2.   
  3. main: $(OBJS)  
  4.     cc -o main $(OBJS)  
  5.   
  6. main.o: main.h  
  7.   
  8. a.o: a.h  
  9.   
  10. b.o: b.h  
  11.   
  12. <span style="color:#FF0000;">.PHONY: clean  
  13. clean:  
  14.     rm -f main $(OBJS)</span>  
伪目标就是以关键字.PHONY开头的,表示clean是一个为目标,因为我们并不会去生成一个clean文件,所以伪目标并不是一个文件而是一个标签,既然在编译时生成了一下目标文件与可执行文件,就应该可以将他们都删除(make clean)以便重新编译,

  1. OBJS = main.o a.o b.o  
  2.   
  3. <span style="color:#FF0000;">.PHINY: all  
  4. all:main</span>  
  5.   
  6. main: $(OBJS)  
  7.     cc -o main $(OBJS)  
  8.   
  9. main.o: main.h  
  10.   
  11. a.o: a.h  
  12.   
  13. b.o: b.h  
  14.   
  15. <span style="color:#FF0000;">.PHONY: clean  
  16. clean:  
  17.     rm -f main $(OBJS)</span>  

  1. OBJS = main.o a.o b.o  
  2.   
  3. <span style="color:#FF0000;">.PHINY: all  
  4. all:main</span>  
  5.   
  6. main: $(OBJS)  
  7.     cc -o main $(OBJS)  
  8.   
  9. main.o: main.h  
  10.   
  11. a.o: a.h  
  12.   
  13. b.o: b.h  
  14.   
  15. <span style="color:#FF0000;">.PHONY: clean  
  16. clean:  
  17.     rm -f main $(OBJS)</span>  
如果将Makefile写成这样就可以使用make all 编译全部  make clean 删除中间件以及生成的可执行文件,这也是写Makefile的一般规则 当然可以有更所的伪目标根据项目需要

变量的使用
在Makefile中变量就是 一个字符串文本 在使用的地方会自动原样展开,上面那个例子就用到了OBJS这个变量, 展开后就是main.o a.o b.o
在变量是使用时 使用$在变量之前最好加上()或者{ }  $(OBJS)   如果要表示真正的$符就得用$$表示,

  1. FOO = $(PHELLO)  
  2. PHELLO = $(HELLO)  
  3. HELLO = hello  
  4. all:  
  5.     echo $(FOO)  

  1. FOO = $(PHELLO)  
  2. PHELLO = $(HELLO)  
  3. HELLO = hello  
  4. all:  
  5.     echo $(FOO)  

make all 时候会打印出hello出来, 这样看起来 变量的声明是不需要一定顺序的 ,但是看起来很费劲, 你得FOO引用到PHELLO 而PHELLO在FOO下面定义,这样如果一个定义引用到隔着n多行的定义 看起来就没那么容易了
为了避免上边的问题,make中使用另一种变量定义方法这样他前面的变量不能使用后面的变量

  1. X := main.c  
  2. Y := $(X) a.c  
  3. Z := $(Y) b.c  

  1. X := main.c  
  2. Y := $(X) a.c  
  3. Z := $(Y) b.c  

Z的值是main.c a.c b.c
而如果这样:

  1. Z := $(Y) b.c  
  2. Y := $(X) a.c  
  3. X := main.c  

  1. Z := $(Y) b.c  
  2. Y := $(X) a.c  
  3. X := main.c  
那么Z的值也就是 b.c 因为在Z上面没有定义Y所以默认Y就是空

上面是比较简单的例子看一个带MAKELEVEL的(这是指当make嵌套的层数)

  1. <pre name="code" class="plain">ifeq ($(MAKELEVEL), 0)  
  2. CUR_DIR := $(shell pwd)  
  3. WHOAMI := $(shell whoami)  
  4. HOST_TYPE := $(shell arch)  
  5. endif  

  1. <pre name="code" class="plain">ifeq ($(MAKELEVEL), 0)  
  2. CUR_DIR := $(shell pwd)  
  3. WHOAMI := $(shell whoami)  
  4. HOST_TYPE := $(shell arch)  
  5. endif  

  1. FOO ?= bar  
  2. 等价于  
  3. ifeq($(origin FOO), undefined)  
  4.     FOO = bar  
  5. endif   

  1. FOO ?= bar  
  2. 等价于  
  3. ifeq($(origin FOO), undefined)  
  4.     FOO = bar  
  5. endif   
?=表示在此变量没有定义事对变量赋值,如果变量已经定义了则此句无效

追加变量值

  1. OBJS = main.o a.o b.o  
  2. OBJS += c.o  
  3. OBJS展开后就是main.o a.o b.o c.o  

  1. OBJS = main.o a.o b.o  
  2. OBJS += c.o  
  3. OBJS展开后就是main.o a.o b.o c.o  

  1. <span style="font-size:16px;"></span>  

make 是如何工作的
1、make target会在当前目录中查找Makefile 或者 makefile GNUmakefile 或者是自己用-f指定的文件(最好是Makefile)

2、找到后它会执行相应目标(如果没有target则默认执行第一个目标),并将这个目标作文最终目标

3、如果目标文件不存在或者目标依赖的文件修改时间新于目标文件修改时间,则执行这个命令生成新的目标

4、如果依赖存在而依赖在Makefile中也有作为target,就根据上边的规则生成依赖文件

Makefile中有五个规则:显示规则,隐式规则,变量,文件,注释
从简单的说起
注释: 以#开头的字串在一行内 如果需要换行则在行末 加 \ 表示下一行是接上一行的
显示规则: 用全部的命令说明如何生成目标(命令必须用tab键开头)
隐式规则: 利用make的自动推导能力,不给目标生成的命令,甚至不给目标,让他自己生成一些文件
变量: 类似C语言中的宏定义,在使用的时候展开
文件:

1、Makefile文件
2、include的文件 相当于将include的文件在此处打开
3、预编译的很多命令行 可以累世#define 来自定义一些命令行 在执行命令时候调用

  1. #config.mk  
  2. GCC = gcc  
  3. GXX = g++  
  4.   
  5. CFLAGS = -g -o  
  6. CCFLAGS = -g -o  

  1. #config.mk  
  2. GCC = gcc  
  3. GXX = g++  
  4.   
  5. CFLAGS = -g -o  
  6. CCFLAGS = -g -o  

  1. <span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code" class="plain" style="margin: 4px 0px; background-color: rgb(240, 240, 240);">#Makefile  
  2. #remarks  
  3. include config.mk               #file include  
  4. OBJS := main.o fto.o start_fto.o gps_power.o    #变量  
  5. INC_FILE := fto.h gps_power.h           #  
  6. SOURCE := main.c fto.c start_fto.c gps_power.c  #  
  7.   
  8. .PHONY: all                 #伪目标  
  9. all: gpsrouter                            
  10. gpsrouter : $(OBJS)             #                 
  11.     $(GCC) $(CFLAGS) $@ $<               #显示规则  
  12.   
  13. $(OBJS): $(SOURCE) $(INC_FILE)            
  14. #使用隐式规则  

  1. <span style="font-family:Arial, Verdana, sans-serif;"><span style="white-space: normal;"></span></span><pre name="code" class="plain" style="margin: 4px 0px; background-color: rgb(240, 240, 240);">#Makefile  
  2. #remarks  
  3. include config.mk               #file include  
  4. OBJS := main.o fto.o start_fto.o gps_power.o    #变量  
  5. INC_FILE := fto.h gps_power.h           #  
  6. SOURCE := main.c fto.c start_fto.c gps_power.c  #  
  7.   
  8. .PHONY: all                 #伪目标  
  9. all: gpsrouter                            
  10. gpsrouter : $(OBJS)             #                 
  11.     $(GCC) $(CFLAGS) $@ $<               #显示规则  
  12.   
  13. $(OBJS): $(SOURCE) $(INC_FILE)            
  14. #使用隐式规则  

  在Makefile中也可以使用通配符 *.c 就表示对应目录下得所有文件以.c结尾的文件。在Makefile中如果用到相应文件,但文件找不到就会报错,在命令执行错误时也会报错make无法继续执行,比如在Makefile中需要新建一个目录而这个目录已经存在此时会报错并停止执行make而这个错误是可以容忍的,这时可以在命令的命令开头加‘-’符 或者在include前加‘-’就可以避免类似问题。

  1. #remarks  
  2. -include config.mk              #file include  if can't find the file skip  
  3. OBJS := main.o fto.o start_fto.o gps_power.o    #变量  
  4. INC_FILE := fto.h gps_power.h           #  
  5. SOURCE := main.c fto.c start_fto.c gps_power.c  #  
  6.   
  7. .PHONY: all                 #伪目标  
  8. all: gpsrouter                        
  9. gpsrouter : $(OBJS)             #                 
  10.     $(GCC) $(CFLAGS) $@ $<\  
  11.     -mkdir executable\  
  12.     cp $@ executable  
  13.   
  14. $(OBJS): $(SOURCE) $(INC_FILE)            
  15. #使用隐式规则  

终于将C++代码porting到了bootloader中,唯一的收获就是熟悉了Makefile,有工厂的公司伤不起啊,每次都得先做一些东西满足工厂端测试的需求,为了能够做到工厂在boot中测试的要求,经过与芯片公司讨论,只有将C++弄到了boot中,真是一个很二的决定boot最终做到了2M比有些kernel还要大。不过老板要这么做就硬着头皮做了现在做成了心中还蛮高兴的,原来被逼之后人的潜能还是蛮大的。

        对于Makefile中所用的函数还在研究中,之后会写一写主要函数的用法。

         因为从事着android驱动开发所以天天要接触Android.mk。
        1、在运行  . build/envsetup.sh 会生成一些操作例如:chooseproduct mmm
        2、运行 chooseproduct project  选择所要编译的工程
        3、运行make,回去编译整个android source

怎么说到了编译Android Source的方法呢

切入主题 先写一个简单的Android.mk

  1. LOCAL_PATH := $(call my-dir)             #指定当前目录   
  2.      
  3. include $(CLEAR_VARS)                    #引入编译变量   
  4.   
  5. LOCAL_MODULE_TAGS := optional            #编译选项便是在何种情况下编译   
  6.   
  7. LOCAL_SRC_FILES := hello.c               #源文件(可以指定多个)    
  8.    
  9. LOCAL_MODULE := hello                    #编译出来的模块名   
  10.   
  11. LOCAL_MODULE_CLASS := EXECUTABLES        #指定编译之后放置的位置(此处指示放在system/bin下)   
  12.   
  13. include $(BUILD_EXECUTABLE)              #引入编译成可执行文件的规则   

  1. LOCAL_PATH := $(call my-dir)             #指定当前目录   
  2.      
  3. include $(CLEAR_VARS)                    #引入编译变量   
  4.   
  5. LOCAL_MODULE_TAGS := optional            #编译选项便是在何种情况下编译   
  6.   
  7. LOCAL_SRC_FILES := hello.c               #源文件(可以指定多个)    
  8.    
  9. LOCAL_MODULE := hello                    #编译出来的模块名   
  10.   
  11. LOCAL_MODULE_CLASS := EXECUTABLES        #指定编译之后放置的位置(此处指示放在system/bin下)   
  12.   
  13. include $(BUILD_EXECUTABLE)              #引入编译成可执行文件的规则   

以上的Android.mk 会编译出一个hello的可执行文件,并放入system/bin

LOCAL_PATH : 指定目录,以上是一般用法指示当前目录(my-dir函数是Android编译时. build/envsetup.sh命令生成的 能够获取当前目录的路径)

CLEAR_VARS: android有自己的一套代码编译规则跟编译选项等变量的定义,此变量会引入,实际是android/build/core下的clear_vas.mk,

clear_vas.mk

  1. ##########################################################  
  2. ## Clear out values of all variables used by rule templates.  
  3. ###########################################################  
  4.   
  5. LOCAL_MODULE:=                                                                            
  6. LOCAL_MODULE_PATH:=  
  7. LOCAL_MODULE_STEM:=  
  8. LOCAL_DONT_CHECK_MODULE:=  
  9. LOCAL_CHECKED_MODULE:=   
  10. .  
  11. .  
  12. .  
  13. LOCAL_CERTIFICATE:=  
  14. LOCAL_SDK_VERSION:=  
  15. LOCAL_NDK_VERSION:=  
  16. LOCAL_NO_EMMA_INSTRUMENT:=  
  17. LOCAL_NO_EMMA_COMPILE:=  
  18. LOCAL_PROGUARD_ENABLED:= # '',optonly,full,custom  
  19. LOCAL_PROGUARD_FLAGS:=  
  20. LOCAL_PROGUARD_FLAG_FILES:=  
  21. LOCAL_EMMA_COVERAGE_FILTER:=  
  22. LOCAL_MANIFEST_FILE:=  
  23. LOCAL_BUILD_HOST_DEX:=  
  24. LOCAL_DEX_PREOPT:=  
  25. LOCAL_DEX_PREOPT:=  
  26.   
  27. # Trim MAKEFILE_LIST so that $(call my-dir) doesn't need to  
  28. # iterate over thousands of entries every time.  
  29. # Leave the current makefile to make sure we don't break anything  
  30. # that expects to be able to find the name of the current makefile.  
  31. MAKEFILE_LIST := $(lastword $(MAKEFILE_LIST))  

  1. ##########################################################  
  2. ## Clear out values of all variables used by rule templates.  
  3. ###########################################################  
  4.   
  5. LOCAL_MODULE:=                                                                            
  6. LOCAL_MODULE_PATH:=  
  7. LOCAL_MODULE_STEM:=  
  8. LOCAL_DONT_CHECK_MODULE:=  
  9. LOCAL_CHECKED_MODULE:=   
  10. .  
  11. .  
  12. .  
  13. LOCAL_CERTIFICATE:=  
  14. LOCAL_SDK_VERSION:=  
  15. LOCAL_NDK_VERSION:=  
  16. LOCAL_NO_EMMA_INSTRUMENT:=  
  17. LOCAL_NO_EMMA_COMPILE:=  
  18. LOCAL_PROGUARD_ENABLED:= # '',optonly,full,custom  
  19. LOCAL_PROGUARD_FLAGS:=  
  20. LOCAL_PROGUARD_FLAG_FILES:=  
  21. LOCAL_EMMA_COVERAGE_FILTER:=  
  22. LOCAL_MANIFEST_FILE:=  
  23. LOCAL_BUILD_HOST_DEX:=  
  24. LOCAL_DEX_PREOPT:=  
  25. LOCAL_DEX_PREOPT:=  
  26.   
  27. # Trim MAKEFILE_LIST so that $(call my-dir) doesn't need to  
  28. # iterate over thousands of entries every time.  
  29. # Leave the current makefile to make sure we don't break anything  
  30. # that expects to be able to find the name of the current makefile.  
  31. MAKEFILE_LIST := $(lastword $(MAKEFILE_LIST))  
在编译C/C++代码时候常用的变量定义

LOCAL_MODULE 指示当前编译出来的模块名

LOCAL_MODULE_TAGS 标识在什么情况下去编译起模块

有几个选项

user 模块只在user下编译
eng 模块在eng模式下编译
tests test状态下编译
optional 此模块在所有版本下都编译
即 TARGET_BUILD_VARIANT=eng 编译TAGS为eng和optional的模块

LOCAL_SRC_FILES 表示编译出此模块需要的源程序 可以有多个

LOCAL_C_INCLUDE  如果不去调用标准库头文件,跟当前目录的头文件,则需要在此指定头文件的位置,在此指定头文件的目录。

LOCAL_STATIC_LIBRARIES:  指定需要连接的静态库像一些比较通用的标准库就无需指定了

LOCAL_SHARED_LIBRARIES: 指定需要连接的动态库

最后include $(BUILD_XXX) 表示编译出来的模块类型,有三种

BUILD_EXECUTABLE 编译成可执行的模块 build/core/host_executable.mk
BUILD_STATIC_LIBRARY 编译成静态库 build/core/host_static_library.mk
BUILD_SHARED_LIBRARY 编译成动态库 build/core/host_shared_library.mk
LOCAL_MODULE_CLASS 标识了所编译模块最后放置的位置,如果不指定,不会放到系统中,之后放在最后的obj目录下的对应目录中。

LOCAL_MODULE_CLASS := ETC                                    #表示放于system/etc目录

LOCAL_MODULE_CLASS := EXECUTABLES               #放于/system/bin

LOCAL_MODULE_CLASS := SHARED_LIBRARIES     #放在/system/lib下


build/core下有很多编译的全局的mk,如编译C/C++规则definitions.mk ......


再说一下编译内核模块的Makefile

  1. ifneq ($(KERNELRELEASE),)  #查看是都已定义kernel版本  
  2.   
  3. obj-m := gps_onoff.o       #如果已定义则编译出得模块名是gps_onoff.o  
  4.                            #(在linux2.6编译模块后会生成module_name.ko module_name.o   
  5.                            #使用insmod module_name.ko来装载模块moudule_name.o已经弃用   
  6.                            #即moudule_name.ko 是最终产物)  
  7.   
  8. else                       #第一次运行时候会走此分支  
  9.   
  10. PWD := $(shell pwd)        #指定源文件目录          
  11.   
  12. KDIR ?= /home/zk/POP_TD/marvell-pxa920-kernel      #指定到已经编译的内核的目录  
  13.                            #如果编译PC机上的模块则指定到当前运行的内核  
  14.                            #(uname -r 查看当前的内核版本)  
  15.                            #(此时内核版本也被定义KERNELRELEASE非空)  
  16.                                 
  17. #内核编译命令 此处可以指定硬件体系与交叉编译工具  
  18. all:   
  19.     $(MAKE) -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-eabi-   
  20.   
  21. #重新编译内核前需要先make clean 清空编译生成的文件 否则编译会出错  
  22. clean:   
  23.     rm -rf .* *.ko *.o *.cmd *.tmp_version *.mod.c *.order Module.*endif  

  1. ifneq ($(KERNELRELEASE),)  #查看是都已定义kernel版本  
  2.   
  3. obj-m := gps_onoff.o       #如果已定义则编译出得模块名是gps_onoff.o  
  4.                            #(在linux2.6编译模块后会生成module_name.ko module_name.o   
  5.                            #使用insmod module_name.ko来装载模块moudule_name.o已经弃用   
  6.                            #即moudule_name.ko 是最终产物)  
  7.   
  8. else                       #第一次运行时候会走此分支  
  9.   
  10. PWD := $(shell pwd)        #指定源文件目录          
  11.   
  12. KDIR ?= /home/zk/POP_TD/marvell-pxa920-kernel      #指定到已经编译的内核的目录  
  13.                            #如果编译PC机上的模块则指定到当前运行的内核  
  14.                            #(uname -r 查看当前的内核版本)  
  15.                            #(此时内核版本也被定义KERNELRELEASE非空)  
  16.                                 
  17. #内核编译命令 此处可以指定硬件体系与交叉编译工具  
  18. all:   
  19.     $(MAKE) -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-eabi-   
  20.   
  21. #重新编译内核前需要先make clean 清空编译生成的文件 否则编译会出错  
  22. clean:   
  23.     rm -rf .* *.ko *.o *.cmd *.tmp_version *.mod.c *.order Module.*endif  

编译模块的Mkefile会被读取两次,Makefile从命令行调用时候KERNELRELEASE尚未设置,

在运行到KDIR时即会指向一个内核构造树,

在运行$(MAKE)时 会会第二次运行make命令,此时设置obj-m 构造真正的内核模块

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:463917次
    • 积分:7774
    • 等级:
    • 排名:第2798名
    • 原创:291篇
    • 转载:252篇
    • 译文:1篇
    • 评论:25条
    最新评论