唉!最近忙新项目,周末把笔记整理了下忘了发出来了,今天继续分享下源码编译的第三阶段,make阶段,由于这个阶段的工作主要是编译工具进行的,像gcc这种其内部实现是相当复杂,个人功力有限,暂时没法深入去了解!但是我们可以从大概看下的过程,然后再去看看make,m,mm等命令的具体区别!!
注意:不了解第一第二个阶段的可以先看看下面这两篇文章
Android源码编译详解(一)
Android源码编译详解(二)
从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下的一个编译工具。
对于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这些指令到底都做了些什么。
我们之前介绍过,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!
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系统的源码了,包括整个流程中的几个步骤也有了大致了解,但是这里仅仅是编译而已,并没有涉及到定制!比如说我们如何针对某个版子定制一个系统?驱动文件该如何放置?需要修改哪些配置文件?如何重新编译内核?这一系列的工作虽然难度不是很大,但是都是非常繁琐的,一个不小心很有可能你就把系统搞挂了。。。。不过没关系,做事情总得一步步来。后续会继续把笔记分享出来,大家一起学习共同进步!!!