一、背景
1.1 问题
移动端开发交付项目中,由于交付团队技术沉淀参差不齐,开发流程极不规范,经常出现这打包在本地,甚至是集中在某个外包开发自己,这样的方式存在以下问题:
- 不安全:代码、证书、签名泄漏风险
- 低效:环境不可靠,稳定,打包效率慢,小的改动打包可能涉及到大量的人工操作
- 低质量:受本地环境如缓存、分支、配置等因素影响较大,出包质量参差不齐
- 协同性低:基本集中在某个人身上,高度依赖
- 版本管理差:个人记录,归档基本不存在
- 其他(持续集成、安装分发、权限控制没法做····)
1.2 目标
也是由于存在以上诸多问题,所以,统一的构建流水线是必须的,至少要实现云端统一构建,支持证书管理、版本管理、权限管控、安装分发等能力
1.3 方案
想要实现流水线构建,需要具备以下基本的环境:
- 公网访问
- 打包机(Android linux环境即可、iOS需要xcode环境)
1.3.1 公网访问
这个是交付项目的客观存在,所以不可能直接面向集团内部资源,我们这里就直接考虑入口在GTS统一工作台的大禹研发域
1.3.2 打包机
- Android,linux环境即可,我们平时时候的流水线jenkins服务都运行在linux环境上,所以Android没有太大的问题(Android由于难度较低,本文不做分析。)
- iOS,需要xcode环境,iOS生态是比较封闭的,所以目前调研的有3种方案:
- mac机器,需要解决机器的托管以及外网访问问题
- mac虚拟技术,有一定的技术壁垒,成本较高
- 技术合作
决策:技术合作是成本最低,见效最快的方式。
合作方给我们提供了非常友好的对接方式,他们提供xcode运行环境,剩下的我们自己想怎么玩就怎么玩。
二、设计
想要设计一个移动端流水线,当然不能简单的打个包就行了,我们需要解决的是上面提到的各种问题,以及要基于流水线以及移动端产品的特性而开发一些额外的功能
2.1 功能大图
这里功能简单罗列构建相关核心功能点
项目 | 说明 |
证书管理 | 证书的上传、下载、访问权限管理 |
版本管理 | 构建记录 构建code与源码commit映射 |
产物分发 | 支持扫码安装 支持访问权限配置 支持有效期配置 |
构建参数 | - Version 版本号
- 唯一的安装ID,一般用于一机多装
- AppName 名称
- ad-hoc,dev,enterprise,app store
- 环境、日志等其他信息
|
2.2 环境准备
在正式的开始构建前,这里先解决两个技术点:
2.2.1. 合作方的调用链路;

合作方案调用方式
2.2.2 .code specs的访问权限
大部分iOS开发已经是基于pods管理的组件化的方案,所以需要打通私有specs仓库和公共仓库的访问

