Fastlane-移动开发自动化之道
\\本人一直认为:在程序的世界里,一切重复性的,流程化的工作都可以交给自动化去完成。
\\在移动开发中也是如此:其实写代码只是我们开发过程中的一部分,除此之外我们还需要进行编译,打包,上传,部署,库管理,版本控制等等Coding之外的杂事,而正是这些乏味而重复的工作占用了我们宝贵的时间。
\\所以在“懒人”遍布的工程师世界中,总会有人想尽办法做出改变,于是这些“懒人”们乐此不疲的造出许多美妙的轮子,既方便了自己,又帮助了他人,让这个世界变得更加美好。
\\今天就给大家介绍其中一个轮子:Fastlane,这个Github上的明星项目截止到目前共获得1万多个Star,并且还有1500多个Fork。
\\Fastlane在我们团队中的推广和应用
\\怎么样,听起来是不是很牛?不过先别急,在进入正题之前,我想跟大家简单分享一下我们移动团队在开展持续测试和持续交付工作中的一些心得体会。
\\大家都知道,最近几年,随着智能手机的普及,移动端不仅要承载更多业务场景的实现,并且还要应对不断变化业务需求。这就要求我们移动团队能够迅速响应变化,快速的迭代。那么随之而来的问题就是如何保障在不牺牲质量的前提下,尽可能的提升速度,我认为这一切需要建立在高质量的持续测试和持续交付体系之上。
\\但是移动端本身兴起的时间就比较短,各方面的成熟度也有所欠缺,能够拿来用的工具更是少之又少,随着业务深度广度的增加,迭代速度的加快,诸如证书管理,打包,上传,发布这类重复而毫无技术含量的工作逐渐占用了大家的时间,团队内部对此诟病不已。
\\所以我们的架构团队从去年初就一直在寻找这样的一种工具,一种解决方案,旨在彻底解放工程师的“双手”。
\\刚开始我们尝试使用Jenkins+Fir搭建了一套持续测试的环境,流程如下图:
\\(点击放大图像)
\\ \\说实话,效果还是可以的,至少在一定的时期内满足了我们的要求,但是Jenkins本身只是一个通用的CI流程管理系统,本身并不提供诸如ITC提包和Meta内容管理,签名,证书管理等等和移动端业务紧密结合的场景,而且配置的过程相当繁琐。
\\去年年底的时候,机缘巧合之下,我们在Github上发现了Fastlane,看了Readme后感觉有戏,于是决定尝试一下。其实刚开始的时候,我们也只是用Fastlane来解决iOS团队内证书同步和上传ITC的问题,但是随着深入的研究,发现其实Fastlane能做的更多,于是我们将其逐步应用到iOS端的更多的场景,比如:私有Pod的发布,代码的静态检查,UIAutomation测试等等,接着又推广到Andriod平台,完成诸如私有AAR的发布,Monkey测试等等一系列任务。现在可以说Fastlane已经变成了我们工作中密不可分的一部分。
\\另外,Fastlane本身也可以和Jenkins,Circle等主流CI系统做很好的集成,并且由于主要的CI流程都由Fastlane来管理和执行,所以从根本上降低了这些系统配置的复杂度。
\\Fastlane简介
\\说了这么多,我们回到今天的主角身上,首先先简单介绍一下:Fastlane是用Ruby语言编写的一套自动化工具集和框架,每一个工具实际都对应一个Ruby脚本,用来执行某一个特定的任务,而Fastlane核心框架则允许使用者通过类似配置文件的形式,将不同的工具有机而灵活的结合在一起,从而形成一个个完整的自动化流程。
\\到目前为止,Fastlane的工具集大约包含170多个小工具,基本上涵盖了打包,签名,测试,部署,发布,库管理等等移动开发中涉及到的内容。
\\关于这些工具的描述和使用可以看这里:https://docs.fastlane.tools/actions/Actions/
\\如果这些工具仍然没有符合你需求的,没有关系,得益于Fastlane本身强大的Action和Plugin机制,如果你恰好懂一些Ruby开发的话,可以很轻易的编写出自己想要的工具。
\\其实真正官方出品的工具大约占一半左右,剩下的都是Github社区成员贡献的,本人有幸也贡献过其中一个。(这里建议移动开发工程师还是需要学习一门脚本语言的,比如Ruby)
\\Fastlane的安装非常简单,和Cocoapods一样,Fastlane也可以通过RubyGems来安装,如果你的电脑上有Ruby环境的话,那么只需要执行如下命令,即可完成:
\\\gem install fastlane\\
移动客户端持续测试和持续交付的常见场景及痛点
\\Fastlane本身能做的事情很多,但是其中一个最为重要的作用就是能够无缝嵌入在持续测试和持续交付体系中。
\\下面,为了便于大家理解,我拿iOS为例,举几个我们在移动开发过程中常常会遇到的场景:
\\场景一
\\当一个迭代开发测试结束,服务器端上线之后,我们会使用Testflight进行线上跟测,一般会执行如下流程:
\\- 执行Git Pull命令,拉最新的代码到本地\\t
- Pod Install安装最新的依赖库\\t
- 在Xcode中将Build Version增加\\t
- 在Xcode点击Archive编译并打包\\t
- 选择输出一个iOS AppStore模式的ipa文件\\t
- 通过Application Loader将IPA上传到ITC(TestFlight)\\t
- 然后等待ITC Process完成后,登录上去选择刚才的Build进行TestFlight测试\\t
- 由于修改了版本号,所以需要将代码Commit和Push一下\
如果线上跟测发现有问题,那么需要修复完毕后重复上面的8个步骤。
\\其实做过这件事的同学应该都有体会,顺利的话差不多一次得30分钟吧,如果某一次Build Version忘记增加了,那么前面的工作就白做了。
\\在我们团队早期的时候,由于自动化体系尚未建立,我们有一个同事专门负责此事,在线上跟测的这两天,他有半天时间几乎干不了别的,基本上都在打包上传,说出来都是泪。
\\场景二
\\随着业务的发展,产品线的增加,我们需要将APP拆分为若干个基础组件和业务组件,以便跨APP使用,并且方便管理维护(这又是另外一个大的议题,就不在此赘述了)。每个组件都由一个私有Pod来管理,Pod的发布和更新也成为了我们日常工作的一部分,对于这些Pod,一般我们团队内部的原则是:谁制作,谁管理,谁发布,Pod的负责人我们内部称之为库管,这件事也就分担到了每个库管身上,库管发布一个Pod的流程大约如下:
\\- 增加Podspec中的版本号\\t
- 执行pod lib lint命令进行库验证\\t
- Git Commit代码\\t
- Git Push代码到远端\\t
- 打一个Git Tag\\t
- 将Tag Push到远端\\t
- 执行pod repo push命令发布库到私有仓库\
如果只有两三个库的话,并且库的更新频率较低的时候,每次手动来处理还好。但是当库逐渐增多的时候这件事就变得相当麻烦,尤其是当顶层的库依赖底层库的时候,那么升级一个库,影响面将远远超过其本身,通过人工的方式处理的话,整个过程会变得相当痛苦。
\\说到这里,我相信但凡是操作过的同学,应该都对此深有感触。
\\所以我们来看看针对以上这两个场景,如何使用Fastlane来解决。
\\其实说起来也不难,首先在项目下执行:
\\\fastlane init\\
然后跟随配置引导,填写App和ITC相关信息,然后Fastlane会在项目目录下创建一个fastlane目录,里面包含所有和此项目相关的配置,剩下要做的就是将以上的流程配置在fastlane目录下的Fastfile中,
\\场景一的Fastfile(可以忽略HipChat部分):
\\\desc 'Deploy a new version to the App Store'\lane :do_deliver_app do |options|\ ENV[\"FASTLANE_PASSWORD\"] = options[:itc_password]\ project = options[:project]\ scheme = options[:scheme]\ version = options[:version] \ build = options[:build] || Time.now.strftime('%Y%m%d%H%M')\ output_directory = options[:output_directory]\ output_name = options[:output_name]\\ hipchat(message: \"Start deilver app #{project} at version #{version}\")\\ hipchat(message: \"Git pull\")\ git_pull\\ hipchat(message: \"Pod install\")\ cocoapods\\ hipchat(message: \"Update build number to #{build} and building ipa\")\ update_build_number(version: build, plist: \"#{project}/Info.plist\")\ gym(scheme: options[:scheme], clean: true, output_directory: output_directory, output_name: output_name)\\ hipchat(message: 'deliver to itunesconnect')\ deliver(force: false, skip_screenshots: true, skip_metadata: true)\\ hipchat(message: \"Upload #{project} to itunesconnect successfully!\")\\ git_add(path: '.')\ git_commit(path: '.', message: \"update build number to #{build} and upload to itunesconnect\")\ git_pull\ git_push(branch: \"test\")\end\\
场景二的Fastfile(可以忽略HipChat部分):
\\\desc \"Release new private pod version\"\lane :do_release_lib do |options|\ target_version = options[:version]\ project = options[:project]\ path = \"#{project}.podspec\"\\ hipchat(message: \"Start release pod #{project} at version #{target_version}\")\\ git_pull\ ensure_git_branch # 确认 master 分支\ pod_install\ pod_lib_lint(verbose: true, allow_warnings: true, sources: SOURCES, use_bundle_exec: true, fail_fast: true)\ version_bump_podspec(path: path, version_number: target_version) # 更新 podspec\ git_commit_all(message: \"Bump version to #{target_version}\") # 提交版本号修改\ add_git_tag(tag: target_version) # 设置 tag\ push_to_git_remote # 推送到 git 仓库\ pod_push(path: path, repo: \"GMSpecs\