安卓开发的工作流程

 

Table of Contents

关于本文档库的说明,包括如何编辑、更新本文档,如何访问本文档库其他页面,请参考 文档项目说明

1 工作环境的安装与配置

我们的安卓开发基本上都在 Linux 系统下进行,发行版是 Ubuntu,版本建议 14.04(12.04 和 16.04 在某些安卓版本上都会出编译错误,参考 faq: 安装使用 ubuntu-16.04 注意事项)。

装好 Linux 系统后,用这条命令配置基于 system-config 的开发环境:

bash -c "$(curl -s http://172.16.0.9/setup-system-config.sh)" setup-system-config.sh

对于开发环境有任何问题,比如 Java 版本该如何切换 Java6 和 Java7 的问题,请参考 开发环境常见问题解答

关于 system-config 的更多信息,可以参考这个视频: 我的十年 Linux 使用经验,以及 这篇介绍原理的博客 和 这篇关于如何使用的博客

关于 system-config 相关的个人安卓开发技巧,参考 http://172.16.2.18/docs/bhjs-tricks.html

1.1 升级 system-config

运行

system-config-update

或者重新运行上面的配置命令,都可以升级 system-config。

如果你有自己对 system-config 的定制,可以提交到服务器:

cd ~/system-config; git push origin HEAD:refs/heads/sandbox/+你的公司邮箱前缀

如果你有自己的改动,那么在升级的过程中,有可能会发生冲突,这种情况下需要你自己解一下冲突。

另外,如果你觉得你的改动对其他人也有帮助,那你可以把代码提给我 review,然后我会考虑要不要把你的改动合入到我的 system-config 项目中。

在使用过程中遇到什么问题的话,请参考 开发环境常见问题解答

1.1.1 自己定制、重置 system-config 的配置

system-config 本身是一个开源的项目,它的 github 网址:我的 system-config 项目

在我们公司里使用的 system-config 是多个子项目的合集(其中包括一个不开源的锤子内部项目:smartcm)。

