理解Android编译命令

   <blockquote>     <p>工欲善其事,必先利其器,对于想要深入学习Android源码,必须先掌握Android编译命令.</p>    </blockquote>    <h2>一、引言</h2>    <p>关于Android Build系统,这个话题很早就打算整理下,迟迟没有下笔,决定跟大家分享下。先看下面几条指令,相信编译过Android源码的人都再熟悉不过的。</p>    <pre>  <code>source /opt/android1204_17.conf   source setenv.sh  lunch  make -j12  </code></pre>    <p>记得最初刚接触Android时,同事告诉我用上面的指令就可以编译Android源码,指令虽短但过几天就记不全或者忘记顺序,每次编译时还需要看看自己的云笔记,冰冷的指令总是难以让我记忆。后来我决定认真研究下这个指令的含义。知其然还需知其所以然,这样能更深层次的理解并记忆,才能与自身的知识体系建立强连接,或许<strong>还有意外收获</strong>,果然如此,接下来跟大家分享一下在研究上述几条指令含义的过程中,深入了解到的Android Build(编译)系统。</p>    <h2>二、编译命令</h2>    <p>准备好编译环境后,编译Android源码的第一步是 <code>source build/envsetup.sh</code>,其中source命令就是用于运行shell脚本命令,功能等价于”.”,因此该命令也等价于<code>. build/envsetup.sh</code>。在文件<code>envsetup.sh</code>声明了当前会话终端可用的命令,这里需要注意的是当前会话终端,也就意味着每次新打开一个终端都必须再一次执行这些指令。起初并不理解为什么新开的终端不能直接执行make指令,到这里总算明白了。</p>    <p>接下来,解释一下<strong>本文开头的引用</strong>的命令:</p>    <pre>  <code>source /opt/android1204_17.conf  //初始化jdk环境变量(这个不是必需的,因厂商而异)  source setenv.sh  //初始化编译环境,包括后面的lunch和make指令  lunch  //指定此次编译的目标设备以及编译类型  make  -j12 //开始编译,默认为编译整个系统,其中-j12代表的是编译的job数量为12。  </code></pre>    <p>所有的编译命令都在<code>envsetup.sh</code>文件能找到相对应的function,比如上述的命令<code>lunch</code>,<code>make</code>,在文件一定能找到</p>    <pre>  <code>function lunch(){   ...  }    function make(){   ...  }  </code></pre>    <p>具体实现这里就不展开说明,下面精炼地总结了一下各个指令用法和功效。</p>    <p>2.1 代码编译</p>    <table>     <thead>      <tr>       <th>编译指令</th>       <th>解释</th>      </tr>     </thead>     <tbody>      <tr>       <td>m</td>       <td>在源码树的根目录执行编译</td>      </tr>      <tr>       <td>mm</td>       <td>编译当前路径下所有模块,但不包含依赖</td>      </tr>      <tr>       <td>mmm [module_path]</td>       <td>编译指定路径下所有模块,但不包含依赖</td>      </tr>      <tr>       <td>mma</td>       <td>编译当前路径下所有模块,且包含依赖</td>      </tr>      <tr>       <td>mmma [module_path]</td>       <td>编译指定路径下所有模块,且包含依赖</td>      </tr>      <tr>       <td>make [module_name]</td>       <td>无参数,则表示编译整个Android代码</td>      </tr>     </tbody>    </table>    <p>下面列举部分模块的编译指令:</p>    <table>     <thead>      <tr>       <th>模块</th>       <th>make命令</th>       <th>mmm命令</th>      </tr>     </thead>     <tbody>      <tr>       <td>init</td>       <td>make init</td>       <td>mmm system/core/init</td>      </tr>      <tr>       <td>zygote</td>       <td>make app_process</td>       <td>mmm frameworks/base/cmds/app_process</td>      </tr>      <tr>       <td>system_server</td>       <td>make services</td>       <td>mmm frameworks/base/services</td>      </tr>      <tr>       <td>java framework</td>       <td>make framework</td>       <td>mmm frameworks/base</td>      </tr>      <tr>       <td>framework资源</td>       <td>make framework-res</td>       <td>mmm frameworks/base/core/res</td>      </tr>      <tr>       <td>jni framework</td>       <td>make libandroid_runtime</td>       <td>mmm frameworks/base/core/jni</td>      </tr>      <tr>       <td>binder</td>       <td>make libbinder</td>       <td>mmm frameworks/native/libs/binder</td>      </tr>     </tbody>    </table>    <p>上述mmm命令同样适用于mm/mma/mmma,编译系统采用的是增量编译,只会编译发生变化的目标文件。当需要重新编译所有的相关模块,则需要编译命令后增加参数<code>-B</code>,比如make -B [module_name],或者 mm -B [module_path]。</p>    <p><strong>Tips:</strong></p>    <ul>     <li>对于<code>m、mm、mmm、mma、mmma</code>这些命令的实现都是通过<code>make</code>方式来完成的。</li>     <li>mmm/mm编译的效率很高,而make/mma/mmma编译较缓慢;</li>     <li>make/mma/mmma编译时会把所有的依赖模块一同编译,但mmm/mm不会;</li>     <li>建议:首次编译时采用make/mma/mmma编译;当依赖模块已经编译过的情况,则使用mmm/mm编译。</li>    </ul>    <h3>2.2 代码搜索</h3>    <table>     <thead>      <tr>       <th>搜索指令</th>       <th>解释</th>      </tr>     </thead>     <tbody>      <tr>       <td>cgrep</td>       <td>所有<strong>C/C++</strong>文件执行搜索操作</td>      </tr>      <tr>       <td>jgrep</td>       <td>所有<strong>Java</strong>文件执行搜索操作</td>      </tr>      <tr>       <td>ggrep</td>       <td>所有<strong>Gradle</strong>文件执行搜索操作</td>      </tr>      <tr>       <td>mangrep [keyword]</td>       <td>所有<strong>AndroidManifest.xml</strong>文件执行搜索操作</td>      </tr>      <tr>       <td>sepgrep [keyword]</td>       <td>所有<strong>sepolicy</strong>文件执行搜索操作</td>      </tr>      <tr>       <td>resgrep [keyword]</td>       <td>所有本地res/*.xml文件执行搜索操作</td>      </tr>      <tr>       <td>sgrep [keyword]</td>       <td>所有资源文件执行搜索操作</td>      </tr>     </tbody>    </table>    <p>上述指令用法最终实现方式都是基于<code>grep</code>指令,各个指令用法格式:</p>    <pre>  <code>xgrep [keyword]  //x代表的是上表的搜索指令  </code></pre>    <p>例如,搜索所有AndroidManifest.xml文件中的<code>launcher</code>关键字所在文件的具体位置,指令</p>    <pre>  <code>mangrep launcher  </code></pre>    <p>再如,搜索所有system_app的selinux权限信息</p>    <pre>  <code>sepgrep system_app  </code></pre>    <p><strong>Tips:</strong> Android源码非常庞大,直接采用grep来搜索代码,不仅方法笨拙、浪费时间,而且搜索出很多无意义的混淆结果。根据具体需求,来选择合适的代码搜索指令,能节省代码搜索时间,提高搜索结果的精准度,方便定位目标代码。</p>    <h3>2.3 导航指令</h3>    <table>     <thead>      <tr>       <th>导航指令</th>       <th>解释</th>      </tr>     </thead>     <tbody>      <tr>       <td>croot</td>       <td>切换至Android根目录</td>      </tr>      <tr>       <td>cproj</td>       <td>切换至工程的根目录</td>      </tr>      <tr>       <td>godir [filename]</td>       <td>跳转到包含某个文件的目录</td>      </tr>     </tbody>    </table>    <p><strong>Tips:</strong> 当每次修改完某个文件后需要编译时,执行<code>cproj</code>后会跳转到当前模块的根目录,也就是Android.mk文件所在目录,然后再执行mm指令,即可编译目标模块;当进入源码层级很深后,需要返回到根目录,使用<code>croot</code>一条指令完成;另外<code>cd -</code> 指令可用于快速切换至上次目录。</p>    <h3>2.4 信息查询</h3>    <table>     <thead>      <tr>       <th>查询指令</th>       <th>解释</th>      </tr>     </thead>     <tbody>      <tr>       <td>hmm</td>       <td>查询所有的指令help信息</td>      </tr>      <tr>       <td><strong>findmakefile</strong></td>       <td>查询当前目录所在工程的Android.mk文件路径</td>      </tr>      <tr>       <td>print_lunch_menu</td>       <td>查询lunch可选的product</td>      </tr>      <tr>       <td>printconfig</td>       <td>查询各项编译变量值</td>      </tr>      <tr>       <td>gettop</td>       <td>查询Android源码的根目录</td>      </tr>      <tr>       <td>gettargetarch</td>       <td>获取TARGET_ARCH值</td>      </tr>     </tbody>    </table>    <p><strong>Tips:</strong> 当忘了前面的所有指令时,可以执行一个hmm便可输出这些指令的帮助信息。</p>    <p><strong>其他指令:</strong></p>    <ul>     <li>make clean:执行清理操作,等价于 <code>rm -rf out/</code></li>     <li>make update-api:更新API,在framework API改动后需执行该指令,Api记录在目录<code>frameworks/base/api</code>;</li>    </ul>    <h2>三、编译系统</h2>    <p>Android 编译系统是Android源码的一部分,用于编译Android系统,Android SDK以及相关文档。该编译系统是由Make文件、Shell以及Python脚本共同组成,其中最为重要的便是Make文件。关于编译系统可参考 <a href="/misc/goto?guid=4959669980657349992">理解 Android Build 系统</a>。</p>    <h3>3.1 Makefile分类</h3>    <p>整个Build系统的Make文件分为三大类:</p>    <ul>     <li><strong>系统核心的Make文件:</strong>定义了Build系统的框架,文件全部位于路径<code>/build/core</code>,其他Make文件都是基于该框架编写的;</li>     <li><strong>针对产品的Make文件:</strong>定义了具体某个型号手机的Make文件,文件路径位于<code>/device</code>,该目录下往往又以公司名和产品名划分两个子级目录,比如<code>/device/qcom/msm8916</code>;</li>     <li><strong>针对模块的Make文件:</strong>整个系统分为各个独立的模块,每个模块都一个专门的Make文件,名称统一为”Android.mk”,该文件定义了当前模块的编译方式。Build系统会扫描整个源码树中名为”Android.mk”的问题,并执行相应模块的编译工作。</li>    </ul>    <h3>3.2 编译产物</h3>    <p>经过<code>make</code>编译后的产物,都位于<code>/out目录</code>,该目录下主要关注下面几个目录:</p>    <ul>     <li>/out/host:Android开发工具的产物,包含SDK各种工具,比如adb,dex2oat,aapt等。</li>     <li>/out/target/common:通用的一些编译产物,包含Java应用代码和Java库;</li>     <li>/out/target/product/[product_name]:针对特定设备的编译产物以及平台相关C/C++代码和二进制文件;</li>    </ul>    <p>在/out/target/product/[product_name]目录下,有几个重量级的镜像文件:</p>    <ul>     <li>system.img:挂载为根分区,主要包含Android OS的系统文件;</li>     <li>ramdisk.img:主要包含init.rc文件和配置文件等;</li>     <li>userdata.img:被挂载在/data,主要包含用户以及应用程序相关的数据;</li>    </ul>    <p>当然还有boot.img,reocovery.img等镜像文件,这里就不介绍了。</p>    <h3>3.3 Android.mk解析</h3>    <p>在源码树中每一个模块的所有文件通常都相应有一个自己的文件夹,在该模块的根目录下有一个名称为“Android.mk” 的文件。编译系统正是以模块为单位进行编译,每个模块都有唯一的模块名,一个模块可以有依赖多个其他模块,模块间的依赖关系就是通过模块名来引用的。也就是说当模块需要依赖一个jar包或者apk时,必须先将jar包或apk定义为一个模块,然后再依赖相应的模块。</p>    <p>对于Android.mk文件,通常都是以下面两行</p>    <pre>  <code>LOCAL_PATH := $(call my-dir)  //设置当编译路径为当前文件夹所在路径  include $(CLEAR_VARS)  //清空编译环境的变量(由其他模块设置过的变量)  </code></pre>    <p>为方便模块编译,编译系统设置了很多的编译环境变量,如下:</p>    <ul>     <li>LOCAL_SRC_FILES:当前模块包含的所有源码文件;</li>     <li>LOCAL_MODULE:当前模块的名称(具有唯一性);</li>     <li>LOCAL_PACKAGE_NAME:当前APK应用的名称(具有唯一性);</li>     <li>LOCAL_C_INCLUDES:C/C++所需的头文件路径;</li>     <li>LOCAL_STATIC_LIBRARIES:当前模块在静态链接时需要的库名;</li>     <li>LOCAL_SHARED_LIBRARIES:当前模块在运行时依赖的动态库名;</li>     <li>LOCAL_STATIC_JAVA_LIBRARIES:当前模块依赖的Java静态库;</li>     <li>LOCAL_JAVA_LIBRARIES:当前模块依赖的Java共享库;</li>     <li>LOCAL_CERTIFICATE:签署当前应用的证书名称,比如flatform。</li>     <li>LOCAL_MODULE_TAGS:当前模块所包含的标签,可以包含多标签,可能值为debgu,eng,user,development或optional(默认值)</li>    </ul>    <p>针对这些环境变量,编译系统还定义了一些便捷函数,如下:</p>    <ul>     <li>$(call my-dir):获取当前文件夹路径;</li>     <li>$(call all-java-files-under, ):获取指定目录下的所有Java文件;</li>     <li>$(call all-c-files-under, ):获取指定目录下的所有C文件;</li>     <li>$(call all-Iaidl-files-under, ) :获取指定目录下的所有AIDL文件;</li>     <li>$(call all-makefiles-under, ):获取指定目录下的所有Make文件;</li>    </ul>    <p>示例:</p>    <pre>  <code>  LOCAL_PATH := $(call my-dir)     include $(CLEAR_VARS)          # 获取所有子目录中的Java文件    LOCAL_SRC_FILES := $(call all-subdir-java-files)          # 当前模块依赖的动态Java库名称    LOCAL_JAVA_LIBRARIES := com.gityuan.lib          # 当前模块的名称    LOCAL_MODULE := demo          # 将当前模块编译成一个静态的Java库    include $(BUILD_STATIC_JAVA_LIBRARY)</code></pre>    <p>来源:http://gityuan.com/2016/03/19/android-build/ </p>