Android源码编译详解(三)

 

唉!最近忙新项目,周末把笔记整理了下忘了发出来了,今天继续分享下源码编译的第三阶段,make阶段,由于这个阶段的工作主要是编译工具进行的,像gcc这种其内部实现是相当复杂,个人功力有限,暂时没法深入去了解!但是我们可以从大概看下的过程,然后再去看看make,m,mm等命令的具体区别!!

注意:不了解第一第二个阶段的可以先看看下面这两篇文章

Android源码编译详解(一)

Android源码编译详解(二)

一、make函数

1、make函数

从envsetup.sh脚本中我们知道make函数被定义,这也是我们最终执行make操作的入口,那么我们看看它的具体实现:

function make()
{
    local start_time=$(date +"%s")
    $(get_make_command) "$@"
    local ret=$?
    local end_time=$(date +"%s")
    local tdiff=$(($end_time-$start_time))
    local hours=$(($tdiff / 3600 ))
    local mins=$((($tdiff % 3600) / 60))
    local secs=$(($tdiff % 60))
    local ncolors=$(tput colors 2>/dev/null)
    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
        color_failed="\e[0;31m"
        color_success="\e[0;32m"
        color_reset="\e[00m"
    else
        color_failed=""
        color_success=""
        color_reset=""
    fi
    echo
    if [ $ret -eq 0 ] ; then
        echo -n -e "${color_success}#### make completed successfully "
    else
        echo -n -e "${color_failed}#### make failed to build some targets "
    fi
    if [ $hours -gt 0 ] ; then
        printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs
    elif [ $mins -gt 0 ] ; then
        printf "(%02g:%02g (mm:ss))" $mins $secs
    elif [ $secs -gt 0 ] ; then
        printf "(%s seconds)" $secs
    fi
    echo -e " ####${color_reset}"
    echo
    return $ret
}

这一堆脚本代码我们需要关注的其实只有$(get_make_command) "$@"这一句,而其它的只不过是获取下时间计算下编译所需时间而已,最后根据$?获取到返回的编译结果判断下,然后就提示是否编译成功等等!

那么我们看下$(get_make_command) "$@",其中$()这是个命令替换符,不认识也没关系,我们只需要知道他里面的get_make_command函数会被执行即可,那么按照惯例去看下get_make_command函数吧:

function get_make_command()
{
  echo command make
}

呵呵,你没有看错,确实就这一句代码,其实make过程之所以不好理解,就是因为连代码都没得看。。。。不过没关系,既然make过程是从command make这句代码开始的,那么他就是突破点。不太了解shell可以查看下linux相关的知识,这里的command make的意思就是接下来会执行make这个命令,而这里的make并不是指我们前面所说的envsetup.sh中定义的make()函数,这个make是linux下的一个编译工具。

2、make工具

对于make编译工具,这里建议大家就不深入去看了,毕竟内部涉及的东西比较晦涩,但是我们需要知道的是make工具最基本的功能是调用makefile文件,通过makefile文件来描述源程序之间的相互依赖关系并自动维护编译工作,那么我们不妨先去看下源码根目录下的makefile文件:

### DO NOT EDIT THIS FILE ###
include build/core/main.mk
### DO NOT EDIT THIS FILE ###

可以看到,加载了main.mk文件,其实到了这里,如果我们只是想编译个系统玩玩,那么我们也就没必要去深究main.mk到底都做了些什么,因为对mk语法不熟悉的话看起来也是一头雾水,有兴趣的朋友可以自行去查看相关的资料!

至此,我们的编译其实就已经开始了,但是我们平时使用最多的并非make这个全编指令,其实我们使用更多的是mm、mmm这样的指令,当然有的人也喜欢用m,所以我们很有必要去了解m、mm这些指令到底都做了些什么。

3、首先我们看看m

我们之前介绍过,m、mm、mmm这些函数都是在envsetup.sh中定义的,所以要了解他们的实现原理,还是需要去看源码,那么我们先看看m的源码:

function m()
{
    local T=$(gettop)
    local DRV=$(getdriver $T)
    if [ "$T" ]; then
        $DRV make -C $T -f build/core/main.mk $@
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return 1
    fi
}

m函数其实并不复杂,他首先是利用gettop函数去获取源码根目录,其内部获取原理其实都是通过寻找build/core/envsetup.mk这个文件实现的,只要找到该文件,那么就表示当前目录是源码的根目录。至于getdriver函数,这个我们暂时不知道干什么用的,返回了一堆字符串,不过我看其他的源码有时候也没有这一句,暂时不管它吧!!

其实最主要的也就这一句而已:make -C $T -f build/core/main.mk $@,这里我们先对T进行判断,gettop如果返回源码根目录给我们的话就执行make命令,并且通过-C跳转到根目录,并且指定main.mk文件,如果有参数的话还会通过$@获取参数。其实m做的事情就是对make的一个简单的封装,并没有实际的功能,所以我们平时直接用make即可,没必要m!

4、接着看mm

