iOS工作流设计文档

 

前言

新的一年开始了,随着公司不断的发展壮大,我们的业务线也随之快速横向裂变。之前的三期工程化方案是针对当时公司单一主包、多业务线合并的思路设计而来的,足以满足当时的使用场景。

但是现在的公司已经开始扩展各种海外单包,并且不同的单包之间还出现了业务上的独立发展,并且由于防止因代码雷同而导致的审核风险,我们必须对现有的代码进行混淆处理才能对安心上线。

诸如此类的业务发展方向的变化,导致了之前的工程化工作已经不能完全满足现有的需求,因此我们组长之间也对此进行了讨论,认为更新现有业务组织迭代方式迫在眉睫。本篇内容将更多的在理论环节对workflow的工作进行阐述,如有建议或者意见,欢迎大家反馈。

解决目标

  1. 通过ci流程自动化处理重复的操作,如语法检查、单元测试、代码混淆,code review、版本打包等
  2. 合并现有的业务仓库,重新设计多业务线多单包下的版本管理方案,优化工作流程提高复用性
  3. 解决日常开发的耗时痛点,如编译资源慢、无法利用编译缓存、组件提交需要等待二进制打包等问题

明确方向

目前我们的开发流程依旧遵循制定需求–>拆分任务–>分工开发–>测试验收–>合并提交–>产品上线这样的流程进行的。在最原始的状态下,整个流程的进行大概如下:

  1. 进入产品需求会议,确定版本需求
  2. 由小组组长分好每个需求点,并安排给每个人需求和工期
  3. 组员根据需求进行开发,并在过程中推动开发进度
  4. 开发收尾阶段开始进入测试,反复测试直到符合发版标准
  5. 合并业务代码,并进行终测
  6. 提交版本到渠道端

在这个过程中,开发所参与的环节主要是中间三个,即分工开发测试验收合并提交这三点,因此我们的workflow也将由这三点展开。

在工程化的工作当中,我们主要做了以下几件工作:

  1. 制定了分工开发的代码提交规范、review规范
  2. 制定了组件化的开发方案,通过更细颗粒度的模块拆分解决代码合并,功能复用,版本依赖的问题
  3. 通过推行bdd来减少测试验收环节的长尾风险
  4. 通过ci对组件进行二进制化,减少合并业务代码带来的时间消耗
  5. 改进了cocoapods的管理方式,提高了组件集成效率

对于这些工作,带来了一些流程上的优化和自动化工具的使用,一定程度上提高了效率,但是也有对应的问题没有解决:

  1. 虽然制定了代码规范、review规范,但是这些规范依旧通过人工合并代码来进行审查,难免遇到遗漏
  2. 组件化同时也带来了一些别的方面的问题,组件的快速膨胀导致我们在对某些功能进行调试的时候很不方便,并且会导致部分编译缓存失效,重新编译耗时较大
  3. bdd的推行非常耗时,并且单纯只由开发方来做这些工作并不符合预期
  4. ci没能做到完全自动化,并且目前的ci流程不适合多单包并行开发
  5. cocoapods改造不够彻底,很多特殊场景无法很好的支持,并且没有配置扩展能力,导致切换签名等工作很麻烦

通过复盘工程化的工作内容,可以看出来我们的工作还是以基础性建设为主,很多地方做的还不够完善,因此我们对于workflow建设主要还是为了解决目前的遗留问题。

设计方案

在明确了解决问题的方向之后,根据目前列出的难点痛点,我们可以进行如下归纳:

  1. 新的workflow希望可以接入自动代码审查,避免遇到低级语法错误,并希望尽早检查问题
  2. 提高代码编译效率,解决每次编译资源需要重新合并的问题
  3. bdd需要明确的规范标准,进行bdd测试时需要得到有效反馈,并且希望测试也参与到bdd的流程中来
  4. ci完全自动化,定时编译+触发器编译,通过ci流程自动进行各个检验环节,并提高对多单包并行的扩展性
  5. cocoapods加入可扩展性配置,并支持更多的自定义接入选项

为了实现以上几点,整个workflow计划需要完成以下工作:

1. 构建完整的Gitlab CI

Gitlab CI简介

Gitlab 在 8.0 版本之后,就集成了 Gitlab CI,随着版本的迭代,其功能越来越强大。 Gitlab CI通过yml语言对工作流程进行描述并进行组合执行,使其可以完成各类CI任务。一个标准的Gitlab CI流程如下: alt

Gitlab CI将工作任务分为三种不同维度行为pipelinestagejob

  1. pipeline实际是一组stages中执行job的集合,代表着使用者触发的一次构建 。任何提交,包括 MR 在符合配置文件要求的情况下都可以触发pipeline,其在网页中的体现如下: alt

  2. pipeline中的jobs按照构建阶段进行分类,这些分类就是一个个stage。如pipeline示意图所示,一个pipeline中可以定义多个stage,比build, test, staging, production,其对应配置语法如下:

     stages:
       - build
       - test
       - staging
       - production
    
  3. job表示stage中实际执行的任务。如pipeline示意图所示,一个stage中可以有多个job,比如Test stage的test1test2 job,其对应配置语法如下:

     test1:
     stage: Test
       script: 
         - xxxx
       only:
         - xxxx
       tags:
         - xxxx
        
     test2:
       stage: Test
       script: 
         - xxxx
       only:
         - xxxx
       tags:
         - xxxx
    