System-config 自带了一个非常强大的搜索源代码的功能,beagrep,各位如果对源代码有疑问,可以考虑使用这个工具(参考 beagrep 的源码和这篇博客:http://baohaojun.github.io/beagrep-cn.html):

cd ~/system-config # 到你想阅读的代码目录下

for-code-reading # 创建代码索引。这个命令会运行比较长的时间,取决于代
                 # 码量,在 system-config 下要几分钟,如果是安卓那个量
                 # 级的代码,基本上跟做一次全编的时间差不多。


beagrep -e "hello world" # 搜索源代码

在 system-config 的环境里,我们建议如下 3 种自定义开发环境的策略,供参考:

  1. 直接修改 system-config 的源代码,并考虑提交给包昊军 Review、合入(如果你认为对其他同事也有用的话)

    如果不提交给我 Review 并合入的话,后续你再升级 system-config 的时候,你的改动就可能跟我的改动发生冲突,需要你花费一些精力去解决。

  2. 修改自己的定制文件,比如自己的 ~/.bashrc

    但是请注意,最好是要在 ~/.bashrc 文件的末尾加入你的改动,而不是在这个文件开头的部分。因为有些环境变量比如 PATH 在 system-config 里是会强行重置的,如果你在 system-config 之前改了 PATH,那被 system-config 一重置这个改动就无效了。

    另外注意确保自己的修改不会影响 system-config 的功能。

  3. 通过 system-config 提供的 sc 命令直接关闭 system-config,需要时再打开

    sc 命令还提供更多方便的功能,详情请打开源代码文件查看。

一般情况下你很少需要修改 system-config,因为如果有个命令你觉得不好用,你别用它,当它不存在就可以了。但 system-config 通过重定义覆盖了一些原有的命令,比如 adb,我对它进行了重度的封装,有时候你可能会觉得有些提示信息太烦琐,希望直接使用未修改的 adb 版本,这里简单写一下如何操作(详情请参考 system-config 原理与使用的简要说明)。

  1. 用 type 命令看一下你希望重定义的命令,比如 type adb,你会看到输出是 adb is aliased to `my-adb',也就是说,它是一个 bash 的别名(请运行 info bash 来阅读 bash 的 info 手册)
  2. 如果你看到的命令是一个别名,请在 ~/.bashrc 的末尾加入 unalias XXX 取消这个别名的定义(请在 bash info 手册中搜索 unalias)
  3. 如果你看到的命令是一个函数,请在 ~/.bashrc 的末尾加入 unset -f XXX 取消这个函数的定义(请在 info 中搜索 unset)
  4. 如果你看到的命令是一个文件,请运行 type -a XXX 查看 XXX 的所有版本,确认一下它排在第一行的文件是由 system-config 提供的,然后考虑一下是否可以直接删除这个文件

2 下载代码、编译、提交

2.1 下载代码

我们使用谷歌官方提供的 repo/gerrit 来管理代码。但下载代码时,我们封装了一个非常简单的方式,就是在 system-config 下,使用 sse 命令(sse 代表 smartisan software engineers,它支持许多子命令,所有的子命令脚本都在 ~/src/github/smartcm/sse-helpers 目录下,如有需要可自己打开查看),选择 get-source-code 子命令。

通过这个方式下载代码,sse 最后会告诉你如果自己用 repo init/repo sync 的话,应该使用什么样的命令,你可以记一下,下次有可能会用上。

上面的方式下载到的代码,都是各个产品分支的最新代码。但有时候你会需要同步到某一天的正式版本的代码(一般有两种情况下需要这样做:你要查那天的版本的问题;你当前的代码有别人引入的编译错误,导致你编译出错),这种情况下,你可以在一个已经下载过代码的仓库目录下,使用 repo sync --help 查看一下帮助,里面有个选项允许你通过一个临时的 manifest.xml 文件来同步代码,而我们的每个正式版本,在刷机包目录下都有一个 manifest.xml 和 oem-manifest.xml,里面保存着编译时的代码快照。

如果刚入职时发现不能同步安卓代码的话,请参考 5.51.1

2.1.1 代码分仓权限控制说明

由于公司信息安全管控要求,需要对一些关键仓储管控权限,这些仓储的源码原则上只对模块维护组开放,不对其他开发组开放。

涉及的仓储有:AppStoreSmartisan(APP3)、CloudServiceSmartisan(APP3)、GameStoreSmartisan(APP3)、LaucherSmartisanNew(Framework)、Tpassword(APP2)、OEM(BSP)、Brower(APP1)

其中 OEM 和 browser 代码通过另外的 manifest 管控,不影响大家的下载习惯。其他几个管控仓储下载时(有权限的前提下)需要注意:

repo init 时需要添加:-g default,$group-name。 $group-name 的值有:sos-app3、sos-fw、sos-app2。如果需要多个 group,$group-name 之间用逗号分隔。示例:

repo init -g default,sos-fw -u ssh://gerrit.smartisan.cn/qualcomm/platform/manifest.git -b sanfrancisco -m osborn-rom.xml --repo-url smartisan:googlesource/git-repo --reference ~/src/android-mirror

如果你是使用 sse 下载代码,sse 会提示你是否需要下载额外的 repo group 的仓库。如果尝试下载自己没有权限的仓库,repo sync 最终会出错退出。sse 下载时文字提示界面如下:

1) none 只下载普通权限仓库
2) all 下载所有仓库
3) sos-app2 额外下载一些属于 app2 组的保密仓库,比如 Tpassword
4) sos-app3 额外下载一些属于 app3 组的保密仓库,比如 AppStoreCommon
5) sos-fw 额外下载一些属于 frameworks 组的保密仓库,比如 LauncherSmartisanNew
6) done 选择完毕,退出此选单
7) help 显示关于此选项的帮助
请选择哪些额外的需要特殊权限的 repo group 是你需要下载的(如有不明,请选 help)。当前已选择: '' (Type ? for help)>

如果确认自己属于某个项目组,但却没有相关项目的代码下载权限,请给自己的 Leader 发邮件并抄送给 scm@smartisan.com 申请相关权限,Leader 回复邮件批准后,cm 会负责更改相关权限。

2.2 搜索代码

公司的各个产品的主线代码,均提供了每天更新的搜索引擎索引,以加快各位同事全局搜索代码的速度。在解问题的时候,比如想查找 logcat 中 Checking URI perm 输出的代码位置,可以在自己的安卓源代码目录下运行如下命令:

abc-x grep "Checking URI perm"

其工作原理参考 beagrep 的博客

一般情况下 0.5 ~ 5 秒之内就可以完成搜索。

如果想要自己在本地搜索代码,比如非主线代码、或项目初期还没有拉出主线,只有安卓原生线时;或想要搜索其他任意开源项目的代码的时候——可以自己运行一下 for-code-reading 命令在本地创建一个代码索引,然后用 beagrep -e 'hello world' 命令搜索“hello world”。

关于它的更多用法请参考 5.23

2.3 编译

使用 sse 命令,选择 build 子命令。里面会问你各种详细的编译选项,最后再告诉你下次你自己想不通过 sse 选择,直接启动编译的话,该使用什么简单的命令。

2.3.1 system-config 下编译详解

一般来讲,安卓的开发、调试过程中,有 3 种编译方法,分别是:全编、只编某个目录、只编某个目录及其依赖。对应的命令分别是 makemmmma

但上面的 3 个命令使用起来比较麻烦(有坑,参考 5.44 和 5.44.1),所以在 system-config 下,提供了对应的 3 个命令分别是 android-makemmmma

安卓官方命令system-config 封装
. build/envsetup.shN/A(system-config 下强烈建议不要使用,各个编译命令应该自动调用此脚本)
lunchlunch(但提供了更友好的选择界面)
make(全编,必须先 lunch)android-make(全编,如果没有 lunch 过,自动帮你 lunch)
make systemimageandroid-make systemimage(参数一致)
make showcommands(编译时打印调用的命令)android-make -v
mm(只编当前目录)mm(是一样的,但需要的话会自动 lunch)或 mm -q
mm showcommands MODULE (只编某模块,并显示命令)mm -v MODULE
mma(只编当前目录及其依赖)mma(自动 lunch)
没有这样的功能mm-adb(用 mm 编译,并把有更新的 /system 下文件用 adb push 到手机上)
  

针对上面这 3 种编译方法,建议平时尽量少用全编(因为实在太浪费时间),除非你调试问题确实有需要的话,再考虑用全编。

同时第一次编译代码时,一般必须要用 mma,因为依赖还没有编出来。

后续在调试自己相关的模块时,尽量用 mm,不停的快速迭代。注意 system-config 的 mm 有个 -q 参数,可以让它的编译变得更快。

除此之外,如果有其他的编译需求,比如编译 ota 包、编译刷机包,等等,请参考 sse build 里使用的编译命令。

项目后期调试的过程中,可能还会碰到各种签名相关的问题,请参考 5.12

2.4 编译出错信息快速定位

sse build 的终端打印输出同时还会保存在 .repo/build.log 文件下,可以使用 grep-errors .repo/build.log 命令快速定位。

如果你有一个自己的出错编译输出文件 BUILD-OUTPUT.TXT ,也可以试试用 grep-errors BUILD-OUTPUT.TXT 看看能不能快速定位。

如果发生了编译错误,认为不是自己的代码的改动引起的,可以参考 faq:5.15.

很多同事编译安卓的时候,喜欢上来就用 make 命令,但这样的话出错之后查找起真正出错的那一行就非常麻烦,只能用鼠标回滚终端文本,然后用肉眼查看;或者再单独把这些输出从终端上拷贝下来,再保存到一个文件里,然后再自己用编辑器或 grep 等文本处理工具查看。如果用 sse build 或 android-make 命令编译的话,就完全不用操心这些问题了。

这个命令的工作原理很简单:

  1. 用常见的出错关键字过滤一遍 build.log
  2. 用常见的“非出错关键字”再过滤一遍步骤 1 的结果,尽可能的消除“噪声”

目前公司的持续集成就使用了这个脚本,可以帮大家快速的定位是哪个模块出现了编译错误。

2.5 提交代码 review

运行 gerrit-push-review 命令。加 --help 参数可以显示帮助:

使用方法:gerrit-push-review [OPTIONS]... REVIEWERS...

这是一个方便往 Gerrit 上 push review 的脚本。功能:
    - 默认自动计算该往哪条分支上提交;也可自己用 -b 参数指定
    - 允许全局 push(当前目录下有 .repo 目录) 或单个 git 仓库 push(当前目录下有 .git)
    - push 的同时允许指定 Reviewers(后面跟一个或多个同事的账号作为参数)

所有功能最后都是调用 git 命令实现,请阅读脚本源码并参考 git 官方文档,比如 git push --help(git push 最简单的调用方法是不带任何参数,git 会自动帮你计算往哪个 remote 服务器、哪条分支上 push 哪个 commit,这是最方便的用法,此脚本就是受此启发而写出来的对 gerrit push review 操作的封装)。

Options and arguments:
      --[no-]do-rebase        要不要先 rebase 到远程服务器分支,默认是要 rebase
      --[no-]draft            提一个 draft review,打个草稿,继续完善,暂时不开放给其他同事
  -t, --patch-topic=PATCH_TOPIC
                              要全局多个 repo push 或一个 repo 下 push 多个 patch 的时候,加个 topic,方便 review
  -r, --push-rev=PUSH_REV     要 push 哪个 revision,默认是 HEAD
      --repo-remote=REPO_REMOTE
                              提给哪个 repo remote,默认是 REPO_REMOTE 变量或 git remote 或 repo manifest 中设置的 remote(依此顺序)
      --review-push-url=REVIEW_PUSH_URL
                              使用自己制定的 push url,不要让脚本自动计算(适于用:自动计算结果有误;测试等)
      --[no-]skip-gerrit      不要提到 gerrit 上 review,直接往 refs/heads/ 上 push
  -R, --[no-]skip-local-review提交前不要做 local review(鼓励做 local review!)
  -b, --to-branch=TO_BRANCH   提到哪个分支上,若不指定,默认是 git 当前在跟踪的分支或 repo manifest 中设置的分支

2.6 获取 CM 每天编译的刷机包

每天主线编译的刷机包放在共享目录下 \\172.16.2.240\flash\daily (Linux 下路径:\\172.16.2.240\flash\daily 参考:版本共享目录变更说明),各产品按产品名划分。

每个刷机包内有关于该版本的如下信息:

  1. manifest.xml 和 oem-manifest.xml,这个是 repo manifest 命令 export 出来的 manifest 快照,里面有每个项目的 git 版本号。
  2. change.log 和 oem-change.log,与上一个版本的 build 相比,都有哪些改动。
  3. repo-init.txt,该 build 是怎么 repo init 的,里面包含了完整的 repo init 命令,你可以拿来参考,比如应该用哪个 manifest.xml 去做 repo init。

共享文件夹的访问方法(域名、用户名、密码等信息)参考 本条常见问答

2.6.1 版本共享目录变更说明

原版本共享服务器 2.225 成为了大家工作效率的严重瓶颈,测试同事想刷个机结果出现了一早上都没有刷完的现象,并导致 CM 这边无法按正常速度编译出版本(编译完成后把版本拷贝到共享目录的操作一早上也没有完成)。

因此,将版本共享服务器更换成 2.240,继续对所有同事开放。

同时,每个 team 可以提供 1~3 台固定 IP 的刷机专用 PC,通过 2.18 上开放白名单允许这些专用 PC 访问。在刷机专用 PC 上获取版本要注意:一定要将版本拷到本地!不要在线刷机!这样,每个版本一般只要拷贝一次就可以,后续有同事要刷这个版本(或者通过网络从专用 PC 上下载这个版本),都不需要再占用服务器资源。如发现在专用 PC 上在线刷机的情况(比如一天内多次下载同一版本),CM 会考虑给各 Leader 发邮件通报“批评”、下调该 PC 服务优先级等措施。

也就是说,之前我们提供的共享服务器是 \\172.16.2.225\flash,这台服务器已关闭共享服务,现在相应的改成了 \\172.16.2.240\flash(所有人均可访问,可能会成为性能瓶颈)和 \\172.16.2.18\flash(只有各 Team 的专用 PC 可以访问)。

各 Team 的刷机专用 PC 申请记录如下(如需知道机器物理位置,请咨询申请人):

申请人部门ip
门善良3 楼测试部172.16.5.104
张莉婷性能测试组172.16.5.20、172.16.5.50
罗晨封闭开发192.168.50.254
武照东成都测试172.19.4.167
张保永Trident 产品测试172.16.6.66、172.16.6.67
唐瑜测试172.16.5.24
李妍用户反馈组172.16.5.136
王洋BSP 测试组172.16.7.48、172.16.6.126
闫慧BSP172.16.15.97 172.16.15.104 172.16.26.22
米晓川Frameworks172.16.15.131
   

2.7 刷机

在 Windows 下刷机需要使用工具组提供的刷机工具,在 \\share.smartisan.cn\share\Tools\DownloadTool 下,具体需要怎么使用、该用哪个版本请咨询测试组会使用刷机工具的同事。

BSP 组的同事针对较新的高通平台产品,提供了 Linux 下的刷机方法,如果你发现在刷机包目录下有 edl-flash.sh 文件的话,你可以在刷机包共享目录下直接使用 sse .edl 命令进行刷机。

较老的高通平台产品,比如 T1、T2 和 U1,工程师在 Linux 下刷机可以使用手机版 Debian 系统,具体请参考 这篇博客(注意:此方法已很久没有维护)。

2.8 获取 Build 符号信息

CM 每天的 Build,都会带相应的符号信息,主要分为 3 类:

  1. 高通 oem 符号信息
  2. Kernel 符号信息(也即 vmlinux 文件)
  3. System 符号信息

其中,1 和 2 的信息对 Bsp 的同事解 Ramdump 问题比较有用,3 的信息对做上层开发的同事比较有用。

获取这些信息的方法都是一样的,通过 http://172.16.2.18/vmlinux.html 这个页面获得。

在这个页面上可以通过输入 3 种信息(Linux 版本字符串,刷机包目录名,系统编译时间)中的任意一种,然后通过数据库查询,查到对应的 symbol 版本。

其中 Linux 版本字符串,可以通过以下命令从 ramdump 文件中获取,这在有时 Tester 给出的版本不正确时非常有用:

strings DDRCS1.BIN |grep 'Linux version'

2.9 获取 OTA 版本信息

日常和 Daily 编译的 OTA 版本的链接,可以通过以下页面获取。

主线 OTA 版本(显示最新的 260 条记录):http://172.16.0.9/ota-out/list_nightly.php

MOL OTA 版本(显示最新的 260 条记录):http://172.16.0.9/ota-out/list_release_ota.php

正式对外发布的 OTA 版本:http://172.16.0.9/ota-out/ota_release_records.php

3 工作流程的详解版

3.1 git 开发环境的配置详解

请参考 这篇博客

  1. 需要配置 ~/.gitconfig ,加入 user.name 和 user.email 的配置
  2. 生成 ssh rsa 公私钥,登录 Gerrit 服务器(有两台,一台是 公司内部的,另一台是 对 ODM 厂商开放的 ),加入你的公钥。
  3. 安装 Oracle 提供的 jdk6。这个已经放到 Gerrit 代码服务器上,直接下载即可,免安装。
  4. 用 apt-get install 安装一些需要的系统程序(如果某程序安装不上的话,请忽略,随着 ubuntu 版本升级,有可能会出现这种情况,system-config 的安装脚本会自动处理这种错误)。

    antlibc6-dev-i386python-crypto
    bclibc6-i386python-mysqldb
    bisonlibdatetime-perlpython3
    build-essentialliblua5.2-devqt5-default
    ccachelibssl-devqtchooser
    command-not-foundlibstring-approx-perlrsync
    curllibstring-shellquote-perlschedtool
    daemonlibswitch-perlstrace
    expectlibtext-csv-perlsubversion
    filelibtext-glob-perltime
    flexlibtext-levenshtein-perlunzip
    genisoimagelibxml-parser-perlwget
    gitklibxml-simple-perlwmctrl
    gperflibxml2-utilsxbindkeys
    htoplua5.2xclip
    iputils-pingmysql-clientxmlstarlet
    jqnet-toolsxorriso
    lessopenssh-clientzenity
    lftpopenssh-serverzip
    lib32stdc++6pkg-configzlib1g-dev
    lib32z1policycoreutils 

3.1.1 如何在别的系统上配置代码下载环境

注意:本人自己目前并不使用 Windows 或 Mac 系统,因此本条目建议仅供参考。如果有问题的话建议自己多折腾,本人无法提供技术支持。

如果自己没有装过 Linux 环境的话,建议考虑用虚拟机装个 Linux 作为参考系统。

在 Windows 系统下,Win10 的话建议装个 Ubuntu,然后一切流程就跟这篇文档里提供的一样了。Win10 以前的系统建议装个 cygwin,装完以后你可以得到一个 Unix 的环境。

Mac 系统的话开发环境是比较像 Linux 的。

要在这些系统上用 git 连接公司的服务器的话,请参考配置好的 Linux 环境下的 ~/.gitconfig 文件和 ~/.ssh 目录(尤其是 ~/.ssh/config 文件)。建议可以直接把这些文件拷贝过去试试。另外注意 ~/.ssh 目录下的文件有特殊权限要求,请用 ls -l 查看,并在需要的时候用 chmod 修改。

如需使用 repo 命令,可以在配好 git 连接 gerrit 之后,从 ssh://gerrit.smartisan.cn/googlesource/git-repo clone 下来后把里面的 repo 文件拷贝到自己的 PATH 路径里,然后就可以开始使用了。

3.2 获取代码详解

请确保已经完成 工作环境的安装与配置

我们的代码管理工具主要有这 3 个:

git

最基本的代码版本管理工具,参考 git 简介

repo

把 N 个 git repo 集中管理的工具,参考 repo 简介

gerrit

提供网页版和命令行版本的代码 review 功能,参考 gerrit 简介

3.2.1 下载各产品主线安卓代码

我们下载安卓项目代码,用的是谷歌提供的 repo 命令,里面最关键的三个参数分别是:manifest 仓储的地址(-u URL),manifest 仓储的分支(-b BRANCH),manifest.xml 文件的名字(-m MANIFEST.XML)。

以 T1 项目的主线为例,这三个参数分别是 ssh://gerrit.smartisan.cn/qualcomm/platform/manifest.gitsanfranciscosfo-rom.xml ,所以下载代码的命令是:

repo init -u ssh://gerrit.smartisan.cn/qualcomm/platform/manifest.git \
     -b sanfrancisco \
     -m sfo-rom.xml \
     --repo-url ssh://gerrit.smartisan.cn/googlesource/git-repo \
     --reference ~/src/android-mirror/ \
     --depth 1

repo sync -j4 -c -d
  • 注意上面的 --depth 1 参数,这个参数允许你只获取服务器上最新的一个提交,这样是最节省服务器带宽、本地存储空间的。如果不加这个参数的话,repo sync 的时候会把所有的历史纪录全部下载下来,下载一份代码的时候可能从 --depth 1 的 10 分钟左右变成 1 小时左右。

    如果某些仓储需要查看其完整历史纪录的话,可以在该仓储下使用 sse unshallow-source-code 命令把“浅”的历史纪录全部补齐。

  • 注意上面的 --reference 参数,这里指向了之前创建的 repo 镜像。

    以前我们的流程里鼓励大家使用 create-repo-mirror 命令自己创建一个所有仓库的镜像,之后下载代码时可以重用这份镜像,但现在发现这个镜像占用的空间太大了,已经达到 200~300 G 之巨,并且使用了 --depth 1 参数后,镜像带来的效果也大大降低了,因此现在已经不鼓励大家使用 create-repo-mirror 创建完整镜像了。

    但是,上面 --depth 1 里提到的 sse unshallow-source-code 这个机制,如果能重用镜像数据的话,也是非常有帮助的,所以继续保留这个 --reference ~/src/android-mirror/ 参数。

  • 注意上面 repo sync 命令的 -j4 选项,意思是以 4 个进程(jobs)同步获取代码,能加快速度。这里要提醒大家,千万不要设一个巨大的值,会害人害己,使自己的机器变得剧慢,同时 gerrit 服务器也变得剧慢,其他同事提个 review 都大受影响。尤其是有些同事连接到远程 Linux 上工作,直接指定 -j64,但他自己是感觉不到本地机器的桌面变慢的,因为 -j64 是在远程执行的!对于这样的滥用服务器行为,一经发现,将自动封禁其账号 2 小时并通知其主管。
  • 关于上面的 –repo-url 参数,repo 命令本身只是一个简单的初始脚本,它后台需要一整套 python 脚本的支持。repo init 默认会把这套 python 脚本从谷歌的官网上下载回来,但因为 GFW 的关系,谷歌官网无法访问,所以我们在本地做了个镜像,并且大家需要指定 --repo-url ssh://gerrit.smartisan.cn/googlesource/git-repo

其他不同产品、不同分支安卓代码的 MANIFEST.XML,参考 安卓 MANIFESTS 列表

3.2.2 下载各产品主线 OEM 代码

一般只有 BSP 的同事需要下载 OEM 代码,其他同事可以跳过此节。

高通 OEM 代码(如果有的话),我们也是用 repo 管理的,所以下载命令只有 -m MANIFEST.XML 参数不同,以 T1 主线 OEM 代码为例:

repo init -u ssh://gerrit.smartisan.cn/qualcomm/platform/manifest.git \
     -b sanfrancisco \
     -m sfo-rom-oem.xml \
     --repo-url ssh://gerrit.smartisan.cn/googlesource/git-repo \
     --reference ~/src/android-mirror/

repo sync -j4 -c -d

同样,其他不同产品、不同分支 OEM 代码的 MANIFEST.XML,参考 OEM MANIFESTS 列表

3.2.3 安卓 MANIFESTS 列表

公司的各个产品的主线、MOL、高通线代码 manifest 信息,可以通过以下页面获取:

https://review.smartisan.cn:8080/gitweb?p=baohaojun/smartcm.git;a=blob;f=smartcm-setup.sh;hb=refs/heads/dev

内容如下表,各行的格式是 android_manifests[产品名-分支名]=manifest.xml 文件名

其中产品名顾名思义,如 t1、u1 等。分支名大家最需要关注的是 dev ,这个即代表主线开发分支。

再次强烈建议使用 sse 命令,它会自动帮你确定该使用哪个 manifest。

old_android_manifests[aosp-$aosp_branch]=aosp-$aosp_branch.xml
android_manifests[aries-dev]=aries-rom.xml
android_manifests[aries-smandroid]=smandroid/sdm710-r2.0_010.xml
android_manifests[ocean-6.2-mol]=mol/ocean-rom-6.2.0.xml
android_manifests[ocean-6.6-mol]=mol/ocean-rom-6.6.0.xml
android_manifests[ocean-6.6-rls]=rls/ocean-mol-6.6.0.xml
android_manifests[ocean-cta]=cta/ocean-rom-20180607.xml
android_manifests[ocean-cts]=cts/ocean-mol-6.2.0-20180702.xml
android_manifests[ocean-dc]=dc/ocean-mol-6.2-20180719.xml
android_manifests[ocean-dev]=ocean-rom.xml
android_manifests[ocean-factory]=factory/ocean-rom-20180530.xml
android_manifests[ocean-tiyan]=sandbox/ocean-rom-6.6.0-mol-tiyan.xml
android_manifests[ocean-trinity-6.6-mol]=mol/ocean-trinity-6.6.0.xml
android_manifests[ocean-trinity]=ocean-trinity.xml
android_manifests[odin-4.4-mol]=mol/odin-rom-4.4.0.xml
android_manifests[odin-mol-4.1]=mol/odin-rom-4.1.0.xml
android_manifests[osborn-4.4-mol]=mol/osborn-rom-4.4.0.xml
android_manifests[osborn-mol-4.1]=mol/osborn-rom-4.1.0.xml
android_manifests[osborn-trinity]=feature/osborn-trinity.xml
android_manifests[oscar-4.4-mol]=ontim-oscar/mol/oscar-rom-4.4.0.xml
android_manifests[oscar-mol-4.1]=ontim-oscar/mol/oscar-rom-4.1.0.xml
android_manifests[oscar-oversea]=ontim-oscar/oversea/oscar-mol-4.1.0-20180409.xml
android_manifests[oxford-4.4-mol]=mol/osborn-rom-4.4.0-oxford.xml
android_manifests[playground-dev]=playground/smartcm-test.xml
android_manifests[surabaya-4.4-mol]=mol/surabaya-rom-4.4.0.xml
android_manifests[t1-dev]=sfo-rom.xml
android_manifests[t2-4.4-mol]=mol/icesky-rom-4.4.0.xml
android_manifests[t2-mol-4.1]=mol/icesky-rom-4.1.0.xml
android_manifests[trident-4.4-mol]=mol/trident-rom-4.4.0.xml
android_manifests[trident-6.1-mol]=mol/trident-rom-6.1.0.xml
android_manifests[trident-6.6-tiyan]=sandbox/trident-rom-6.6-mol-tiyan.xml
android_manifests[trident-cta]=cta/trident-rom-20180129.xml
android_manifests[trident-dc]=dc/trident-rom-mol-4.4.0.xml
android_manifests[trident-dev]=trident-rom.xml
android_manifests[trident-factory]=factory/trident-rom-20180404.xml
android_manifests[trident-trinity-6.6-mol]=mol/trident-trinity-6.6.0.xml
android_manifests[trident-trinity-factory]=factory/trident-trinity-20180827.xml
android_manifests[trident-trinity]=trident-trinity.xml
android_manifests[u1-4.4-mol]=mol/u1-l-rom-4.4.0.xml
android_manifests[u1-mol-4.1]=mol/u1-l-rom-4.1.0.xml

3.2.4 OEM MANIFESTS 列表

下表中,各行的格式是 oem_manifests[产品名-分支名]=manifest.xml 文件名

其中产品名顾名思义,如 t1、u1 等。分支名大家最需要关注的是 dev ,这个即代表主线开发分支。

再次强烈建议使用 sse 命令,它会自动帮你确定该使用哪个 manifest。

oem_manifests[product−smandroid]=
{oem_manifests[$key]}
oem_manifests[aries-dev]=smandroid/oem-sdm710-r2.0_010.xml
oem_manifests[aries-smandroid]=smandroid/oem-sdm710-r2.0_010.xml
oem_manifests[ocean-6.2-mol]=mol/oem-ocean-rom-6.2.0.xml
oem_manifests[ocean-6.6-mol]=mol/oem-ocean-rom-6.6.0.xml
oem_manifests[ocean-6.6-rls]=rls/oem-ocean-mol-6.6.0.xml
oem_manifests[ocean-cta]=cta/oem-ocean-rom-20180607.xml
oem_manifests[ocean-cts]=cts/oem-ocean-mol-6.2.0-20180702.xml
oem_manifests[ocean-dc]=dc/oem-ocean-mol-6.2-20180719.xml
oem_manifests[ocean-dev]=smandroid/oem-ocean-sdm710-r007.0.xml
oem_manifests[ocean-factory]=factory/oem-ocean-rom-20180530.xml
oem_manifests[ocean-trinity-6.6-mol]=mol/oem-ocean-trinity-6.6.0.xml
oem_manifests[odin-4.4-mol]=mol/oem-odin-rom-4.1.0.xml
oem_manifests[odin-mol-4.1]=mol/oem-odin-rom-4.1.0.xml
oem_manifests[osborn-4.4-mol]=mol/oem-osborn-rom-4.4.0.xml
oem_manifests[osborn-mol-4.1]=mol/oem-osborn-rom-4.1.0.xml
oem_manifests[oscar-4.4-mol]=ontim-oscar/mol/oem-oscar-rom-4.4.0.xml
oem_manifests[oscar-mol-4.1]=odm/ontim/oscar/mol/oem-oscar-rom-4.1.0.xml
oem_manifests[oscar-oversea]=ontim-oscar/oversea/oem-oscar-mol-4.1.0-20180409.xml
oem_manifests[oxford-4.4-mol]=mol/oem-osborn-rom-4.4.0-oxford.xml
oem_manifests[playground-dev]=playground/oem-smartcm-test.xml
oem_manifests[surabaya-4.4-mol]=mol/oem-surabaya-rom-3.6.0.xml
oem_manifests[t1-dev]=sfo-rom-oem.xml
oem_manifests[t2-4.4-mol]=mol/oem-icesky-rom-3.7.0.xml
oem_manifests[t2-mol-4.1]=mol/oem-icesky-rom-3.7.0.xml
oem_manifests[trident-4.4-mol]=mol/oem-trident-rom-4.4.0.xml
oem_manifests[trident-6.1-mol]=mol/oem-trident-rom-6.1.0.xml
oem_manifests[trident-cta]=cta/oem-trident-rom-20180129.xml
oem_manifests[trident-dc]=dc/oem-trident-rom-mol-4.4.0.xml
oem_manifests[trident-dev]=smandroid/oem-trident-sdm845-r00058.1.xml
oem_manifests[trident-factory]=factory/oem-trident-rom-20180404.xml
oem_manifests[trident-trinity-6.6-mol]=mol/oem-trident-trinity-6.6.0.xml
oem_manifests[trident-trinity-factory]=factory/oem-trident-trinity-20180827.xml
oem_manifests[u1-4.4-mol]=mol/oem-u1-l-rom-3.6.0.xml
oem_manifests[u1-mol-4.1]=mol/oem-u1-l-rom-3.6.0.xml
oem_manifests[如果没有找到你想要的产品、配置,你可能需要升级system-config了]=sfo-rom-oem.xml

3.3 编译详解

3.3.1 安卓官方编译方法

注意: 这里记录一下安卓官方编译方法,只是为了完整起见。在 system-config 环境下,有更简洁、更不容易出错的方法,强烈建议不要使用安卓官方的编译命令和流程(如何使用 system-config 的编译方法请参考 2.3.1。安卓官方流程的缺点请参考 5.44 和 5.44.1)。

安卓官方提供的三步编译方法如下:

  1. 引入安卓 shell 环境配置: . build/envsetup.sh (system-config 环境下完全不需要这一步)
  2. 设置编译选项,以 T1 项目为例, lunch msm8974sfo_lte-userdebug 或 lunch msm8974sfo-userdebug (system-config 环境下,也几乎完全不需要这一步)
  3. 编译: make、mm、mma(system-config 环境下,对应的命令分别是 make: android-makemm: mmmma: mma,其中 make 是一个系统命令,所以必须提供一个不一样的名字,另外两个是安卓自己定义的,所以 system-config 直接覆盖了)。其中:
    • make 做安卓全局编译,它会扫描并读取所有 Android.mk,确定所有依赖关系,最后编译出 boot.img、system.img 等安卓标准文件。

      make 可以加一些参数,比如 -jN,以 N 个线程进行并行编译,以加快编译速度。你可以试试不同的 N 值,一般以你的 CPU 核数*1~2 是比较合理的。取太小的值会使编译变慢,取太大的值会使系统变得剧慢,并且也会使编译变慢(系统大部分时间用于进程切换,而不是真正的工作进展)。

      还可以给 make 指定其它编译目标(make targets),比如 bootimage,这样的话就可以只编译 boot.img。

    • mm 做当前目录编译,并且不读取所有 Android.mk,所以其编译速度是非常快的。但前提是你已经做过完整编译(或者至少当前目录的所有依赖都已经编译过——见下一条 mma 命令)。
    • mma 做当前目录编译,并读取所有 Android.mk,并且编译当前目录下的 make targets 的所有依赖。

大部分时候,建议使用 mm。第一次编译,推荐使用 mma。实在没办法的情况下,才使用 make 做全局编译。因为安卓全局编译实在是太费时间了。

mma 的工作原理是解析出当前目录下的 make targets,然后调用 make 并传入相应的 make targets。

mm 的工作原理是解析出当前目录下的 make targets,然后调用 make 并传入相应的 make targets,并且设置 ONE_SHOT_MAKEFILE=$M 环境变量,其中 $M 是当前目录下的 Android.mk 在代码树中的路径(就是因为这个变量的存在,使安卓编译系统不再查找并读取其他 Android.mk)。

3.3.2 system-config 提供的更佳编译方法

接下来要讲一下安卓官方的编译方法存在的严重问题及 system-config 提供的解决方案。

  1. 不支持刷机包、ota 包编译。这是因为我们的代码并不是直接来自谷歌,而是来自芯片厂商高通。高通的刷机包对 system.img、userdata.img 有自己额外的定制(拆分成多个文件);并且还有其他额外的分区对应的刷机文件。要编出这些文件,唯一的途径是通过 system-config。可以使用界面友好的 sse 命令(一步一步有引导,不需要文档)。也可以使用更直接的 smartcm 命令,见 这篇博客
  2. 三步编译方法使用比较繁琐

    每次打开新的窗口都要重新设一遍,每次换到不同的产品目录下工作也要重新设一遍。

为此,我在 system-config 里封装了一个 android-make 脚本,避免了以上问题。以 T1 为例,用法如下:

android-make -c msm8974sfo_lte-userdebug
  • 用过一次 -c 选项之后,system-config 会帮你把配置保存在 buildspec.mk 里(注意这是谷歌官方支持的机制),以后就可以不必每次都加这个 -c 参数,除非你想换一个产品配置。
  • 另外, android-make -c 之后直接输入 lte 并按 Tab 键,是支持智能补齐的。
  • 如果你之前没有用过 -c 参数指定过,直接运行 android-make 也是可以的,它会提示你选哪个产品配置。注意它的提示选项比安卓官方的要友好很多!你可以直接输入数字(像官方的 lunch 那样),也可以输入 lte 回车,缩小选择范围之后再输入数字回车或直接回车(如果要选第一个选项的话)。
  • mm 和 mma 命令被单独封装成脚本,可以直接调用。
  • 提供 mm-adb 命令,允许把 mm 编译命令更新了的 system 目录下的文件,直接 push 到手机上。

3.3.3 BSP 同事需要掌握的编译方法

android-make bootimage

只编译 boot.img

android-make kernel

直接用 mm 的方式编译 kernel,省去读取所有 Android.mk 的时间

注意在第一次使用此命令之前,需要保证之前已经编译过 bootimage

android-make lk

直接用 mm 的方式编译 lk bootloader

android-make selinux

直接用 mm 的方式编译 sepolicy

以上三个命令,仅限于高通平台,如有无法使用的情况,请咨询自己的 Team Leader 更常规的做法。欢迎 BSP 工程师协助维护此脚本的这些用法。

sse

允许编译 oem 包

NON-HLOS.bin 的编译方法

1.同步 oem-release 仓库,并在该目录下修改代码

2.执行 oem-release/common/build 下的 python 脚本生成 NON-HLOS.bin , 以 T3 项目为例,操作如下:

cd common/build
python build.py --nonhlos

3.验证成功后在 oem 的代码目录下(与 oem-release 目录在同一层的 modem_proc、common、others 等仓库)修改代码,并提交 review。

3.3.4 如何编译高通 oem

最简单的办法是通过 sse 的 build 子命令,里面有关于 oem 编译的引导。

以下是自己手动执行的步骤:

  1. 安装、升级 system-config
  2. 运行 setup-oem-compiler
  3. 运行 oem 代码目录下的 oem-cm-scripts/build-all.sh 文件

    上面这个命令会编译所有子系统。如果只想编译某个子系统,比如 rpm,可以打开 oem-cm-scripts/build-all.sh 文件搜一下相关子系统的编译命令。

    2017-04-25 更新 如果需要只编译某个子系统,也可以试试 sse 的 build-oem 子命令。

在开发 oem 的过程中,有一些常见问题,在这里说明一下。

高通的代码分为两部分发布,一部分是开源的安卓代码,另一部分是不开源的,一般称其为 oem 代码。在 oem 代码里,又分为两部分,有一部分是要跟安卓合并在一起的,即 vendor/qcom/proprietary 代码;另一部分则是各种子系统的代码,比如 boot_imagesmodem_proc,等等。

子系统应该如何编译,高通有专门的 release notes 文档,那是最权威的参考资料,如果各位对 oem-cm-scripts/build-all.sh 里的编译步骤还有疑问的话,请参考高通的文档。工程师可以找自己的 Leader 问一下,Team 内部有没有一个专门的分享这些文档的地方。

这些子系统的代码编译之后,其输出文件最后会出现在手机上,一般可能有以下几种方式,请大家自己研究一下自己的模块是如何发布到手机里的:

  1. 出现在手机上的某个分区中,比如 boot_images 下最后编译出一个 bootloader,直接刷到 sbl 或 xbl 分区上。
  2. 拼到 NON-HLOS.bin 文件中,最后刷到手机的对应分区上(一般叫 modem 分区,大家可以自己在刷机包目录下运行 rgrep NON-HLOS.bin *.xml 命令查看对应的文件刷到了哪个分区)。

    NON-HLOS.bin 其实是一个 FAT 文件系统,大家可以自己在 Linux PC 上 mount、修改它里面的内容。

    NON-HLOS.bin 里最后会打包进去什么文件,都是通过 oem 代码下的 contents.xml(在新的项目下一般其位置在 others/contents.xml)来管理的,大家如果需要往里面打包新的文件,请自己修改这个.xml 文件。

    打包 NON-HLOS.bin 的命令一般在 common/build/build.py(以前的项目下的名字叫 update_common_info.py),具体的用法请参考对应的安卓代码项目下的 flashing-files/.scripts/update-common-info.sh 文件(如果没有这个文件的话,那该产品使用的就是 ~/system-config/src/github/smartcm/update-common-info.sh 文件)。因为我们 CM 打包 NON-HLOS.bin 都是在编译安卓之后,所以这个脚本跟安卓代码放在一起,各位工程师使用这个脚本,应该是在 oem 代码下开发的时候,这时候运行这个脚本,最后 system.img 和 userdata.img 文件肯定打不出来,因此是会出错的,但应该不影响 NON-HLOS.bin 被正确打包出来,这一点请注意。

  3. 通过安卓的 Android.mk 文件里的配置,把某些固件最后拷贝到对应的 /system 或 /vendor 目录下。

另外,根据自己的研发过程中,要更新的手机上的文件及其方法的不同,可能你编译 oem 的方法也不同。如果是一个独立的模块,比如 boot_images,那可能单独编译它一个就可以了;如果是 NON-HLOS.bin 这样由 N 个模块打包在一起,那你在第一次打包之前,最好是把整个 oem 代码都编一遍,不然打包出来的 NON-HLOS.bin 里可能会少文件或者用到高通 prebuild 的版本,最后都会引起不必要的问题。

最后在这里专门讲一下高通的 oem 子系统代码和安卓代码最后是怎么拼在一起,出刷机包的。

  1. oem-release 的工作原理

    因为 oem 代码及其编译结果及其庞大,非常不利于版本管理和发布追踪,所以 CM 使用了一个 oem-release 的目录,专门用来存放最后拼在一起出刷机包的时候要用到的 oem 文件。

    其具体的工作原理是这样的:

    1. 在项目开始的时候,CM 会拿高通的基线代码跑一遍生成刷机包的流程
    2. 在这个流程中,生成刷机包的时候被用到的文件(根据文件访问、修改的时间戳判断),CM 会把它们拷贝到 oem-release 目录,拷贝的过程保持原有的目录结构不变,整个平移过来

      因为这个文件集合比整个 oem 的编译结果要小很多,只有几⼗兆,所以是可以用版本管理的。整个 oem 代码+编译结果有几十个 G,根本无法进行长期的版本管理。

    3. 把 oem-release 目录用 git 管理起来,每次 oem 编译完,都要从 oem 代码目录往 oem-release 目录下重新拷贝更新其所有文件。

    结合以上关于 oem release 流程的说明,你在开发的过程中,如果发现:

    • 需要往手机上增加一个新的文件(或者往 NON-HLOS.bin 里增加一个新的文件),请注意一定要修改以下两个文件之一:

      • 修改 contents.xml 文件,把你要打包到 NON-HLOS.bin 里的文件参考其已有其他文件的格式修改一下。

        这种改法适用于要打包到 NON-HLOS.bin 里的文件。

        (最近 CM 刚刚更新了一下编译脚本,把 contents.xml 里列的文件也都会自动发布到 oem-release 里去。)

      • 修改 oem-cm-scripts/oem-release-copy-files.txt 文件,把你要加的文件在 oem 代码下的相对路径(可以使用 system-config 下的 ap FILE 命令获取 FILE 在 gerrit/repo 管理的代码目录下的相对路径并自动放到剪贴板里,ap = Android Path,类似的命令还有很多,参考 ~/system-config/bin/ap)填进去。

        这种改法适用于最后要通过其他方法打包到手机上的文件

      只有做了这些修改,你的新增文件才会被发布到 oem-release 仓库里去,最后安卓编译的时候才能看到这个新文件。

    • 需要在 oem 代码里删除一个已经被发布到 oem-release 目录下文件

      目前为止(2017-12-19),我们在项目中还从未遇到过这样的需求,但以后一旦遇到的话,请一定记住要同时删除 oem-release 目录下的对应的文件。

      否则的话,oem 的编译会出错。最后在 release 的时候发现有一个需要 release 的文件不存在。

3.4 提交代码规范详解

大家改完代码之后,用 git add、git commit -s 命令提交到本地 git 历史中。

提交的时候,注意我们的 commit message 格式要求:

  1. 全部英文
  2. 第一行为对改动的简要总结,长度不超过 50。(某些编辑器会对超出的文字用不一样的颜色显示)
  3. 第二行为空行
  4. 第三行开始,是对改动的详细介绍,可以是多行内容,每行长度不超过 72,中间也可以有空行,用于分段。
  5. 空行
  6. Ticket: Mantis ticket number,每个修改需要在 Mantis 系统 中存在相应的 Bug Ticket,在这里记录 Ticket number。

    每个改动都有对应的 Ticket,对测试 Team 的意义十分重大,可以允许他们检查每天的版本都有哪些 Bug 被 Fix 了,然后更有针对性、更有效的进行测试

  7. Change-Id:, 系统自动生成1

    Change-Id: 字段十分重要,是 Gerrit 系统判断两个 git commit 逻辑上是否属于同一个 Patch 的依据。在一条 git 分支上,一个 Change-Id 只能出现一次。Change-Id 都是 gerrit 定制的 git commit hook 脚本自动生成的,如果你发现跟 Change-Id 相关的错误,请参考 这条 faq 或咨询自己的 Leader。

  8. Signed-off-by:,由 git commit 的 -s 参数自动添加

3.4.1 Commit message 示范

commit 1db22817cf935fb4d4ca1065c06a91dab4e416c7
Author: lixin <lixin@smartisan.cn>
Date:   Mon Nov 4 14:44:04 2013 +0800

    Add README file

    This file is used to define the environment variable, include repo
    url and git url

    Ticket: 0005001
    Change-Id: I8b5df9429fd2841d078eefcef354c3f8729fffbb
    Signed-off-by: lixin <lixin@smartisan.cn>

3.4.2 将代码 push 到 Gerrit 上进行 Review

注意以下的大部分步骤,使用 system-config 的 gerrit-push-review,都是可以自动处理的。

  1. 你需要确定当前代码仓储对应的两个参数:git remote 名,git branch 名。方法是通过打开 .repo/manifest.xml 文件进行察看,参考 manifest.xml 文件格式

    以 T1 主线代码的 system/core 仓储为例,git remote 名是 smartisan,git branch 名是 sfo-rom,以下示例命令均基于这两个变例,实际使用时需自行修改,比如 T2 项目主线的 system/core 仓储,git remote 不变,但需要将 git branch 名改为 icesky-rom;而 T2 项目主线的 packages/apps/EmailSmartisan 仓储,则需要保持 git branch 名为 sfo-rom 不变(因为与 T1 共线),但 git remote 名要改为 smartisanos 。这么复杂的规则,是我一再强调要用 gerrit-push-review 命令的原因。

  2. 确保你的代码是基于服务器上最新版本

    git fetch -v smartisan sfo-rom # 或者用 repo sync -n -c .
    
    git rebase smartisan/sfo-rom
    

    如果 rebase 时有冲突的话,说明其他人已经进了新的代码,并且与你的改动冲突,你需要自己解冲突,有必要的话,需要与相关 patch 作者进行沟通。

  3. 用 git diff smartisan/sfo-rom 先自己在本地做一下 review
  4. push for review (注意此命令已经不能直接使用,必须先配置一下,请参考 为什么不能直接 git push review 了

    git push smartisan HEAD:refs/for/sfo-rom
    

    在这个 push 命令的终端输出中,会显示该 Patch 相应的 Gerrit Review 网址

  5. 打开相应的 Gerrit Review 网页,添加你的 Team Leader 或相关工程师作为 Reviewers。

    如果你的 Reviewers Review 通过,那么恭喜,流程到此结束,你的代码就进了服务器了,可以去开发下一个功能、解下一个 Bug 了。

    如果你的 Reviewer 们提出问题,需要你进行修改的话,进入下一个步骤:

  6. 在原来的提交的基础上,进行相应的改正,然后用 git commit --amend 命令再次提交。因为 --amend 参数的关系,原来的 commit message 会显示出来,你可以编辑,但是!绝对不要改 Change-Id!因为在逻辑上,你还是在解决同一个问题。

    回到上面的第 2 步,重新来一遍(所以提升自己的水平,争取一遍就把 Patch 通过,格外重要;使用 gerrit-push-review 命令, 简化以上步骤,格外重要;同时,一次 Review 不过,多次 Review,格外重要——不管不顾代码质量,提上来就保证通过 Review,是给大家挖坑作死的节奏)。

    这时候在第 4 步时,因为你的 Change-Id 没变,Gerrit Review 的网址也不会变,只是会生成一个新的 Patch Set。

    第 5 步添加 Reviewer 可以省略,Reviewer 们会自动收到邮件通知有新的 patch set 生成。

以下为一些注意事项、你可能需要知道的技巧:

  • 逻辑上独立的改动,尽量用一个 Patch 解决。
  • 一个 Patch 里面,一定不要包含多个问题的修改。
  • Gerrit 上的 Review,如果代码质量问题太多的话,可以考虑 abort。

3.4.3 确保所有仓储均已提交

如果你确信自己只改了一个仓库的话,在该仓库下用上面的步骤就可以了。

如果你改过多个仓库,并且有点不能确定到底哪些仓库改了哪些没改的话,system-config 下有个 repo-changes? 命令可以帮你,在安卓代码顶层目录下运行,它会列出所有有改动的代码。

注意如果自己的代码仓库有改动尚未提交至服务器的情况下,也可以用 repo sync 同步代码,但是记住千万不能使用 -d 参数,用过之后你的代码历史就与服务器取齐了。

(你可以用 git reflog 找回你最近的本地提交。但如果时间久远了、或者你自己忘了哪些仓库下可能有本地提交的话,就麻烦了。)

4 版本管理工具介绍

在安卓开发的过程中,主要要用到两种版本管理工具,侧重点各有不同。第一种叫 git。第二种叫 repo,是在 git 之上的一层封装,可以把多个 git 仓储(也叫 git repository,简称 git repo —— 这些术语容易混淆)放在一起来管理。

4.0.1 git 简介

在安卓开发的过程中,我们这两个工具都需要掌握,并且对其熟练程度要求是比较高的,这里结合我们的工作流,讲一下这两个工具的常见用法。注意 git 和 repo 命令及其子命令都自带帮助文档,比如下面马上提到的 git init ,你可以用 git init --help 打开其帮助慢慢的看,是英文的,如果有困难的话可以多看几遍。强烈建议对下面提到的子命令,有任何疑问,立刻先打开帮助文档看一下。

网上也有很多相关的资料,比如 git 官方网站上有本名为“Pro Git”的书,免费,并且有 中文版 的。也有些网页,用示意图的形示讲解 git,比较方便理解,比如 这个中文网页,感觉还不错。当然,网上的资料质量参差不齐,请一定以官方的手册为准。实在理解有困难的话,可以自己在本地做几个实验,说不定就搞清楚了。

在理想的情况下,我们只做一个项目,并且只有一条开发分支,并且只有一个人在做这个项目的开发,并且不需要做各种试错。这种情况下,我们只需要掌握 3、4 条 git 命令就够了:

git init

创建一个项目

git add

把改动过的文件放到 git index 里。git index 是个有点绕的术语,你可以把它想像成预备提交的区域,你运行 git commit 的话,默认是把你用 git add 放到 git index 里的改动,提交到 git 历史记录中。你可以把它理解为索引,一个文件被索引到了,意思就是 git 已经知道这个文件,准备要收录了。还有很多种别的说法,比如 stage,上台准备试演了,没问题的话也可以正式提交进入 git 历史。相应的从 git index 里拿掉,也叫 unstage。

git commit

把改动提交到 git 历史中。恭喜你,你的项目有了一个新的版本,快点发布然后通知大家下载尝鲜吧!等一下!说不定你只是引入了一个新的 Bug 而已。

但事实上,理想归理想,现实归现实。我们几乎永远不可能一个人工作,总是在别人的基础上,所以你还要再掌握这些 git 命令:

git clone

从别的地方(别人已经创建好的 git 仓库、或者你自己以前创建、保存在服务器上的仓库)克隆一个项目,在此之上开始工作。

git push

把你的工作成果 push 到远程服务器上,用于保存(万一自己的机器硬盘坏掉)、分享(让别人基于你的工作成果继续开发)。

注意 git push 的时候你可能需要指定 git remote 的名字或 url,以及目标 git branch 的名字,比如这样: git push origin HEAD:refs/heads/master ,这里 origin 是 remote 的名字,master 是目标分支的名字。

但在有种情况下,你不需要这样做,直接 git push 就可以,git 自动帮你补上 remote 和 branch 的信息。那就是你的本地分支设置了跟踪上游远程分支的时候,参考 git branch 的 --set-upstream--track 等选项。比如默认的 git clone 命令之后,你的本地 master 分支,一般是在跟踪上游 origin 的 master 分支。

受此简化方法的启发,我写的 gerrit-push-review 命令,在 push review 的时候,也不需要自己填 repo remote 和 repo branch 的信息。

git pull

从远程服务器上把别人的最新提交取下来,更新你的当前历史,以包含服务器上的改动。

默认采用的合并历史记录的方式是用 git merge。但如果你使用 git pull --rebase 参数的话,默认采用的方式是 git rebase。

git fetch

从远程服务器上把别人的提交取下来,但不更改你的历史记录,只保存在 FETCH_HEAD 中。可以指定 fetch 某个分支,也可以 fetch 所有分支。

git branch

git 允许你非常方便的拉分支。想开发一个比较大的新功能,有时候不可能一下子搞定,可能会需要试错。这种情况下,为了不影响其他人在主线上工作的稳定性,你可以拉一条自己的分支。

产品开发进入一定阶段,需要有一个稳定的版本给最终用户用,这时候公司的 PM、各位研发 Leader、各位 Test Leader 会一起决定,从某个比较稳定的、开发功能基本已经完备的版本,拉出一个 mol 分支。原则上 mol 分支只进 Bug Fix 相关的 Patch。

注意拉 mol 分支的时机比较重要,拉得太晚了,可能会影响项目按期发布;拉得太早了,可能会导致增加大家工作量,功能还不完备的话,很多主线上的开发 Patch,还需要继续进 mol 线。

git rebase

把某条分支(一般是当前分支)上的修改,移植到另一条分支上。

git merge

把某个提交上的所有改动(从该提交与当前分支分线开始),整合到当前分支上。

git cherry-pick

把另一条 branch 上的某个 Patch,打到当前 branch 上。

另外,因为我们是人类,不可能不犯错、不可能永远不需要试错,不可能从来不需要怀疑自己,所以 git 还提供了这些命令:

git diff

察看 A 版本与 B 版本代码的差异。A、B 版本可以是当前代码改动、git index、最后一次提交、任意某次提交任选其二。

git log

察看提交历史记录

git status

察看当前代码改动、git index 的状况。显示改了哪些文件,哪些文件已经加入到 git index,等等。

git reset

改变当前代码、git index。比如有些改动你已经加入到 git index,现在反悔了,可以用 git reset HEAD <file>... 把它从 git index 里撤出来。在你运行 git status 的时候,如果 git index 里有改动过的文件,它也会提示你可以用什么方法“unstage”。比如最新的提交你整个都不想要了,也可以用 git reset --hard HEAD^ 。 请花点时间了解一下 git reset --softgit reset --hard 的区别。

git rm

被 git add 进 git index 的文件,除了上面的 git reset,也可以用 git rm --cached 从 git index 里移除;更多时候是用来删除某个文件。

4.1 repo 简介

repo 是用来管理 N 个 git 仓储的工具。它通过一个 manifest.xml 脚本来记录都有哪些 git 仓储要一起下载下来,组成一个完整的项目。

4.1.1 manifest.xml 文件格式

manifest 文件格式示例如下(T1 主线代码):

<?xml version="1.0" encoding="UTF-8"?>
<manifest>
  <remote  name="smartisanos"
           fetch="ssh://172.16.0.9:29418/"
           review="172.16.0.9:8080" />

  <remote fetch=".." name="smartisan" review="172.16.0.9:8080"/>

  <default remote="smartisan" revision="sfo-rom" />
  <project name="device/common" />
  <project groups="pdk" name="device/generic/armv7-a-neon" />
  <!-- 还有好几百行 “<project name="..." path="..." remote="..." revision="..." />” 这样的内容 -->
  <project name="packages/apps/ScreenRecorderSmartisan" remote="smartisanos" />
</manifest>
  • 一个.xml 文件中可以有多个“remote”,每个 remote 有各自的名字(name 属性),通过各自的 fetch 属性给出了相应的 project git remote URL 的计算方法。
  • 其中有一个 remote 和 revision 默认值设置 <default remote="XXX" revision="YYY"/>
  • 每个 project 都可以指定 remote(如果没有指定就采用前面的 default remote)和 revision(如果没有指定则采用 default revision)。

    revision 一般是一个分支的名字,但也可以是某个 git commit 的 hash,尤其是在 manifest 快照中。

  • 每个 project 指定了 name 属性和/或 path 属性。如果只指定了一个,则另一个取相同的值。其中 path 属性决定代码同步下来之后,该 project 在文件系统中的路径。name 属性与该 project 的 remote 属性(及 xml 中该 remote 的 fetch 属性)相结合,确定出该项目的远程服务器 URL。

    (如果该 remote 的 fetch 属性值是 “..” 等相对路径的话,则还要结合该 manifest.xml 所属的 git 远程服务器的 URL)。

4.1.2 repo 子命令介绍

在运行正确的 repo init 命令之后,所有 repo 子命令的用法可以自己查看帮助,比如 repo sync --help 会打印一个简单的帮助,repo help sync 则会打印完整的使用手册。

repo init

上面已经介绍过了,必须先 repo init 之后,才能使用所有其他 repo 子命令(repo help 除外)

repo help --all

列出所有 repo 子命令及其简介

repo sync

同步代码

repo forall

针对每个项目(或指定的项目),执行一个 shell 命令

repo manifest

CM 经常需要使用的生成 manifest.xml 快照命令,把每个项目的 git commit 号记下来。

其他

我几乎不用其他任何 repo 子命令。要不就是太慢了,要不就是有问题,没法用,要不就是我自己已经实现了更方便的脚本。

4.1.3 system-config repo 相关命令介绍

repo-switch

用于快速切换、处理 repo manifest。

  • 在 T1 的代码目录下,为了节省时间,是可以重新 repo init、sync 成 T2 的代码目录的。但 repo init 命令太长了,指定 manifest 名字也太麻烦了,所以我会直接运行 repo-switch (不带参数,会让我选用哪个 manifest.xml)。
  • 打印我之前用的 repo init 命令,方便拷贝、粘贴、分享
  • 在编辑器中打开我当前的 manifest.xml。

repo-XXX

其中 XXX 可以是 remote、remote-url、project、path、branch、remote-branch 中的一个,它们会自动的从 manifest.xml 中查询我当前代码仓储的相关信息。

  • 用于脚本编程,非常方便。我的 gerrit-push-review 等脚本就大量使用了以上命令进行计算。
  • 即使不是 repo 仓建的仓储,而是 git clone 出来的,以上命令也会打印出合乎逻辑的信息。

4.2 Gerrit 简介

Gerrit 有网页版图形界面的,这个没什么好说的。

但另外它还支持一些命令行操作,这个感兴趣的话可以参考一下 这个文档

system-config 里包含一些 gerrit 相关的脚本,这里介绍一下最有用的几个:

gerrit-push-review

前面已经提到过很多次了

gerrit-fetch-review

在某 git 仓储下,用此命令后面跟一个该仓储的 Gerrit Review 网址,即可 git fetch 该 review 的当前 Patch set. 比如,在 ~/src/android-documents 下运行 gerrit-fetch-review https://review.smartisan.cn:8080/#/c/140148/

也可以在安卓代码的顶层目录下运行此命令,它会自动计算 patch 所对应的 project 仓储路径。

5 常见问题解答

如果你在使用开发环境的过程中,有任何问题,请参考如下步骤:

  1. 查看本节中各常见问题(注意利用本节的 Table of Contents,在下面为你重新显示一下)

  2. 谷歌 /bing 等搜索引擎,但建议不要使用百度
  3. 问身边的同事
  4. 问自己的 leader
  5. 给 cms@smartisan.com 发邮件
  6. 其他 CM 解决不了的问题,再通过 CM 找包昊军现场支持

请注意这里面的顺序,如果所有问题上来就问包昊军一个人的话,会造成瓶颈,导致效率低下。

同样的道理,在问自己的 leader 之前,先问问身边的同事或者比较熟的同事,能避免给 leader 造成瓶颈。通过先问身边同事,可以形成一定程度的负载均衡、知识分享等,是比较好的方法。很多公司里都是有 Mentor 机制的,形成传帮带,这是一种很好的公司文化,供大家参考。

5.1 如何更改其他软件版本

可以参考一下 system-config 原理与使用的简要说明

system-config 会修改你的系统的一些环境变量,其中最重要的一个是 PATH,在 ubuntu/debian 自带的 PATH 变量前面,加上一些 system-config 自己的 PATH 路径,比如 ~/system-config/bin 等。所以像系统 java 版本的问题,因为 system-config 会在 ~/external/bin/Linux/ext/jdk/bin 下放一个 oracle 的 1.6 版 java,并且这个路径在 PATH 变量里排在 /usr/bin/ 前面,所以不管你怎么用 update-alternatives 修改 /usr/bin/java 的版本,你在命令行输入 java -version 看到的永远是 1.6 。

所以,要修改你的 java 版本,有两种办法:

  1. 临时修改一下 PATH 环境变量,比如在命令行上执行 PATH=/usr/bin:$PATH ,然后再执行 java
  2. 在 ~/system-config/bin/overide 创建指向一个 /usr/bin/java 的软链接。

以下是一些查看各种程序版本的一些有用的命令:

which java # 显示 java 程序在 PATH 变量里找到的第一个路径
type java # 与上面类似,但如果 java 被定义成一个 alias、function,也可以有显示
type -a java # 显示 java 的所以版本(alias、function、在 PATH 变量里能找到的所有版本)

注意如果要永久修改自己的 PATH 环境变量的话,一定要在 ~/.bashrc 的末尾处修改,这样能保证 system-config 对 PATH 变量的修改已经完成。如果放在 system-config 之前修改 PATH 的话,system-config 再修改的时候,你的修改会被冲掉。

5.2 我的 adb(或其他应用程序)好像被 system-config 改出问题来了,怎么办?

还是请先参考一下 system-config 原理与使用的简要说明 ,里面有一些关于 Bash 的原理介绍。

一般来说,你可以先用 type -a adb (或 type -a 其他你认为有问题的程序)看一下我做了什么样的修改。这里你可以看到 adb 被我别名到 my-adb 了,所以你可以用这个办法绕过去:

在 adb 的前面加一个 Bash Builtin 命令, command 。比如你认为 adb logcat 有问题,那你可以用 command adb logcat 把这个问题绕过去。

又比如 cd 命令,如果你认为我改的有问题,你也可以用 type -a cd 看一下,然后发现它被我别名了,本身原来是一个 Bash Builtin。所以你可以在 cd 的前面加另一个 Bash Builtin 命令, builtin ,把问题绕过去: builtin cd args...

5.3 第一次配 system-config 或 system-config-update 时提示安装包冲突

很多情况下是因为在 ubuntu 的系统软件和升级设置里改了升级源引起的。默认情况下,IT 给装的机器把所有升级都给配上了,比如 security 升级、backports 升级。一旦你自己把它取消掉的话,以后很多包就无法再安装了,每次都会提示版本冲突。

software-updates.png

这种情况下,解决的方法就是把上面的那些✓全部都勾回去,然后运行一下 sudo apt-get update 再重新配置、更新 system-config。

注意,千万不要随便升级到 Ubuntu 16.04。可以考虑把上面图中最后一个开关改成不提醒 Ubuntu 系统版本更新。

5.4 提交 review 时没有 Ticket 号或 Ticket 为 0 报错

发生这种错误,原因可能是以下几种,请检查清楚后按自己的情况处理:

  1. 你只提交一个 Patch,但 git commit 时没有给自己的 Patch 填 ticket 号或 ticket 为 0

    解决方法是用 git commit --amend 重新提交,写上正确的 ticket 号

  2. 你希望一次提交多个 Patch,但其中某一个或多个 Patch 没有 ticket 号或 ticket 为 0

    解决方法是重新整理 Patch,比如可以考虑把 Patch 合并,然后填上正确的 ticket 号

  3. 你用错分支了,比如在 t1-dev 线上整了一个 patch,然后想提到 t2-dev 线上

    解决方法是切到 t2-dev 线上,重新 cherry-pick 你的 Patch,然后再提交。

    这是最坑的低级错误,我个人来讲的话,之前犯过一两次之后,后来就再也没有犯过,因为给自己总结了一下流程,并且封装了一些脚本。以上面的情况为例,我在 t1-dev 上进了一个 Patch,然后又想进到 t2-dev 线上的话,我会按下面的步骤操作:

    1. 第一步,是运行 git co -B,这是我自己封装过的,它会把当前仓储下所有 git 分支列出来,让我过滤、选择。

      我直接输入 ice(t2-dev 线的名字叫 icesky-rom),然后回车,脚本会过滤出所有与 ice 匹配的分支,方便我进一步选择。

      如果有太多比如 mol 的分支的话,我也可以输入 !mol 回车,这样所有匹配了 mol 字眼的分支就全都消失了,选择范围变小了,眼睛可以不用那么累。

      过滤到最后如果第一个选项就是我需要的分支,我直接输入回车就可以,不需要先输入 1 再回车。

      接下来脚本就从 remote/smartisan/icesky-rom 上拉出本地的 icesky-rom 分支来,并设置好了上下游分支跟踪关系。

    2. 我直接输入 git cherry-pick,这也是我封装过的,它会列出最近的 git commits 让我选(其实就是调了一下 git reflog)。选择的界面跟上面是一样的操作。

      一般输入 2 然后回车,就能 cherry-pick 到我刚刚在 t1-dev 线上的那个提交,它刚好是第 2 个(reflog 的第 1 个是刚用 git co -B 从 t2-dev 线上拉出来的分支头)。

    3. 如果有 cherry-pick 冲突,就解冲突,并思考一下为什么会有冲突,能不能想办法消灭这个冲突(解冲突都是 effort…)
    4. 最后,直接运行 gerrit-push-review 命令,push 到 gerrit 上进行 review。

      这个脚本会认出你当前分支的上下游分支关系,自动提示你应该 push 到哪个分支上,一般直接确认就好。如果不能直接确认的话,需要好好思考一下这是为什么。

  4. 你在处理基线升级的 rebase 任务,需要把一条主线上的所有 Patch 全部 cherry-pick 到新的升级线上

    很多老的 Patch 都没有 Ticket,对这些 Patch 不应该修改它们的 git commit msg。

    之前有发过一封邮件,所有的 Rebase(不管会不会发生 no-ticket 检查错误)都不应该走 Gerrit Review,应该线下 Review 有冲突的代码,然后直接 push 到目标分支上。如果目标分支因权限问题不能直接 push,请给 CM 发邮件统一处理(请抄送 Leader、rebase 负责人)。

5.5 Gerrit: 您的连接不是私密连接

为了确保公司 odm gerrit 服务器的网页登录不被人监听,我们的 Gerrit 服务器(odm 的、内部的)都采用了 https 协议。但因为属于公司内部服务器,并不对公众开放,所以并没有买 https 证书。

因此你第一次连接的时候,会看到类似这样的警告:

https-warn.png

点高级选项,选信任此证书就好了。各浏览器采取的操作大同小异,请自行 Google/Bing(关键字:你的浏览器型号比如 Firefox 或 IE + 信任 https 证书)。

5.6 Ramdump 文件与符号表不匹配,无法解 Bug

5.6.1 Ramdump 版本查询大陷阱及应对方案

首先要确认 Tester 给的版本对不对,有时候 Tester 给的版本是错的。所以基本上只要是 Ramdump 的 Bug,建议不要看 Tester 给的版本,直接用这条命令从 ramdump 文件中取 Linux 版本号,然后再用这个信息去查 http://172.16.2.18/vmlinux.html

strings DDRCS0.BIN |grep 'Linux version'

尤其是,现在公司有一个 Crash Report 网站,里面会自动上报一些 Crash、Dump 的 Bug,上报的时候甚至已经给出了 Vmlinux Kernel 的那条版本字符串,但是!请注意,这条版本字符串很有可能是不正确的,一定不能轻信。因为这种场景的存在:用户刷了个 9 月 20 号的版本,Ramdump 了;然后用户觉得这个版本没法用,升级到 9 月 21 号的版本,然后一开机,自动把 Ramdump 报上来了。那么,请思考一下,你认为 Crash Report 上报的版本号会是 9 月 20 号的,还是 9 月 21 号的呢?

所以在此建议最好大家要统一一下解 Ramdump 的习惯,所有 Ramdump,一律通过 Ramdump 文件本身包含的 vmlinux 字符串来确定真正的版本。

5.6.2 确认版本匹配后,概率性无法解 ramdump 的说明

确定版本没有问题之后,需要注意不管是 Modem dump 还是 Kernel dump,都存在一定概率的解 ramdump 失败的情况,一般并不是 CM 出的版本 symbol 错误引起的,而是其他未知的原因(比如 Tester 操作有误,比如高通 dump 逻辑有 Bug 等,以及发生 dump 的错误比较严重,损坏了关键数据等)。

出现这种情况,想确定是不是 CM 版本问题,需要 BSP 同事自己 trigger 一个 kernel panic、modem dump,然后重新看一下是不是同样无法解 dump。如果能解 dump,那就证明不是 CM 的问题。如果不能解 dump,那就说明 可能 是 CM 的版本有问题,这时候还需要 BSP 的同事帮忙继续查,比如换一个版本看看是不是同样的情况。

到目前为止,一年多的时间内, 几乎 从未发现是 CM 给的版本有问题导致无法解 ramdump 的现象。

所以出现解 ramdump 失败之后,一般的做法都是让 Tester 尽快重现,然后祈祷下一次出现的时候抓到的 ramdump 是可以解的。

5.7 如何查找某天的 Build 对应的 symbols 文件

参考 2.8

注意我们目前只支持 4 种查找 symbols 的方法:

  1. 通过“Linux version”字符串查找
  2. 通过刷机包目录名查找
  3. 通过 ota 文件名查找
  4. 通过手机上的 ro.build.date.utc 属性查找

通过此页面,除了可以找到相关的 symbols 目录位置,还可以找到相关的刷机包的位置——如果一开始是通过 ota 升级上来的,并不知道刷机包位置的话,可以通过上面第 3 种和第 4 种方式进行查找。

5.8 刷机包版本目录名规则

所有刷机包目录,会以如下规则命名:

  1. 前缀(可选、可组合)

    我们会有 3 种前缀,分别是 SE、K、SA。SE 是高通提供的允许对启动过程中的某些关键引导文件进行签名的底层硬件支持的机制,因为是 CPU 硬件直接提供,所以安全级别非常高,发现签名不匹配直接禁止启动。K 前缀代表的是安卓软件系统 App 所使用的签名密钥,如果有 K,代表使用了公司正式签名;没有的话,表示使用了非正式签名(跟工程师自己编译时使用的签名是一样的)。SA 是锤子内部早期定义出来的一个配置变量,现在已经弃用,为了兼容旧版本,不得不一直留着,大家可以不用太关注,把它当成 user 版本也可以。

    SE

    某些有指纹识别、支付功能的手机,对安全要求特别高,因此还要求实现 Security Boot。此处 SE 即表示打开了 Security Boot 的开关。

    Security Boot 的具体原理简介如下:手机硬件上有一个熔断保险丝,该手机第一次被刷上一个 SE 版本的时候,该保险丝会被烧断,刚烧进去的版本的 SE 签名信息会被永久记录在手机芯片内,以后不论如何刷手机软件版本,必须检测到新版本的 SE 签名信息与首次熔断时使用的 SE Key 匹配才允许继续开机,否则无法启动。

    在手机开发的前期,一般 SE 的功能开发尚未完成,从工厂里拿回来的试产手机都是非 SE 的版本。除了软件版本称为非 SE 版本,我们也管这样的手机叫非 SE 手机。

    在手机开发的后期,SE 功能开发完成、验证通过,从工厂里试产回来的手机都是 SE 的版本。同样,这样的手机我们也直接称为 SE 手机。

    烧错 SE 版本的话——非 SE 手机烧了 SE 版本或 SE 手机烧了非 SE 版本——都可能导致手机无法开机,必须非常注意!并且一般手机出厂时如果是非 SE 版本,一般不建议工程师再自己把它刷成 SE 版本,因为据说有些功能必须在生产的时候第一次就刷 SE 版本才可以正常使用——这个具体需要咨询 BSP 同事。

    通过 http://172.16.2.18/vmlinux.html 网页(输入 command adb shell getprop ro.build.date.utc 输出的手机现有版本编译时间戳)可以查到手机目前版本的刷机包路径名,根据该名字是否包含 SE 前缀,可以判断手机是 SE 还是非 SE。

    经与 PM、Learder 讨论决定,主线非 SE 版本一般放在 \\172.16.2.240\flash\daily\PRODUCT\dev(参考:版本共享目录变更说明)共享目录下,SE 版本一般放在 \\172.16.2.240\flash\daily\PRODUCT\no-flash\ 共享目录下(因为 SE 版本刷到非 SE 手机上有可能会永久地改变此手机的硬件熔丝状态,以及给工程师调试带来不便,所以加了 no-flash 标志,提醒大家刷此版本时要谨慎)。

    K

    表示我们使用了正式的安卓系统软件签名密钥系列。

    我们原来 T1、T2、U1 项目的安卓 apk 签名 Key,每个工程师都可以获取,并且开放给了 ODM 厂商,这是非常危险的做法。所以现在我们有一个公司正式的 apk 签名 Key,对所有工程师保密,只有极少数人可以获取这个正式 Key。

    以后新项目给最终用户的出货版本,一定会是用这个正式 Key 签名的。老项目已经到了用户手里,是不可能改 Key 了。

    刷机包目录如果带了 K 的前缀,说明它是用公司内部保密的新密钥签名的;否则说明它是用之前那个老的密钥签名的。

    SA

    表示没有任何后门的 user 版本。我们的 user 版本之前因为调试需要,曾经分过两种编译选项,一种是 user-root,用安卓的 user 版本编译,但可以 root。另一种则没有这些后门,是给最终用户的版本。

    因为专门出一个 user-root 版本的做法,风险太大且不可控,目前我们已经没有这个版本区分,所有的 user 版本,都是 SA 的,在其刷机包同级共享目录下,有一个加 .root 后缀的目录,包含了一个 boot-root.img,可以用于 root 手机。

    但因为向后兼容的关系,以后我们的 user 版本,虽然已经没有 user-root,为了确保所有配置正确,一定会加上 SA 的选项,才能确保没有后门开关被不小心残留下来。

    (none)

    没有前缀。比如给非 SE 手机用的 userdebug 版本,一般是没有前缀的。

    以上的前缀,是可以组合的,比如 KSA 或 SEKSA 等等。其中 SA 一般与 user 一起配置,因为 SA-userdebug(不留后门的 userdebug)是没有意义的。K、SE 前缀则可以与 user、userdebug、eng 等自由组合。

  2. manifest.xml

    在刷机包目录名里会体现我们是使用哪个 manifest.xml 来进行编译的。比如 mol/u1-rom-idn-2.6.3-odm.xml ,最后体现在刷机包目录名里,会变成 mol%u1-rom-idn-2.6.3-odm (去掉.xml;把/替换成%)。

  3. 所用的安卓编译 lunch config。 比如 icesky_msm8992-userdebug
  4. 时间戳。基本上保证与手机上的 ro.build.date prop 保持一致
  5. emmc size。比如 32g、16g。注意有些产品该字段无效,比如 MTK 的 bono 项目,它的一个刷机包可以支持任意 emmc 尺寸,但为了脚本的一致性,其目录还是包含该字段。

    该字段默认为 32g,注意在项目的初始阶段,有可能并不能正确反映 emmc 的实际尺寸(可能存在目录名为 32g,但实际 emmc 尺寸为 16g 的情况——取决于 BSP 对系统 partition.xml 的修改)。

5.9 刷机包共享目录的访问方法

  1. 域名是 smartisan.cn
  2. 用户名是你的公司邮箱前缀,比如 baohaojun
  3. 密码是你的公司域密码(跟公司邮箱登录密码不是同一个)。可以通过 http://password.smartisan.cn/ 更改密码,如果忘记密码,请联系 IT 重置。

    提示:如果你是工程师,那么你访问 gerrit 网页的时候,用的也是这个密码。

  4. 如果是 Linux 下登录,推荐使用命令行的方式,在 system-config 下有非常方便的 cd 命令的封装(cd smb://172.16.2.240/flash),直接可以一步一步的帮你 mount 相应的共享目录并 cd 到该目录下(参考 本小节的 Faq。如果你坚持要用 GUI 界面,请参考这张截图,但请注意 Linux 下很多图形界面访问共享目录可能有各种各样的问题,并且目前来说是无法解决的:

    Linux-share-folder-visit.png

  5. 如果是 Windows 下登录,参考这张截图:

    Windows-share-folder-visit.png

如果在访问共享目录碰到问题,请联系 IT 同事寻求帮助。以下是 Windows 和 Linux 下访问共享目录时的常见问题

5.9.1 Linux 下访问共享目录出错

Linux 下有很多种访问共享目录的方法,其中有些命令行上的方法看起来比较简陋但比较稳定,有些图形文件管理器看着比较花哨但使用起来经常出问题(就是死活 mount 不上,不停地哐哐弹窗让你重输密码,即使你对灯发誓并没有输错域名、用户名和密码)。

这里跟大家分享一条非常方便的方法。在 system-config 环境下,直接命令行上敲 cd '\\172.16.2.240\flash' 或者 cd smb://172.16.2.240/flash ,它会一步一步提示你该输入什么域名、用户名以及共享目录服务器密码,然后直接 cd 到那个共享目录下。

你也可以使用 . cd-adb 命令,这样如果你的 adb 连接公司的手机的话,这个命令让你可以直接 cd 到对应的刷机包目录下(直实就是把 http://172.16.2.18/vmlinux.html 网页、 adb getprop ro.build.date.utc 等封装了一下)。

使用前请考虑用 system-config-update 更新一下配置环境,刚刚(2017-07-11)把脚本改得更人性化了一点。

5.9.2 Windows 下访问 172.16.2.25 共享目录

注意:硬件 Team 同事访问 172.16.2.25 文件共享服务器碰到问题时,请一定要先按照本节内容自行操作。因为历史原因,硬件 Team 在使用这台机器作为文件共享服务器,以前由硬件 Team 同事自己负责管理,但该同事离职后,由本人(包昊军)友情提供管理此服务器的支持。但因本人精力有限,请各位碰到问题的话,一定要先努力尝试一下通过下面的步骤自己解决问题,感谢理解与支持)

172.16.2.25 这台机器可能配置出了点问题,导致有些同事在 Windows 下通过文件管理器无法访问上面的共享目录,这种情况下,请试试通过命令行(cmd Windows 终端)是否可以解决问题。

相关的命令如下:

  1. net use

    输入上述命令,可以显示目前正在使用的共享目录连接。

  2. net use \\172.16.2.25\ipc$ /del

    上述命令可以用于断开有问题的连接。请把与 172.16.2.25 相关的所有共享连接都通过上述命令删除。

    net use 的具体使用方法,请查阅 net help use 命令显示的帮助(也可自行上网搜索)。

  3. net use \\172.16.2.25\SHARE * /user:smartisan.cn\USERNAME
    • \\172.16.2.25\SHARE 请自行替换成自己想访问的共享目录名,比如把 SHARE 换成 Rf

      通过 net view \\172.16.2.25 这个命令,可以列出这台服务器上有哪些共享目录。如果这个命令不能打印出共享目录列表的话,请访问 http://172.16.2.18:18080/hw_share/index 这个页面,里面会显示共享目录列表(不显示的话就多刷新几次)。

    • * 必须输入,这代表 net use 命令会提示你输入你在公司内网的密码。
    • smartisan.cn 是公司的域名,不可以更改。
    • USERNAME 请替换成自己的内网用户名(一般就是你的公司邮箱前缀)。回车后此命令会提示输入公司内网密码。

如果以上步骤还有问题的话,请与本人(包昊军)联系,请将你的内网域密码改成简单的、方便告诉我的密码(请访问 http://password.smartisan.cn/),然后通过邮件发送给我,等我帮你解决完问题之后,你再改回去。

5.10 APK 签名 Key 的 MD5 值

以下为所有 APK 签名 Key 的 md5 和 sha1 值。其中:

  1. formal 为以后我司自研产品使用的 Key,比如 T3 量产版本
  2. odm 顾名思义为外包项目使用的 Key,比如 Bono;
  3. old 为以前的产品在使用的 Key,比如 T2 主线和 T2 量产版本,以及所有产品的主线版本。
  4. SmartisanApps 是各条产品线上都可以使用的独立 App 的 Key。最初设置这个 Key 的原因是有些第三方平台一款软件只允许你使用一个 Key,连测试 Key 都不行。(可以在安卓代码目录下运行 abc-x grep SmartisanApps 命令看一下目前有哪些 App 使用了这个 Key)。

此外,安卓自身共提供了 4 组 Key,新的 Apk 研发时请按照实际情况考虑用哪组 Key(如果不能确定,最好咨询一下自己的 Leader):

  • media
  • platform
  • releasekey
  • shared

已有的 App 使用的是哪个 Key,可以通过该项目代码下的 Android.mk 文件确定,看一下里面有没有指定 LOCAL_CERTIFICATE,如果没有指定的话,那使用的就是默认的 releasekey。在网上也可以搜到“通过一个 apk 二进制文件,如何确定它使用的 Key 的 md5 值”。

所有 md5 值获得方法为: keytool -printcert -file KEY.x509.pem

sign-apk-formal/media.x509.pem
	 MD5:  07:CB:E9:7E:68:6C:76:3B:1F:B6:AA:6F:97:B7:45:D0
	 SHA1: 47:F1:6C:55:8A:9C:BA:F1:3C:97:DF:D1:D7:6A:2C:FF:50:FF:CD:DE
sign-apk-formal/platform.x509.pem
	 MD5:  9D:ED:93:B4:15:26:A1:5B:5B:16:27:36:B6:FE:F0:12
	 SHA1: 7C:1B:8D:C2:82:44:87:B2:38:B5:BD:23:EB:BB:8E:29:45:EA:2D:83
sign-apk-formal/releasekey.x509.pem
	 MD5:  AF:9D:69:73:43:CB:C3:A7:2F:75:40:56:73:4F:88:37
	 SHA1: 2B:A3:A9:5F:3A:50:BC:53:77:8F:BB:33:42:77:85:70:E5:D5:36:BB
sign-apk-formal/shared.x509.pem
	 MD5:  C8:A4:B4:5A:A7:03:C0:DD:D8:AB:6B:92:F3:B3:65:BD
	 SHA1: 94:F3:FB:BE:E4:3E:82:BF:9F:63:AF:1E:AA:74:FA:C1:4C:78:75:6F
sign-apk-odm/media.x509.pem
	 MD5:  F7:0A:FD:7E:53:66:0F:0F:74:B7:10:4B:E2:B6:C1:34
	 SHA1: 6E:AF:D6:FE:51:2E:F1:BA:7A:8D:5C:54:33:08:29:8B:2D:05:A3:44
sign-apk-odm/platform.x509.pem
	 MD5:  BF:86:92:95:72:63:F3:F2:51:AF:F1:25:6B:9F:F4:C6
	 SHA1: 9B:86:53:BE:49:67:63:18:4C:B6:CA:4E:15:A3:F6:17:72:98:DD:03
sign-apk-odm/releasekey.x509.pem
	 MD5:  99:2C:D8:01:C6:50:76:84:B0:81:E3:43:C6:4A:88:EA
	 SHA1: 48:ED:99:E3:0D:0B:35:23:BB:70:4C:4B:9E:3B:20:42:17:38:F8:55
sign-apk-odm/shared.x509.pem
	 MD5:  02:AE:49:53:73:69:A1:58:F5:33:76:42:15:3E:D2:37
	 SHA1: 2C:30:94:1D:47:32:15:B8:10:C4:FF:77:37:9C:99:64:E6:E2:20:F4
sign-apk-old/media.x509.pem
	 MD5:  11:C4:54:C0:02:92:87:C3:16:B0:06:00:52:52:B4:C3
	 SHA1: C7:D0:41:AE:32:42:CF:15:17:D2:D4:10:2C:E6:C0:11:06:BF:C1:4A
sign-apk-old/platform.x509.pem
	 MD5:  10:63:75:22:AA:8F:84:01:70:C5:0F:CB:E6:12:27:AC
	 SHA1: F0:21:56:1C:50:33:B5:06:F4:6A:FB:87:C3:DD:CC:63:7B:B7:05:E7
sign-apk-old/releasekey.x509.pem
	 MD5:  5F:D1:D7:57:99:92:CF:F7:01:EA:20:87:3E:8A:C9:9F
	 SHA1: 23:39:EE:67:0C:89:6B:45:D9:48:2E:17:C4:D0:08:B1:BD:0C:A7:E7
sign-apk-old/shared.x509.pem
	 MD5:  8F:E2:F4:BF:F9:EC:81:23:BA:F1:F0:E0:BA:E4:B9:AA
	 SHA1: 4B:A5:AE:5B:E2:75:5E:74:B6:4A:21:B3:BE:2B:48:73:D4:10:89:6C
sign-apk-old/testkey.x509.pem
	 MD5:  E8:9B:15:8E:4B:CF:98:8E:BD:09:EB:83:F5:37:8E:87
	 SHA1: 61:ED:37:7E:85:D3:86:A8:DF:EE:6B:86:4B:D8:5B:0B:FA:A5:AF:81
SmartisanApps:
         MD5:  B8:8F:EF:CC:5A:B5:11:49:99:24:07:AB:41:F0:6D:9C
         SHA1: 37:13:8E:01:AF:50:8C:1F:74:7E:B6:BD:E5:C9:F0:F0:E0:D9:6A:51

5.11 同一个 App,不同的 Build,适配不同的 Key

有很多 App 使用了第三方的平台,我们会把我们的 Key 的 md5/sha1 值(具体取决于第三方接口)提前告诉对方,对方再给我们一个用我们的 md5/sha1 值生成的用于校验的值,在调用第三方接口的时候,要把我们的 Key md5 和对方给的值一起传过来验证,验证通过才允许继续调用。

一旦我们在 user、userdebug(其实是 K 与非 K 的区别,但一般 user 版本为 K,userdebug 为非 K)版本里采用不同的 Key,意味着第三方要为我们生成多套对应的校验值,我们在不同的(formal/old/odm)版本里需要使用不同的值。

具体的做法请参考 packages/apps/WeatherSmartisan/fix-AndroidManifest.sh 和 packages/apps/WeatherSmartisan/Android.mk 这两个文件。

5.12 APK、boot.img verity 功能、SE security boot 功能签名的网页地址

所有的签名功能都使用 http://172.16.2.18:18080/apksign/index 这个网页。一开始的时候只为 APK 签名服务,所以网页的名字也是 apksign,后来慢慢加入了其他签名的支持,在此做一简要说明。

各种签名与 CM 出的刷机包前缀有对应关系,请参考 5.8

使用此网页需要额外的步骤、时间、精力上的开销,因此建议大家日常开发的时候尽量避免使用此网页,只在万不得已的时候使用,或者自己做好平衡、取舍,尤其是 BSP 的同事。比如一个问题既能在正式签名的手机上重现,也能在非正式的手机上重现,那你有两个选择:

  1. 使用正式签名的版本调试问题,你的每一次改动在自己的 PC 上编译完后,都必须再使用此网页重新签一下名,再刷到手机上进行调试。
  2. 使用非正式签名的版本调试,你的改动编译完成后,直接可以刷到手机上。

做选择的依据还包括 CM 提供的 Daily 的编译版本是哪种签名,SE 与非 SE 版本的手机多寡,等等,一般情况下 CM 与 PM、各 Team 的 Leader 协商后决定 Daily 该出什么版本,目前的策略如下:

  1. 主线上为最大程度方便大家调试,一般出 userdebug、不带 K、非 SE 版本
  2. 主线上随着研发、生产进入后期,工厂里回来的手机都是 SE 版本,也会出 SE、非 K 的 userdebug 版本,放在 \\172.16.2.240\flash\daily\sse- 产品名 \no-flash (参考:版本共享目录变更说明)下。
  3. mol 线上一般都是 SEK 版本,因为出货版本从 mol 线出,并且手机全是 SE 的,必须用正式的 Key。

    一般为了大家调试方便,mol 线上还会出 userdebug 版本,也是 SEK 的。

    • 手机一般是 SE 的,所以版本也是 SE 的
    • 考虑到 mol 线调试时常常不能刷机破坏 userdata 分区,而要保留数据进行分析,与 user 版本之间必须可以相互 ota,所以必须也用 K。

      不同的 K 签名版本之间无法相互 ota,这也是安卓官方的机制。

5.12.1 APK 签名

安卓系统的每个 APK 都要签名,用来验证这个 APK 是不是签名拥有者开发的,而不是别人假冒的。

因为代码里原系统签名未进行加密保护,导致 T1、T2、U1 使用的系统签名被所有工程师、合作过的 ODM 厂商获知,故重新整理系统 APK,针对自研项目和 ODM 项目,分别增加一套签名(media、platform、releasekey、shared)。

另外,再对某些应用,提供独立于产品的签名,这样,此应用可以确保不管是在自研项目还是 ODM 项目上,用的都是同一个签名。主要是一些需要使用微信 SDK 的应用,会有此需求,第三方平台不允许一个应用使用多个签名。

因为所有正式签名均被加密保护,需要自己签名测试的话,请访问 http://172.16.2.18:18080/apksign/index

点击提交之后,签完名的 apk 文件会放到共享目录下,具体地址会在提交后的网页中显示。

5.12.2 对 Ota.zip 文件进行签名

常见的应用场景在于把 boot.img 重新打包成一个单刷 boot 分区的 ota 包,用于 user 版本获取 adb root 权限。

有时候工程师自己编译了一个 boot.img,想通过这种方法生成一个 ota 包,该如何操作呢?

请参考 5.31.1

5.12.3 对 boot.img 文件重新签名

Odin 及之后的项目启用了对 boot.img 的 verity 签名,这是谷歌提供的一种系统安全机制,为了确保系统里每一个组件都是由厂商提供,而不是被恶意修改过的,这个签名主要对 boot 分区进行保障。下表是该 Key 的使用情况。

产品使用谷歌公开 Key 签名使用公司 Key 签名是否强制检查
M1/M1L 及之前
Odin不带 K 的版本带 K 的版本user 版本在安卓 lk bootloader 里检查
Osborn不带 K 的版本带 K 的版本SE 版本在高通 oem bootloader 里检查
    

一般来说 boot.img 单独签名的场景很少,因为我们对公司 verity key 的启用是在 SEKSA-user 版本上,这种版本上你单独签了 boot.img 也是无法刷到手机上的。

所以它的使用一般都是跟 user root ota.zip 包一起,参考 5.31.1

5.12.4 SE(Security Boot)相关文件签名

BSP 工程师需要对文件进行 secure boot 签名时,请访问:http://172.16.2.18:18080/apksign/index

勾选相应的 Security Boot 签名,并上传需签名的文件。签单一文件时可直接上传,签多个文件时需将待签名的文件压缩成 zip 后上传。

点击提交之后,可根据提示到共享目录下获取签完名的文件。

关于哪些文件需要进行 SE 签名(比如 adsp.mbn,在 oem 代码目录下编译出来后有很多个不同路径的版本),请参考该 OEM 代码对应的安卓代码项目下的 flashing-files/.scripts/files-signed-before-build 文件。

5.12.5 对 NON-HLOS.bin 文件进行 SE 签名

首先,这个 faq 的标题不是很准确,NON-HLOS.bin 是一个类似于 system.img 的 vfat 格式的文件系统映像文件,它本身是不适用于高通的 SE 签名机制签名的。真正需要签名的是它内部所包含的一些高通的子系统 firmware 文件。

所以这里讲一下:

  1. 如何签名高通子系统 firmware 文件
    • 所有自助签名都通过这个网页进行:http://172.16.2.18:18080/apksign/index
    • 高通的子系统 firmware 文件 SE 签名有一个特殊的地方需要注意,那就是签名前是一个文件,比如 qdsp6sw.mbn,签完名后会被打散成 N 个文件,比如 qdsp6sw.mbn -> modem.XXX。

      在更新 NON-HLOS.bin 的时候,需要自己注意应该是把签名后打散的文件拷贝到对应的路径下,比如签出来的 modem.b00 应该拷贝到 NON-HLOS.bin 的 image/modem.b00 路径。

    • 如果需要签名、更新多个子系统 firmware 文件(甚至是 NON-HLOS.bin 里包含的所有 firmware 文件),可以把这些 firmware 文件打成一个 zip 包,然后再通过网页上传、签名。
  2. 如何将签完名的高通子系统 firmware 文件直接更新到手机上

    很多时候可以考虑直接把签过名的 firmware 文件给 push 到正在用于开发、调试的手机上,具体的做法是:

    • remount 手机上的 /firmware:adb shell mount /firmware -o rw,remount
    • adb push 相应的文件:adb push modem.b00 /firmware/image/modem.b00,有多少个就 push 多少次(建议自己写一个批处理脚本)。
    • 重启手机,以确保新的 SE 签名的 firmware 文件失效。
    • 如果手机重启后不工作,请自己仔细检查上面哪个步骤出错了
  3. 如何将签完名的高通子系统 firmware 文件更新到 NON-HLOS.bin 文件中
    • NON-HLOS.bin 作为一个 vfat 文件系统映像,可以 mount 它:

      sudo mkdir -p /media/temp
      sudo mount NON-HLOS.bin /media/temp
      
    • 把需要更新的文件拷贝到上例中的 /media/temp 相应的子目录下
    • sudo umount /media/tmp,原来的 NON-HLOS.bin 就已经被更新了

5.13 一些高通 SE 签名相关的研发问题

大家最关心的,可能是 SE 的软件版本,与非 SE 的软件版本之间有什么区别。其实区别非常简单,我们从编译过程中的区别展开讲一下。

  1. SE 的版本在安卓编译之前,要提前对一些文件进行签名

    以 trident 项目为例,需要签名的文件列表保存在 flashing-files/.scripts/files-signed-before-build;执行签名动作的脚本在 flashing-files/.scripts/before-android-build.hook

  2. SE 版本在安卓编译完成之后,要再签一些文件(目前的项目上只有一个文件要签,就是 bootloader,比如 trident 上的 abl.elf)。

    Trident 项目下执行此操作的脚本在 flashing-files/.scripts/update-common-info.sh

  3. 在启动的时候,SE 的手机(硬件 security boot 功能已被使能的手机,参考 1.1)必须刷 SE 版本的软件,因为高通规定要签名的每个文件会被检查,如果发现签名不正确的话,就会
    • 中止启动

      比如 bootloader(abl 分区上刷的 abl.elf 文件)签名不正确的话

    • 某些模块无法正常工作

      比如 wifi 模块的 firmware(ipa_fws.*)签名不正确的话,就无法打开 wifi 功能。

      或者 GPU 模块的 firmware (在 trident 上文件名是 /vendor/firmware/a630_zap.*)签名不正确的话,屏幕可能就不亮了。

5.13.1 如何在 SE 的手机上进行需要签名的模块的开发?

  1. 按照非 se 的情况正常编译你的模块
  2. 把你的编出来的文件通过 http://172.16.2.18:18080/apksign/index 这个网页进行签名

    也可以考虑 sse .sign-apk --help 命令,通过命令行进行签名。

此外,请参考本文档中其他与 SE 签名相关的说明。

5.13.2 如何自己编译 SE- 版本的刷机包?

参考 这个 how-to

5.14 java 版本问题

在安卓编译的时候,不同安卓版本要求使用不同 Java 版本,比如 T1 要用 1.6 的 Java(也叫 Java6?),M1 要用 Java7,后面的 Odin、Osborn 等等要使用 Java8 了。 system-config 下的脚本在编译安卓的时候,会自动识别当前安卓版本要求使用什么版本的 Java,然后自动设置 PATH 环境变量确保正确的 Java 在 PATH 路径里被第一个找到。参考 ~/system-config/etc/.fix-java-version

随着安卓版本的更新,比如在最新的 Trident 项目上,system-config 识别版本的代码过时了,导致无法识别到正确的 Java 版本,这种情况下,请大家升级一下 system-config 就可以了,CM 已经修改了相应的脚本代码逻辑。

如果更新了 system-config 之后发现还是有 java 版本识别问题,请向 cms@smartisan.com 发邮件反馈。

5.14.1 如何手动更改 java 版本

system-config 提供了一个 java7 和 java6 的命令,你可以直接运行,系统会启动一个新的 shell,并且提示你是 java7 版本还是 java6 版本,如图所示:

java7.png

也可以在 java7/java6 之后直接输入命令,比如在 T2 代码下编译 kernel: java7 make kernel

(现在已经有 java8 命令了,每个命令都是指向同一个脚本文件的软链接,根据脚本调用时的名字来判断需要什么版本的 Java,以后有 Java9、Java10 的话,只要多做几个软链接就好了。)

5.14.2 应该安装何种 java 版本

安卓 5.0 之前的项目,比如我们的 T1,用的必须是 oracle 的 java 1.6。换成用 openjdk 1.6 是不行的。 安卓 5.0 及之后的项目,比如 T2、U1(升级之后),用的必须是 OpenJDK 的 java 1.7。类似的,换成 oracle 的 java 1.7 也是不行的。

这两个版本的 java 在配置好 system-config 之后,都已经安装在系统上,你只需要按照需求配置一下你的环境变量即可。参考上节。

如果自己安装了错误版本的 java,请删掉该版本或请参考上面的方法更改环境变量。

5.14.3 system-config 安装时 apt-get update 或 install-java8 失败

近来不断有同事遇到这个问题,其原因是我们公司的网络流量被联通劫持导致。一般出错的时候都是抱怨(关键字 Hash Sum mismatch):

W: Failed to fetch http://ppa.launchpad.net/openjdk-r/ppa/ubuntu/dists/trusty/main/binary-amd64/Packages  Hash Sum mismatch

针对相关地址直接用 wget 命令,加 -v 参数,可以看到

HTTP request sent, awaiting response... 302 Found
Location: http://120.52.72.24:80/ppa.launchpad.net/c3pr90ntc0td/openjdk-r/ppa/ubuntu/dists/trusty/main/binary-amd64/Packages.gz [following]

这就是联通对我们的网络流量进行劫持的证据,它通过网络数据包注入的方式,强行返回了它自己的 cdn 服务器上缓存的文件,而这个文件因为没有被更新与服务器上的版本同步,导致其数据校验和检查出错。

这个问题没有根本的解决方法,只能就目前遇到的问题大致讲一下:

  1. 换 apt-get 镜像服务器。比如 security.ubuntu.com 出错了,可以把它换成 mirrors.aliyun.com(打开 /etc/apt/sources.list 文件和 /etc/apt/sources.d 下所有文件进行批量文本替换,把所有 security.ubuntu.com 换成 mirrors.aliyun.com)。同理如果 mirrors.aliyun.com 出错的话,再查一下香港的 Ubuntu 云服务器。
  2. ppa.launchpad.net 是不可以换成 mirrors.aliyun.com 的,因为并没有被阿里云镜像过,这种情况下,可以把 ppa.launchpad.net 换成 ppa-xxx.launchpad.net ,然后自己在 /etc/hosts 下把 ppa-xxx.launchpad.net 的 ip 地址设置成 ppa.launchpad.net 的 ip 地址。

    注意换机器名不换 ip 地址的做法并不是所有源都支持的,像 security.ubuntu.com 就不支持这样操作。

  3. 过几天之后,联通 cdn 被更新,说不定问题又自动消失了。
  4. 实在不行,找公司的安全专家王洁林买个 VPN 顶一下,具体怎么通过代理使用 apt-get,建议自行上网查一下。用上 VPN 代理之后,一般就能绕过联通的 cdn 了。

5.15 碰到奇怪的编译错误,怎么办

首先,跟大家分享一下一个故事,是我从“Coders at Work”这本书里看来的,这是一本开源软件业界最厉害的一些程序员的采访集,里面有一个程序员被问道,“你 debug 的能力非常强,整个业界都是有名的,以前经常有同事、开源小伙伴几星期、几个月也搞不定的问题,拿来请教你,你经常几天甚至几个小时就搞定了。请问你是怎么做到的?”。他的回答是,“跟你说实话吧,我其实几乎从来不 debug,我每次都是一上来就把他们有问题的代码重头写一遍”。

——接下来这个关于编译错误的 faq,思路会跟上面这个故事非常像,搞不定的话就建议你“重头再来”,比如有些编译问题最后甚至可能需要重装系统才能解决。如果你想了解怎么“重头再来”的话,请继续往下看;如果你已经知道怎么重头再来的话,下面的内容几乎完全不用看了。

平时大家工作时经常会碰到编译错误,一般来说,如果是自己刚写的代码引起的编译错误,解决起来是最简单的,自己肯定能解决。这里主要讲一下一些“莫名其妙”的编译错误。

这种编译错误解决起来其是也是非常简单的,因为我们 CM 每天都会编译 Daily 的版本,并且每个项目都有持续集成,确保尽快发现大家不小心提到服务器上的编译错误。最后非常重要的一点是,CM 所使用的环境跟大家使用的环境是完全一模一样的,都使用 system-config。因此,一般来讲大家碰到奇怪的编译错误的话,可以参考一下以下的解决思路:

  1. 代码是否与 CM 编译最新的 Daily 版本所使用的代码完全一致?请参考 代码不一致?
  2. 是否是 Clean 编译?请参考 没有 Clean 编译?
  3. 所使用的编译命令是否完全一样?如果不一样,是不是被包含在 CM 编译版本使用的编译过程中?请参考 编译方法不一致?
  4. 以上 3 条都试过了,还是有编译错误,该怎么查? 请参考 其他常见编译错误
  5. 常见编译错误也看了,还是无法解决,怎么请 CM 帮你看一下?请参考 请 CM 帮忙查看编译错误

    这里有一个非常有用的命令则是 grep-errors,很多同事碰到编译错误后只会看最后几行输出,这是远远不够的,因为安卓编译时会打印海量输出,很有可能真正发生问题的地点离最后出错退出的地方相差几百上千行,用肉眼找起来会非常费劲,所以我写了这个脚本,把常见的出错关键字全给过滤出来了,参考 2.4

下面结合上面这个思路,详细的讲解一下工程师如何自己去定位、解决在自己的 PC 上编译时发生的奇怪的编译错误。

另外,注意 CM 现在提供了一个名叫 fix-build-error 的 sse 子命令,大家在安卓顶层目录下运行一下就自动帮你执行下面的这些操作(运行 sse 然后选择此子命令,如果没有这个子命令的话,请先 升级 system-config)。

5.15.1 代码不一致?

CM 每天半夜出 Daily 版本的编译,每次都会从服务器上拉取一份全新的代码。

所以如果 CM 的 Daily 正确的编译出了版本,而你的代码却出现编译错误的话,请考虑一下你的代码跟 CM 编译时用的代码之间是不是有什么差异:你自己是不是改过代码?你是怎么取到的代码?repo sync 了所有的仓库,还是只更新了几个仓库?上次做全部仓库的 repo sync 是什么时候?最后,即使你刚刚 repo sync 了一份全新的代码,也是有可能有编译错误的(因为可能刚好有个同事进了一个有问题的 Patch)!

  1. 确定是不是代码不干净引起的

    system-config 提供了一个 repo-changes? 命令,在安卓代码顶层目录下运行,它会输出所有代码仓库中有改动的仓库的列表。

    如果存在有代码改动的仓库,请确认编译错误是不是这些改动导致的。

    如果不能确认是不是这些改动导致的,那接下来请备份一下这些改动,因为我们需要把所有改动都丢弃,才能进一步确认编译错误的原因。

    system-config 提供的 my-rfa -d repo-changes.log -j1 'git reset --hard; git clean -xfd;' 命令可以把所有有改动的仓储都清掉。请在清除完所有改动之后再重新编译试试。

    repo-changes.log 会在 repo-changes? 运行时生成. 如果没有先运行这个命令查看、确认,想直接全局丢弃所有改动–注意这是一个危险的操作,可能丢掉自己尚未提交的改动–可以把 -d repo-changes.log 参数去掉: my-rfa -j1 'git reset --hard; git clean -xfd;'

    repo-changes? 命令的工作原理是利用 git 命令,如果:以下 3 种情况下,某仓储被认为是“不干净”的:git status -s 有任何输出、git commit hash 与远程不一致、git clean -xfdn 有任何输出。)

  2. 在确定代码已经干净的前提下,还出错的话,可以把代码同步到当天 CM 成功 daily build 出来的 manifest.xml 快照上再编译。

    每天的 daily build manifest.xml 快照跟 刷机包 保存在一起。

    在现有安卓代码目录下使用快照 xml 同步的方法是 repo sync -j4 -d -c -m "快照 manifest 文件的路径.xml" – 请参考 repo sync -h 给出的帮助。

    比如,如果我把 \\172.16.2.240\flash\ (参考:版本共享目录变更说明) mount 在了 /mnt/flash 下,那我运行如下命令,可以获得与 M1L 3.1.2 发布版本一模一样的代码:

    repo sync -j4 -d -c -m /mnt/flash/smart-builder/surabaya/mol-3.1/release-20161021/build-zhangshuang-2016-10-21-16.10.17/SEKSA-mol%surabaya-rom-3.1.0-colombo-user-20161021-164424-32g/manifest.xml
    
    

    注意:如果 CM 当天也编译错误了,没有出 Daily 的版本,这种情况下请依次取前一天、再前一天的该产品、分支的日常版本代码快照。

5.15.2 没有 Clean 编译?

如果代码已经与 Daily 版本快照对齐,编译还出错,并且你无法解释出错原因,请做一个 clean 的 build。把整个 out 目录删掉(如果你用的是 system-config 的 android-make 系列命令,真正的 out 目录在 .repo/out-PRODUCT-CONFIG 下,请删掉那个目录),然后重新运行你的编译命令。

因为有些情况下 build 出错原因并不在你的源代码改动里,而是在你之前的编译输出文件里。

举例说明

比如有一种改动,工程师修改了 Makefile,改了一个编译选项。这种情况下,因为 Makefile 并不在该程序的编译依赖里(依赖里可能都是 .c/.cpp/.h 文件),导致 Make 认为该程序不需要更新。而事实上这种情况下重编的话,编译选项的修改可能是会导致重编失败的。

现在,再假设有另外一个程序 B 依赖于上面这个被改了编译选项,但没有被更新的程序 A。这种情况下,如果 Make 决定要重编 B,但不重编 A 的话,那就可能出错了,而代码本身是没有错误的,只能通过 Clean Build 加以解决(对此类问题进行分析非常不合算)。

上面这个例子既讲到了“该出错的时候不出错”,又讲到了“不该出错的时候出错”的两种情况。

注意 clean 的 full build 非常耗时,请尽量采用 mma、mm 等能够只编译与你的模块相关的代码的编译命令。

5.15.3 编译方法不一致(比如你在用 mm/mma)?

CM 的 Daily 版本编译目前来讲主要是:

  1. 安卓 full build(相当于在安卓代码顶层目录下配置好 lunch 之后直接运行 android-make
  2. 编译出刷机包
  3. 编译出 ota 全包、差分包等等

所以如果你的编译方法跟上面的流程有差异的话,有时候出现编译错误是正常的,这里简单讲一下两种常见错误。一种是工程师在使用了 system-config 环境之后,根据我的建议,不再 source build/envsetup.sh,直接 lunch,然后运行 make 或 make MODULE 命令,这种情况下高通的代码下会出错(安卓官方的代码是不会出这个错误的):

FAILED: out/soong/.bootstrap/soong-cc-config/test/test.passed 
out/soong/.bootstrap/bin/gotestrunner -p ./build/soong/cc/config -f out/soong/.bootstrap/soong-cc-config/test/test.passed -- out/soong/.bootstrap/soong-cc-config/test/test -test.short
open : no such file or directory
panic: SDCLANG_PATH can not be empty

goroutine 1 [running]:
android/soong/cc/config.setSdclangVars()
        /home/bhj/src/android/build/soong/cc/config/global.go:288 +0x835
android/soong/cc/config.init.2()
        /home/bhj/src/android/build/soong/cc/config/global.go:179 +0xcd6
android/soong/cc/config.init()
        /home/bhj/src/android/build/soong/cc/config/tidy_test.go:42 +0x3cbb
main.init()
        /home/bhj/src/android/out/soong/.bootstrap/soong-cc-config/test/test.go:64 +0x58
[40/56] compile out/soong/.bootstrap/soong-cc/test/android/soong/cc.a

解决的方法是使用 system-config 里封装的 android-make 代替 make 命令。

5.15.4 mma/mm 编译时每次都出编译错误

简单的来讲,解决方法是

  1. 加一个 -k 参数,mm -k 或 mma -k,把能编的子模块都编出来(我正在考虑要不要把 -k 做成默认的)
  2. 自己判断一下,加了 -k 之后,自己想要编的模块(比如一个.apk 文件)是不是已经可以编出来了?
    • 如果已经编出来了,那么这个编译错误就不用管了(请看后面的说明)
    • 如果没有编出来,那请你自己认真查一下怎么解决这个编译错误,或者请 Leader 帮忙一起查

以下是详细的解释。

另外一种情况是工程师为了提升编译速度常用的 mm、mma 编译命令,常会有一些可以忽略的编译错误,在这里解释一下。

在有些安卓目录下,出现 mma/mm 命令编译失败是正常的。这些命令的定义是“编译当前目录下所有模块”。然而有些模块实际上是没有人维护的,在最终出系统刷机包的时候也不会编译到这些模块,所以也不会发现这些模块的编译错误。这些模块一般是一些比如用于测试的模块,或者是一些用于跨平台编译的模块,等等等等。比如在 odin 项目的 system/core/libcutils 目录下,直接用 mma 必现一个 host_cross_libcutils 模块的编译错误。

下面跟大家说一下怎么绕过去这样的编译错误。

  1. system-config 下的 mma/mm 命令可以加 -k 参数(与 make(1) 命令一致,比较好记),不加这个参数的话,任一模块出错,整个编译就中止了;加了这个参数的话,还会继续编译,直到所有能正常编译的模块都被编译出来为止。

    如果发现加了 -k 参数之后,你想要的模块还是编不出来,说明这是一个不可忽略的编译错误,请参考本条 faq 之前列出的步骤(1. 代码差异;2. clean build)。

  2. 运行过一次 mma -k 之后,再编译自己的模块时,使用 system-config 提供的 mm 脚本,它有一个 --only-module=ONLY_MODULE 的编译选项,可以在后面加你真正需要的模块的名字进行编译,这种情况下,就不会再去编那些无关的模块了。

    注意 1 :: 安卓自带的 mm 是在 envsetup.sh 里提供的一个函数,它每次都必须编译当前目录下所有模块,没有让你指定模块的选项。

    注意 2 :: 安卓的 8.0 版本(目前的 trident 项目)对编译脚本有重大更新,mm/mma 的用法跟以前不一样了,以前只要有一个 Android.mk 就可以使用 mm,现在这个 Android.mk 必须是在全编的时候会被安卓编译系统读取到才可以(不是很确定)

    举例说明:在 osborn 项目的安卓代码顶层目录下,创建 x/ 和 x/y 两层目录,后者是前者的子目录,然后在这两个目录下分别创建一个空的 Android.mk 文件,然后到 x/y 目录下运行 mm,mm 会正常退出,输出中有这样一行:

    ninja: no work to do.
    

    但是,一模一样的操作在 trident 项目下做的话,最后 mm 会出错,错误输出如下:

    ninja: error: unknown target 'MODULES-IN-x-y'
    

    这里,x/ 目录没有出错,原因是它的 Android.mk 能被安卓自动扫描到并读取,而 x/y 会出错的原因则是因为它的 Android.mk

    1. 既不会被安卓自动扫到(因为安卓扫到其上级目录的 .mk 之后就停止扫描了)
    2. 上级目录也没有拉它一把(通过如下语句:include $(call first-makefiles-under,$(LOCAL_PATH)),参考 system/core/Android.mk

    于是就导致安卓不知道 x/y 目录下有什么模块,所以在 x/y 下用 mm 就提示不知道 'MODULES-IN-x-y' 的错误了。

    所以如果你自己在研发的过程中碰到类似的错误的话,请确保

    1. 确认一下每个上级目录是否有 Android.mk
    2. 上级目录是否每个都有 include
    3. 当前目录的 Android.mk 以及它 include 进来的 .mk 文件是否确实有定义任何模块? 这里举一个不好的例子,像 vendor/iflytek/SpeechSuite 这个目录,它所有的文件都不是依赖于安卓的 Makefile 机制来更新(git 版本:5a85ed54e6cbad90f40ebc6d2fcf4523a6f36d0e),而是自己在 Makefile 里用 $(shell cp XXX YYY) 的方式,通过这种方式的话,用 full build 编译这些操作会被执行,但在 trident 上用 mm 命令,就会报 unknown target 的错误了。

    注意 3 :: 基于上述安卓 8.0 的改动,systme-config 提供的 mm、mma 命令脚本的用法有了修改:以前想单编某个模块的时候直接在 mm 后跟此模块名字;现在必须使用 --only-module 选项后面加模块的名字。请参考 mm -h 的帮助文本。

顺便说一下,如果你是在谷歌工作的话,我想,上面这种“某些模块全编时不会出错,因为编不到;但单编时就出错,因为被编到了”的情况,我觉得应该是很少会碰到的,碰到的话,也是可以找到人把它给正面解决掉的。为什么呢?是因为谷歌的工程师比我们更认真、更负责吗?是因为他们主动地自发地把所有模块,不管用得到,还是“用不到”,所有的编译错误都给找出来干掉吗?我觉得很可能不是这样的。根本的原因可能是谷歌比较有钱,她能招到足够多的人、买来足够多的服务器来干这些事情。这些平时不编,单编会出错的模块重不重要?长远来看,像我们公司目前这样,只关注 full build,不关注其他测试模块,这种做法,会不会是在给咱们自己挖坑?这些问题,留给大家思考。

5.15.5 常见编译错误收集

这里记录一下常见的、比较典型的一些编译错误及其解决方法。注意如果你用的是 system-config 的环境,编译完成后在安卓的 out/ 目录下会有一个 build.log 或 mm.log 文件,里面保存了整个编译过程输出的 log。system-config 提供了一个非常简单的方法可以从这个 log 文件过滤出真正的编译错误,参考 编译出错信息快速定位

如果你定位了出错的关键 log 信息,请在下面的常见或不常见错误里查一下,看是不是以前我们已经碰到过的编译错误。

5.15.6 jack out of memory

出错关键字

Out of memory error (version 1.2-rc4 'Carnac' (298900 f95d7bdecfceb327f9d201a1348397ed8a843843 by android-jack-team@google.com)).

出错原因

系统内存太小了。随着安卓版本升级,对开发编译机器的要求越来越高,目前 16G 内存是最低要求,并且即使是 16G 的机器,也偶尔会出这个错误。

还有一些同事喜欢用虚拟机工作,比如自己的系统是 Mac(或 Windows),然后装一个 Linux 的虚拟机,在里面搞编译,这种情况下这个问题就无解了,唯一的建议是自己多试几次,并且调整一下虚拟机的内存配置,看能否在保证系统稳定运行的情况下,还能完成编译。

是否必现

否。与机器内存大小及使用情况有关

解决方法

确保系统有 16G 或以上物理内存(可以通过 linux 系统自带的 free 命令查看系统内存大小),如果发现自己机器内存小于 16G,请找 Leader 申请新机器。

在此基础上,请使用 system-config 的 fix-jack-oom 命令把 jack 编译的并发数量降下来,还出这个错的话就再运行,一直把并发数降到 1。

并且注意这个错误不是必现的,不删除 out 目录重编的话,每次运行都能编一些新的文件出来,所以多试几次之后说不定就能编过去。

5.15.7 proguard 相关编译错误

这里记录的 Proguard 相关的一些编译错误都比较不好复现,因此也不容易查错,所以专门记录一下,下次出现的话可以节省一些时间。

解释一下 proguard,它的主要功能是:1. 优化,2. 混淆。听上去非常诱人,但是大家如果碰到跟它相关的 Bug 的话,就知道它有多坑了,建议大家慎用这个技术(不仅是编译错误哦,运行时也可能会碰到,见 2),碰到任何只要有一丝怀疑跟它有关的问题,第一反应就是把它关掉再试试(重头再来)。

注意 Proguard 相关的编译错误一般在 user 版本上出现,这是因为 userdebug 下对 proguard 的配置一般比较宽松,基本没有优化、混淆等操作。注意上面这句话里的 一般 两个字,我认为在 userdebug 上完全不打开 proguard 是比较合理的,但最近也有见到一些模块在 userdebug 下也开着 proguard(可能为了省事儿,也可能技术的趋势已经变成在 userdebug 上也一直开 proguard 了),然后在 ocean_cmcc 项目上就碰到了一个 userdebug 下也必现的 proguard 导致的编译错误,最后 CM 只能建议该模块的 owner 自己把 proguard 配置先关掉,然后再一步一步调整它的参数(重头再来)。

另外注意一些 Proguard 的错误不是必现的,比如我们曾碰到一个 Patch 在持续集成服务器上编译时一直报错;但是在 Daily Build(一般安排在更快的服务器上)的时候却能编过去;在工程师自己的机器上也不能复现的情况,进一步增大了调试、解决这种问题的难度。因此,如果你在自己的机器上碰到了,并且确认跟自己的模块、App 无关的话,建议考虑如下方案:

  • 反馈给相关模块 owner
  • 反馈给 scm@smartisan.com
  • 直接删除这个出错的仓库,后续不再编译它

以下是 CM 记录的 Proguard 相关出错记录:

出错关键字

Found virtual method with same index as direct method:

出错原因

参考 https://review.smartisan.cn:8080/#/c/280256/,加上这个 Patch,在持续集成服务器上必现,去掉这个 Patch,不再出现。

是否必现

在持续集成服务器上一直出现,在 Daily 的时候没有出现,在工程师的机器上也没有出现。

修正过程记录

一开始对这个编译错误没有什么头绪,从安卓相关的代码上看很难看到怎么解决,出错的时候只给了一个 id,没法找到更多的信息;另外一般只有 CM 的服务器上会碰到(因为编译频率比较高),工程师自己比较难碰到。这个问题主要是混淆引起的,工程师一般都只编 userdebug 版本,默认是不混淆的,所以几乎从来不会碰到。

对这个问题 CM 采取了复现后保护现场的措施,及时把服务器下线,以确保不会被其他的编译任务清掉其现场,然后再慢慢查这个问题。

几天后就又复现了:

log:136492:dex2oatd E 57118 57118 art/compiler/oat_writer.cc:2186]
Failed to open dex file from oat file. File:
/system/app/CalculatorSmartisan/CalculatorSmartisan.apk Error:
Failure to verify dex file
'/system/app/CalculatorSmartisan/CalculatorSmartisan.apk':
Found virtual method with same index as direct method: 296

看了一阵安卓代码后,还是不知道怎么改一下代码获取更详细的 dex 文件信息,可以刚好把出错的 method idx(296,十六进制的 0x128)对应的函数名字给打印出来。我本来打算自己解析 dex 文件的格式了,但后来想起来 debian 系统里封装了一个安卓官方的工具叫 dexdump,应该可以帮我省掉文件格式解析的工作。果然,用 apt-get source dexdump 命令下载到 dexdump 的代码后,稍微做了一下修改,把 methodIdx 给打印了出来:

--- android-platform-dalvik-7.0.0+r33.orig/dexdump/DexDump.cpp
+++ android-platform-dalvik-7.0.0+r33/dexdump/DexDump.cpp
@@ -1077,9 +1077,9 @@ void dumpBytecodes(DexFile* pDexFile, co
     startAddr = ((u1*)pCode - pDexFile->baseAddr);
     className = descriptorToDot(methInfo.classDescriptor);

-    printf("%06x:                                        |[%06x] %s.%s:%s\n",
-        startAddr, startAddr,
-        className, methInfo.name, methInfo.signature);
+    printf("methodIdx: %06x: %06x:                                        |[%06x] %s.%s:%s\n",
+           pDexMethod->methodIdx, startAddr, startAddr,
+           className, methInfo.name, methInfo.signature);
     free((void *) methInfo.signature);

     insnIdx = 0;

然后再用 dpkg-buildpackage 重编一下 dexdump.deb 包,重编的时候会出错,因为发现代码有改动,它要求你先用 dpkg-source --commit 命令提交一下,提交完了再重编,最后还会出一个 .deb 文件签名 Key 文件找不到的错,但不影响 dexdump.deb 文件的生成,直接 dpkg -i dexdump_*.deb 安装就好了。

装完以后用 dexdump -d -f classes.dex |grep idx.*128 -i 重新 dump 出错的 .dex 文件并 grep 一下编译出错信息里提到的 methodIdx,这回就把出错的函数名信息都找出来了:

其中一个函数是这样的,注意它是一个私有函数,在计算器 app 的 Logic.java 文件中,名字叫 ac(),其 methodIdx 是 0x128:

    #3              : (in Lcom/smartisanos/calculator/Logic;)
      name          : 'ac'
      type          : '()V'
      access        : 0x0002 (PRIVATE)
      code          -
      registers     : 3
      ins           : 1
      outs          : 1
      insns size    : 38 16-bit code units
methodIdx: 000128: 00a6e8:                                        |[00a6e8] com.smartisanos.calculator.Logic.ac:()V
00a6f8: 5420 e700                              |0000: iget-object v0, v2, Lcom/smartisanos/calculator/Logic;.mCal:Lcom/smartisanos/calculator/Calculator; // fi
eld@00e7


另一个函数是这样的,它是一个公有的虚函数,也在同一个 Logic.java 文件中定义,名字也叫 ac(),其 methodIdx 也是 0x128:

  Virtual methods   -
    #0              : (in Lcom/smartisanos/calculator/Logic;)
      name          : 'ac'
      type          : '()V'
      access        : 0x0001 (PUBLIC)
      code          -
      registers     : 2
      ins           : 1
      outs          : 1
      insns size    : 43 16-bit code units
methodIdx: 000128: 00ae04:                                        |[00ae04] com.smartisanos.calculator.Logic.ac:()V
00ae14: 5410 e700                              |0000: iget-object v0, v1, Lcom/smartisanos/calculator/Logic;.mCal:Lcom/smartisanos/calculator/Calculator; // field@00e7
00ae18: 3900 0300                              |0002: if-nez v0, 0005 // +0003
00ae1c: 0e00                                   |0004: return-void
00ae1e: 5410 e700                              |0005: iget-object v0, v1, Lcom/smartisanos/calculator/Logic;.mCal:Lcom/smartisanos/calculator/Calculator; // field@00e7

然后我打开 Logic.java 文件,发现里面只有第二个 ac() 函数的定义,找不到第一个私有 ac() 函数,所以这个私有的 ac() 应该是被混淆生成出来的。

到了这里,问题和解决方法应该已经很明显了。以前我们已经碰到过的(参考下一个“出错关键字”,vtable entries 22 and 23 are identical for): 混淆函数名的时候,如果有一些原来就特别简短的函数名字,特别容易跟被混淆后的名字产生冲突。

请计算器 App 的负责同事帮忙解一下这个问题,以免影响正常 Daily 出版本。

修正方法(TBD)

CM 以后碰到此类错误,会把出错的文件保存到共享目录下,供相关 App 的负责同事分析。具体分析方法请参考上面的过程。

出错关键字

vtable entries 22 and 23 are identical for

出错原因

混淆的时候同名了。参考 https://172.16.0.9:8080/#/c/262125/

是否必现

一旦出了之后,基本上在服务器上就必现。但在工程师自己的机器上据说没有出现。

5.15.8 其他不常见编译错误

  • 有些同事在自己 lunch 的时候曾设错配置,请试试 sse 命令,CM 把所有正确的产品配置都配好了,并且无关的配置全部删掉了,你只要选就可以。
  • Full build 最后 system.img 创建时“failed to allocate 6312 blocks, out of space?”

    这种情况一般是因为你已经连续做过很多遍 build 了,每次都没有清空 out 目录,导致 out/target/product/XXX/system 目录下的文件越积越多,最后导致生成 system.img ext4 文件系统的时候超 size 了。

    解决的方案是把 out/target/product/XXX/system 目录整个删掉重新 build。这样操作是不会浪费很多时间重新编译的,只是从各个 XXX_intermediates 目录底下重新拷贝一遍而已。

    如果还出同样错误的话,请参考上面几点,看看是不是因为自己的改动引起的,比如是不是加了一些特别大的第三方 .apk 文件?

5.15.9 请 CM 帮忙查看编译错误的方法

各位工程师同事,在碰到编译错误后自己认真阅读了上面的文档、努力地尝试、请教了 Leader 之后,都无法解决编译错误的情况下,请按如下简单步骤请 CM 帮你查看,非常感谢!

  1. 发邮件给 cms@smartisan.com,提供如下信息:
    • 出错时的整个终端的 log,建议可以放到附件里发出来
    • 发生问题时正在执行的编译命令
    • 在发生问题的终端下执行一下 declare -x > env.txt, 然后把 env.txt 也放到附件里
    • 如能加上一些简单的描述、分析就更好了
  2. 尽量不要用拍照的方式发 log,这会让 cm 分析问题非常不方便
  3. 一定要抄上自己的 Leader。作为一个 Leader,他/她把你招进来让你帮他干活,我们认为他是有责任和义务帮你解决问题的,既然这一个编译错误他没有办法帮你解决,那如果 CM 能帮你解决的话,请他一起学习一下,争取下次类似的问题不用找 CM,这非常重要。
  4. 请不要直接到我的工位上拉我过去看编译错误,这样非常打扰我的工作,同时几乎可以保证我不能很好的帮你解决编译错误。

谢谢大家,拜托了。

5.16 如何修复持续集成编译错误

5.16.1 正常的修复流程(“这确实是我搞出来的编译错误”)

当你收到持续集成出错的通知邮件时,大部分情况下,你能够很快确认出来,这确实是你的 patch 有问题导致的。

修复主线持续集成编译错误的时候,本质上就是在主线上继续开发自己提交的有问题的 Patch,所有操作大家都已经很清楚,在此不展开讲了。

在 mol 等分支上的持续集成编译错误,提交、通过 Code Review 之后,可能会发现无法 submit 的问题,这是因为这些分支一般有更为严格的权限管理。这时候请回复持续集成的出错通知邮件,在里面贴一下你修复的 patch 在 gerrit 上的 review url,让 cm 或 pm 帮忙 submit。

所有跟导致编译错误的 patch 有关的信息都已经贴在通知邮件里,如果你发现还有别的很有用的信息尚未提供,请向 cm 反馈,后续会一起添加到通知邮件来。

5.16.2 非正常的修复流程(“这不是我搞出来的编译错误”)

特殊情况下,你可能会收到一个跟上去跟你的 patch 没有什么关系的“莫名其妙”的 CI 报错。这时候可能需要花费你的一点时间,协助软件团队一起解决这个错误(一个好消息是,一般来讲此类编译错误发生的概率很低)。

这种奇怪的编译错误是无法避免的,主要是两方面:

  1. 有些 patch 会导致必须要 clean 的全量编译一次,否则就会一直出错。

    这种情况下目前请你跟 cm 反馈一下,然后 cm 就会手工启动一次 clean 的重编。后续这种操作可能会开放给各位工程师,大家在页面上点击一下就可以重启 clean 重编。(以前我们是通过连续第 2 次 CI 出错就自动做 clean 全编来解决这个问题,但目前发现时间的开销特别大,已经无法继续实施了)

    还有一种类似的情况则是某些 patch 其实会导致编译错误,但是必须要做一个 clean 的全量编译才能发现——这就是另一个问题了,大家暂时不需要关注。

    以上两种现象发生的原因在 关于编译错误的 faq 里有描述,供参考。

  2. 你的 Patch 本身并没有问题,但你所依赖的其他代码模块被别的同事修改了,比如增删改了 api 接口之类,但没有跟你做好沟通同步,导致出错,并且报错的位置刚好是你提交的最后一个 patch。

    这种情况下请你帮忙一起分析一下,真正的导致出错的 patch 的责任人是哪位,然后将 CI 出错邮件转发抄送给他,让他赶紧修复;或者如果你分析不出来的话,请考虑与你的 Leader 沟通,让他帮你一起看一下。

5.17 如何在自己的机器上进行 jenkins 编译

条目已转移至 ./cm-how.html#biy

5.18 如何获取高通基线版本(build id)

在给高通提 Case 的时候,通常都要填一个 build id,这个 build id 在高通每次 release 的基线代码的 about.html 里可以获取。

在我们的每个刷机包的目录下,有这个 about.html 文件的拷贝,请自行查阅。

关于应该如何解读该 about.html 文件,请咨询你自己团队内部其他同事,或你的 Team Leader——不同模块可能对 about.html 的解读有所不同。

5.19 如何获取、合入高通 CR

注:关于高通 CR 的相关知识,如果你以前没有处理过,第一次处理的时候碰到了问题,请

  1. 仔细阅读下面的文档
  2. 如果对整个流程还不是很清楚的话,请你的 Leader 或让 Leader 找一个以前有 CR 处理经验的同事帮你一起过一遍
  3. 以后有新人需要你提供 CR 相关的帮助的话,也请尽量帮他/她一下
  4. 如果对文档有任何建议,请与 cms 反馈,帮助团队提升知识储备

CR 是一个软件工程中的专用名词,代表 Change Request,有些我们自己解决不了的问题,必须找高通解决,高通最后会通过 CR 把 Patch 给到我们,一般就是高通的 OEM git 代码仓库里的一个提交。

各位可以通过 这个网页 自己启动获取高通 CR 的任务,CR 从高通下载回来之后,会给你(启动任务的人)和你在启动时指定的人发邮件告知下载完成。请收到邮件后再详细阅读一下本节文档关于如何合入 CR 的说明。主要是高通的一个 git commit,我们不得不把它拆分到多个仓库下。这是因为:

  1. 一般来讲,高通的 CR 全部都是通过 OEM 代码的 git 仓库给出来的,说是 OEM 代码,其实里面也有一部分是高通安卓代码中不开源的部分,也就是 vendor/qcom/proprietary

    这就已经决定我们必须把高通的 git 仓库拆成至少两部分,一份给安卓,另一份给真正的 OEM 子模块。

  2. 高通的所有 OEM 代码都在一个 git 仓库里,极其巨大的一个仓库,操作起来慢得要吐血。

所以我们通过使用 git 的一些命令(filter-branchsubtree),把它的仓库拆分成相对独立的多个仓库,然后各个 OEM 子模块部分通过单独的 oem repo manifest.xml 管理,vendor/qcom/proprietary 部分则合入到相应项目的安卓 repo manifest.xml 中进行管理。

所以,我们拿到一个高通 CR 之后,需要先确认这个 CR 的修改涉及到我们自己内部分出来的哪些子仓库,然后把涉及到的 每一个 子仓库都更新一下就可以了。这里特别强调一下是 每一个 子仓库下的每一个文件,绝对不能只挑自己觉得跟自己的模块有关系的子仓库甚至部分文件进行更新,原因是:

  1. 高通在一个 CR 里给了 N 个文件,默认的意思就是我们要把这 N 个文件全部更新,如果我们随意按自己的喜好、感觉只更新里面的一部分文件,后续可能就会掉进坑里了。

    如果你在更新某个子仓库的过程中,发生冲突,一定要找相关同事甚至高通确认,为什么发生冲突,并认真把冲突解决。绝对不能发生冲突就认为这个文件可以不用更新,然后把它跳过去。

    只有一种情况你可以忽略某些文件,那就是你从高通得到确认,某个文件是他们给的太随意了,导致后面发生很多冲突,无法继续更新维护,并且!最重要的是,必须由高通来确认过该文件即使不更新也完全不会影响手机的功能。

  2. 特别提醒一下,我们分库之后,有一个名叫“others”的不影响编译的仓库,很容易被忽略,但不更新这个仓库的话,就会导致后面跟高通再提 Case 的时候,版本信息对不上,导致解问题的效率极大的降低,这种低级错误重复多次之后,也非常可能导致高通负责支持我司的工程师认为我司办事不靠谱,在心中降低对我们的评级和支持力度。

    每次给高通提 Case,它都会让你填一个“Build ID”,这个 build id 就保存在“others”仓库下的 about.html 文件里。

一般来讲,高通的 CR 即使被我们拆分了之后,也不会影响太多仓库,一般只有 2 个(一个是真正的代码改动仓库,另一个则是每次都必须更新的 others/about.html),很少会有 3 个仓库或以上。如果涉及的仓库太多的话,那就简直不能叫 CR,而应该叫基线升级了

5.19.1 怎么确认自己的 CR 涉及修改了哪些仓库?

各位提交的同步 CR 的任务完成后,会收到一封通知邮件,格式如下:

CR:                Commit label r00520.1.124156.2a.126873.1 - N/A 0.0.520.1.124156.2a.126873.1
对应分支:         qualcomm/snapdragon-high-mid-2017-spf-1-0_amss_standard_oem/qualcomm/S124156.2a.126873@SDM660.LA.1.0@contents.xml
commit id:        db74bd3eea078d5b90a54cbe8e30397549a4ad15
manifest:         qualcomm/snapdragon-high-mid-2017-spf-1-0_amss_standard_oem/qualcomm/S124156.2a.126873@SDM660.LA.1.0@contents.xml/oem.xml
改动的文件与目录:
    SDM630.LA.1.0/
    SDM636.LA.1.0/
    SDM660.LA.1.0/
    WLAN.HL.1.0.1/
    about.html
更详细的文件改动详情,请参考:http://172.16.0.9/gitweb/?p=snapdragon-high-mid-2017-spf-1-0_amss_standard_oem.git;a=commit;h=db74bd3eea078d5b90a54cbe8e30397549a4ad15
****************************************************************
...

底下可能还有更多的其他 CR 的信息,这是因为 CM 不知道你具体要下载的 CR 是哪一个,所以只好列出最近有更新的 10 个 CR 让你自己去搜一下。

找到自己要的 CR 之后,注意上面给出的各种信息,通过这些信息,你能确定该修改哪些仓库。

比如上文中给出了修改了哪些文件,一般来讲,可以分为 3 种情况:

  1. 只修改了安卓部分 vendor/qcom/proprietary 的代码和 about.html(或者还有 contents.xml,问题不大,是跟 about.html 在一起的)。
  2. 只修改了 OEM 部分除了 vendor/qcom/proprietary 之外的其他子模块的代码,以及 about.htmlcontents.xml
  3. 既修改了 vendor/qcom/proprietary、又修改了其他子模块,以及 about.htmlcontents.xml

    但第 3 种情况其实是第 1 种和第 2 种的合集,所以这里不再展开讲,你自己依次处理每个仓库就好了。

上面摘的邮件内容里已经大概给出了有改动的各个最上层的子目录或文件的名字,如果看得不够明白的话,请打开邮件里给的那个 CR 所对应的 gitweb 网页查看具体需要合入哪些仓库。这是因为高通在有些项目里把真正的子模块目录还往下挪了一层,比如以前叫 boot_images,在 odinosborn 项目里可能是 BOOT.XF.1.4/boot_images

根据这些信息,你应该可以判断出自己的 CR 涉及修改了哪些仓库。这里唯一要提醒一下的是,高通的 git 仓库里 about.htmlcontents.xml 是放在 oem 代码顶层目录下,但我们改成用 repo manifest.xml 管理之后,这些文件只能放在 oem 代码里名为“others”的仓库下了,合 CR 的时候要注意这一点。

确定了自己的 CR 都改了哪些仓库的代码之后,剩下的事情就是在相应的代码仓库下,把相应的 Patch 给 cherry-pick 过去。

下面讲的所有的仓库的处理都是大同小异,无非就是:

  1. 用 repo 命令或 git 命令同步到每个要修改仓库的代码

    这里要提醒大家尽量用 repo sync REPO_PATH 的方式来单独同步 REPO_PATH 所代表的你真正需要同步的仓库,原因是:

    • 节省大量时间,OEM 下很多仓库非常庞大;Android 下我们有几百个 Repo,跟高通 CR 相关的只有 vendor/qcom/proprietary 等少数几个。
    • 一些上层系统的工程师也需要更新 OEM 下的某些仓库比如 common/、others/ 等,但他们没有其他子模块如 modem 等仓库的访问权限。

      如果有发现你需要更新的某仓库没有权限的话,这属于公司制定的权限控制不合理,请及时联系 CM 与你的 Leader 一起讨论决定修改权限。

  2. 用 git fetch REMOTE_NAME 命令取到服务器上所有的分支
  3. 用 git log REMOTE_NAME/CR_BRANCH_NAME 命令检查、确认自己的 CR 是否已经同步到本地
  4. 用 git cherry-pick REMOTE_NAME/CR_BRANCH_NAME 或 git cherry-pick CR_COMMIT_ID 命令把 CR 打到自己的工作分支上

    注意如果自己的工作分支切得不对,比如 CR 一般应该打到主线,但你当前工作在 mol 分支上的话,你需要自己用 git 命令先把分支给切回来。

  5. 如果有冲突的话,注意解冲突、并找 Leader 帮你参谋。
  6. 编译、测试通过后,提交到 Gerrit 服务器让 Leader 帮你 Review、Submit。

至此,一个 CR 的合入就完成了。

5.19.2 怎么同步主线安卓、OEM 代码

这个问题在这篇文档的其他地方已经讲得很清楚了,建议自己搜一下。唯一要提醒的是,有些上层安卓系统工程师,平时可能不怎么同步底层的 OEM 代码,但是合 CR 的时候,又必须合入 OEM 的 others 仓库的代码, 这种情况下,请注意也可以到每天主线 daily build 的刷机包目录下查看 oem-smartcm-build.info 文件,里面一开始就给出了同步 oem 的代码所使用的 repo init 命令,然后你就照着这个命令下载就好了:

****************************************************************
Here's how the repo is inited:
    repo init -g all,platform-linux -u ssh://smartisan/qualcomm/platform/manifest.git -b ci/mol/oem-osborn-rom-4.0.0.xml -m mol/oem-osborn-rom-4.0.0.xml --repo-url smartisan:googlesource/git-repo --reference ~/src/android-mirror

repo init 完成后,你简单的 repo sync -cd others 一下,就可以只同步出 others 仓库,而不同步其他大块头的 oem 代码仓库,以节省时间。

另外上层的工程师可能不大清楚 oem 的 others 仓库主线使用的是哪条分支,建议自己 repo sync 同步完成后,打开 .repo/manifest.xml 文件确认一下。参考 manifest 文件格式

5.19.3 怎么合 CR 的 Patch

在每个需要修改的仓库下,运行如下命令:

git fetch $(repo-remote) # 下载所有的分支,repo-remote 是 system-config 封装的命令,就是 git remote 的名字而已
git log $(repo-remote)/CR_BRANCH # 查看、确认一下高通的 CR 分支是否已经同步到本地,记一下你要的 CR 的 commit id
git cherry-pick CR_COMMIT_ID
# 编译、测试、提交 Review 等等

5.19.4 注意:合 others 仓库也一定要用 cherry-pick,不要直接拷贝 about.html!

最近(2017-11-08)有发现有些同事直接把高通 CR 里的 about.html 文件拷贝到我们的相应开发分支上,这种做法可能会覆盖其他同事从其他分去上打进来的 CR,这是非常严重的操作错误,会导致后续跟高通提 Case 时无法正确的分析 Bug,浪费整个项目推进的时间。所以请大家一定要注意尽量用 git cherry-pick 及时发现冲突错误。

发现冲突不知道怎么解的话,请找 Leader 帮忙,或者找到跟你冲突的修改是谁改的,然后跟他确认。

5.20 如何下载高通的开源安卓代码

5.20.1 如何下载公司项目的高通基线安卓代码

以 osborn 项目为例,在 osborn 主线的安卓代码的 oem-release 目录下,有一个名为 about.html 的文件,用浏览器打开它,里面有这样的一行:

qualcomm-about-html.png

这一行给出了该项目对应的高通开源 Gerrit 上所使用的安卓 manifest.xml 的信息(不要问 CM 我们是怎么知道的——一半是猜的,另一半则可能是很久以前看到高通的某个文档里有写过,有经验了。我们完全不知道高通的名字是怎么起的)。

一般所有项目的高通基线,都会在我司 Gerrit 上进行备份,在上面的这个例子里,你可以到 osborn 主线安卓代码的 .repo/manifests 目录下运行 find . -name 'LA.UM.6.1.r1-09900-sdm660.0*' 命令,注意这里要对图中显示的 LA.UM.6.1.r1-09900-sdm660.0-1 稍做修改,去掉最后的 -1,然后才能搜到对应的 manifest.xml。所以你要下载对应的高通代码的话,请使用如下命令:

repo init -u ssh://gerrit.smartisan.cn/qualcomm/platform/manifest.git -b refs/heads/sanfrancisco -m qualcomm/LA.UM.6.1.r1-09900-sdm660.0.xml --repo-url= -p auto --reference /home/bhj/src/android-mirror/ -g all,platform-linux --depth 1

repo sync -j4 -d -c

2017-11-08 注

高通的基线版本 manifest.xml 应该叫什么名字,现在越来越不好猜出它们的对应关系了。

比如最新的 trident 项目,11 月 2 号来了个叫“r00375”的版本,但是它的安卓部分的 manifest 文件名字叫“caf_AU_LINUX_ANDROID_LA.UM.6.3.R1.08.00.00.301.057.xml”。。。

这种情况下,想要自己确定高通的安卓基线版本,可以有几种方法:

  1. 查看对应的高通 oem 项目里是不是有个名为 LINUX/android/sync.sh 的文件,里面给出了对应的安卓项目的下载方法,其中包括 manifest.xml 文件的名字。CM 每次都会把这个 manifest.xml 表示的代码镜像到内部 Gerrit,并把 manifest.xml 放到 manifest.git 的 qualcomm/ 子目录下。
  2. 在最新的 .repo/manifests 目录下查找对应的文件,比如想找“r00375”相关的文件,可以直接运行 find . -name \*375\*,然后你可以发现结果如下:

    ./smandroid/oem-trident-sdm845-r003751.xml
    ./smandroid/trident-sdm845-r00375.1.xml
    ./smandroid/oem-trident-sdm845-r003751.xml.base
    ./smandroid/oem-trident-sdm845-r00375.1.xml
    ./mtp/trident-sdm845-r00375.1.xml
    

    其中最后一个 mtp 设备所使用的版本,就是高通的基线版本(并且是已经加上了高通放在 oem 里的 vendor/qcom/proprietary 私有代码的)。我试着查了一下在 mtp 的 xml 里 frameworks/base 是什么版本,然后在 .repo/manifests 底下用 rgrep 搜了一下,发现 caf_AU_LINUX_ANDROID_LA.UM.6.3.R1.08.00.00.301.057.xml 这个文件跟它是匹配的,所以这样就可以确认,这就是高通的基线版本了。

另外,一定要注意,高通原生代码下载完后,不一定能编译通过、如果编译通过也不能保证刷到高通 MTP 设备上能正常起机,这是因为它的安卓代码并不完整,有一部分(vendor/qcom/proprietary)需要从它的对应的 OEM 代码里拼出来的。

最后,如果你只想看某个仓库下的安卓原生代码的话,在该仓库下 git fetch $(repo-remote) 然后查看对应的(沿用上面的例子) qualcomm/LA.UM.6.1.r1-09900-sdm660.0 分支就好了。

  1. 下载高通基线代码时提示 oem-gerrit-mirror 错误

    使用 mtp 版本的 manifest(如:mtp/trident-sdm845-r00375.1.xml)下载代码时,如遇到 ssh: Could not resolve hostname oem-gerrit-mirror: 的错误,请在 ~/.ssh/config 文件中添加以下内容(仅供参考!请拷贝该文件中关于 gerrit.smartisan.cn 的设置,然后改一下其 Host 和 Hostname,这台机器是 Gerrit 服务器的一个镜像,用户名和 ssh key 是共用的,在 gerrit.smartisan.cn 上添加过的 ssh 公钥会自动同步到这台机器上):

    Host oem-gerrit-mirror
         Hostname 172.16.0.10
         # 下面这三行请确保跟 gerrit.smartisan.cn 的设置一致
         Port 29418
         User 你的GERRIT用户名
         IdentityFile ~/.ssh/id_rsa
    

5.20.2 如何下载高通开源安卓代码

高通的开源安卓代码大家都可以自行从高通网站获取,具体下载方法可以在网上搜到,这里作一简单介绍:

  1. 下载完整安卓代码

    前提是你需要知道自己想要下载哪个 manifest.xml(如果是跟公司项目相关的高通代码,一般在公司内提供了基线的镜像,请参考本 faq 上一小节)。

    repo init -u git://codeaurora.org/platform/manifest.git \
         -b release \
         -m default_LNX.LA.3.7.1.1-09110-8x09.0.xml \
         --repo-url smartisan:googlesource/git-repo \
         --reference ~/src/android-mirror
    
  2. 下载单个安卓目录

    建议先从 Smartisan 的 Gerrit 上同步一下你想要的仓储,然后再从高通下载,可以节省网络带宽。比如你想获取 Surabaya 产品的 Kernel 代码对应的高通仓储,做法如下:

    1. 用 sse get-source-code 命令下载 Surabaya 主线代码
    2. 到 kernel 目录下,用 git remote -v 查到对应的 Smartisan 服务器地址: ssh://172.16.0.9:29418/qualcomm/kernel/msm-3.18,把 ssh://172.16.0.9:29418/qualcomm 替换成 git://codeaurora.orggit remote add q git://codeaurora.org/kernel/msm-3.18
    3. 用 git ls-remote q 命令察看高通的服务器上有什么分支
    4. 用 git fetch q BRANCH 从高通服务器下载你想要的分支。直接 git fetch q 的话下载所有分支,数据量可能会很大。

    具体 git 命令请用 --help 查看对应的帮助,比如 git remote --help

5.21 如何下载谷歌官方安卓代码

目前我们会定时从谷歌官方安卓源码网站镜像其重要的安卓版本,主要有 3 种:

  1. 安卓官方主线 master
  2. 版本代号-dev,一般是大版本,如 oreo-dev 对应的是安卓 8.0
  3. 版本代号-mrN-dev,其中 N = 1,2,…, 一般是小版本,如 oreo-mr1-dev 对应的安卓 8.1

各位如需下载,请使用 sse get-source-code 命令,然后按下面的示例选择就可以下载目前(2018-01-04)最新的 oreo-mr1-dev 代码(因为之前我已经运行过一次了,通过历史记录的机制第一个选项就是我要选的,所以此处每个选项我都是直接回车):

开始根据需求选择代码下载.
请按‘回车’继续..

1) android
2) oem
请输入你需要下载的是 qualcomm oem 还是 android 代码: (Type ? for help)>

1) aosp
2) osborn
3) oscar
4) trident
5) T2M : T2 的 Marshmallow 升级(已取消)
6) odin
7) playground
8) surabaya
9) t1
10) t2
11) u1
请输入你需要下载的代码所属产品名称 (Type ? for help)>

1) oreo-mr1-dev
2) jb-dev
3) jb-mr1-dev
4) jb-mr1.1-dev
5) jb-mr2-dev
6) kitkat-dev
7) lollipop-dev
8) lollipop-mr1-dev
9) marshmallow-dev
10) marshmallow-mr1-dev
11) master
12) nougat-dev
13) nougat-mr1-dev
14) nougat-mr2-dev
15) oreo-dev
请输入你需要下载项目的分支类型: (Type ? for help)>

5.22 Ubuntu 程序包安装出错、装不上 java7

首先确认系统必须是 14.04 的,用 lsb_release -a 命令可以快速查看。

然后确认安卓编译相关的 package 已经全部安装,在 system-config 环境下运行: install-pkgs android-build

如果还是找不到 java7 版本的话,请确认你没有犯过这个错误:在曾经打开过 ubuntu-updates 之后,又将其关闭。请参考 5.3

5.23 abc-x grep 支不支持中文搜索、远程搜索?

abc-x grep 的用法之前有比较多的限制,最近将其进行了一次升级,以便更好的服务大家(大家可能需要先通过 system-config-update 升级自己本地的 system-config):

  1. 支持远程 grep,不需要本地存在代码

    比如, abc-x grep -r -b bono-rom ContextImpl ,其中,-r 参数意思是在远程(remote)进行 grep, -b bono-rom 参数意思是在服务器上 bono-rom 的源代码目录下进行搜索。

    • 如果不指定 -b 参数,abc-x 会从你的当前代码的 repo init 时使用的 manifest.xml 参数来计算出你想使用的对应远程代码目录
    • 如果不能从 manifest.xml 计算出正确的远程代码目录,abc-x 会通过类似 sse 下载源码的命令行接口提示你选择相应的源码目录
  2. 支持中文、日文等远程 grep,比如:

    abc-x grep -r 'ありがとうございます' -b surabaya-rom

    注意,这种用法只能用于远程 grep,之前的 abc-x grep 不加 -r 参数的话,无法处理中文字符参数。

  3. 支持函数定义搜索,比如

    abc-x grep -t -b bono-rom ContextImpl

    只要把 -r 参数换成 -t 参数即可,代表查找 tags。

  4. 允许下载远程代码索引至本地使用

    有同事反映自己用 for-code-reading 创建一个安卓代码索引还是太慢了,而有时候出差又无法连到 abc-x 的服务器。这时候可以试试把服务器上的代码索引下载到本地使用,具体用法是:

    • cd 到自己的安卓源码目录下
    • 运行 sse,选择 get-source-index 子命令
    • 可能会有 Bug,导致不好用,如有问题,可以联系 CM,或者直接咬咬牙,用 for-code-reading 从头创建一个索引。

5.24 同步代码出错怎么处理?

最常见的同步代码出错处理起来非常简单,可以分为 3 种情况:

  1. 没有相关仓库权限

    解决方法:

    • 用 sse 同步代码时选正确的分组,不确定的话建议选 none,不同步任何特殊权限的分组。
    • 如果确认需要特殊分组的权限,请给 cms@smartisan.com 发邮件,抄送给 Leader,Leader 同意后开放相关分组的权限
  2. 某个或某几个仓库出错。

    解决方法:

    • 如果是 manifest 仓库出错(刚运行 repo sync 马上出错):删掉 manifest 相关的两个仓库,重新 repo init

      具体是哪两个仓库?.repo/manifests、.repo/manifests.git

    • 如果是某个代码仓库出错:删掉跟这个代码仓库相关的 3 个目录,重新 repo sync

      具体哪 3 个目录?以 bionic 为例,分别是 bionic、.repo/projects/bionic.git、.repo/project-objects/platform/bionic.git,更具体的信息可以打开 .repo/manifest.xml 搜索出错的仓库名字。

  3. 其他环境、配置问题导致的错误

    比如 git 版本太低、没有同步到最新版本的代码等等。在下面的小节里有说明。

下面是更详细的说明。

另外要特别提醒注意的是一个终端的回滚行数问题。同步代码出错的话,如果是在安卓代码顶层目录用 repo sync 同步所有仓库的代码,需要一直往回翻看终端的输出,确定是哪个仓库出错。只看最后几行输出的话,有时候是不够的。确定出是哪几个仓库出错,对查出错原因、解决错误都是非常重要的,如果你的终端不支持回滚,或者能回滚的行数不够多,请试着自己改一下配置,或者换一个终端软件(这里推荐 tmux 虚拟多终端程序)。

5.24.1 权限错误

下载代码出错,有个常见的原因是尝试下载自己没有权限的仓库。如果你 repo sync 的时候提示如下:

................................................................
From ssh://smartisan/qualcomm/platform/developers/buildiB/s
 * [new branch]      odin-rom   -> smartisan/odin-rom
Fetching project CyanogenMod/android_packages_apps_Settings
error: Cannot fetch packages/apps/WalletSmartisan 15.38 MiB/s
Fetching project platform/cts
remote: Counting objects: 3514, done        MiB | 15.38 MiB/s
remote: Counting objects: 14748, done
remote: Finding sources: 100% (3514/3514)
remote: Total 9240 (delta 1844), reused 5069 (delta 1844)
Receiving objects: 100% (9240/9240), 142.03 MiB | 11.84 MiB/s, done.
Resolving deltas: 100% (1844/1844), done.)
remote: Finding sources: 100% (14748/14748)           iB/s
error: Cannot fetch packages/apps/AppStoreSmartisan
From ssh://smartisan/qualcomm/platform/external/autotestB/s
 * [new branch]      odin-rom   -> smartisan/odin-rom
Receiving objects: 100% (3514/3514), 55.10 MiB | 11.47 MiB/s, done.
remote: Total 3514 (delta 704), reused 2217 (delta 704)
Resolving deltas: 100% (704/704), done.

error: Exited sync due to fetch errors
Command exited with non-zero status 1
43.30user 11.46system 1:44.25elapsed 52%CPU (0avgtext+0avgdata 89812maxresident)k
25296inputs+1965392outputs (80major+860268minor)pagefaults 0swaps

注意上面以 error: 开关的行,这里有几个仓库下载不下来,最终导致 repo sync 失败(error: Exited sync due to fetch errors)。

关于这种出错的原因,请参考 2.1.1,里面详细说明了公司对代码权限的管理方法、下载出错时的解决方法等。

(解决的方法有两种,请按自己的情况确定:1. 用 sse 重新选择一遍下载时的设置,选合适的分组,比如“none”;2. 给自己的 Leader 发邮件并抄送给 scm@smartisan.com,申请相关仓库的访问权限)。

5.24.2 某个仓库数据损坏引起的错误

在用 repo sync 同步代码的时候,有时候会出现某个仓储同步不下来的情况。最常见的原因是上次代码同步的时候被中断,导致 git 有些关键文件出错(.lock 文件锁未删除等)。这种情况下,你需要确定是哪个仓储出的错,一般来讲从 repo sync 的终端输出里直接能看到出错的仓库的名字,比如:

From smartisan:qualcomm/platform/vendor/proprietary
 + 388d94d...ce100c5 smandroid/8953-r02000.3 -> smartisan/smandroid/8953-r02000.3  (forced update)
Fetching projects: 100% (509/509), done.
Syncing work tree:  67% (342/509)  Traceback (most recent call last):
  File "/home/liuchaowei/gerrit/odin/.repo/repo/main.py", line 513, in <module>
    _Main(sys.argv[1:])
  File "/home/liuchaowei/gerrit/odin/.repo/repo/main.py", line 489, in _Main
    result = repo._Run(argv) or 0
  File "/home/liuchaowei/gerrit/odin/.repo/repo/main.py", line 162, in _Run
    result = cmd.Execute(copts, cargs)
  File "/home/liuchaowei/gerrit/odin/.repo/repo/subcmds/sync.py", line 699, in Execute
    project.Sync_LocalHalf(syncbuf)
  File "/home/liuchaowei/gerrit/odin/.repo/repo/project.py", line 1269, in Sync_LocalHalf
    lost = self._revlist(not_rev(revid), HEAD)
  File "/home/liuchaowei/gerrit/odin/.repo/repo/project.py", line 2357, in _revlist
    return self.work_git.rev_list(*a, **kw)
  File "/home/liuchaowei/gerrit/odin/.repo/repo/project.py", line 2551, in rev_list
    p.stderr))
error.GitError: kernel/msm-3.18 rev-list (u'^b08ae8be8fa187a55ecc1b5e3bd12789ed24130d', 'HEAD', '--'): error: Could not read 6009fc101781f1416aadd8fc5d1d833993026520
fatal: revision walk setup failed

Command exited with non-zero status 1
60.95user 9.25system 4:58.19elapsed 23%CPU (0avgtext+0avgdata 2334884maxresident)k
3805296inputs+2119144outputs (8921major+2357265minor)pagefaults 0swaps

比如上面这个,是在 kernel/msm-3.18 这个仓储。注意这里显示的是他在服务器上的路径,与最终同步下来的代码路径有可能是不一样的,具体可以打开 .repo/manifest.xml 这个文件查看一下。

另外,还有一种情况,你不是很容易确定是哪个仓库出错,比如下面这种情况,它只是告诉你哪个文件出错了:

...
error: unable to read sha1 file of gradle-api-3.3.jar (c895f6f08252381e46dec638fd4117e2c21a659c)
error: unable to read sha1 file of gradle-api-3.4.1.jar (a14c13e5222087218882bed3244a104d1b5a48bb)
error: unable to read sha1 file of gradle-api-3.5-20170307000048+0000.jar (147791798b5085bb1cd7ba02815fd516e00921df)
Traceback (most recent call last):
  File "/home/hegang/src/android-trident-trinity/.repo/repo/main.py", line 531, in <module>
    _Main(sys.argv[1:])
  File "/home/hegang/src/android-trident-trinity/.repo/repo/main.py", line 507, in _Main
    result = repo._Run(argv) or 0
  File "/home/hegang/src/android-trident-trinity/.repo/repo/main.py", line 180, in _Run
    result = cmd.Execute(copts, cargs)
  File "/home/hegang/src/android-trident-trinity/.repo/repo/subcmds/sync.py", line 821, in Execute
    project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
  File "/home/hegang/src/android-trident-trinity/.repo/repo/project.py", line 1327, in Sync_LocalHalf
    self._InitWorkTree(force_sync=force_sync)
  File "/home/hegang/src/android-trident-trinity/.repo/repo/project.py", line 2486, in _InitWorkTree
    raise GitError("cannot initialize work tree")
error.GitError: cannot initialize work tree

那么,这种情况下怎么可以确认到是哪个仓库呢?方法很简单,就是利用 CM 提供的快速代码搜索工具,abc-x grep,它有一个 -r 选项,允许你在远程直接调用 beagrep 搜索,而 beagrep 除了 grep 出文件内容匹配的功能外,还包含了一个文件名字匹配的功能。所以从上面的出错文件,你可以很方便的找到其具体的仓库,方法就是运行 abc-x grep -r 'gradle-api-3.4.1.jar',(注意上面有一个文件的名字不适合直接用来运行 abc-x gre,就是那个名字中带 + 字符的,这是一个正则表达式的元字符):

( smartcm@cmdev | ~/src/android-trident-rom | Remote:True [trident-userdebug] )
$abc-x grep -r 'gradle-api-3.4.1.jar'
make: Entering directory `/home/smartcm/src/android-trident-rom'
tools/external/gradle/BUILD:39:    srcs = ["gradle-api-3.4.1.jar"],
tools/external/gradle/gradle-api-3.4.1.jar:1: ****************!

这样,你很快就确定了出错的仓库是 tools/external/gradle 这个仓库。

确认了是哪个仓库出错后,你可以把相关的三个目录都删除,然后重试一遍:

  1. 代码目录,比如上面的出错信息对应的是 kernel 目录
  2. .repo/projects 下的 .git 目录,比如上面对应的是 .repo/projects/kernel.git 目录
  3. .repo/project-objects 下的 .git 目录,比如上面对应的是 .repo/project-objects/kernel/msm-3.18.git 目录。

删除完之后,可以直接用 repo sync “目录名或 project 名” 的方式单独同步一下这个目录,快速验证是否已经解决问题。一般如果还没有解决 这个仓库 的同步问题的话,可能要看一下是不是属于本条 faq 其他小节里给出的情况,比如:

  1. 用了 repo mirror,并且 mirror 数据有错误
  2. git 版本太低导致的错误
  3. .repo/manifests 目录有 git 数据错误

如果有多个项目都会出错,请依次处理。如果出错的项目太多,处理不过来,请换个全新的安卓代码目录从头同步一次——用 --depth=1 同步,速度挺快的。

如果从头同步还是出错的话,可能是服务器问题或其他不常见问题,请查看一下本 faq 条目里列出的其他常见同步代码出错情况,如果还是不能解决,请联系 CM 同事查看、解决,比如给 cms@smartisan.com 发邮件。

5.24.3 .repo/manifests 目录或其他代码目录因 shallow clone(depth=1)mirror 数据出错

我们结合公司安卓项目实际情况,在多次尝试之后,向大家推荐了比较折中的结合使用 mirror + shallow clone 的代码下载方式,即,首次下载完整安卓代码,使用 --depth=1,后续某仓库如果要获取完整代码历史的话,再使用 sse unshallow-source-code 命令,这个命令会在 ~/src/android-mirror 目录下创建该仓库的镜像。

使用这种方法,目前来看有些同事不知道是否因为操作方法的问题,偶尔有发现类似这样的错误:

{ XXX@XXX-pc /home/XXX/src/android-osborn-dev/packages/apps/SanBox [osborn-userdebug] }
$!repo
repo sync -cdj4 .
project .repo/manifests/
error: Could not read c237084044c9668b7b69492a704c580036d6c562
error: Could not read c237084044c9668b7b69492a704c580036d6c562
error: Could not read c237084044c9668b7b69492a704c580036d6c562
fatal: 拒绝合并无关的历史

这里提几个注意事项:

  1. 不能随便动 ~/src/android-mirror 目录下的镜像数据,一旦某个镜像仓库数据损坏的话,所有使用它的安卓代码仓库都无法使用,因为它们的一部份 git 历史是复用镜像仓库数据的。
  2. 如果发现某个使用了镜像的数据仓库损坏,想要节省时间单个修复一下该仓库的话,除了上面提到的安卓代码目录下的已有的三个相关的 .git 目录(代码目录本身、.repo/projects.repo/project-objects 下对应的 .git 目录)都需要删掉清除错误之外,还需要把 ~/src/android-mirror 下对应的镜像仓库也清掉。

    这里特别提一下 .repo/manifests 这个仓库,有同事多次反映它的数据损坏,目前 CM 尚不清楚原因,但是要修复它的话,可以尝试如下操作:

    • 运行 repo-switch -p 命令记一下当前代码目录 repo init 时使用的命令
    • cd .repo
    • rm manifests manifests.git manifest.xml -rf
    • 用上面记录下来的命令重新 repo init
    • 另外注意 ~/src/android-mirror/qualcomm/platform/manifest.git 这个目录,如果使用了它做镜像的话,也需要删掉(请查看 .git/objects/info/alternates 文件的内容确认是否使用了镜像)。

    如果觉得修复单个仓库太麻烦,或者损坏的仓库太多,无法单个修复(比如不小心把整个 ~/src/android-mirror 目录都删了的话),那只能把整个安卓代码目录也都全部删除重新从头下载一份代码了。做这个操作之前如有必要,请备份自己改动过的文件,重新下载完毕后,再拷贝回去(同时用 git diff 简单的做一下 Review,看看是否在这个过程中有其他同事刚刚提交的改动被你覆盖了)。

  3. 平时使用的过程中多注意总结,目前来讲可能还存在 CM 也不清楚的造成数据损坏的原因,但基本上按照各位工程师同事反馈问题的情况比较少这一点来看,上面的方案一般的使用场景下是没什么问题的,所以如果你的使用中经常出问题的话,可能需要自己总结一下,最近有没有做一些特殊的操作,比如(上面已经提到的):

    • 下载代码的时候 Ctrl-C 中断
    • 动了 ~/src/android-mirror 下的数据

    如果能总结出常见操作问题,请帮忙反馈给 CM,非常感谢!

5.24.4 git 版本太低导致“Server does not allow request for unadvertised object… 或 error: Cannot fetch…error: Exited sync due to fetch errors”

比如勒晓东同步代码时出现下面的错误,经查,是 git 版本太低导致的,他的版本是 2.13,升级到 2.14 或 2.15 就好了。system-config 提供了一个名为 upgrade-git 的脚本,在 ubuntu 系统下运行一下,就能升级 git 版本。

Fetching projects:   2% (14/658)  Fetching project
packages/apps/SmartisanOSTheme
error: Server does not allow request for unadvertised object
e8a30ac9bca43c20a14c63d72bae62195f7914c5
error: Server does not allow request for unadvertised object
1fb384ad6b201ea63b70b95376d82d792558a8f3
error: Server does not allow request for unadvertised object
e8a30ac9bca43c20a14c63d72bae62195f7914c5
error: Cannot fetch packages/apps/SoundRecorder
Fetching project platform/frameworks/opt/emoji
remote: Total 0 (delta 0), reused 0 (delta 0)
error: Cannot fetch packages/apps/SmartisanOSTheme

error: Exited sync due to fetch errors
Command exited with non-zero status 1
0.69user 0.44system 1:18.62elapsed 1%CPU (0avgtext+0avgdata
26748maxresident)k
0inputs+392outputs (0major+133156minor)pagefaults 0swaps

如果 manifest 里有指定 revision, 且在同步时使用了 --depth=1,git 版本太低时会导致以下错误,此时需要执行 upgrade-git 脚本升级一下 git 版本。

error: Cannot fetch packages/services/ThirdPartyLibs/Payment
error: Cannot fetch packages/services/VoiceAssistantService
error: Cannot fetch packages/apps/BrowserChrome

error: Exited sync due to fetch errors

5.24.5 同步代码没有出错,但是明显没有同步到最新的代码

这种情况可能发生在成都的同事的系统上,原因是北京代码主服务器的版本往成都镜像服务器同步的时候进程被堵塞了,导致成都同事无法同步到最新的提交。

根本的解决方案应该是解决服务器之间的镜像堵塞问题,但这有时候可能需要一点时间,所以需要麻烦碰到问题的同事自己执行一下如下的操作:

  1. cd 到你需要修改的代码仓库目录下
  2. 运行 git pull --rebase $(repo-remote-url|perl -npe 's,ssh://.*?/,ssh://review.smartisan.cn/,') $(repo-branch)

    其中 repo-remote-url 和 repo-branch 命令均在 system-config 里定义,前者打印出当前仓库的远程 URL,后者打印出当前仓库所使用的分支名。

    一般远程的 URL 都是 ssh://smartisan/ 这样的格式,所以上面的 perl 文本替换命令把成都的 URL 强制改成了北京的服务器所使用的 URL。

    成都的同事在 ~/.ssh/config 里配置的 smartisan 或 gerrit.smartisan.cn 服务器使用的是成都本地的镜像服务器,review.smartisan.cn 则使用的是北京真正的服务器。

5.24.6 git push 时出错:unpack error

这个问题非常好解决,在 google 上一搜“unpack error”第一条就是:git unpack error on push to gerrit - Stack Overflow

解决的方法是在 git push 时加一个 --no-thin 参数。

5.25 Patch 合入其他代码分支的操作步骤

  • 输入变量

    :在下面的操作步骤中,请将如下变量用你的实际数据进行替换

    • 待合入的 patch 的 git commit id,记为 $GIT_COMMIT_ID,比如 afc54b1a5ede5f33a9e6e22fd1779fd5efee43c8
    • 需合入的目标分支,记为 $TARGET_BRANCH,比如 mol/ocean-trinity-6.6.0
    • 在哪个代码仓库下工作,其路径记为 $REPO_PATH,比如 ~/src/android/system/core
  • 步骤:
    • cd $REPO_PATH
    • sse unshallow # 确保当前代码 git 历史是完整的,而非 shallow clone
    • git fetch $(repo-remote) # 确保已经取到所有分支的最新代码
    • git co -B $(repo-remote)/${TARGET_BRANCH} ${TARGET_BRANCH}
    • git cherry-pick ${GIT_COMMIT_ID}
      • 如果有冲突的话,需要自己解冲突。
      • 如果 cherry-pick 提示结果是一个空提交,说明你的改动已经在目标分支上,请确认一下这个改动是怎么提进去的

        一般空的提交不处理也可以,如果有 PM 在跟这个 patch 合入操作的话,你可以考虑跟 PM 反馈一下(比如在他提供的 excel 表里标记一下)。

      • 没问题的话,就可以考虑提交到 gerrit 上了
    • gerrit-push-review

      因为之前用 git co -B 创建过目标分支的上下游跟踪,所以 gerrit-push-review 脚本会自动计算出要 push 到哪条分支上

5.26 ssh 连接到 Gerrit 服务器提示私钥冲突

因为 ssh 服务器、客户端版本升级的关系,ssh 的私钥类型有时会发生变化(比如从 rsa 变成 ECDSA),所以大家在连接 Gerrit 服务器的时候,有可能会不停的遇到如下的出错提示。

Warning: the ECDSA host key for '[review.smartisan.cn]:29418' differs from the key for the IP address '[172.16.0.9]:29418'
Offending key for IP in /home/wangyuelei/.ssh/known_hosts:1
Matching host key in /home/wangyuelei/.ssh/known_hosts:11
Are you sure you want to continue connecting (yes/no)? yes
remote: Counting objects: 7923, done
remote: Finding sources: 100% (2153/2153)

这种情况下,请直接用编辑器打开 ~/.ssh/known_hosts 文件并删掉所有提示有冲突的行(上面的例子中是第 1 行和第 11 行,删的时候注意顺序,先删了第 1 行之后,第 11 行就变成了第 10 行——删错甚至全删掉也没太大关系,但你以前连接过的所有服务器全部需要重新接受一遍私钥提示),然后重新用 ssh HOST 连接一下有问题的主机,重新接受一下服务器的私钥即可,在下面这个提示下输入“yes”并回车:

The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:4m20eYLhlOFZcbwsi+qmiCi0jS5BzfSTgWyu/9FijRs.
Are you sure you want to continue connecting (yes/no)? 

注意这个地方必须手工输入 yes,直接回车默认相当于不接受此私钥指纹,这一点与 system-config 下用大小写(Yes/no、yes/No)来区分直接回车的默认选项是有人机交互上的差别的。

5.27 关于手机 APK 签名 Key 的问题

在这篇文档里其他地方也有跟 Key 相关的信息,请仔细阅读,在这里解释一下跟 Key 相关的常见问题。

5.27.1 为什么要有 Key

这是安卓自己的安全机制,所有的 APK 都必须有签名,整个系统也必须有签名。具体可参考这两篇文档:

https://developer.android.com/studio/publish/app-signing.html

https://source.android.com/devices/tech/ota/sign_builds.html

5.27.2 为什么要有多套 Keys

我们默认开发用的那套 Keys,所有研发工程师都可以获取,甚至早期合作的第三方 OEM 公司,都知道我们这套 Key,出于安全考虑,必须换一套保密的 Key。

5.27.3 默认我的 Apk 用的是什么 Key?

简单的说,我们有两套 Keys,内部开发共享的一套称为 old keys,外部发布保密的另一套称为 formal keys。每套 Keys 里都包含了名字相同的四个 Key:platform、releasekey、shared、media。

具体到系统里的某个 Apk,你要选择用上面 4 个 Key 里的哪一个。这个是在你的 Apk 的 Android.mk 里通过 LOCAL_CERTIFICATE 指定的,如果没有指定的话,默认为 releasekey。如果 Apk 有比较高的权限要求,则一般定义为 LOCAL_CERTIFICATE := platform,但强烈建议这样做之前跟你的 Leader 一起 Review,因为一旦你的代码里包含有漏洞,同时又拥有 platform 权限,被利用的后果就严重了。

5.27.4 各种 Key 的编译策略是什么样的?

为了内部开发方便测试、调试,我们制定了这样的 Daily 编译策略:

  1. userdebug 版本一般都用 old key(工程师都可以获取的一套 Key)

    使用 old key 编出来的版本,刷机包目录名不带 K 的前缀

  2. user 版本一般都用 formal key (工程师不可以获取)

    使用 formal key 编出来的版本,刷机包目录带 K 的前缀

  3. 如有临时特殊需求,需要专门申请,比如也可以编一个使用 old key 的 user 版本。

5.27.5 跟第三方合作,需要我们的 Key 的 sha1/md5 信息,该如何操作?

这种情况下,一般需要注意参考如下流程

  1. 确认自己用的一套 Keys 里的哪一个,是 platform 还是 releasekey
  2. 把 formal keys 和 old keys 里的相关 Key 的信息都提交到第三方申请

    为了调试,必须把 old keys 里的 Key 提给第三方;为了最终产品可以正常使用,必须把 formal keys 里的 Key 提给第三方。

  3. 在编译脚本里,根据使用的 Keys 套装不同,需要确保正确的第三方相关信息被用于编译

    具体的方法,请参考 packages/apps/SettingsSmartisan/WallpaperProvider/Android.mk 和 packages/apps/SettingsSmartisan/WallpaperProvider/fix-AndroidManifest.sh,写完自己的脚本之后,也可以提交给 CMs 进行 Review。

  1. 第三方只允许提交一个正式 Key,不允许提交测试 Key,怎么办

    这种情况非常少见,目前为止只有发现微信是这样规定的。具体可参考 SmartisanApps 这个 Key 相关的做法(在安卓代码里搜索 abc-x grep SmartisanApps),并咨询搜到的相关代码的维护者(一开始是杨阳提出并实现了此机制)。

5.28 怎么获取我手机系统版本信息,用的是哪天的刷机包、Symbols 目录在哪里、使用的是新 APK Key 签名还是旧 APK Key 签名

这个信息是非常重要的,在任何时候搞错版本都可能导致南辕北辙,焦头烂额。因此 CM 提供了一些简便的方法来获取这些相关信息,希望所有同事都能掌握。

5.28.1 使用 http://172.16.2.18/vmlinux.html 查询

  1. 获取手机编译格林尼治时间戳: adb shell getprop ro.build.date.utc
  2. 把上面的命令输出的时间戳输入到 http://172.16.2.18/vmlinux.html 相应条目内进行查询,会显示手机系统版本对应的刷机包、Symbols 目录的路径。
  3. 如果刷机包目录名字前缀带大写的 K,说明是新 Key,否则是旧 Key。参考 5.8

    此外,工程师也可以自己再想想有没有更简便的方法获得 Key 的信息。比如得到系统中某个 Apk 的签名的 md5 值,然后在本文档中搜索该 md5 对应的是新 Key 还是旧 Key。

5.28.2 system-config 提供的快捷方法

System-config 用户想快速获得刷机包、symbols 包所在位置信息的话,还有一个更简便的方法就是:

  1. 运行 sse
  2. 选 get-phone-version-info 子命令

此外,还可以输入 . cd-adb 命令,可以直接改变当前目录到相应的 Windows 共享目录下,前提是你已经用文件管理器打开过相应的共享目录(在 /var/run/user/$UID/gvfs/ 下有挂载)。

5.29 版本信息查询大全:从 ota 版本查刷机包、从刷机包版本查 ota 包、从手机 adb getprop、kernel log 信息查刷机包、编译时代码版本

CM 每天都会出很多版本的软件,每个版本都会有几组互相对应的信息,这些信息对工程师查 Bug 来讲可能非常重要,如果查错版本的话很可能徒劳无功,因此 CM 提供并在这里总结一下这些信息的快速自助查询方法。比如大家可能关注的这些信息:

  1. 刷机包共享目录名(及其绝对路径、user 版本的话,其 root 小包所在的共享目录路径在刷机包目录后加 .root 的后缀)
  2. OTA 包名字(可能有很多个 ota 包)
  3. 编译时间戳(可以从手机上用 =adb shell getprop ro.build.date.utc = 命令打印)
  4. Kernel 版本(通过从刚启动的手机上用 dmesg|grep 'Linux version' 命令查看,启动一段时间后该 log 信息会被冲掉)

    BSP 同事也有可能从 ramdump 文件里提取到这个字符串(通过 strings 和 grep 命令)。

  5. OEM Symbols 路径
  6. Linux vmlinux symbol 文件
  7. Android symbols 文件
  8. 编译时所有代码版本的 manifest.xml 快照(在刷机包目录下、smartisan:tools/released-manifests git 仓库里)

等等等等。

在掌握某一组信息的情况下,大家可以通过 http://172.16.2.18/vmlinux.html 这个网页,查到对自己更有用的信息,比如我手上有个手机,那我就可以打印一下它的编译时间戳,然后用时间戳查询;比如测试报问题的时候,版本信息填了一个 OTA 的版本信息,但我更想要知道刷机包的位置,等等。该网页的每一个输入框内都给出了可供参考的输入字符串大概是什么样的。这个网页一开始设计的时候,只是给 BSP 工程师查 vmlinux 信息对应的刷机包的位置用的,后来逐渐加入了更多信息的相互查询,但名字一直都叫 vmlinux.html 不变了…

查到刷机包位置之后,打开该目录下保存的代码版本快照 manifest.xml 文件,结合 git log 命令,可以知道该版本编译时某仓储的代码是否以经包含了我的某个改动,等等等等。

System-config 配置环境下,也对这些信息的查询进行了一些封装,比如 . cd-adb 命令,可以直接一步 cd 到刷机包的共享目录下,如果共享目录还没有 mount,这个命令甚至会自动帮你 mount

5.29.1 正式外发上市用户版本备份说明

目前由于产品线比较多,我们编译出来的版本,会在服务器上保存一个月。

但是我们所有给用户开放过 OTA 的版本、工厂正式生产的 DC 版本,最后都会保留一个备份下来,备份里包含上面提到的所有文件,刷机包、ota 包、ap/oem symbols 等等。

这个文件都按照产品、编译配置、版本等信息备份在 \\172.16.2.240\flash\backup\auto-backup 目录下,大家可以自己到这个目录下查找。备份的目录路径比较深,大概是这样的:

. # 当前目录是 smb://172.16.2.240/flash/backup/auto-backup
├── colombo-user # 这一层子目录是安卓 lunch 的配置;下一层是 “ota-base.zip文件名.刷机包目录名”
│   ├── ota-smartisanos-3.0.0-2016092310-user-col.zip_base.zip.SEKSA-mol%colombo-rom_3.0.0_DC-colombo-user-20160923-101245-32g
│   │   ├── ap-symbols # 这一层目录是备份的版本文件的类型
│   │   │   └── mnt # 以下的子目录是版本文件原来的路径的目录结构不变
│   │   │       └── flash
│   │   │           └── smart-builder
│   │   │               └── surabaya
│   │   │                   └── symbols
│   │   │                       └── mol-3.0-DC
│   │   │                           └── release-20160923
│   │   │                               └── build-liulina-2016-09-23-09.51.35
│   │   │                                   └── SEKSA-mol%colombo-rom_3.0.0_DC-colombo-user-20160923-101245-32g


5.29.2 如何获取研发老版本编译信息(manifest 快照、smartcm-build.info)

基于跟上节相同的原因,我们的研发版本只多只保留一个月。有时候有同事希望重编某几个版本用于查问题、解 Bug,但刷机包目录已经被删除了,无法获取其准确的名字。

比如系统组同事张金山有一次来问,已知 6 月 5 号的 ocean mol-6.2 版本是好的,6 月 20 号对应分支的版本是有问题的,希望用二分法去查是在什么时候引入的问题,这种情况下,可以通过如下方法获取中间每天的版本的刷机包名字:

在 \\172.16.2.240\flash\.smartcm\released-manifests\mnt\flash 共享目录下,保存着所有历史版本的刷机包目录镜像,每个目录下只保存原刷机包下对应的编译信息文件,包括 manifest.xml、smartcm-build.info 等。

所谓镜像意思是指该目录的相对路径与原刷机包路径保持一致,比如 smb://172.16.2.240/flash/smart-builder/osborn/4.4-mol/release-20180819/build-litianyu-2018-08-19-09.01.26-507d2b/SEKSA-mol%osborn-rom-4.4.0-osborn-user-20180819-090932-32g 这个刷机包,即使它被删除了,它的编译信息镜像目录会一直保存,其共享路径只需要把这个刷机包路径前面的 smb://172.16.2.240/flash 替换为 smb://172.16.2.240/flash/.smartcm/released-manifests/mnt/flash 即可。

如果不知道原刷机包的名字,则可以自己去 released-manifests 目录下按照之前手动查找刷机包的规则去查找对应的编译信息、版本名字(即刷机包目录名)。

注意 .smartcm 在某些系统上相当于“隐藏文件”,用 ls 或文件管理器不加参数或不改设置有可能是不可见的,建议直接在 system-config 下运行 cd smb://172.16.2.240/flash/.smartcm/released-manifests/mnt/flash 命令以进入此目录。

5.30 加 patch 重编某版本的一些问题说明

有时候工程师会需要基于某版本加一些 patch 重编,这里说一下一些常见的问题

  1. 打 patch 时冲突了怎么办?

    这种情况一般多发生在代码差异比较大的时候,比如主线上的 patch 往 mol 线版本上打等等。

    解决的方法是工程师自己在本地下载目标分支,切换到目标 git commit id(参考重编版本的 manifest.xml 快照),然后 cherry-pick 主线上的那个 patch 并解决冲突。

    解完冲突后,再把这个 patch 直接提交到目标分支上进行 review:gerrit-push-review --no-do-rebase -b TARGET-BRANCH,注意这里的 --no-do-rebase 参数,目的是确保在提交之前直接基于快照的版本进行提交,不要先 rebase 到目标分支的服务器版本(rebase 的话过程中又可能产生冲突)。

  2. 目标分支已经被锁线了,不允许 push review 了,怎么办

    • 下载目标分支,切换到需要的 git commit id 版本(与上面相同)
    • 基于这个版本,创建一个 sandbox/ 分支,这个“目录”下允许大家随便创建分支、直接 push refs/heads/,而不是通过 refs/for/ 来创建 review:

      git push $(repo-remote) HEAD:refs/heads/sandbox/rebuild-$(today)

    • 自己手动下载、合入 patch 并解冲突(与上面相同)
    • 把新的 patch 提交到之前创建的 sandbox 分支上进行 review,把上面那条 git push 命令的 heads 改成 for 就可以了:

      git push $(repo-remote) HEAD:refs/for/sandbox/rebuild-$(today)

    • 注意不要把新的 patch 不走 review 就直接 push 上去(用 refs/heads 而不是 refs/for),因为这样就没有 review url 可供 smart build 加 patch 重编了。

      如果已经不小心 push 上去了,只好重来一遍,创建一条新的 sandbox/ 分支,比如 refs/heads/sandbox/rebuild-$(today)-try2,try3,一路 try 下去。

    总结一下:基于 base 版本创建 sandbox 分支;打上 patch;提交一个该 sandbox 分支上的 review。

最后,注意所有加 patch 重编均需要大家请自己的 leader review +2。

5.31 user root 版本的获取、使用方法

我们的 user root 功能,是通过 刷机包目录.root 共享目录下一些名为 boot-XXX.img 的文件提供给大家(如果是安卓 8.0 之前的产品,还有一些对应的 -ota.zip 文件)。大家想要 user root 版本的话,根据安卓版本不同,有以下方法:

  1. 通过刷机的方式。

    在刷机前把刷机包里的 boot.img (以及 vbmeta.img,如果 刷机包目录.root 下有对应的 vbmeta-XXX.img 文件的话)替换成 .root 目录下的版本。应该选哪个 XXX 版本请参考 刷机包目录.root 目录下的 README.html 文件。

    如果想要通过刷机的方式,但同时要保留 userdata 分区用户数据的话,system-config 的 sse .edl 命令提供了这一功能,大家可以了解一下 这封邮件

  2. 通过 ota 的方式(仅限安卓 8.0 之前的产品)

    安卓 8.0 之前的产品,我们还提供了一个 boot-XXX-ota.zip,大家可以通过 sideload 把它刷到自己的手机上,效果相当于替换了刷在 boot 分区上的 boot.img。

    8.0 之后的产品,安卓大改了 ota 的逻辑,没法再方便的生成一个这样的 ota.zip 包了,所以取消这种 root 方法的支持。

5.31.1 自己生成 boot-ota.zip

注意:这里描述的方法对安卓 8.0 之后的产品是没用的,安卓 8.0 之后,user 版本安全机制变得极为严格,基本上不可能再通过自己编一个 boot.img 然后替换到 CM 编出来的整包里的方式进行调试、开发了。

目前所有的 user 版本,都会在刷机包目录同级目录下生成一个刷机包名加 .root 后缀的目录,里面有一个 .zip 文件,这是个 ota 包,可以用 adb sideload FILE.zip 命令把它装到手机上。

如果需要自己编译 boot.img 并更新到手机里的话,可以通过以下步骤:

  1. 取得 CM 发布的适当版本的 boot-ota.zip 包,比如 \\172.16.2.240\flash\daily\surabaya\mol-3.2\release-20170402\SEKSA-mol%surabaya-rom-3.2.0-colombo-user-20170402-003356-32g.root\boot-ota.zip参考:版本共享目录变更说明
  2. 解压该 zip 文件到一个新建的 boot-ota 目录
  3. 用自己编译出来的 boot.img 替换解压开的 boot-ota 目录下原有的 boot.img 文件
  4. 重新压缩 boot-ota 目录下所有文件至 boot-ota-new.zip,注意必须保持原有的目录结构,也即所有的文件在 .zip 文件的顶级目录下,不能随意创建子目录(比如直接压缩整个 boot-ota 目录)
  5. 用 http://172.16.2.18:18080/apksign/index 的网页签名工具给新的 ota.zip 包重新签名

    M 系列及之前的产品选择 Release Key,Odin 项目及之后的产品请选 Verity,并勾选“OTA .zip 包重新签名请选择此选项”,然后上传你刚打包的用于 user-root 的 OTA.zip 文件。

  6. adb sideload 新生成的 \\172.16.2.240\flash\apksign\邮箱\boot-ota.zip\boot-ota.zip.formal 文件 (参考:版本共享目录变更说明

如果需要更新其他 binary 分区的 image,也可以采用上面的方法,比如 lk bootloader,其分区名为 aboot,其刷机包文件名为 emmc_appsboot.mbn,你把这个文件拷贝到 boot-ota.zip 解压目录下,然后更新一下 META-INF/com/google/android/updater-script 文件即可:

package_extract_file("emmc_appsboot.mbn", "/dev/block/bootdevice/by-name/aboot");

以上这些复杂的步骤,也可以用 sse 命令的 .switch-ota-boot-image 子命令一步完成,在 system-config 环境下用法如下:

. cd-adb #切换目录到手机软件版本对应的刷机包目录下
cd $PWD.root #切换目录到 user root 对应的目录下
sse .switch-ota-boot-image -z boot-ota.zip -b ${PWD%.root}/boot.img #使用当前目录下的 boot-ota.zip 文件,使用刷机包目录下的 boot.img 文件(可用于去除 user root 功能)

生成的新 boot ota 文件自动下载在 ~/tmp/.switch-ota-boot-image.$PID 下,注意用完以后自己及时删除该目录。

具体的使用方法还可以通过 sse .switch-ota-boot-image --help 查看帮助

5.31.2 user root 版本的 remount 问题

随着安卓官方对安全的要求越来越严格,我们调试问题的时候也越来越麻烦,这里讲一下 user root 之后的版本,怎么 remount 的问题。

安卓官方的版本里,只有 userdebug 和 eng 编译的版本才能 remount,user 版本是不可以的。这里稍微提一下 userdebug、eng 版本下执行 adb remount 所需要的步骤:

  1. 先运行 fastboot unlock 解锁 bootloader(安卓 oreo 版本中新加的限制)

    如果没有解锁 bootloader 就直接运行下面的 disable-verity、remount 步骤的话,手机有可能会每次重启都强制进入 fastboot 或直接变砖(如果发生了这种情况,请参考 5.64 自行解决)。

  2. 关闭 verity 验证机制(需 root 权限,请确保已运行 adb root):adb disable-verity(安卓官方在 2014-10-09 首次加入此 adb 命令)

    注意:关闭 verity 之后必须重启手机:adb reboot

  3. 运行 remount 命令(需 root 权限,请确保已运行 adb root):adb remount

注意 1 和 2 只需要做一遍(除非重新刷机)。

User 版本的问题就非常复杂了,主要是因为:1. 安卓默认不允许在 user 版本进行高级的 debug 操作;2. 不同的研发阶段、不同的开发分支上我们的安全机制随时有可能发生变化。

下面就简单说明一下在 user 版本上执行 remount 需注意的问题,所需要的步骤跟上面的 userdebug 版本相同,但所使用的方法更复杂了。

  1. 如何在 user 版本的手机上运行 fastboot unlock
    • 开发到了后期阶段,user 版本的 bootloader 里的 fastboot 功能可能会整个被关闭(出货版本是必须关闭的)
    • 即使 fastboot 没有被整个关闭,它也可能被“阉割”,比如不允许你解锁 bootloader。
    • 解决的方法是换 userdebug 版本的 abl.elf,然后再进 fastboot

      一般来说,换完 userdebug 版本的 abl.edl 之后,手机是不能正常开机的,但它会允许你使用完整的 fastboot 功能。

      • 我试过 SEKSA-user 的系统上可以刷 SE-userdebug 的 abl.elf,并获得完整的 fastboot 功能
      • 通过这个完整的 fastboot,把你的手机解锁,然后再用 fastboot flash 命令刷回原来的 user 版本的 abl.elf,你就完成了 user 版本的 bootloader 解锁
      • 上述方法也可以用于 user 版本的 fastboot 整包刷机
  2. 如何在 user 版本的手机上运行 adb disable-verity
    • User 版本即使 root 了也是不允许运行 adb disable-verity 的(想要支持的话,还有很大的开发、维护的工作量及造成安全漏洞的风险),但是 BSP 同事刘朝威给大家提供了一种方法,直接修改 vbmeta 分区,达到 disable-verity 的效果。
    • 我已经把这个方法集成到 system-config 的 adb 脚本里了

      如果你不用 system-config 的话,请自己把 vbmeta 分区(在 a/b 分区的系统上,根据当前启动的 slot,选 vbmeta_a 或 vbmeta_b)的第 123 字节用 dd 命令从数值 0 改成数值 1

    • 这个方法,在下一个安卓版本或下一个产品里是不是还一直能用,是无法保证的。如果不能用了,我们会尝试找找有没有别的办法。
  3. User root 版本上尝试 remount 碰到任何问题,请:
    1. 升级 system-config
    2. 仔细阅读这个 faq
    3. 因精力有限,本人不负责支持 user remount 过程中碰到的任何问题,包括数据丢失、手机变砖、以及 remount 不成功等。

      请各位 Leader 自己掌握 user remount 的方法,并支持 team 里的其他遇到问题的同事。

5.31.3 user trial 版本无法调试黑屏问题的解决方案(建议)

我们在 user root 版本的基础上,每个新产品都会展开 user trial 的流程。之前曾经发生过用户同事手里的手机黑屏,但是拿到研发同事手里没法用 adb 连接取 log 的问题。测试同事抱怨过这个事情,并且建议用 force-adb 版本做 user trial。

现对此问题解释、建议如下:

  1. 没法连接 adb 是因为发生黑屏问题之后没法点击确认“允许 USB 调试吗”的对话框
  2. 我认真思考了一下,认为我们不能改成用 force-adb 的版本。因为这个版本有非常严重的安全问题(共享充电宝了解一下?),不能用做 user trial。

    另一个考虑是 force-adb 对产品定义的改动比较大,有可能导致不能及时发现 usb 相关的问题。

  3. 建议的解决方案是:
    • 所有 user trial 的同事,均需保证自己的手机能在自己的电脑上连接过 adb,免得碰到黑屏问题时无法打开 adb、无法确认允许调试。
    • 研发同事碰到问题的时候,请把 user trial 同事电脑上的 ~/.android/adbkey 文件(Windows 下的位置请上网查一下)拷贝到自己的机器上(可以先把自己的备份一下),然后应该就能直接连上 adb 了。
    • 发生过一次无法取 log 的事情之后,建议及时采取措施,避免下次继续无法取 log。

5.32 user root 版本的调试问题

user 版本有很多调试的坑,很多在 userdebug 下可以用的调试手段,拿到 user 版本下,是无法使用的,即使我们用上面提到的 user root 手段 root 了手机,也是无济于事。

列举一些相关的坑如下:

  1. 某些产品如 M1/M1L 手机上 user 版本即使 root 了,也无法 adb remount 让 system 分区可写。这是因为出于安全的考虑,业界普遍在出货手机的 Kernel 里做了最大化的安全限制,不需要写的分区一律不让写。这些手机一般是采用了 ufs 存储,硬件有个寄存器设置,直接从硬件上关掉写权限(感谢 BSP 同事刘朝威提供该信息)。Odin 手机因为采用的是 eMMC 存储,所以没有这个限制。

    这导致 user 版本即使 root 了,也只能看看一些 root 之前没权限看的 log 等等,无法替换 system 分区下的 apk 文件。解决的方法是 ota 升级手机的 aboot 分区,这个分区的内容是一个 boot loader,是它把 boot 分区给引导起来的,并且设置了哪些分区是只读的,哪些是可写的。我们把它换成 userdebug 版本的 aboot image 文件即可,在高通平台上一般叫 emmc_appsboot.mbn。替换的方法请参考 5.31,这里给出一条 sse .switch-ota-boot-image 命令的用法如下:

    sse .switch-ota-boot-image \
        -b ../SEK-mol%surabaya-rom-3.2.0-colombo-userdebug-20170402-002429-32g/emmc_appsboot.mbn \
        -z boot-ota.zip \
        --partition aboot
    

    使用此命令之前,请升级 system-config,这里加入了 --partition 参数的处理(2017-05-12)。

    (通过合理的 -b BOOT.IMG --partition BOOT_PARTITION 的参数,可以轻松更新手机上大部分 raw data 格式的分区。但一般替换了 aboot 分区之后,就不需要再这么麻烦了,直接用 dd 命令可以往分区里写,建议参考 adb-push-partition 命令: adb-push-partition ./boot-root.img boot。)

    注意尽量采用同一天的 userdebug 版本,并且保持 SE、K 这两个版本标志的一致性。

  2. PROGUARD 选项导致 java 类、方法被混淆、优化,有时直接整个方法被删除,导致父类的方法被调用

    解决方案是在 Android.mk 里暂时去掉所有 PROGUARD 混淆。也请自行上网搜索如何调试 PROGUARD 导致的问题。

  3. .apk 文件被 oat 优化,导致与 frameworks 的 binary 文件版本之间强耦合,无法替换

    本来在 userdebug 下编出来的 .apk 直接 adb push 到手机上 /system/app/ 或 /system/priv-app/ 目录即可,但 user 版本下,这样做是不行的,因为其 odex 文件里已经预先优化过,没有 java .class 文件了,无法在手机上进行第二次 odex 优化操作。

    这种情况下,出错的 apk 会不断 crash,启动不起来。

    有一种可能的解决方案是编译一个 userdebug 版本的.apk,然后 push 到手机上试试。或者在 user 版本下 Android.mk 里把 odex 优化临时禁掉,重新编个版本(请自行上网查找相关方法)。

  4. framework.jar (以及其他系统.jar 库文件)无法更新

    在 userdebug 版本下,这个不是问题,如果更新了这个.jar 文件,系统会把所有.apk 文件重新在手机上优化一遍。但在 user 版本的手机上,因为上面提到的原因,更新了 framework.jar 等系统库文件之后, .apk 文件是无法再次优化的(已经被 strip 掉了)。

    这种情况下,整个系统应该都启动不起来了。

    有一种可能的解决方案是每次编译一个完整的 system.img,然后把 /system 目录下所有文件全部更新一遍,但这只是一种猜测,并没有实际操作过。

  5. 除此之外,还需要考虑工程师手里拿着的手机版本的问题,是 SE,还是 SEK?还是 K?应尽量简化问题,考虑各种方案的性价比,如果可以用不带任何前缀的手机版本,对工程师调试来说,代价是最小的,所以请妥善选择(比如手机上是带 K 的版本的话,工程师想更新 apk,还必须用 CM 提供的签名网页重签一次名)。

鉴于以上种种不便之处,一般来讲,我们应该尽量在 userdebug 上复现、调试、解决问题,只有实在没办法的时候,才不得不工作在 user 版本上,之前也是一直这样鼓励、强调的。并且一旦发现某问题只在 user 版本出现,不在 userdebug 版本出现,都应该非常重视,解决之后总结、分享,争取下次不再摔进同样的坑里面。

以上,如有遗漏、不正之处(事实上,这里使用的术语可能很有问题,并不一定准确)、或者有更好的方法、实战经验分享等等,请回复此邮件大家讨论。

5.33 我用 sse 同步下来最新的代码,用 git log 看只有两条历史记录

简单的说,解决方案是再到你需要完整历史记录的代码目录下运行一下这个命令: sse unshallow-source-code

复杂的话,出现这种情况是因为最新的 system-config 里,默认同步下来的代码是 shallow clone。这样做的目的是:

  1. 可以大大节省服务器资源。

    目前因为工程师人数增加,产品数目增加,如果所有人都直接用 git clone 完整历史记录的话,很快就可能会出现服务器不堪重负排队队列过长失去响应的情况。

  2. 可以大大加快代码同步的速度。以前即使创建过 repo 镜像,不能经常保持镜像更新的话,镜像带来的速度提升慢慢会变得没有那么明显。但使用 shallow clone 的话,代码同步的速度不受此影响。
  3. 节省你本地的硬盘空间

    同样,之前的 repo 镜像方案也可以节省磁盘空间,但随着镜像越来越旧、某些二进制仓储比如 oem-release 增长速度惊人,很快镜像带来的空间节省会没那么明显。但使用 shallow clone 的话,不受此影响。

关于 git shallow clone,可以看一下 https://www.perforce.com/blog/141218/git-beyond-basics-using-shallow-clones

5.34 BSP 同事如何获取 MTP(高通原生开发板)的版本

BSP 同事有时会有获取 MTP 版本刷机包或代码的需求,在此记录一下,方便大家自助获取:

  1. 所有刷机包都在 \\172.16.2.240\flash\mtp (参考:版本共享目录变更说明)共享目录下,按项目名或 CPU 型号分,以 trident 项目对应的 mtp 版本为例,下面描述一下如何获取该项目最新的 mtp 刷机包版本(请仔细阅读下面的脚本里的注释!谢谢):

    cd smb://172.16.2.240/flash/mtp
    # system-config 提供了封装,可以直接 mount 并 cd 到该共享目录下
    
    ls -ltd trident/* sdm845/*
    # ls 的 -t 参数代表以修改时间排序,-d 代表显示目录本身,而非目录下的文
    # 件。其中 sdm845 是 trident 项目所使用的高通 cpu 型号,在项目的初期产品
    # 名字未定时,CM 也会使用 cpu 的名字作为刷机包的路径。
    
    # 该命令运行输出如下:
    
        # drwxr-xr-x 2 bhj root 0  6 月 6 15:57 trident/android-8.1/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.0/
        # drwxr-xr-x 2 bhj root 0  3 月 16 16:59 trident/symbols/
        # drwxr-xr-x 2 bhj root 0 11 月 3  2017 sdm845/symbols/
        # drwxr-xr-x 2 bhj root 0  9 月 1  2017 trident/test/
    
    # 注意上面的第一行,说明最新的 mtp 编译版本存放在此目录下,因此,请继续 ls 该目录下的子目录:
    
    ls -ltd trident/android-8.1/*
    
    # 输出如下,第一行就是最新的 mtp 刷机包的位置:
    
        # drwxr-xr-x 2 bhj root 0  6 月 6 15:27 trident/android-8.1/mtp%trident-sdm845-r00056.1-sdm845-userdebug-20180606-101622-32g/
        # drwxr-xr-x 2 bhj root 0  6 月 6 15:27 trident/android-8.1/sdm845-r00056.1/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.1/sdm845-r000431.1/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.1/sdm845-r000331.1/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.1/sdm845-r00026.1/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.1/sdm845-r00020.3/
        # drwxr-xr-x 2 bhj root 0  4 月 8 11:37 trident/android-8.1/sdm845-r00005.1/
    
  2. 查到了 mtp 版本的刷机包位置之后,再参考 5.35 来获取当时的代码,以及如有必要的话,自己重编版本并调试。

另外,在下载高通源码的时候,可能会碰到一个 ssh 服务器错误,请参考 5.20 和 5.20.1.1

5.35 如何对齐 CM 编译出来的版本代码以及如何重编该版本

有时候工程师需要在特定的版本上重现问题、调试,这种情况下,经常需要在自己的机器上确保代码版本、编译命令与 CM 提供的版本对齐,下面就描述一下如何操作。

首先,CM 出的所有软件版本,在其刷机包目录下都:

  1. 有一个 manifest.xml 代码快照文件

    如果是 bsp 工程师,如果在做高通 oem 相关的调试,可能还需要关注里面还有一个 oem-manifest.xml 快照文件,记录了该版本的 oem 代码快照。

  2. 有一个 smartcm-build.info,里面记录了当时执行代码同步和编译所使用的命令、环境变量等。

    如果是 bsp 工程师,请关注可能还有一个 oem-smartcm-build.info 文件。

因此,你可以通过如下步骤对齐版本快照并尝试自己编译可用于调试的 image 文件:

  1. 创建一个新的工作目录并 cd 到该目录下,比如

    mkdir -p ~/src/android-XXX
    cd ~/src/android-XXX
    
    

    以下的所有命令都假设当前目录(PWD)是在 ~/src/android-XXX 下。

  2. 运行 grep 'repo init' PATH-TO-FLASHING-FILES/smartcm-build.info 命令,以查看代码是如何下载(repo init)的

    其中 PATH-TO-FLASHING-FILES 代表你所关注的 CM 版本在共享目录下的路径,比如我把 \\172.16.2.240\flash mount 在了 /mnt/flash 目录、当前关注的刷机包目录名叫 SEKSA-mol%trident-rom-4.4.0-trident-user-20180629-042510-32g,则其对应的路径应为 /mnt/flash/smart-builder/trident/4.4-mol/release-20180629/build-litianyu-2018-06-29-01.18.35-da7754/SEKSA-mol%trident-rom-4.4.0-trident-user-20180629-042510-32g(参考 5.29)。

  3. 用上面查出来的 repo init 命令初始化当前工作空间 ~/src/android-XXX
  4. 不要直接运行 repo sync -j4 -d -c,在后面加一个 -m PATH-TO-FLASHING-FILES/manifest.xml 的参数再运行。

    请使用 repo sync --help 命令查看一下 -m 参数的文档。

  5. 用 grep do-cm-build PATH-TO-FLASHING-FILES/smartcm-build.info | head -n 1 命令查看一下 CM 编译该版本时所使用的命令及配置参数。

    比如上面提到的版本,可以查到其编译命令是:

    /bin/bash /home/smartcm/system-config/bin/smartcm -x do-cm-build -c SEKSA+trident-user -C -b -r -R /mnt/flash/smart-builder/trident/4.4-mol -o -J
    

    这里大家主要关注一下这个 -c 参数即可,它相当于安卓编译时所使用的 lunch 命令的参数(我们稍微扩展了一下,前面加了一个 SEKSA+ 的前缀,参考 5.8

  6. 结合上面查到的 -c 参数,使用适当的编译命令重编版本

    比如:

    • 重编安卓官方 image 文件如 system.img 等:

      andorid-make -c trident-user # 这里不支持 SEKSA 等前缀!
      
    • 重编高通刷机包

      请参考 sse build 命令或 ./cm-how.html#biy-se

最后,特别说明一下,所有 CM 版本中,带用 K 前缀的版本大家是无法自己重编出来的,请自行去掉 K,比如你想重现某 SEKSA 的版本,则你应该尝试用 SESA 的配置进行重编、解问题。理论上,这个问题应该完全不影响大家进行对齐版本的调试。大部分情况下,请考虑使用不带任何前缀的配置重编,比如 trident-userdebug

5.36 如何获取正式发布的最终用户版本的刷机包、OTA 包?

CM 对最终用户版本有进行定期备份,包括对应的所有 刷机包、OTA 包、OTA Base 文件、Symbols 文件等。

所有的备份都放在 \\172.16.2.240\flash\backup (参考:版本共享目录变更说明)共享目录下,比如想获取 3.2.7 外发版本的刷机包,可以在该目录下运行此命令进行过滤:

ls *3.2.7* -d

输出结果为:

ota-smartisanos-3.2.7-2017042500-user-col.zip_base.zip/
ota-smartisanos-3.2.7-2017042500-user-is.zip_base.zip/
ota-smartisanos-3.2.7-2017042500-user-sur.zip_base.zip/
ota-smartisanos-3.2.7-2017042503-userdebug-col.zip_base.zip/
ota-smartisanos-3.2.7-2017042503-userdebug-sur.zip_base.zip/
ota-smartisanos-3.2.7-2017042505-userdebug-511.zip_base.zip/
ota-smartisanos-3.2.7-2017042505-userdebug-is.zip_base.zip/
ota-smartisanos-3.2.7-2017042510-userdebug-sfo.zip_base.zip/
ota-smartisanos-3.2.7-2017042510-userdebug-sfo_lte.zip_base.zip/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32-user-20170425-113257-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_603-user-20170425-113525-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_603_younger-user-20170425-131832-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_cdma-user-20170425-114421-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_cdma_younger-user-20170425-134331-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_cmcc-user-20170425-120724-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_cmcc_younger-user-20170425-141515-32g/
ota-smartisanos-3.2.7-2017042511-user-511.zip_base.zip.SA-mol%u1-l-rom-3.2.0-msm8916_32_younger-user-20170425-124653-32g/
ota-smartisanos-3.2.7-2017042511-user-sfo.zip_base.zip/
ota-smartisanos-3.2.7-2017042511-user-sfo_lte.zip_base.zip/

此处,is 代表 T2(icesky),sfo 代表 T1,sfo_lte 代表 T1 的 4G 版本,col 和 sur 分别是 M1L 和 M1,等等等等。U1 的版本比较多,所以加上了刷机包的名字加以区分。

以 ota-smartisanos-3.2.7-2017042511-user-sfo.zip_base.zip 目录为例,其下共有如下文件夹:

ap-symbols/
flashing-files/
oem-symbols/
ota-zips/

其中 flashing-files 是刷机包(里面很多层子目录一路点进去);ap-symbols 是安卓 symbols 文件;oem-symbols 是 oem symbols 文件;ota-zips 下还有两个二级子目录,mnt/ota-base 和 mnt/ota-out,前者是 ota 编译的 base 文件,后者是 ota 的升级文件(可用于 ota sideload,size 最大的那个是全包 ota 升级文件)。

5.37 为什么不鼓励工程师用 Smart Builder 的服务器编译

目前有些工程师可能是出于图省事儿(当然也可能确实是有需求),经常通过 Smart Builder 在公司的服务器上启动编译。我们不鼓励这样操作,主要原因是为工程师自身的职业发展考虑。

大家都知道做安卓开发比较苦逼,迭代周期比较长,改一个东西、编译、调试一下非常不方便。不像人家做互联网行业的,可以做快速迭代。从某种程度上讲,我们的开发流程简直一点也不“敏捷”,Unix 分时系统的出现,大家可以跟机器快乐的交互,从批处理系统中被解放出来,极大的提升了生产力。然而我们搞安卓开发的,却好像还沦陷在批处理的时代,改个东西之后要很久才能拿到结果。

当年 Mozilla 公司发明 Javascript 语言的那哥们,你猜他花了多少时间弄出可以运行的 Javascript?在当年那么慢的机器上?一星期!有人问他你问什么那么牛逼?这么快就可以搞出这么好的语言来?他说主要是因为 Unix 系统的交互太赞了,开发迭代起来的速度非常快,做完一个修改马上就编译,马上就运行,马上就得到结果对还是不对。

现在回到我们自己的安卓编译上来,如果大家都养成在服务器上启个编译查问题、解问题的习惯,最后会怎么样?你的迭代速度比人家慢啊!别人搞安卓开发,在自己机器上配好环境,不用 10 分钟就编出版本完成一圈迭代,你弄不好要等两小时。差了 10 多倍。等别人年薪百万的时候,你还是 10 万

你可能会问,为什么不让我们工程师直接登录到服务器上进行迭代?做完一次全编然后把环境给我留着,允许不停的重编、调试?嗯,主要是因为我们穷,买不起那么多服务器。话说回来,如果有一天我们不穷了,嗯,不穷了你还搞什么安卓开发?

要跳出这个怪圈,必须鼓励大家尽量在自己的机器上编译。这里跟大家分享一些技巧:

  1. 尽量不要在上班时间启动 full build。在每人只有一台机器的情况下,启个 full build 基本上机器可能就卡得不能用了。可以整个定时任务,在每天下班后启动 full build。

    当然,该在上班时间启动 full build 的话咬咬牙也就启动了。原因很简单,一把修改没搞定的话,在自己机器上编完一次以后你就可以开始快速迭代。在服务器上启动的话,你没法快速迭代。

  2. 尽量注意不要掉进各种坑里(参考 各种编译错误),比如同步到一份“脏”的代码,连持续集成都通不过,那你自己编译出错、编出来结果运行时出错也都是有可能的。
  3. 尽量多用 mma、mm 等能加速编译的抄近道方法。平时也可以自己多总结一些方法,多分享。老板一看你这么爱分享,说不定给你多加点工资。
  4. 之前有个同事比较不走运,一入职分到一台机器只有 8G 内存,根本没法做全包编译,不停的出 Jack OOM 错误。后来他不停的在服务器上起编译任务。他 Leader 给他解决机器问题的方法是向 IT 申请加内存,结果两个月也没加上。其实应该申请直接换机器的!申请换机器比换内存快多了。

    希望老板能重视上面这个同事的需求,尽快帮他搞到一台机器。

当然,必须要用服务器编译的时候,也请大家不用犹豫,放心大胆的用就好了。后续 CM 会考虑调整服务器优先级的配置策略,更好的满足大家的编译需求。

以上,共勉。

另外,还有一个鼓励大家不要随便用服务器编版本的非常重要的原因是,要把服务器留给真正有需要的同事。像现在这种状况下,很快大家会发现自己提交上去的编译任务,到下班了还没有开始编译。

5.38 常用网址列表

Smart Builder

自助启动版本编译: http://172.16.2.18:18080/

请节约服务器资源,尽量在自己的机器上编译版本,虽然第一次 clean build 会花很长时间,但后续非 clean build 的话,就不会需要等太久。另外还可以尽量采用 mm、mma 等命令局部编译,加快编译、调试速度。

如确实有必要(比如要 SE 版本、K 版本)的时候,再考虑使用服务器编译。

参考 为什么不鼓励工程师用 Smart Builder 的服务器编译

Android Doc

用 Git 维护的安卓文档系统(本 workflow.html 文档也是其一部分): http://172.16.2.18/docs/

外发版本查看

http://172.16.0.9/ota-out/ota_release_records.php 这个页面提供了对外 OTA 开放的记录,也可以用来查看正式外发的版本。一般正式外发的版本其“open_status”一列应为“all”。

Gerrit

https://review.smartisan.cn:8080/#/dashboard/self

注意这个网页是用 https 登录,不要用 http,会出错的。

常用 Jenkins 任务

以下是一些常用的 Jenkins 编译任务,各位可以自己登录到 http://172.16.2.18:8080/ 上去运行这些任务

这个列表由 CM 脚本自动维护,因此不再放在这个 workflow 文档里,请大家访问这个单独的页面,里面的每个任务都给了简单的说明(更详细的说明,请参考 Jenkins 任务自带的说明):

http://172.16.2.18/docs/cm/global-jenkins-projects.html

5.39 sandbox/分支的使用

各位有时候有些代码不想提 gerrit review,但是想在服务器上备份一下;或者有些文件超大的仓库,比如高通的某些 oem 仓库,因为 patch size 太大,提到 gerrit review 网页上的话,会直接把 gerrit 系统搞挂掉。

这些情况下,各位可以通过 git push 命令把代码提到sandbox/分支,加了这个前缀的分支是对大家开放直接 push 权限的,请大家善加利用。

用法:git push REMOTE HEAD:refs/heads/sandbox/MY-BRANCH-NAME。如果 MY-BRANCH-NAME 在服务器上不存在的话会直接创建;如果已存在并且你的版本分支正确,则更新服务器分支到你的当前版本(详情请参考 git push –help 文档,或者考虑自己开个 github 开源项目玩一下,熟悉熟悉 git 的基本用法;注意不要泄露公司机密就好了)。

5.40 关于 git 分支的“规定”

说点关于 git 分支的题外话。大家都知道一个 git 仓库下有很多个 commit。其实每个 commit 都是一个分支——只不过它们都是匿名的分支而已。

我们平时所说的 git 分支,只不过是对某个 git commit 起了个名字而已,每个 git commit 都可以用一个 md5 值代表,它是一个 40 位的十六进制数字。所以如果我们给它起了个名字的话,记起来就会变得更方便(就好像 IP 地址和 DNS 域名之间的关系一样)。

另外,我们起了分支名字之后,这个分支就可以生长了,它现在指向 commit-1,我提交(git commit)一个新的 commit-2,它就自动指向了 commit-2。

刚刚为什么说每个 git commit 都是一个匿名的分支,就是因为即使没有分支名字的话,git 历史也是可以生长的(比如你运行一下 git checkout HEAD^ 的话,git 就会告诉你,当前已经没有分支名了,但是不要担心,还是有个叫 HEAD 的特殊分支名指向当前这个 git commit,然后你可以继续提交,HEAD 会跟随着指向你的新提交)。

这里说到 HEAD 了,这是 git 的一个约定,你可以想像成有一个名叫 HEAD 的文件,它里面记录了当前的 git commit 的 md5(事实上确实有一个这样的文件,它在路径是 .git/HEAD,有时候它记的是一个 md5,有时候它记的是一个 refs/heads/BRANCH-NAME)。

refs/heads/BRANCH-NAME 是 git 的另一个约定,所有的 git 分支名都放到 .git/refs/heads/ 目录下,对应着一个文件,里面记录着这个分支的头部 commit 的 md5。(这些文件也可能被压缩成一个 .git/packed-refs 文件,里面每一行记录着一个 MD5 refs/heads/BRANCH-NAME。所有关于 git 分支的操作,包括 git commit 命令,默认都会操作 refs/heads/ 这个“路径”。

refs/for/BRANCH-NAME 则是 gerrit 的一个约定,它有某些“魔力”,发现有人往这个位置 push 提交的话,会把这些新的提交变成一个一个的 review 页面。(所谓“魔力”是浪漫化的说法,说白了其实就是 gerrit 的软件它是这么规定的、这么实现的的意思)。

也就是说是 gerrit 对 git 进行了扩展,赋给了 refs/for/ 特殊的含义。你如果在自己的机器上某个 git 仓库下运行如下命令的话:

git remote add test-remote $PWD # 把这个 git 仓库目录自身当成一个 remote
git push test-remote HEAD:refs/for/hello

你可能会有点失望,因为这回 refs/for/ 的“魔力”消失了,你只会发现出现了一个名叫 .git/refs/for/hello 的文件,它里面记着当前提交的 md5。

5.41 [SMailingList] 关于 Gerrit Review Patch 下载的错误、解决方法

有些同事从 Gerrit Review 网站上的“Cherry Pick”按钮点击获取当前 Patch 的下载命令。这时候一定要注意不要选 http 方法,这种方法需要特殊配置(生成 http 密码等等),一般是肯定会出错的。

解决方法有两种,一种是选 ssh 协议(见下图,注意标注部分)。

gerrit-review-patch.png

另一种是使用 system-config 提供的 gerrit-fetch-review 命令,后面直接跟 Gerrit Review Patch 的网址即可,比如图中显示的 Patch,可以用下面的命令下载:

gerrit-fetch-review -c https://review.smartisan.cn:8080/#/c/251841/

可以用 gerrit-fetch-review -h 查看一下帮助。有时候有些改动一个 PatchSet 没有改好,Reviewer 提出改进意见,再提一个 PatchSet 上来,Reviewer 如果想只 Review 前一个 PatchSet 和后一个 PatchSet 之间的改动的话, gerrit-fetch-review 命令也是可以支持的,加入一个 -d '1 2' 选项就可以做 PatchSet 1 和 PatchSet 2 之间的 diff。

而且 gerrit-fetch-review 命令不需要在相应的 git 仓储下面运行,在安卓顶层目录或任意子目录下运行时,会自动帮你算出对应的仓储,然后 cd 到该仓储下运行。如果找不到对应的仓储,会自动帮你在 ~/src 目录下 git clone 一个新的 repo 出来。

5.42 如何获取各产品的代码分支信息、Daily 编译信息

5.42.1 产品的分支信息

大家可以用 system-config 的 sse 命令的 get-source-code 子命令,一步一步选取哪个产品、哪条分支(dev 代表主线,其他 mol 线工程师一般不太需要关心,如果需要的时候,请与 Learder、PM 或 CM 确认)。

一般的产品名字,在 sse get-source-code 里就跟大家平时最常使用的称呼一致,比如 t1 就叫 t1、t2 就叫 t2,后来不让叫 t3 以后,就叫 surabaya、odin 等。如果是特殊的项目,比如 t2 往安卓 Marshmallow 版本的升级项目,产品名字叫 T2M,这个容易给大家造成困惑,很多没有参与到这个项目的同事不知道这个 sse 选项是干什么用的,跟 t2 有什么区别,以后这样特殊的产品(项目),会在后面加一个文字描述,帮助大家更直观的选择。

sse get-source-code 命令会根据你指定的产品、分支选择正确的 repo manifest.xml 来同步安卓或 OEM 代码,代码同步完成后,所有仓储的分支信息都在 manifest.xml 里指定好了,通过 repo-branch 命令可以打印出当前仓储使用的是哪条 git branch。如果你使用 gerrit-push-review 命令来提交 gerrit review 的话,你甚至不需要指定 git branch,脚本会自动根据当前安卓代码所使用的 manifest.xml 算出来应该提到哪条分支上(请在本文档中搜索 gerrit-push-review)。

5.42.2 Daily 编译信息

我们所有 Daily 的编译,最终刷机包会存放到 \\172.16.2.240\flash\daily\sse-产品名\sse-分支名 (参考:版本共享目录变更说明) 共享目录下,比如 t2 产品,其 sse- 产品名就是 t2,其主线的 sse- 分支名是 dev(所有产品的主线在 sse 里都叫 dev)。

如果需要获取与某刷机包对应的其他文件信息,比如 ota 包、symbols 文件,请使用 http://172.16.2.18/vmlinux.html (请在本文档里搜索 vmlinux.html)

此外,请参考: 5.8

5.43 Crash Report 系统里给出的版本信息对应的 Symbols 文件查询方法

从 这个页面 里点进去看到软件版本之后(见下图),大家可能需要关于这个版本的更详细信息才能调试查问题,这里解释一下怎么查版本信息,比如对应的 Symbols 文件的位置。

crash-report.png

方法就是把软件版本号输入到 http://172.16.2.18/vmlinux.html 的 OTA 字段里进行查询。

注意查询出来可能会有多个结果,这个是因为这个版本号只是 OTA 的字段,而 OTA 字段一开始设计的时候,就存在两个不同配置的版本,因为其启动编译的时间相同,导致最后编出来的 OTA 版本字段也重合的现象,目前只能请大家注意筛选(在 vmlinux.html 页面会列出所有 OTA 字段匹配的版本信息)。

解决上面这个问题可能需要

  • 负责 OTA 的同事对 OTA 版本字段进行修改,保证没有重合

    但这个方案影响比较大,现有出货产品上可能不好操作

  • 负责 Crash Report 系统的同事对软件版本号提供更多信息,比如 ro.build.date.utc ,这个也可以用于版本信息查询,并且基本上重合的可能性非常小。

另外据说 Crash Report 系统里还提供了 Linux Kernel 启动时打印的 vmlinux 信息,但并不准确,所以提醒大家可能需要注意。

5.44 为什么 lunch 没有 user 选项?怎么办?

最近有几位同事提出,lunch 的时候没有 user 选项,比如最新的 osborn 项目,只有 osborn-userdebug,但是没有 osborn-user,跑来问 CM 这是怎么回事,osborn 是不是还不支持 user 编译。关于这个问题,给大家作一说明。

  1. user/userdebug/eng 不一定全部列出,大家需要的话,可以自己在 lunch 命令后面加参数。

    比如下面的这个列表,是从 odin 下打 lunch 命令列出来的,大家可以看到,像谷歌自己给配置的 aosp_arm 这种,只有 eng,并没有 userdebug 和 user 版本,那是不是说 aosp 上就只能编 eng 版本,不能编 user、userdebug 版本了呢?显然不是的,需要编 user 版本的话,只要在 lunch 后面跟参数就可以了,lunch aosp_arm-user

    我们现在的做法也有很大的问题,很多项目负责系统编译配置的同事(好像是 BSP 的同事)配了一个 userdebug 之后,总有些同事嫌还不够,结果不得不又给配上 user 版本,比如下表中既有 odin_ct-user,又有 odin_ct-userdebug,这种做法非常不好,你看谷歌为什么不把 aosp_arm 的 eng、userdebug、user 三种选项都给加齐了?为什么只列了一个 aosp_arm-eng?列那么多完全没有意义嘛!并且把列表挤得非常的长,想选出你想要的那个,更加的费眼睛了。

    Lunch menu... pick a combo:
         1. aosp_arm-eng
         2. aosp_arm64-eng
         3. aosp_mips-eng
         4. aosp_mips64-eng
         5. aosp_x86-eng
         6. aosp_x86_64-eng
         7. mini_emulator_arm64-userdebug
         8. m_e_arm-userdebug
         9. mini_emulator_x86-userdebug
         10. mini_emulator_x86_64-userdebug
         11. msm8953_64-userdebug
         12. odin-userdebug
         13. odin-user
         14. odin_cmcc-user
         15. odin_cmcc-userdebug
         16. odin_ct-user
         17. odin_ct-userdebug
         18. odin_oversea-user
         19. odin_oversea-userdebug
    
    Which would you like? [aosp_arm-eng]
    
    
  2. lunch 的用法,存在一个缺陷

    那就是,我每次新开一个终端窗口,都必须重新配置一下 . build/envsetup.sh; lunch。重启 PC 之后,也必须重新配置。

  3. 对于需要在多条产品上线工作的同事来说,lunch 的使用,存在一个陷阱

    比如你在一个终端窗口下,设置了 T1 的编译环境。过了一会经理转给你一个 T2 的 Bug,然后你在同一个终端窗口下,转去搞 T2 的编译,结果肯定就莫名其妙的出错了。

    这个问题,我自己有碰到过、并且也发现有其他同事碰到过。

针对以上情况,建议大家

  1. 尽量不要用 lunch,比如我,就几乎从来不用 lunch

    system-config 里封装的很多命令,最后都会通过 android-set-product 脚本自动帮你设置安卓编译配置,并且人机交互界面比 lunch 友好很多,比如下面的操作中,用户输入了 cmcc 和 debug 几个字进行过滤,最后选择了 odin_cmcc-userdebug

    $android-set-product
    1) odin
    2) odin_cmcc
    3) aosp_arm
    4) aosp_arm64
    5) aosp_mips
    6) aosp_mips64
    7) aosp_x86
    8) aosp_x86_64
    9) mini_emulator_arm64
    10) m_e_arm
    11) mini_emulator_x86
    12) mini_emulator_x86_64
    13) msm8953_64
    14) odin_ct
    15) odin_oversea
    请选择你要编译的产品>cmcc
    1) user
    2) userdebug
    3) eng
    请选择你要编译的选项>debug
    
    

    这些会自动帮我设置安卓编译选项的 system-config 命令包括:

    • android-make
    • mma
    • mm (没错,mma 和 mm 太有用了,受不了每次都必须先 . build/envsetup.sh 才能使用,所以我把它们也封装了)
    • sse

    它们最后都是调用 android-set-product 脚本设置编译选项。安卓自己提供了一套机制,允许你设置过编译选项之后,把一些变量保存下来,之后除非你想改变这个设置(重新运行 android-set-product 就可以),否则不管你怎么重新打开终端窗口,或重启电脑,只要你一进入这份安卓代码,它就是已经配置好了的。两份安卓代码之间的配置,也永远不会弄混。

  2. 坚持用 lunch 的话,至少要知道可以自己在后面加参数,不要一看没有 user 就怀疑咱们的产品还不支持 user 版本的编译

5.44.1 lunch is harmful

总结一下 这个常见问题,安卓的 lunch 机制有很多问题:

  1. 步骤太繁琐。必须先 . build/envsetup.sh,然后 lunch,而且每次新开终端窗口或重启后都必须做一遍,忘记的话就浪费时间和精力。
  2. 多产品环境下容易出错,比如设置了 T1 的编译选项,然后到 T2 的目录下开始编译。
  3. 无法多配置快速切换,比如先编了 T1 的 userdebug,又想编一个 user 版本的话,整个 out 目录基本上要清空。又或者先编了个 odin 的 userdebug 版本,然后又需要再编个 odin_cc 的版本。

    在 system-config 的环境下,安卓的编译输出 out 目录其实是一个软链接,真正的目录保存在 .repo/out-PRODUCT-CONFIG 下,这样的话,大家如果有切换 user/userdebug 编译版本的需求,可以快速的切换。

    这里有一个问题,就是大家第一次从官方的安卓编译做法切换到 system-config 下的做法的时候,以前编好的 out 目录是一个真正的目录,这时候执行 system-config 提供的 android-set-product 命令的话,这个 out 目录会被删除。但幸好这个问题只会发生一次,所以我们不会把它当成一个真正需要解决的问题,只是在这里说明一下。

  4. 人机接口不够友好,列出长长的一个选单,有重复内容,比如 odin-userdebug 和 odin-user 列在一起;用眼睛搜索自己想要的选项非常费劲,没有过滤功能。

综上所述,system-config 环境里基本上不鼓励大家在命令行上手动使用 . build/envsetup.sh,而是帮大家封装了一个直接可以用的 lunch 命令。

5.45 安装使用 ubuntu-16.04 注意事项

目前公司的安卓产品,在 odin 之后的项目可以用 ubuntu-16.04 开发,经 CM 测试,编译过程没有问题。

odin 之前的项目:T1、U1、T2、M1/M1L,均无法在 ubuntu-16.04 下编译,会出类似这样的编译错误:

log:119936:out/host/linux-x86/obj/SHARED_LIBRARIES/libartd_intermediates/arch/x86_64/quick_entrypoints_x86_64.o:function art_quick_deoptimize: error: unsupported reloc 42

所以建议大家如果还经常需要开发、调试与老产品相关的 Bug,请勿随便升级到 16.04 系统。

5.46 如何强制重启、如何重启进入 recovery 等各种启动模式

下表给出了我司各个手机产品的启动模式组合键,都是从源代码中查出,最新产品如果组合键代码有更新的话,本文档也需要相应更新,所以如果你使用此文档中给出的按键发现无效的话,请以代码为准。

产品强制重启进 recovery 按键进 fastboot 按键bootloader 仓库下代码位置
T1长按电源键左下 + 右下vol-up + vol-downapp/aboot/aboot.c
T2同上home + vol-upvol-up + vol-downapp/aboot/aboot.c
U1同上vol-up + bright-downvol-down + bright-upapp/aboot/aboot.c
M1/M1L同上左上右下左下右上app/aboot/aboot.c
Odin同上Home + 音量上 + 音量下音量下app/aboot/aboot.c
Osborn同上音量下音量上 + 音量下QcomModulePkg/Application/LinuxLoader/LinuxLoader.c

5.46.1 组合键相关知识详细说明

一般来说,在各手机上,长按电源键 15~30 秒左右就可以强制重启。一般这个强制重启是硬件电路的看门狗设置,软件上无法做更多灵活配置。

我们的手机也可以在启动时强制进入 recovery 模式,这个各个产品上的按键可能有不一样,都是通过软件可以设置的。一般都在 bootable/bootloader/lk/app/aboot/aboot.c 文件里设置,比如以 odin 为例,参考这段代码:

if (keys_get_state(KEY_HOME) && keys_get_state(KEY_VOLUMEDOWN) && keys_get_state(KEY_VOLUMEUP))
    boot_into_recovery = 1;

也就是说,在 odin 上面要强制进入 recovery 的话,按住所有的键就对了。

如果是 osborn 产品,这个代码是这样的(建议在 bootable/bootloader/edk2 目录下 git log 然后搜索 recovery):

commit 3dbb96fcb62bba2f4b0f659175db62c14c3563e5
Author:     hanzhen <hanzhen@smartisan.com>
AuthorDate: Mon Aug 21 17:30:25 2017 +0800
Commit:     cmbuild <cmbuild@smartisan.com>
CommitDate: Sun Sep 3 23:28:53 2017 +0800

    ABL:Press V- to enter recovery mode

        V+ :fastboot mode
        V- :recovery mode
        Remove esc key

    Ticket:0181702
    Product:osborn

    Change-Id: I1779f9244e87054545c4bd2ca5e7582b76437bad
    Signed-off-by: hanzhen <hanzhen@smartisan.com>
    (cherry picked from commit 3317eb2cef6de808e3fd27b2f26c2fea2809cb07)

diff --git a/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c b/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c
index a18f0741a..c45549b7c 100644
--- a/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c
+++ b/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c
@@ -178,12 +178,12 @@ EFI_STATUS EFIAPI LinuxLoaderEntry(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABL
        Status = GetKeyPress(&KeyPressed);
        if (Status == EFI_SUCCESS)
        {
-               if (KeyPressed == SCAN_DOWN)
-                       BootIntoFastboot = TRUE;
                if (KeyPressed == SCAN_UP)
+                       BootIntoFastboot = TRUE;
+               if (KeyPressed == SCAN_DOWN)
                        BootIntoRecovery = TRUE;
-               if (KeyPressed == SCAN_ESC)
-                       RebootDevice(EMERGENCY_DLOAD);
+//             if (KeyPressed == SCAN_ESC)
+//                     RebootDevice(EMERGENCY_DLOAD);
        }
        else if (Status == EFI_DEVICE_ERROR)
        {


其他产品也请自己查看相应的 abbot.c 代码。如果不确定这个文件的路径的话,甚至可以先在 lk 的代码目录下先 grep -i recovery 然后再仔细搜索慢慢缩小范围。

5.47 关于持续集成

目前我们的持续集成方案比较简单但是有效。

  1. 每次更新代码、进行编译,如果成功,保存当前代码版本记录

    这里的编译都是在上一次的编译结果之上编译,不会清除整个 out 目录。这样做是为了提升持续集成的性能,每次都 clean 编译的话,花费时间太长,不能有效快速的捕捉到错误。

  2. 如果失败,如果是第一次失败,则直接给相关同事发邮件。

    相关同事的确定方法是把当前代码跟上次成功的代码版本进行对比,中间所有提交了新代码的同事,错误一般就是这些同事中的某一位或几位的代码提交导致的。

    但这种做法有时候可能会有误报,所以我们在邮件中会告知这是第一次出错(未清除 out 目录),并且后续还有一个措施:

  3. 如果是连续多次失败,从第二次失败开始,我们会先不清 out 目录编一次,如果成功,则按处理,并且给相关有代码提交的同事发一封邮件,告知持续集成错误已解。

    如果失败,则将 out 目录整个清除,再重新编译一遍,如果仍然失败,则继续给所有有代码提交的同事发邮件,并且告知这是第几次、清除了 out 目录后仍然出错。

    如果清了 out 重新编译成功,则按处理,并且给相关有代码提交的同事发一封邮件,告知持续集成错误已解。

上面提到编译出错有时可能是误报,关于误报的原因,请参考 faq: build error

5.48 关于 user 版本的常见问题和调试方法

最终出货版本一定是 user 版本,在这个版本上,针对安全性和性能做了最严格的控制,所以会给调试带来一些不便,以下针对这些不便带来的常见问题,做一简单分析和总结。

  1. 我们的 user build,自带了一个 user root 版本,如果调试问题时有一些简单的权限限制需要绕开,请考虑使用 user root 版本。
  2. 上层 APK 应用调试,在 user 版本上非常不便,主要体现在这两个安卓编译配置相关的问题上:
    • Proguard 混淆相关的设置,可能会导致 user 版本完全不可用。这个不可用,又可以分为两种情况,一种是编译出错(参考 编译错误相关 faq 的说明),另一种是运行时出错。
      • Proguard 的混淆,会把一些函数的名字变短,比如 callHelloWorld(),它可能直接给改成 a(),然后如果你的类里本来就已经定义过一个名为 a() 的函数,安卓 Dex 编译检查的时候就会发现这个问题并导致编译出错。
      • 比如一些 api,在 proguard 处理的时候,它认为你不会用到这些函数,就整个都给拿掉了,然后到运行时一调用就出错了(比如可能会通过 Java 的反射机制查询某个函数接口再调用,这种情况下 Proguard 通过静态代码分析无法判断出那个函数实际被调用的场所;你在 proguard.cfg 里也没有配置过要保留这个接口,于是 Proguard 认为它是可以被删除的)。
  • User build 出来的 apk,无法像 userdebug 那样直接 push 到手机上或 adb install 装上去后进行调试 —— 会直接崩溃。

    因为配置了 odex 优化的关系,是预编译好的二进制 native 代码,已经不是“编译一次,到处运行”的 java 虚拟机代码了,而是跟当时编译的 frameworks 等系统库文件紧密耦合的,所以在 userdebug 下大家可以用 CM 出的 daily build 版本直接调试,但在 user 版本下,大家用 CM 编的刷机包,即使 root 了以后,也是无法把自己的 user build apk push 上去调试的,很大概率这个 apk 根本无法启动,因为它跟 CM 的 frameworks 不是一起编译出来的,不匹配。

5.48.1 User 版本 apk 问题调试方法建议

根据上面所说的 user 版本调试的两个问题,下面讲一下 user 版本 apk 相关的调试策略,主要是如下 3 点:

  1. 你必须自己编译一个完整的刷机包进行调试。

    上面已经说了,CM 编译出来的 Daily 版本是无法用于 user 版本 apk 相关的调试的,必须在自己的机器上编译才可以。刷机包的编译方法在 sse build 命令里有一个菜单。

  2. 可以考虑暂时先关闭 Proguard 混淆,具体的方法请上网搜索一下或用 abc-x grep 搜一下安卓源代码。

    (我觉得应该是在 Android.mk 里加一句 LOCAL_PROGUARD_ENABLED := disabled 就可以了,请你自己再确认一下或试一下)

  3. 可以考虑暂时先关闭 odex 优化,具体的方法也请自己搜一下。

    (我觉得应该是在 Android.mk 里加一句 LOCAL_DEX_PREOPT := false 就可以了,请你自己再确认一下或试一下)

注意上面 2、3 两个步骤属于临时方案,适用于要快速给出一个临时解决方案,不影响项目进度(项目组有时候会认定因为一个 Apk 导致手机不能发出去 user trial 等等)的情况。通过使用这种方法,给自己找好这个时间缓冲之后,再慢慢把所有优化选项调好。

欢迎有经验的同事在此分享你的经验(这是一个 git 管理的文档项目,clone 命令:git clone ssh://gerrit.smartisan.cn/baohaojun/android-documents)。

5.49 关于整理 Patch 的讨论

最近大家开始关注 Patch 的整理的问题,下面有一封以前发过的邮件,大家可以参考一下。这里主要讲一下一些实际操作的问题。

  1. 先用 git rebase --autosquash 命令,在当前的主线上把 Patch 重新整理到上游(谷歌或高通)的基线上。

    举例:比如当前的主线 osborn-rom,我们先用 git clone、repo sync 等方法同步到最新代码(也可以考虑取成今天 Daily Build 并验证通过的版本),然后,通过 git log 命令并搜索第一个不是由我司同事提交的 Patch(Author、Committer 均不是我司邮箱),简单确认一下这个就是我们的主线最早的时候拉出来的基线版本,记一下其版本号(也就是其 git commit hash)为 BASE_REVISION,然后运行:git rebase --autosquash $BASE_REVISION -i。上面这个命令运行的时候,会先调用你的编辑器给你开一个文件看一下,具体要做哪些操作。

  2. 然后,用 repo-cherry-find-all 和 repo-cherry-pick-all 命令,压缩一下所有的 Revert Patch,具体的用法,请参考 这篇公司内部博客。另外也可以看一下 repo-cherry-pick-all --help 输出的帮助,我加了个 --squash-reverts 选项,想取消 Revert Patches 的话必须加上这个选项才可以。
  3. 最后,考虑用手工整理 Patch。

    这一步是工作量最大的,也是最考量负责整理 Patch 的工程师的水平的,因此如果不知道这一步怎么操作的话,一定要谨慎。实际操作的时候也需要自己多总结,不断迭代、试错,看能不能找到一种更好的方法。

    如果要合并多个 Patch 的话,可以先多次 git cherry-pick,然后 git reset --soft 到第一个 Cherry 过来的 Patch 上,然后再 git commit --amend

    如果要拆分当前的 Patch 的话,可能要先 git reset HEAD^,然后再用 git add -i

    操作的过程中要注意最后不要漏 Patch(参考邮件中关于 Patch 整理质量标准的讨论)。

下面是当时群发的邮件。

5.49.1 Re: 答复: 转发: Trident 项目代码学习和 porting 计划 review

关于这个工作的重要性、可操作性,我以前有过一些总结、分析。(参考我的博客 大公司、小公司、自由开源软件社区的代码管理)。

简单的说,我们的工作跟 Debian、Ubuntu 社区负责打包、发布的工作非常像,项目上游不停的更新,我们自己也会有开发,然后不停的要 rebase 到上游最新版本上。在社区里面这样的下游的开发最后会形成一个 Patch List,维护者的工作很多时候就是要搞好这个 Patch List。Patch List 的质量松松跨跨,最后肯定会越来越累。

当然,有一些非常特殊的不一样地方就是

  1. 我们的 Patch List 会变得超级大,即使是 Debian 社区的 Kernel 维护者也没有维护像我们这么大的 Patch List
  2. 我们的 Patch 很少会提到上游社区去。在开源社区里,一旦一个下游的 Patch 被上游接受了,下游的工作量是会变小的,他们的 Patch List 可以变短一点儿。

综上,再说一下整理 Patch 时几个步骤:

  1. 无脑取消互相 Revert 的 Patch。

    如果一个 Patch 后来被 Revert 了,那么整理过后的 Patch List 里显然不应该包含这两个成对的 Patch。

    这里有一个比较简便的做法,就是通过 system-config 的 repo-cherry-find-all 和 repo-cherry-pick-all,我最近有更新,可以把 frameworks/base 下接近一百个相互 Revert 的 Patch,全部砍掉。并且砍的过程中几乎一次都没有发生过冲突(因为我有提前判断过,两个 Patch 是否可以“完美”的相互 Revert)。

    之前大家的 Rebase 工作里,如果也是用我的这系列脚本的话,事实上是被我坑了。在 Rebase 的过程中,怎么讲,是不应该去做整理 Patch 的工作的,后面我还会再解释一下。

  2. 无脑取消 fixup! 的 Patch。

    之前我们有发现 Git 支持一个 –fixup 的参数,主要目的就是为了后续可以精简 Patch List。

  3. 合并逻辑上有关联的、开发过程中没有处理好的几个 Patch,把它们压缩成一个。

    这个工作就必须由各个部门的最牛逼的工程师来完成了。

最后,讲一下这个整理 Patch 的工作的时机和标准问题。

前面已经有讲到,以前我的 repo-cherry-find-all 和 repo-cherry-pick-all 脚本,会在 Rebase 的时候暗示工程师把相互 Revert 的 Patch 给取消掉,这样是会给大家添乱的做法。

整理 Patch 和 Rebase 应该分开来做。Rebase 的时候所有的 Team(包括 PM)都在盯着整个进度,一旦出一个错(比如漏了一个 Patch)之类的,最后发现搞错了都是很不好调试(比如不开机了、运行不正常了,等等等等),并且压力非常大,所以这个过程中应该尽量减少各种花哨的技术动作。

除此之外,其他任何时间都可以整理 Patch,主要看工程师的心情。

整理的过程中,表面上的质量标准非常简单,整理前和整理后,是不是一个字符的 git diff 差异也没有?这种情况下,几乎不会带来项目上的短期风险(编译版本什么的完全不受影响)。如果有一个字符的 git diff,那工程师必须给出理由(比如他在整理的过程中发现了一个逻辑上的错误之类的),然后让 Leader 们 Review(事实上,这也是应该分开的两个操作,fix error 不属于整理 Patch)。

深层次的质量标准,那就是 Patch List 的质量是不是真正的提高了。Patch 们的逻辑是不是更合理了,下次 Rebase 是不是能减少冲突了。等等等等,这个只能看工程师的修养了。

5.50 如何自己修改 Android.mk 编译脚本

有同事发邮件问道:

我现在有个自己写的 jni 想随着系统编译,并且希望编译好以后能出现在 system/lib 和 system/lib64 下面,我应该在 Android.mk 里面做什么特殊设置么?

这个问题问得非常有代表性。很多时候像这样的问题,通过 CM 提供的安卓全局源代码搜索工具,很容易自己找到解决方法。比如这里的 jni 编出来的 .so 文件,可以拿个手机过来 adb 连上去在那两个目录下找几个例子,比如 libtiff.so,在两个目录下都有,然后回到安卓源码目录下,用 abc-x grep libtiff|grep Android.mk,很容易搜到在哪个目录下有可以直接抄的例子。如果觉得一个例子不够的话,也可以多搜几个对照一下。甚至可以找一些只在 /system/lib 下有,在 /system/lib64 下没有的 .so 文件,用 abc-x grep 搜搜它们的 Android.mk 文件写法有什么特殊的地方。

如果对 Makefile 的语法不熟悉的话,Linux 下直接运行 info make 就可以查询到最详细的官方文档。我自己的话一般在 Emacs 下打开 info 手册,因为查起东西来更方便,想查什么话题的话,它会自动帮你匹配到你刚输入的文字,如图:

info-make-search.png

如果想研究、阅读除了安卓代码之外的其他代码,abc-x grep 就不能用了,因为我们只针对公司的产品的主线分支提供了代码索引。但是,我在 system-config 项目里打包了另一个非常有用的工具,beagrep,abc-x grep 就是针对 beagrep 的一个简单的封装而已,你在自己的代码目录下运行一下 for-code-reading 命令,就可以用 beagrep -e "hello world" 搜索 hello world 了。

另外,上面提到的在手机系统里两个目录下各种查找文件,也可以写一个简单的 shell “one liner”来实现,比如要在 /system/lib 下查找在这个目录下有,但在 /system/lib64 目录下没有的文件,只需在手机 adb 命令行下运行:

cd /system/lib; for x in *.so; do if test ! -e ../lib64/$x; then echo $x; fi; done

如果想查找在两个目录下都存在的文件,只要把上面的 ! 去掉就可以了:

cd /system/lib; for x in *.so; do if test -e ../lib64/$x; then echo $x; fi; done

对 shell 不熟的话,可以在 Linux 下装一下 bash-doc(sudo apt-get install bash-doc),然后用 info bash 命令查看其 info 手册。手机上的 shell 虽然不是 bash,但是其语法跟 bash 非常相似,基本都能用上(Shell 是一个 Posix 标准)。

5.51 不常见问题

5.51.1 刚入职时无法同步代码

偶尔有同事入职后,发现配置环境的过程中出错:

hi,我是新入职的安卓开发,在 git 安装和配置时,报错:即将从 gerrit 服务器获取 jdk6.
    请按‘回车’继续..
Cloning into '/home/zhouzhiqiang/external/bin/Linux/ext/jdk'...
fatal: Project not found: tools/jdk6
fatal: The remote end hung up unexpectedly

其原因一般是公司 IT(韩栋梁)尚未将其用户加入相应的 ldap 组,从而导致 gerrit 上没有权限。

解决办法是先联系韩栋梁将其加入相应的组,然后访问一下这个链接 清除 Gerrit 缓存 (需要先登录 Jenkins 系统,用户名、密码跟 Gerrit 是一样的,都是公司邮箱名前缀和公司域密码)。

5.51.2 gerrit-push-review 出错:invalid committer

大家第一次登录、使用 gerrit,会获得一个默认的邮箱,一般情况下是不需要自己设置的,因为 IT 部门的同事已经帮你设置好了。

但有时候 IT 的同事也会有笔误,比如金明这里,他的邮箱被设置成了: mail: liujinming@smartian.com ,注意是 smartian,不是 smartisan,少了个 s。

这种情况下,可能找 IT 帮忙在 ldap 系统里更正已经没用了,因为邮箱已经从 ldap 里读到了 gerrit 数据库里。但是,大家如果碰到的话(一辈子最多碰到一次),请自己到 https://review.smartisan.cn:8080/#/settings/contact 和 https://odm.smartisan.cn:8080/#/settings/contact 这两个页面添加正确的邮箱,并把 Preferred Emal 也改成正确的那个。错误的那个让他继续留着不用理会就好了。

(附出错邮件如下):

在 2016 年 08 月 11 日 15:47, liujinming 写道:
> 你好!使用 gerrit-push-review 时出错:
>
> { liujinming@liujinming-pc
> /home/liujinming/src/android-bono-dev/bootable/recovery [S10-user] }
> $gerrit-push-review
> 根据 repo manifest.xml 计算出你的 remote 是 odm ,远程分支是 fih/bono-rom,
> 确认? Yes/no: yes
> git fetching remote: odm, branch: fih/bono-rom
> 来自 ssh://odm.smartisan.cn/platform/bootable/recovery
>  * branch            fih/bono-rom -> FETCH_HEAD
> git fetch done
> 在请别人 review 之前,要不要先自己本地 review 一下(加 -R 参数可以跳过此提示)
> Yes/no: no
> git push ssh://odm.smartisan.cn/platform/bootable/recovery
> HEAD:refs/for/fih/bono-rom
> remote:
> remote:
> remote: Processing changes: refs: 1
> remote: Processing changes: refs: 1, done
> remote:
> remote: ERROR:  In commit 628e1ae1cfcce90a7c5e12ff59433f37f8efb398
> remote: ERROR:  committer email address liujinming@smartisan.com
> remote: ERROR:  does not match your user account.
> remote: ERROR:
> remote: ERROR:  The following addresses are currently registered:
> remote: ERROR:    liujinming@smartian.com
> remote: ERROR:
> remote: ERROR:  To register an email address, please visit:
> remote: ERROR: https://odm.smartisan.cn:8080/#/settings/contact
> remote:
> remote:
> To ssh://odm.smartisan.cn/platform/bootable/recovery
>  ! [remote rejected] HEAD -> refs/for/fih/bono-rom (invalid committer)
> error: 无法推送一些引用到
> 'ssh://odm.smartisan.cn/platform/bootable/recovery'
>
> 请问如何解决?
> 谢谢!

5.51.3 如何处理 device/qcom/u3_common 仓储的基线升级操作

:ID: cf02f4aa-d990-40f5-b103-cdfdda64ce47

安卓的 device/qcom/u3_common 仓储,是从高通提供的 device/qcom/msm8953_64 仓储里抄过来的,所以每次高通基线升级的时候,这个仓储都是要跟着升级的,但升级的方法比较“不一样”了,并有其他仓库那么直观。

首先, u3_common 仓储创建的时候,我们有两种做法:

  • 把 msm8953_64 的文件拷贝过来,修改(包括文件名改名),提交(整个仓储这条分支上的第一个或者第二个提交)。
  • 把 msm8953_64 对应的分支整个 clone 过来,分支改名,然后修改、提交。

如果最初采用的方法是第 1 种的话,后续的升级操作就非常麻烦了,基本上相当于手动打 patch,甚至比手动打 patch 还麻烦,相当于手动移植高通的改动。git 提供的一些高级辅助工具完全用不上,生产力大大下降。

幸运的是,即使一开始采用的是第 1 种做法,后续还是可以给它修改成第 2 种。方法如下:

# 先找到高通对应的 base 提交,记为 GIT-COMMIT-HASH
# 然后在 device/qcom/msm8953_64 下将其拉出一条分支,记为 qualcomm-old-base
cd device/qcom/msm8953_64
git branch qualcomm-old-base GIT-COMMIT-HASH

# 回到 u3_common 目录,从 ../msm8953_64 目录拉来 qualcomm-old-base 分支
cd ../u3_common
git fetch ../msm8953_64 qualcomm-old-base:qualcomm-old-base

# 找到我们在 u3_common 下的第一个(或第二个提交,如果第一个提交是个空
# 提交的话),记为 FIRST-PATCH-COMMIT-HASH git checkout 这个提交后,
# reset --soft 到 qualcomm-old-base
git checkout FIRST-PATCH-COMMIT-HASH
git reset --soft qualcomm-old-base

# 把 FIRST-PATCH-COMMIT-HASH 到当前服务器上最新的提交之间的所有 patch,都
# cherry-pick 到当前的分支上,注意顺序要反过来

for x in $(git log --pretty=%H FIRST-PATCH-COMMIT-HASH..$(repo-remote-branch)|reverse); do
    git cherry-pick $x
done

#这样打完的 patch,按理说中间不应该出任何冲突,并且打完以后,与服务器上
#的分支没有任何代码改动,可以用 git diff 来确认一下,没有任何输出就对了,
#有输出就有问题,得查一下:

git diff $(repo-remote-branch)

这样操作完成之后,就相当于我们一开始就采用了第 2 种做法,我们与高通基线之间,有了共同的历史记录,后续升级的话,就非常方便了。

注意上面提供的命令仅供参考,可能会有错误,请正确的理解之后,自己多试一下。

5.52 Userdebug 版本,如何跳过开机向导(激活页面)?

adb shell am start -n com.smartisanos.setupwizard/.SetupWizardCompleteActivity

5.53 常用高通网站

  1. Grep http.*qualcomm 本 git 文档项目下的 ./../cm-ops.html
  2. 看 Release Notes:

https://createpoint.qti.qualcomm.com/dashboard/#productkitToc/402

5.54 为什么不能直接 git push review 了

注意在成都的同事无法直接使用这个命令,因为他们的代码是从一个只读的镜像服务器上下载的,如果直接 push 的话,会出类似这样的错误:

fatal: No argument is allowed: /qualcomm/platform/frameworks/base
fatal: 无法读取远程仓库。

请确认您有正确的访问权限并且仓库存在。

以后即使北京的同事,也可能用负载均衡服务器,也会碰到同样的问题。

这种情况下,如果还是希望通过自己直接 git push REMOTE HEAD:refs/for/BRANCH 的方式来提交 review,而不是使用 gerrit-push-review 命令,那么,你需要直接指定远程 push 的 url,或者用 git config 先配置一下 git remote 的 pushurl:

以前

git push REMOTE-NAME(此例中应为 smartisan) HEAD:refs/for/BRANCH

现在

git push REMOTE-REVIEW-URL(此例中应为 ssh://review.smartisan.cn/qualcomm/platform/build) HEAD:refs/for/BRANCH

也可以自己配一下 git config remote.REMOTE-NAME.pushurl,这样就可以继续用以前的 push 方法,做法如下:

  1. 先通过 git remote -v 命令查看一下各个 remote 的 url

    remote url 的 host 名字一般是 smartisan 或者 gerrit.smartisan.cn(取决于你 repo init 的时候用了 -u ssh://smartisan/ 还是 -u ssh://gerrit.smartisan.cn/),然后

  2. 把它改成 review.smartisan.cn,其余部分保持不变就可以。

    比如在我的 android/build 目录下:

    git-pushurl.png

5.55 在用 Smart Builder 加 Patch 编译的时候,想切分支编译,该如何处理?

各位工程师、PM 在使用 Smart Builder 编译时,有时有非常特殊的需求,需要把某几个仓库完全切换到另一条分支上再进行编译,甚至要先下载一个全新的、在原来的 manifest.xml 里没有的仓库再编译。

在处理这种编译需求的时候,没法直接在自己的仓库上加 Patch,而是需要在 CM 编译之前的处理脚本里加,以完成切换分支、下载新仓库的任务。具体的做法,请参考这个 Patch:https://review.smartisan.cn:8080/#/c/286893/

后续类似的需求的时候,请工程师自己按照此编译脚本 Patch,自己修改生成新的 Patch,再添加到 Smart Builder 里提交编译。

并请注意这些编译脚本的修改,CM 会定期进行 Review,但不是在工程师提交编译之前进行 Review,因为这样的工作流程太过僵化,只需要工程师自己一个人就可以完成的工作,必须要求 CM 在规定的时间掺和进来的话,是对工作流程(flow 状态)的一种干扰,影响工作效率。

5.56 怎么配置 odm 项目的 gerrit 服务器和下载 oem-release 代码

公司的所有 odm 项目,都会利用一个对外的 gerrit 服务器,跟我们内部项目区分开,各位同事需要在 odm 项目上工作并下载 oem-release 仓库的话,需要自己配置一下相关的服务器信息:

  1. 在自己的 ~/.ssh/config 文件里添加如下内容:

    Host smartisan-oem so odm.smartisan.cn
        Port 29418
        User baohaojun(改成你自己的邮箱前缀)
        IdentityFile ~/.ssh/id_rsa
        Hostname 172.16.21.226
    
    
  2. 在自己的 /etc/hosts 文件里添加如下一行:

    172.16.21.226 gerrit-oem odm.smartisan.cn
    
  3. 访问 https://odm.smartisan.cn:8080/#/settings/ssh-keys,添加一下自己的 ssh 公钥

    需要登录,输入公司域账号——即邮箱前缀、密码

  4. 重新 repo init 一下,在原来的 -g all,-notdefault,ADD-GROUPS 之后再加上一个 oem 的分组。

    比如原来你是 app2 的分组,那你在 repo init 的时候使用的 -g 参数应该是:-g all,-notdefualt,sos-app2,现在需要把它改为 -g all,-notdefualt,sos-app2,oem

    如果你之前没有指定过 -g 参数,那你使用的其实是默认的 -g all,-notdefault

    然后再运行 repo sync -dc oem-release 就能下载到 oem-release 的代码。

5.57 Gerrit 提交要 review 的 patch 时提示没有 Change-Id 或格式不正确

这种错误一般出现在:

  1. 自己用 git clone 命令同步下来的代码

    解决的方法是先运行一下 system-config 的 gerrit-fix-hooks 命令,然后用 git commit --amend 命令重新 commit 一次,这样 git 就会自动帮你生成 change-id。

  2. git cherry-pick 命令生成的 patch,过程中解决过冲突

    这种情况下,有时候 git commit 的 msg 里,最后会有 Conflicts: FILE1、FILE2 这样的一段文字,而 Gerrit 的 change-id 要求必须出现在最后一段文字。

    解决的方法是重新提交:git commit --amend,然后把 Change-Id: 手动移到最后一段文字。

5.58 我的提交被编进去了吗?(如何确定 CM 编译的某个版本里是否包含我的改动)

可以通过刷机包目录下的代码快照 manifest.xml 文件来完成。参考 如何对齐 CM 编译出来的版本代码以及如何重编该版本

具体做法如下:

  1. CM 出的每个刷机包目录下都有一个 manifest.xml,是编译时的安卓代码快照(如果是高通 OEM 代码,有一个对应的 oem-manifest.xml)。
  2. 打开这个 manifest.xml,找到自己的改动对应的仓库,再找到这个仓库当时使用的 git commit id(记为 $build_commit_id
  3. 到自己的代码目录下,运行 git log $build_commit_id,然后通过查看历史记录里是否包含你的 commit_id 或 change_id 来确认是否已经包含你自己的改动(比如:git log $build_commit_id | grep $my_change_id)。

    注意:如果你的当前代码目录里还没有包含 $build_commit_id 的话,需要先运行这个命令把该提交从服务器上同步到本地:git fetch $(repo-remote) $build_commit_id,然后再执行一遍上面的查找操作。

  4. 注意某些由 CM 负责自动同步 patch 的仓库、版本,根据不同的同步策略,有时候一个 Patch 进入主线后,需要等到第 2、3 天甚至第 4、5 天才能编译出版本。比如你周五在主线进一个 patch 的话,因为周六、周日自动同步 patch 出错不一定能找到人来解决,所以 CM 会在下周一才合入这个 patch,然后在周二才能编出包含你的 patch 的版本。

5.59 外地同事配置 system-config、下载代码时的网络问题

外地同事(主要是上海团队)多次碰到无法同步代码的问题,每次都发现是其网卡的 MTU 设置过高,VPN 数据不通导致。其中有些完全无法用 ssh 命令连接 gerrit.smartisan.cn,也有些能连 ssh,但 git 克隆一个稍大点的仓库的时候就会返回“Broken pipe. The remote end hung up unexpectedly”。

修改的方法是:

  1. 用 ifconfig 命令查一下自己网卡的名字、MTU 值

    { bhj@baohaojun /home/bhj }
    $ifconfig
    eno1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
            inet 172.16.13.195  netmask 255.255.252.0  broadcast 172.16.15.255
            inet6 fe80::31:ccef:2a90:fdf5  prefixlen 64  scopeid 0x20<link>
            ether 48:0f:cf:53:51:68  txqueuelen 1000  (イーサネット)
            RX packets 24533004  bytes 32155924190 (29.9 GiB)
            RX errors 0  dropped 16  overruns 0  frame 0
            TX packets 9270067  bytes 6840351627 (6.3 GiB)
            TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
            device interrupt 16  memory 0xd1100000-d1120000
    
    
  2. 用 sudo ifconfig IFNAME mtu 1420 命令修改一下自己网卡的 mtu 值,其中: IFNAME 是上面的命令查出来的网卡名字(上例中是 eno1),1420 这个值仅供参考,也有可能还需要调得更低,请参考下面的邮件。

    查清楚了,应该,是你们的 VPN 网络导致 MTU 设置过高,之前跟其他公司合作刚碰到过的一个问题。

    请在自己的机器上执行一下:

    sudo ifconfig eth0 mtu 1300

    然后就可以访问 gerrit 了。我在 chengqiang 的机器上试了,可以。1300 这个参数可以自己多试几次,默认的值是 1500,比如你们可以用二分法选一下 1400 行不行,如果不行的话选 1350 再试;行的话选 1450 再试。试的话在程强的机器上用 ssh gerrit.smartisan.cn 命令就可以,能连通的话会打印“ ** Welcome to Gerrit Code Review **”。

    最后选一个最合适的值对网络带宽会比较好。我这边一改就会导致两分钟内连不上你们的机器,所以你们自己再试一下吧。

  3. 建议将上面的改动放到自己的开机脚本里去,具体可以上网查一下 Linux 怎么修改开机脚本,一般只是把那个命令放到 /etc/rc.local 文件里即可。

5.60 adb、fastboot 常见问题

之前有一位 bsp 同事碰到一些跟 adb、fastboot 相关的问题,在此记录一下。

5.60.1 版本太低、多个版本造成混乱

安卓的不同 adb 版本之间存在兼容性问题,在系统里最好要保证只使用一个版本的 adb,不要使用多个(会导致 adb server 不停的被杀掉然后重启)

system-config 里提供了从安卓官方下载的 adb,并且运行过程中如果发现有多个版本,会打印一些信息提醒。

5.60.2 adb usb 设备没有权限

很多同学因为这个问题养成了用 sudo 启动 adb 的习惯,应该尽量避免这样做,sudo 是一个很危险的命令,不小心的话很容易弄坏自己的机器导致不得不重装系统。

解决的方法从网上也能找到,我在 system-config 里封装了一下,写成了一条脚本,叫 fix-usb-permission,运行时这个脚本会问“请选择你要把上面哪个 usb 设备设置成你自己的 uid/gid(设置完成后记得重新插拔一下)”。

5.60.3 fastboot 刷机时不停出错、adb 不停重启

这种情况一下发生在板子调试的研发早期阶段,一块板子连着电源进行调试的过程中比较容易发现。

这种情况下因为板子的设计、电源连接等可能还有一些小问题,会导致 usb 供电不稳,常有发现在某台 PC 上能正常连接,换一台 PC 就不停出错的情况。

一般加一个带电源的 USB Hub 能缓解这种问题(感谢某 BSP 同事与我分享这个问题最后是如何被解决的,之前负责 Camera Tuning 的一个同事也有碰到过类似的问题,一口咬定是我的 system-config 提供的 adb 有问题,要我负责)。

更具体的此类 USB 问题请咨询 BSP 系统组的同事。

5.61 应用自升级方案说明

目前我们经常碰到手机新系统版本发布之后还有 Bug 的情况,有时候这些 Bug 可能只是某个应用的,这种情况下我们如果专门再出一个手机系统版本通过 ota 升级来解决这个问题的话,成本会非常高,主要表现在:

  1. 内部的人力成本
  2. 升级过于频繁对用户、口碑造成的负面影响

注意这种 ota 版本出得越多,成本会出现飞快的增长,它不是线性的(至少是Θ(N**2))。

所以这种情况下,我们必须对应用 APK 进行自升级,以降低这些成本。

这里对这个自升级的编译系统做一个说明,目前整个流程还没有 100% 完善,因为它非常复杂,涉及到很多部门,所以请大家对此多提意见与建议。

5.61.1 自升级流程测试方法

在真正使用自升级之前,我们必须确保整个流程没有问题,所以需要提前做一个测试。即便你的 APP 完全没有问题,也要提前跑一下这个测试,模拟一下最后真正的场景。

在模拟的时候,我们会假设某个版本(记为 SOS-Vx)外发之后,收到某个 App(记为 App-x) 有 Bug 的反馈。这种情况下,App-x 的研发同事需要:

  1. 提交 N >= 1 个 Patch 修复这个 Bug
  2. 更新 App-x 的版本号(versionCode),必须大于 SOS-Vx 里 App-x 的 versionCode,这样才能升级成功。

但是因为当前是在测试流程,并没有真正的 Bug 需要修复,所以上面的步骤 1 可以省略。

因此,测试流程是:

  1. 测试同事确定要在哪个手机版本上进行测试(请测试同事提供版本信息,比如刷机包名字)

    一般建议用 mol 线的版本。

  2. 测试同事确定哪些 App 要参与此轮测试,并通知到每个 App 的研发同事,通知时要提供刷机包版本信息
  3. App 研发同事根据刷机包版本信息,生成一个修改该 App versionCode 的 Patch,提交到 Gerrit 上,请 Leader Review +2 并 Abandon。

    Review +2 是为了防止不小心提交了“脏”代码,导致服务器环境被损坏,甚至造成更严重的问题,所以必须请 Leader 把关。

    Abandon 是为了怕这样的测试代码被不小心提交到代码线上。

  4. App 作者自己到 http://172.16.2.18:18080/ 这个网页上提交 App 的编译。

    具体操作方法请参考 这个之前写的说明

    • 编译时需要填写两个额外的信息:
      1. 测试同事要使用的刷机包版本
      2. App 修改版本号(versionCode)的 patch
    • 建议提供一些额外的信息,这样流程可以更顺畅
      1. 相关测试同事的邮箱,方便版本编译完成后发通知邮件
      2. 编译任务的简单描述
  5. 编译完成后,测试同事到通知邮件里指定的共享目录下获取该 App 的 apk 文件,通过 adb install 装到手机上,验证其 versionCode 是否已增加,并验证有无其他问题。

    (该目录下可能还有其他文件,直接忽略即可。)

5.61.2 实际操作流程

在实际生产环境中,如果不幸发生了用户手机上的 Smartisan App 有严重问题,必须通过 App 自升级的手段来解决的话,注意在我们之前的测试流程的基础上,还需要确保如下关键点:

  1. App 同事需根据用户的系统版本提供针对该版本解决 App 问题的 Patch。

    这样才能确保此 Patch 提到服务器上编译时可以正确的被合入。如果只是在主线上提了个 Patch,在合入外发版本线时可能会产生冲突,必须由 App 同事解决。

  2. 如果有多个产品的版本都需要解决此 App 问题,建议需要针对每个版本的刷机包提交编译。目前这个问题不知道有没有办法可以优化,所以只能提出最笨的解决方案。这里有几个维度需要考虑:

    • 一个产品的 N 个版本要解,比如 osborn 4.1.3 要解;osborn 4.1.2 也要解。该如何处理?
    • 一个产品的 N 个配置要解,比如 osborn 4.1.3;osborn_cmcc 4.1.3(移动定制版)
    • N 个产品要解,比如 osborn 4.1.3;odin 4.1.3
    • 以上各种情况的组合。

    我们能用一个 .apk 文件推给所有用户吗?这个问题需要大家考虑一下。

  3. 使用过一次此方案之后,需确保所有产品的代码线上该 App 的 versionCode,都被正确的更新。

    否则的话可能会出现这种场景:

    1. osborn 4.1.3 AppX 有严重 Bug,其 versionCode 是 288
    2. AppX 解决了此 Bug,versionCode 升为 388,并单独推送给用户
    3. osborn 4.1.4 发布,AppX 虽然也已经解决了之前的 Bug,并且还加入了很多新的功能,但它的 versionCode 还是 288,没有被正确的更新过
    4. 用户 ota 升级到 4.1.4,这时正确的情况应该使用 4.1.4 里带的 AppX-288/4.1.4,但因为其版本号低于之前单独推送的 AppX-388,所以导致系统继续使用 AppX-388。

    这种情况下,问题就变得更严重了。这种情况下,问题就变得更严重了。运气好一点用户还能继续使用这个 App,只有没法使用新的功能;运气差一点的话,这个 App 可能要不停闪退;再差一点的话,可能直接导致用户提出退机了。

  4. 编译出来的 App 测试完确认没有问题之后,再交付给应用商店、云平台等相关部门的运维同事,推送给客户。

5.61.3 应用自升级的编译方法

我们在系统版本发布之后,有时发现有些 APK 有些 Bug,但又不好再马上发一个系统版本来修正(升级太频繁会给用户造成较大困扰),而是希望通过升级这个 APK 本身的方法来解决。

工程师或测试同事需要自升级 APK 包的时候,需要通过 CM 提供的 Smart Builder 编译一个 APK 版本,具体的方法如下:

  1. 访问 http://172.16.2.18:18080/
  2. 输入自己的邮箱、密码
  3. 根据自己需要选产品,比如 odin 或 osborn
  4. 选分支,比如 dev 还是 mol-4.1(一般这个按照这个自升级 APK 需要针对哪个系统版本来进行选择)。
  5. 选设备,按照实际需求来选
  6. 选编译类型,一般肯定选 SEKSA+user,因为是针对外发版本的 APK 自升级。
  7. 不需要 OTA
  8. 重编某版本:请输入该 APK 所针对要修复的系统版本对应的刷机包目录名(一般由测试同事提供)
  9. 安卓 Patch:请输入修复该 APK 的 Bug 所需要的 patch 在 gerrit review 网站上的网址
    • 如果只是在测试自升级,这里也可以只加一个修改 versionCode 的 Patch。
    • 注意每个 Patch 都要由 Leader Review +2。如果只是在测试自升级功能,可以考虑在 Review +2 之后 abandon 这个 Patch,以免不小心被提交进代码线。
    • 注意要确保这个 patch 可以在重编版本的代码上正确合入

      如果是已经提交到对应的 mol 分支(已 submit)的 patch 的话,这个 patch 直接拿来编译的话可能会产生冲突。产生冲突的原因是分支头与重编版本时该仓库代码之间已经提交过其他 patch 导致的。

      不管是什么原因导致的冲突,解决的办法都是基于重编版本的 git commit 号(可从重编版本刷机包目录下的 manifest.xml 里查到,然后运行 git reset –hard COMMIT_ID),自己重新合一遍这个 patch,然后用 git push REMOTE refs/for/BRANCH 命令重新提交 review。

  10. 输入要编译的 APK 包的名字(这个名字也是在 Android.mk 里定义的 LOCAL_PACKAGE 或 LOCAL_MODULE 的名字)

    输入了 APK 包的名字之后,CM 的编译脚本就会打开必要的开关,确保编出来的 APK 文件会:

    • 打包其对应的 .so 库文件(如果有的话),这样只要装完对应的 apk 之后 app 就是可以用的
    • 不做 predex 优化。做过 predex 优化的 apk 包是不能用于安装的,只能跟着当时编译的系统版本走。
  11. 按需输入编译完成后通知人员名单、编译描述。
  12. 点击提交按钮。

编译完成后会给提交人发邮件。

另外注意 T1、T2、U1 因为安卓版本太低,我们 CM 还没有帮大家研究出来能不能实现 APK 的自升级,所以目前还是不支持自升级的编译的。

5.62 Leader 自助为自己组里同事申请 Gerrit 代码权限的方法

以前 Leader 为自己组里同事申请 Gerrit 代码分组权限都是通过给 CM 发邮件来操作,这种操作效率非常低,因此 CM 给大家开发了自助分配 Gerrit 分组权限的网页

以后 Leader 们有 Gerrit 权限相关的需求的话,请自己登录一下 jenkins,再启动 这个任务 就可以帮组里同事申请 Gerrit 代码权限了,一般的安卓开发相关的权限需求都可以通过此网页操作(如有特殊需求,请联系 CM)。

以下是一些简单的使用说明。

5.62.1 什么情况下要用到这个网页工具?

  1. 想给组里同事提升 Gerrit 等级,以前不能 review +2、submit,现在水平够了,可以给他/她 review +2、submit 权限了
  2. 这个同事刚入职(或刚内部转岗),最基本的安卓代码下载权限也没有,通过这个网页可以暂时先让他/她可以下载到代码。

    这个问题是同事入职或转岗时,Leader(或 HR)没有跟 IT 老师正确沟通该同事应该属于哪个部门(LDAP 分组)导致的,所以根本解决方法也是给 IT 老师发邮件,让他们把该同事的 ldap 分组设置成你们部门的分组。强烈建议 Leader 一定要跟 IT 老师沟通解决这个问题,因为后续可能其他地方(不一定与 CM 工作相关)也会用到这个 LDAP 分组信息。

    如果你想知道自己的 LDAP 分组的话,可以登录到 jenkins 后查看这个页面: http://172.16.2.18:8080/user/YOUR-USER-NAME

    如果想知道其他同事的 ldap 分组的话,只能请他自己登录一下 jenkins 再查一下他的用户主页。

5.62.2 我的 Gerrit 权限不对,没法下载安卓代码,怎么办?

联系你的 Leader,请他/她通过上面的网页工具操作一下;并让他联系 IT,修改你所属的部门。

5.63 加速安卓 8.0 工程师编译的方法讨论

自从安卓升级到 8.0 之后,比如 trident 项目,安卓的编译更加费时了。主要是之前我们用 mm 命令,通常能在 20 秒内完成编译,但现在继续用 mm 命令的话,即使什么也没改编译系统也要 1 分钟左右的时候才能运行结束。这严重影响了调试问题时的工作效率。

目前在开发 trident 上的 user root 功能的时候碰到了这个问题,因此希望能做一些优化。

优化的方法和过程如下:

  1. 对编译的输出加时间戳:

    stdbuf -o L mm --only-module init -q -v | ts
    

    主要是希望通过运行时的输出看一下最费时的步骤是哪一步。

    • 上面的 ts 是 debian moreutils 包里的一个工具,可以对每一行输出加时间戳。
    • stdbuf 也是一个标准的 linux 工具,通过它可以让管道(|)命令强制以行为单位进行输出缓存(否则以管道容量缓存的话,可能会导致时间戳不准)
    • 这里的 mm 是 system-config 封装过的,带一个 --only-module 参数只编译 init。如果不封装的话 mm 是安卓 build/envsetup.sh 里的一个 bash 函数,是无法用 stdbuf 运行的。
  2. 通过 system-config 的 ps.tree mm.only 命令观察编译“挂住”的时候的进程树。这里 mm.only 是以 . 分隔的进程命令行匹配文本,如果一个进程启动时的命令既匹配 mm 又匹配 only 就会被打印出它的整个进程树。
  3. 发现最费时的操作是一个叫 ckati 的命令,它每次运行都花很长时间,然后打印出一句话:“No need to regenerate ninja file”。也就是说花了很多时间,但是做了无用功,所以考虑优化它(直接跳过)
  4. 通过 mm -v 打印出编译时使用的命令,发现它会调用 ninja 命令。所以尝试了一下能不能直接调用 ninja 来完成编译。

    最后发现正确的快速重编 init 模块的命令是(从 mm -v 的输出中获取):

    prebuilts/build-tools/linux-x86/bin/ninja -d keepdepfile init -j 64 -f out/combined-trident-system_core_init_Android.mk.ninja -v -w dupbuild=err
    

    通过上面这条命令,如果我改了 init.cpp 的话,只要 4 秒就可以完成重编。

5.63.1 需要注意的问题

  1. 上面的优化方法适用于只改 .cpp、.java 等源码文件的情况。如果改了 Android.mk 等编译配置文件的话,还是需要老老实实用 mm。
  2. 上述直接用 ninja 重编的方法跟 ccache 一起使用的时候,会引发一些非常奇怪的编译警告错误,比如我在重编 system/core/init/init.cpp 的时候就碰到:

    system/core/init/init.cpp:608:123: error: expression result unused [-Werror,-Wunused-value]                                                                    
            ({ __typeof__(close(pipe_fds[0])) _rc; do { _rc = (close(pipe_fds[0])); } while (_rc == -1 && (*__errno()) == 4); _rc; });                             
                                                                                                                              ^~~                                  
    system/core/init/init.cpp:616:123: error: expression result unused [-Werror,-Wunused-value]                                                                    
            ({ __typeof__(close(pipe_fds[1])) _rc; do { _rc = (close(pipe_fds[1])); } while (_rc == -1 && (*__errno()) == 4); _rc; });                             
                                                                                                                              ^~~                                  
    system/core/init/init.cpp:630:123: error: expression result unused [-Werror,-Wunused-value]                                                                    
            ({ __typeof__(close(pipe_fds[1])) _rc; do { _rc = (close(pipe_fds[1])); } while (_rc == -1 && (*__errno()) == 4); _rc; });                             
                                                                                                                              ^~~                                  
    system/core/init/init.cpp:641:125: error: expression result unused [-Werror,-Wunused-value]                                                                    
            ({ __typeof__(close(child_out_fd)) _rc; do { _rc = (close(child_out_fd)); } while (_rc == -1 && (*__errno()) == 4); _rc; });                           
                                                                                                                                ^~~                                
    4 errors generated.                                                                                                                                            
    ninja: build stopped: subcommand failed.                                           
    

    花了很长时间才发现它跟 ccache 有关,具体原因尚未明确,但可以通过在前面加一个环境变量来解决:

    CCACHE_DISABLE=true prebuilts/build-tools/linux-x86/bin/ninja -d keepdepfile init -j 64 -f out/combined-trident-system_core_init_Android.mk.ninja -v -w dupbuild=err
    
    • 2018-06-06 更新: ccache 的问题已经查清原因,是因为 clang 与 ccache 有一些兼容性问题导致。参考 build/make/core/ccache.mk 文件,以及该文件里面提到的 这篇博客

      说明一下查这个问题的方法。

      1. 把问题记下来,以后再查。这很重要,比如这个 faq 是 1 月份写的,现在 6 月份了,我看了一下我的 GTD 列表,决定再搞一下。
      2. 先试了一下 strace,看看能不能用两种方法(即 make 和 ninja)分别运行一下编译,看看进程启动时的参数有什么区别

        无果。用 make 运行的时候,strace 直接挂住了,必须用 Ctrl-C 退出。

      3. 修改了一下 clang++ 命令,在它运行的时候,把它的所有环境变量都打印到一个临时文件里,然后两次不同的编译方式之间进行对比。

        这一对比就对比出差异来了,如下,以“<”开头的是用 make 编过的,“>”开头的则是用 ninja 编出错的,前者多了好几个跟 ccache 相关的变量,然后用 abc-x grep 搜了一下,就搜到上面的那个 .mk 文件了:

        $diff ~/tmp/good.txt ~/tmp/bad.txt|grep ccache -i
        < CCACHE_BASEDIR=/
        < CCACHE_COMPILERCHECK=content
        < CCACHE_CPP2=true
        < CCACHE_SLOPPINESS=time_macros,include_file_mtime,file_macro
        < CC_WRAPPER=prebuilts/misc/linux-x86/ccache/ccache
        < CXX_WRAPPER=prebuilts/misc/linux-x86/ccache/ccache
        > CCACHE_DIR=/home/smartcm/src/android-ocean-dev/.repo/.ccache
        
        

        这个笨办法我在刚入行的时候就开始用了,想研究一个程序是怎么运行的,比如 /usr/bin/rpm,不知道有 strace,就直接把 rpm 重命令为 rpm.bak,然后写一个名为 rpm 的脚本,在里面打印点信息出来,最后再调起 exec rpm.bak "$@"

        这回这个方法没法照搬照抄过来用(1 月份时有试过,碰到问题就放弃了),需要做一点小小的修改。因为像 clang++ 这样的程序,你要是把它改个名,改成 clang++.bak 的话,它的行为是会发生变化的,可能就不工作了(以前某个版本的 g++ 是一个指向 gcc 的软链接,程序在启动的时候会根据自己的 ARGV[0] 来改变默认是以 C 语言还是 C++ 语言进行编译)。

        这回调查这个问题的时候,把 clang++ 的整个仓库目录 OLD-CLANG-DIR 备份到了 NEW-CLANG-DIR,然后把 OLD-CLANG-DIR/bin/clang++ 改成一个这样的脚本:

        #!/bin/bash
        (
            exec >~/tmp/env.txt 2>&1 # 重定向下面执行的命令的 stdout 和 stderr,因为是在()里,不会影响最后真正的 clang++ 调用
            set # 打印所有 bash 变量
        )
        
        exec prebuilts/clang/host/linux-x86/clang-4053586.bak/bin/clang++ "$@"
        
        

5.63.2 总结

给 android-make/mm/mma/mm-adb 封装了一个 -q|--quick 参数,发现有 ninja 文件的话,直接用 ninja(需自己确认不用更新 .ninja 文件),否则出错(android-make)或自动改成老老实实的调用 make(mm/mma/mm-adb)。

加上这个改动之后,以前封装的 android-make kernel、bl(bootloader) 等命令又可以用了: android-make kernel 其实就是 android-make -q bootimage 的简单封装;android-make bl 则是 android-make -q out/target/product/XXX/abl.elf 的简单封装。

最后再强调一下,用这个优化方法之前一定要看一下这个 android-ninja -h 脚本的帮助:

安卓编译的时候,目前已经不再直接使用 makefile 的机制了,而是从
 makefile 出发,封装了一套 ckati/ninja 的并行编译系统,目前是通过把
makefile 翻译成 ninja 然后再用它进行编译。

每次做一个代码小改动,如果还是调用以前的 make 命令的话,系统都会自动检
查 .ninja 是否需要更新。即使发现 .ninja 文件不需要更新,也会有额外的
 30+ 秒的运行开销(如果需要更新 .ninja 文件,时间开销就更大了,一般在
 2 分钟左右,这还是最近刚运行过 make、.mk 文件还都在缓存里的条件下)。

这个脚本希望帮你省下这 30+ 秒的开销。但是请注意,跟任何优化方法一样,
使用它们都是有条件的,不注意区分的话,可能就掉进坑里半天也爬不出来,爬
出来后可能还要埋怨那个告诉你优化方法的人“是你害得我掉进坑里去的”。

1. 改过任何 makefile 不要用(比如重新同步过代码,也算!)
2. 改过任何跟编译相关的环境变量不要用
3. 发现任何问题,怀疑跟使用这个脚本有关的话,不要用
4. 第一次编译时,没法使用(因为 ninja 文件还没有生成)

用例:
    # 在安卓顶层目录执行编译 abl 的命令,直接 make 重编要 45 秒左右;用这个脚本 10 秒左右
    android-ninja out/target/product/XXX/abl.elf

Options and arguments:
  -j, --jobs=JOBS             多进程编译,默认为 8 个进程
  -k, --[no-]keep-building    出错了也继续编译其他模块
  -f, --ninja-file=NINJA_FILE 使用哪个 ninja 文件,会使用安卓编译系统里默认的那个
  -v, --[no-]verbose          编译的过程中打印执行的命令

5.64 手机“变砖”了(不停进入 fastboot 模式),怎么办?

各位在开发的过程中,难免会不小心操作错误导致手机无法开机的情况,这里跟大家分享一条比较通用的解决方法。一般来讲,大家的需求主要有两个:一、让手机能重新开机;二、保留手机上的 userdata 分区里的数据,不要清除。

以下是具体的方法:

  1. 手机只要不是发生了元器件损坏等硬件问题,一般都可以通过插一根特殊的 USB 线强制重启后进入高通的刷机模式。

    请各组 Leader 考虑向 PM 申请至少一条这样的刷机线,然后学会使用方法,并在自己的 Team 内分享。

  2. CM 编译出来的刷机包,可以自己修改其 xml,删除里面的 userdata 分区相关的 xml element,然后再运行刷机命令,这样就可以确保不会擦除 userdata。

    具体是哪个 xml 文件,需要自己挨个打开每个 xml 文件确认一下。

    本人提供的 system-config 脚本 sse .edl,封装了刷机时保留 userdata 的功能,如有需要请自己研究一下如何使用(参考 sse .edl --help)。

  3. 如果刷完机还是不开机的话,请:
    • 检查所使用的刷机包版本是否有测试报告,确认刷机包本身没有问题;是否存在把非 SE 版本刷到 SE 手机上的情况,等等。
    • 考虑使用 CM 提供的 user root force-adb 版本,开机就有 adb,即使无法进入图形操作,也可以查一下问题原因、备份一下 userdata 数据。
    • 实在不行的话,请咬咬牙,考虑把 userdata 也重刷了吧,数据是保不住了
  4. 使用以上方法前,如希望进一步降低风险,请用一台测试机(不用提心数据是否丢失的机器)提前跑一遍完整的操作流程,并确认有没有丢数据。

免责声明:以上解决方法仅供参考,本人因精力有限,无法提供任何技术支持,如有任何问题,请自己解决或联系你的 Leader 帮你解决。另外如使用本方法后发生手机数据丢失等任何问题,本人 不负任何责任

如有更好的解决方法,请考虑跟大家分享(欢迎更新本文档或提供 patch)。

5.65 北京同事 Ubuntu 系统安装加速的方法

各位在北京办公的同事,在自己安装 ubuntu 系统、或升级系统软件版本的时候,可以考虑一下用局域网内的 apt 代理加快下载速度:

  1. 镜像选 mirrors.163.com
  2. 设置代理的时候输入 http://172.16.21.238:3142

如果是已经安装好的机器,请按如下方法操作:

  1. 打开 /etc/apt/sources.list,将系统软件的镜像改成 mirrors.163.com
  2. 打开 /etc/apt/apt.conf 文件(如果不存在的话也可以考虑创建并打开 /etc/apt/apt.conf.d/02proxy 文件,效果应该是一样的),然后输入如下文字:

    Acquire::http::Proxy "http://172.16.21.238:3142";
    

注意:

  1. 本人不提供 ubuntu 安装服务
  2. 修改配置文件前请做好备份,万一有问题可以恢复
  3. 如有因使用以上方法造成任何问题,本人不承担包括重装系统在内的任何责任
  4. 个人建议每个开发都应该自己装 ubuntu 系统,不应该找 IT 帮忙装

5.66 如何快速获取某产品代码的高通基线(分支、manifest 等)

以 trident 为例,一般大家都已经同步了 trident-rom 的主线分支代码,这时候如果想获取其高通基线的代码分支信息的话,这里有一个非常简便的方法:

  1. 进入一个安卓官方代码仓库:cd system/core (任意一个安卓官方的仓库目录都可以,这里以 system/core 为例)
  2. 运行如下命令:git branch --merged $(repo-remote)/$(repo-branch) -a|grep qualcomm|sort
  3. 上面的命令的输出的最后一行一般就是当前产品的高通基线(一般会有很多行,那是因为我们做过几次高通基线升级)

    比如 trident 的基线,上面打印出来的最后一行是:remotes/smartisan/qualcomm/caf_AU_LINUX_ANDROID_LA.UM.6.3.R4.08.01.00.440.033,去掉前面的 git remote 信息(remotes/smartisan/)之后,得到分支的名字是 qualcomm/caf_AU_LINUX_ANDROID_LA.UM.6.3.R4.08.01.00.440.033

    上面这条命令的工作原理请参考 git branch --help。另外,所有高通基线我们都会加一个 qualcomm/ 的前缀进行区分。

  4. 如果需要同步整个高通基线安卓代码的话,请通过上面的高通分支名字,在 manifest 目录下查找对应的 manifest.xml 文件。

    比如上面的 trident 基线,在分支名字后面加 .xml 就是其 manifest 的名字。

5.67 快速安装 ubuntu 双系统的方法

有些同事碰到 ubuntu 系统出了问题,导致奇怪的编译错误,这时候有个方便的方法可以快速重新安装一个 ubuntu 版本的话,可能可以暂时先顶一阵。

这里提供一个 system-config 下的解决方案:

  1. 运行 cowbuilder-get,脚本会提示你安装哪个 ubuntu(或 debian)版本,请上网查一下如何确定 ubuntu 版本号与其代号之间的关系。

    脚本安装完成后,ubuntu 被安装在你的 ~/external/cowbuilder/ubuntu-trusty-amd64/bare/base.tgz 文件里(把整个根文件系统打了个包)。

  2. 运行 cowbuild-chroot,会解开上一步打包的根文件系统,然后 chroot 登录进去,得到一个 root 命令行提示符。
  3. 在上面的 root shell 下,运行 /root/cowbuilder-chroot.prepare 命令,配置一下这个新的 ubuntu,主要是安装一些必要的应用程序。

    在这个配置的最后,会用 vim 打开 /etc/ssh/sshd_config 文件,请编辑一下里面的 Port(端口),从 22 改为 21404 或 21604(代表第 2 个 ubuntu 1404/1604 系统)。

    在退出之前,请手动运行一下 /etc/init.d/ssh start 命令

  4. 运行 ssh localhost -p 21404 命令(假设你之前改的端口是 21404 的话),获得新的 ubuntu 系统登录 shell
  5. 其他说明
    • 每次重启主机之后,都需要重新手动 cowbuild-chroot,然后启动 ssh。
    • 默认会在新的系统里共享 3 个目录,~/src~/system-config~/external,如果需要共享更多目录,请使用 cowbuild-chroot 的 -m 参数
    • 此方法可用于安装 N 个不同 ubuntu、debian 版本的系统
    • 类似的方法也可以用于在手机上安装一个 debian 命令行系统,参考 adb-install-debian 脚本。
  6. 免责声明

运行此脚本中碰到任何问题如数据丢失、不会使用等,本人因精力有限,不提供技术支持与承担责任。建议自己阅读源代码,实在搞不定就重装系统

Footnotes:

1

repo clone 下来的 git 仓储自带生成 change-id 的 git commit hook,如果是自己 git clone 下来的,需要用 system-config 提供的 gerrit-fix-hooks 命令先添加一下这个 hook。

Author: Bao Haojun

Created: 2018-09-14 金 18:04

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值