代码以及仓库访问权限管理
2.3 构建思路
构建步骤主要为以下清单:
由于资源都是在自己的服务器上,所以这里的步骤跳过了所需要的资源的拉取以及xcode,pods版本的切换
步骤 | 项目 | 说明 |
1 | 资源拉取 | 工程源码、证书、profile文件 |
2 | xcode、pods版本切换 | 根据工程实际情况选择 |
3 | 证书安装 | 核心:证书文件,必须在当前机器安装才能使用 |
4 | profile描述文件安装 | 核心:描述文件,必须在当前机器安装才能使用 |
5 | xcodeproj修改配置 | 核心:修改version,bundleId,app_name,证书、描述文件等信息 |
6 | 额外的插件安装 | 比如mPaaS,需要安装mPaaS相关的资源,这个也是基于项目使用了mPaaS插件,所以也就增加了这个的扩展,一般项目可以在配置单默认关闭即可 |
7 | pods update/install | 核心:依赖拉取 |
8 | archive | 核心:xocdebuild 构建 |
9 | exportArchive | 核心:导出ipa |
10 | deploy | 核心:安装分发配置 |
三、核心点剖析
安全需要,为了讲清楚核心点,以下代码为部分核心代码,并非完整代码,额外需要处理大量的判断逻辑比如:cert和profile以及bundleId、打包类型一致性;证书、描述文件大量时候的删除重装逻辑等。
3.1 证书安装
iOS构建的证书是安装在OSX系统的钥匙链里面,需要使用系统的`security`指令执行创建和访问配置:
security create-keychain -p $keychain_password $keychain_name
security unlock-keychain -p $keychain_password $keychain_name
security import $p12_path -k $keychain_name -P $p12_password -A
security set-key-partition-list -S apple-tool:,apple: -k $keychain_password $keychain_name
security set-keychain-settings $keychain_name
3.2 privosion profile描述文件安装
这个描述文件安装就比较简单,复制到指定目录
`${HOME}/Library/MobileDevice/Provisioning\ Profiles`即可,同时修改文件名为profile对应的uuid
cp $profile_file ${HOME}/Library/MobileDevice/Provisioning\ Profiles/${INSTALL_PROFILE_UUID}.mobileprovision
3.3 xcodeproj修改配置
这一步是个难点,也是个核心大头,由于xcode工程配置用的ruby脚本管理,所以这里的配置也是由ruby进行修改,大体流程如下:
- 找到对应target
- 修改cert和profile
- 修改bundle_id、app_name、build、version_code
- 修改签名机制手动挡
project = Xcodeproj::Project.open(project_path)
project.targets.each do |target|
config = target.build_configuration_list[configuration]
config.build_settings["CODE_SIGN_STYLE"] = "Manual"
config.build_settings["PRODUCT_BUNDLE_IDENTIFIER"] = bundle_id
config.build_settings["DEVELOPMENT_TEAM"] = team_id
config.build_settings["CODE_SIGN_IDENTITY"] = full_cert_name
config.build_settings["PROVISIONING_PROFILE_SPECIFIER"] = profile_name
end
3.4 pod install
podspec 有墙的因素,导致很多github specs以及墙外资源经常性拉取失败,所以在pods install这个阶段做了三种优化:
- 使用自建/国内specs镜像
- Pods目录上传到代码库,跳过pods install阶段
- 使用缓存机制,对Pods资源缓存
3.5 Archive
3.5.1 首先clean
xcodebuild clean -workspace ${workspace_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration}
3.5.2 执行archive
xcodebuild archive -workspace ${workspace_name}.xcworkspace \
-scheme ${scheme_name} \
-configuration ${build_configuration} \
-archivePath ${export_archive_path} \
CHANNEL=true \
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) CUST_NUM1=99'
可以通过GCC_PREPROCESSOR_DEFINITIONS配置自定义的运行时变量
3.6 exportArchive ipa阶段
执行真正的exportArchive前,需要根据参数`生成export_options_plist文件`
/usr/libexec/PlistBuddy -c "Add :compileBitcode bool ${compileBitcode}" $export_options_plist_path
/usr/libexec/PlistBuddy -c "Add :method string ${method}" $export_options_plist_path
/usr/libexec/PlistBuddy -c "Add :provisioningProfiles:" $export_options_plist_path
echo "$extension_export_plist" | while read line
do
/usr/libexec/PlistBuddy -c "$line" $export_options_plist_path
done
xcodebuild -exportArchive \
-archivePath ${export_archive_path} \
-exportPath ${export_ipa_path} \
-exportOptionsPlist ${export_options_plist_path} \
-allowProvisioningUpdates
3.7 Deply 内测分发
这个阶段又叫内测分发,iOS 打包产物想要安装到iOS设备上,比Android复杂很多,除了testflight和App Store正常安装外,日常测试也是需要安装,我们这里做了分发功能,通过扫描二维码即可安装(遵循设备注册和企业包灯等的安装限制)
核心点:
itms-services:///?action=download-manifest&url=https://resource.dayu.work/build-packages/xxx/ios-test.plist
启用一个itms-services标签,指向一个plist描述文件,plist格式如下,有几个点必须注意,严格遵守:
- itms-services标签下的url链接协议必须是https
- itms-services标签下的url链接不能有特殊字符,否则会导致plist文件解析失败,比如'%'不能有
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://xxxx.com/xxxxxxx/download</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.dayu.buildios</string>
<key>bundle-version</key>
<string>1.0.0-MTL-SNAPSHOT</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>流水线测试包</string>
</dict>
</dict>
</array>
</dict>
</plist>
总结
本篇文章主要是对IOS构建做了一个从xcodebuild 基础命令的,包括流水线构建流程做了个剖析,感兴趣的可以自行尝试,目前市面也有很多比较成熟的产品,比如fastlane,托管式的集成方式,甚至不用管理证书,但是涉及到一些扩展或者是修改,灵活性就不那么强了,所以懂了原理之后,想怎么玩看读者自己了。