BBK 工作编译脚本分析
更新日期:
在 bbk 搞 apk 开发,通常一个人要用 N 个仓库,但是又不让 repo ,所以就搞了这个东西,还是借鉴了以前的在搞 MiniGUI 的脚本。
之前我们大家一起摸索出一套在服务器上统一编译 apk 的方法。服务器上的编译是由一个脚本来执行的,这样能够省很多事情。我们本机,如果相关的仓库不多的话,可以手动编译,但是如果相关的仓库很多的话,手动就很麻烦了。这几天抽了点时间,参看了下服务上的编译脚本,自己整理出一套适用于本机的编译脚本。拿出来和大家分享一下,不对的地方欢迎指正。
功能
首先先确定下想要什么功能。我想要的功能是:
- 一个命令,能帮我把我想要的仓库全都 clone 下来。
- 一个命令,能帮我把我想要的仓库全部更新。
- 一个命令,能帮我把我想要的仓库全部切换到指定的版本。
- 一个命令,能帮我把我的模块全部编译出来。
工作环境配置文件(.inc)
根据上面的几个需求,开始打造我们的脚本。首先我们设定一个编译环境:不同的编译环境,环境变量不同,不同的编译环境,彼此不影响。这样的话,对于多个项目就很方便。例如说:有一个项目叫 H8,另外一个叫 H9 。可以先在 H8 中工作,后来因为需要,要切换到 H9 。打造不同的环境能够方便在多个项目中自由的切换。这样的话,就设计一个工作环境配置,然后用一个命令去读取这个配置,并且按照这个配置设置环境。我把配置文件放到在 home 目录的 /bin/inc 下面(注意这里是 linux 下的环境,不是 window 的,window 下可以用 cygwin 来模拟 linux 的环境)。这里还有一个约定,要把 home 目录下的 bin 文件夹设到环境变量里面去,这样这个下面的脚本可以在任何路径下被访问到(我的脚本放在这个目录下)。这个可以在 .bashrc 中设置一下:
先来看看环境配置文件的信息:
我定义了下面几个可以配置的环境变量:
BUILD_PATH:
工作环境目录。这个是这个配置对应的工作环境的目录,这里假设这个工作环境叫 test(对应配置文件为 test.inc)。这个目录是我本机 cygwin 的目录。CONFIG_PATH:
工作环境配置目录。这个目录下放置一些工作环境的配置文件(后面会介绍有哪些配置)。这里设置的是在BUILD_PATH
下面的 config 文件夹。TARGET_REPO_PREFIX:
目标仓库前缀。就是我们的仓库地址(以及每个人的账号)。从仓库 URL 可以看得出,仓库的前缀是一样的,只有后面的路径不一样而已。ANDROID_CMD:
android sdk 命令设置。这个是为了兼顾 linux 和 window 而设置的变量。linux 下,直接使用 android 就可以调用,window 下是 android.bat 。ERROR_OUTPUT:
错误信息输出文件。用来保存错误信息,方便查看。这里配置的是 CONFIG_PATH 下面的 error 文件。MESSAGE_OUTPUT:
信息输出文件。(这个暂时没用到)TARGET_REPOS:
目录仓库配置文件。就是自己模块的以及自己模块相关(依赖)的仓库列表文件。脚本会去读这个列表文件。这里配置的是CONFIG_PATH
下面的 target_repos 文件。BUILD_REPOS:
编译仓库配置文件。由于一些仓库有多级目录,所以这个和TARGET_REPOS
有点不一样。这里配置的是CONFIG_PATH
下面的build_repos
文件。BUILD_LIB_REPOS
:
编译 LIB 配置文件。有些时候不想编译全部的模块,只要编一个就行了,但是需要先把 jar 先编出来,这个是只编译自己依赖的 jar 仓库的。这里配置的是CONFIG_PATH
下面的build_lib_repos
文件。COPY_LIBS_PATH
:
某些 jar 仓库编译比较麻烦,所以服务器上会帮我们编译好,这个时候我们可以偷懒,直接去用服务上编出来的 libout (像 contentview、词典的 jar)。这个路径下放置已经编译好的 jar ,脚本编译的时候会去把这个路径下的 jar、so copy 到 libout 下,当做已经编译好了的。不过要注意一点,切换版本的时候,注意这个下面的库要和你切换的版本对应。也就是说切换版本的时候,记得更换这目录下的 jar 。当然如果你不偷懒,所有的仓库都自己编译,就不需要注意这一点了。这里配置的是CONFIG_PATH
下面的copy_libs
文件夹。
上面这段配置文件作用很明显了,导出几个变量到当前 shell 环境中,然后打印一下这些值,最后自动进入设置好的当前环境工作目录。
工作环境切换脚本(sw)
切换工作环境脚本放到 /bin 下面:
前面说了,在 .bashrc 中把 home 目录下的 \bin 加入到环境变量中,这样 \bin 下面的 sw (switch)脚本就能被找到。这个脚本接收一个参数,使用方式如下:
$ sw test
后面那个参数就是你要切换的工作环境的名字。例如说这里的 test (假设这里的工作环境叫 test,你也可以叫 H8、H9 之类的)。上面脚本第一段是判断下参数是否合法(不为空,只有一个参数)。
如果不合法的话,就打印一段提示,告诉使用者用法。如果合法的话,就会进入到 home 目录下的 /bin/inc 下(前面说,我把工作环境配置文件放到这个目录下,如果你要添加新的工作环境,在这个目录加新的配置文件就可以了)。配置文件以 .inc 为后缀(模板在前面说过了)。然后就在 /bin/inc 下查找匹配输入参数的配置文件。例如我上面输入的是 sw test,那么就会去查找 test.inc ,如果没找到就提示没找到。如果找到了的话:
- 导入(读取)配置文件的内容(source $INCFILE 就是干这件事的)。这里会把配置文件里面定义的环境变量都导出到当前 shell 环境中。
- 改变当前 shell bash 的显示,把切换的环境变量的名字作为 bash 的显示,这样就能清楚的知道自己是在哪个工作环境下面了。并且重新开启一个 bash。上张效果图: ^_^
当你想退出当前工作环境的时候,输入 exit 就可以,或者可以直接 sw xx 切换到另外一个工作环境。
仓库配置文件
这个就是上面的 .inc 文件中指定的那3个仓库列表文件。服务器上的是上次郭跃华收集的依赖关系然后用一个小程序生成出来的。我们本机自己的仓库没那么多,而且也没那么复杂,所以这里我就采用了比较原始的办法,自己手动写。
自己把自己的模块以及依赖关系整理出来(你自己肯定知道,自己都不知道的面壁去),然后按照依赖关系(编译顺序)写成一个列表(其实 TARGET_REPOS 不需要按照这个顺序写的)。拿我负责的3个模块来看看吧:
上边的是我配置的 TARGET_REPOS ,下边的是 BUILD_REPOS。可以看到其实差别就只是在于那些有多级目录的仓库而已。这里写的有3个模块:闹钟、听力测试和中学视频课堂,上面的是依赖库。最后上的那部分是 jar 依赖库,中的是 project 依赖库。其实这里并不是完整的依赖关系,省掉了 contentview.jar 和 dict.jar 的仓库及其依赖仓库。前面也说了,某些 jar 比较难编译(其实就是依赖的东西多),可以偷懒直接用服务器编出来的。这里就直接使用了服务器编译出来的 contentview.jar 和 dict.jar(还带了几个 so):
不过前面也说了,偷懒的话要注意,切换版本的时候,这几个 jar 和 so 要记得自己换成对应版本的。如果不偷懒,全都自己编的话,就不用担心版本问题了,脚本会帮你统一切换的。各有利弊,大家自己衡量吧。
命令脚本(bbk_mk)
这个脚本是主要部分了,和前面的 sw 一样,放到 home 目录的 /bin 下面(bbk makefile ?? 其实这个名字我也是随便取的,大家可以改成自己喜欢的)。先来看看脚本的入口:
- co(clone): clone 配置文件列表中所有的仓库。
- de(delete): 删除配置文件列表中所有的仓库(删除哦,使用前要慎重考虑下)。
- up(update): 更新配置文件列表中所有的仓库。
- rc(recover): 恢复配置文件列表中所有的仓库。
- sw(switch): 将配置文件列表中所有的仓库切换要指定版本。
- lo(local): 本地化配置文件列表中所有的仓库(其实就是执行 android update project,但是这个命令本质上是生成一些本地的环境变量,所以我给它取名为本地化)。
- cl(clean): 清除所有仓库的编译结果(包括 libout、apk 以及每个仓库中的 libs、bin、gen、obj 文件夹)
- bu(build): 编译配置文件列表中所有的仓库(编译之前你要确定你已经调用过 lo 命令了)。
- lb(lib): 编译 lib 库配置文件列表中所有的仓库(这个和上面的区别仅仅是只编译 \lib 下面的仓库而已,其实是由你指定的那个配置文件决定的)。
这个脚本的用法是这样的:
$ bbk_mk command params
例如说要 clone 所有的仓库就敲 bbk_mk co,要切换到某一个分支就敲 bbk_mk sw branch ,要编译所有的仓库就敲 bbk_mk bu 。
上面这8条命令的功能就囊括了我最开始想要要的功能。下面就来看看是怎么实现些功能的吧。
这个脚本确实挺简单的,跑的就只是右边那个 if eles 语句而已。那个语句也是先判断下传入的参数是否合法(必须要有1个以上的参数,并且这个参数不为空)。不合法的话,给出提示(func_tip)。合法的话,就根据不同的命令(第一个参数),执行不同的功能。其实大家看到了,这里所有的功能都是调用一个函数来完成的,从这个函数的命令大概可以猜得到这个函数是干什么的。func_do_trav_repos 这个函数就是遍历所有的仓库列表,然后做某一件事情。
上面就是 func_do_trav_repo 的实现。左边开始的一段可以看做是预处理, lo(local), cl(clean), bu(build) 这3个命令用的仓库配置文件是 BUILD_REPOS, lb(lib)用的是 BUILD_LIB_REPOS,其它的命令用的是 TARGET_REPOS
。根据前面说明就知道为什么要这样。然后 cl(clean) 命令要执行一个 func_clean_output
函数,这个函数就是去删除 libout, apk 目录,比较简单我就不贴出来了。bu(build) 和 lb(lib) 命令要执行一个 func_copy_libs 函数,这个函数就是去 copy 那个方便偷懒的从服务器上弄下来的 jar、so 到 libout 下面的,也是比较简单,我也不贴了。后面就是删除上一次的错误输出文件。
然后后面这句 cat $target_file | while read path ,前面一半就是去读取指定配置文件(linux 的 cat 命令),后面一半是把前面一半读出来的每一行做为 while 循环的输入。循环体里面为了在配置文件中支持空行和注释(以”#”开头),用了一个 case 做判断。当读到有效数据的时候就把当前的累加计数、命令、仓库路径,原始路径,命令参数传给 func_do_tarv_repos_impl(原始路径在执行命令开始的时候使用 $PWD 保存的)。这个函数又根据不同的命令做不同的事情。
func_clone_impl: co 命令的实现:
首先判断下当前工作命令下 lib, libproject, project 这3个目录存在存在,不存在的话就创建。这里是为了和服务器的目录结构保持一致。服务器是用 repo 的,所有不需要手动做,我们本地悲剧点,就只能手动了 -_-|| 。然后下面那段判断也是为了和服务器的目录结构保持一致,判断传递过来的仓库路径是以什么开头的。看看前面定义的仓库配置文件,就会发现有很明显的规律,/lib/xx 的是放在 lib 目录下的,/libproject/xx 的是放在 libproject 目录下的, /project/xx 是放在 project 目录下的。这里使用了 linux 下的一个命令 expr 进行简单的正则表达式匹配。expr match string 返回值代表匹配个数,而匹配表达式最前面的 ^ 表示匹配最开始的部分。这么解释这里就很容易理解了,例如: if [ $(expr match $path “^/lib/“) = 5 ] 就 $path 这个路径,最开头的5个字符是 /lib/ 那么这样就可以判断这个仓库是在 lib 目录下的。根据上面判断出不同的目录,进入到想要的目录,然后在这里执行 git clone 命令。至于完整的仓库路径就是:前缀(就是前面配置里写的,每个人的都不一样,用户名不同) + 路径(也是配置文件里读的)。这里注意一点,要想让脚本顺便的执行 clone 命令,如果你的 private key(私钥)是带密码的,是需要输密码的,可以使用 ssh 代理来帮你输密码(eval ssh-agent
, ssh-add),不然的话,每一个仓库敲一次密码就和手动 clone 没啥区别了。当然如果你的 private key 没密码,就不用管。执行完一次 clone 命令后,重新回到原来的目录下面。
这样 clone 下面的仓库目录结构就和服务器上的是一样的。上张效果图: ^_^
func_update_impl: up 命令的实现:
这个其实很简单,就是 git pull 命令。只不过加了个错误检测而已。错误检测的做法是:
- 先将错误输出流(linux 有12个文件描述符,系统使用的是 0、1、2,分别代表标准输入、输出和错误,自己可以使用的是 3~9)重定向到我们指定的错误文件中(ERROR_OUTPUT)。具体做法是:先拿 4 号文件符临时保存错误输出,然后把错误输出重定向到错误文件中,然后再把原来保存错误输出的 4 号文件重新定向到错误输出(就是恢复一下原来的错误输出)。
- func_check_error 在错误文件中使用 grep 命令查找是否有 Error、error、fatal、BUILD FAILED 这些关键字,如果有的话就认为有错误的,返回 1,如果没错误就返回 0。
- 根据 func_check_error 的返回值来判断,如果有错误就退出脚本执行,并且打印出错误信息,没错误就继续执行。
后面那几个 func_switch_impl、func_local_impl、func_build_impl 都是和这个差不多的,大家自己去看下具体的脚本文件就知道了。我自己感觉用起来还是不错的,大家要就觉得好用可以拿去用。
要是觉得有什么可以改进的地方欢迎提出来。
额,最后说一句:这个是基于 linux 下的 bash shell 脚本,在 linux 下可以使用(废话),window 下 cygwin 也可以使用(相应的软件包要齐全,git、ssh、grep、expr 要装好)。
来个附件: 附件里带的有上面写的脚本,还有例子中的配置文件,大家可以拿去玩一下。