mm这个就很重要了,记得刚开始编译android源码的时候,不管干了什么,只要动了源码,都是直接make,虽然说公司电脑比较好出来第一次之外后来make也就花十来二十分钟时间,但是二十分中也是时间啊!!!后来同事跟我说有些改动可以mm的,够尴尬的。一句话,mm其实就是模块编译,也就是只编译我们想要编译的模块(当然也有所限制)

惯例我们先看看源码:

function mm()
{
    local T=$(gettop)
    local DRV=$(getdriver $T)
    # If we're sitting in the root of the build tree, just do a
    # normal make.
    if [ -f build/core/envsetup.mk -a -f Makefile ]; then
        $DRV make $@
    else
        # Find the closest Android.mk file.
        local M=$(findmakefile)
        local MODULES=
        local GET_INSTALL_PATH=
        local ARGS=
        # Remove the path to top as the makefilepath needs to be relative
        local M=`echo $M|sed 's:'$T'/::'`
        if [ ! "$T" ]; then
            echo "Couldn't locate the top of the tree.  Try setting TOP."
            return 1
        elif [ ! "$M" ]; then
            echo "Couldn't locate a makefile from the current directory."
            return 1
        else
            for ARG in $@; do
                case $ARG in
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;
                esac
            done
            if [ -n "$GET_INSTALL_PATH" ]; then
              MODULES=
              ARGS=GET-INSTALL-PATH
            else
              MODULES=all_modules
              ARGS=$@
            fi
            ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS
        fi
    fi
}

if [ -f build/core/envsetup.mk -a -f Makefile ]

我们看这个判断,先判断build/core/envsetup.mk以及当前目录下的Makefile文件是否合法,这里的-f表示常规文件判断,-a其实是and(与)的意思,当这个添加成立时,认为处于源码根目录,直接执行make,这跟正常的全编一致。

local M=$(findmakefile)

如果上面的判断不成立,那么就会走到else中,执行findmakefile函数:

function findmakefile()
{
    TOPFILE=build/core/envsetup.mk
    local HERE=$PWD
    T=
    while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
        T=`PWD= /bin/pwd`
        if [ -f "$T/Android.mk" ]; then
            echo $T/Android.mk
            \cd $HERE
            return
        fi
        \cd ..
    done
    \cd $HERE
}

顾名思义,这个函数是用于查找makefile文件的,在findmakefile首先HERE保存了当前目录的路径,软后通过 \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) 判断,如果不处于系统根目录以及编译跟目录的话就查找Android.mk文件,找到了就返回绝对路径,找不到就cd ..到上一级继续找,直到条件不满足为止,不管最后有没有找到,都会返回到执行findmakefile()函数的目录,也就是HERE。

好了,看完findmakefile函数我们回到mm函数中。

local M=`echo $M|sed 's:'$T'/::'`

这里我根据findmakefile()函数的实现得知,如果找到了Android.mk文件,返回的是绝对路径,所以$M是以文件系统跟目录开始的,而这里就是将M转换成以源码跟目录为标准的相对路径。

if[!"$T"]、if[!"$M"]

紧接着这两个判断是判断是否找到了源码跟目录,以及是否找到了Android.mk文件,如果两样都找不到,那么就打印错误提示,并退出编译程序。

for ARG in $@;

接下来通过这个循环处理所有参数,并保存到GET_INSTALL_PATH中,其实最后都会保存到ARGS中,然后通过判断GET_INSTALL_PATH是否为空,如果不为空就就赋值给ARGS,当然平时我们执行mm命令的时候,多数是不会带参数的,所以这里我们先假设会走到另一个判断中,导致MODULES=all_modules,也就是编译所有当前目录下所有模块。

ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS

最后,准备好一切之后就开始执行make命令了!此时我们需要特别关注的是ONE_SHOT_MAKEFILE,它保存了$M,也就是上面找到的Android.mk文件,紧接着make会通过-C $T跳到源码跟目录中进行,同时加载main.mk文件,如果你去耐心的看main.mk文件的话会发现在其中会加载ONE_SHOT_MAKEFILE中保存的Android.mk文件的。

好了,到这里mm的过程粗略的介绍了下,也比较喜欢是用mm,至于mmm,只不过是指定了编译路径而已,并没做什么很特殊的事情,如果有朋友想了解的话可自己肯下脚本,其实耐心点的话还是可以理清楚的!

 

二、总结

到这篇文章为止,我们理论上已经能够正常的去编译一个android系统的源码了,包括整个流程中的几个步骤也有了大致了解,但是这里仅仅是编译而已,并没有涉及到定制!比如说我们如何针对某个版子定制一个系统?驱动文件该如何放置?需要修改哪些配置文件?如何重新编译内核?这一系列的工作虽然难度不是很大,但是都是非常繁琐的,一个不小心很有可能你就把系统搞挂了。。。。不过没关系,做事情总得一步步来。后续会继续把笔记分享出来,大家一起学习共同进步!!!

你可能感兴趣的:(android系统开发)