Gitlab Runner

光配置好Gitlab CI流程脚本也是不够的,因为最终job的执行还是需要依托于具体的本地任务,因此执行本地任务的Gitlab Runner就需要我们进行配置。

Gitlab CI与Gitlab Runner的关系如下所示: alt

通过改图我们可以了解到,Gitlab Runner本质上就是一个常驻后台的任务检测服务。一旦请求到需要执行的任务,就会通过接受到的job来执行本地任务。

Gitlab Runner执行stage脚本本质上是将对应脚本存在runner机器的磁盘中,然后根据stage脚本的内容去一步步执行,具体job的脚本可以选择ruby、python或者shell,这一块可以复用一部分我们现有的CI脚本代码。

设计CI/CD流程

单纯有工具,没有合理的流程一样无法提高工作效率,因此我们需要设计出符合我们业务需求的CI/CD流程。由于CI/CD流程设计问题复杂且跟随业务紧密,因此下面列出的流程仅供参考:

  1. 将所有CI/CD脚本全部归纳到一个git仓库中,方便统一管理
  2. 每次执行任务时下载新的CI/CD脚本,任务执行完后删除脚本
  3. 根据业务需求,我们的stage可以分为checklinttestobfuscatepackagepublishreport等步骤
  4. check用于检测主工程对组件的快速校验,比如校验模块依赖关系、校验组件版本是否匹配、校验业务线模块的私有组件依赖关系等组件模块设计规范
  5. lint用于检测组件podspec设置是否合法,也可以用来执行oclint用来检测代码是否符合语法规范,进行静态代码检查
  6. test用于执行组件或者业务模块的单元测试,通过回馈测试结果来判断是否继续执行
  7. obfuscate用于对代码进行混淆,通过对代码混淆字典的读取对现有代码进行混淆,混淆之后将生产替代代码,而不是宏
  8. package用于对组件进行二进制化处理和ipa签名打包,并存储在本地制定目录
  9. publish发布二进制组件、发布ipa包

通过对每个组件/工程都设置合适的pipeline,在每个stage中选择合适的job来进行自由组合,再加上各类条件触发器的使用,就可以完成完整的Gitlab CI流程了。

2. cocoapods插件强化

优化插件

由于依赖集成上我们选择了cocoapods作为基础框架,因此所有的二次开发都是在cocoapods基础上来做的。目前现有pod-wanba包含功能如下:

  1. 集成当前主工程版本业务模块
  2. 切换组件二进制、源码、development工程
  3. 修复不同集成状态导致的编译参数问题
  4. 切换不同单包的签名信息

对于目前对cocoapods插件,我们缺少对详细细节的把控,所有代码混成一块,没有做好明确的功能分类,因此需要有如下改动:

  1. 通过标准cocoapods-plugins进行构建
  2. 使用读取外部配置的方式设置签名信息
  3. 使用专用的二进制组件和源码组件的组件列表,与官方源数据隔开
  4. 集成cocoapods-packager功能,用于二进制打包
  5. 优化二进制/源码版本切换功能
  6. 提供源码仓库本地镜像功能,避免反复拉取相同代码

优化资源合并(待定)

对于iOS程序从编译到打包的整个流程中,主要分为编译链接资源编译自定义脚本签名。这几个过程当中编译已经通过组件静态库化来优化,链接可以通过人工排查减少无用动态库的引用来优化,签名过程基本只能靠苹果官方工具的升级来优化。我们在工作流程中主要能解决的只有资源编译方面的优化。

目前资源编译加速暂时没有太多解决办法,但是我们可以通过对二进制组件的资源进行提前编译的方式来起到加速主工程编译速度的办法。通过CI的二进制化脚本,提前将xib和storyboard编译成nib和storyboardc,集成到组件内。这一方式大概可以提高1-5分钟的主工程打包速度。

实施细节

  1. 主工程Podfile进行完全展开,明确指定版本号
  2. 将主包的工作流推广到组件上,取消feature分支
  3. 使用fork仓库对不同业务线进行区分,使用group做工作小组区分
  4. cocoapods插件自动完成本地源码镜像,dev模式自动复制源码到Modules文件夹下
  5. 组长控制组件主干,控制mr权限
  6. 业务组件二进制当前版本只保留最后一个
  7. 强化Podfile指定本地开发仓库源码功能

Q:

  1. bugfix阶段如何处理mr,是否需要控制合并权限
  2. bugfix阶段怎么解决打包次数频繁的问题
  3. 如何根据需求点来进行测试

A:

  1. 版本开始时就开启当前版本的release分支
  2. 组长只控制合并到release/master分支的权限
  3. 开发过程中自己的feature往主干的release分支中提交mr
  4. 通过mr触发组件打包
  5. 提交mr时需要有独立的commit,并且需要带上merge标签[merge feature/feature1]
  6. 经常变动的service需要拆分,拆分后的service不跟随版本变化
  7. 业务Module对于service和二方库需要指定明确的版本,service和二方库需要保证接口兼容性

未来:发布后台

由于整个发版流程中,上层工程在打包时并不知道下层组件是否还有更新,因此需要业务负责人手动检查下层组件的状态。如果可以创建一个发布后台,将原发布依赖进行逆向设置,就可以在很大程度上解决自动发版的问题。这对于多个单包、多个业务线同时开发将带来很大的便利。