CI

自己动手搭建 iOS 应用内测分发平台

Posted by Roylee on 2018-04-28

前言

看到这个标题,还是觉得有点扯了。现在,国内 fir 与蒲公英等都提供这种应用内测的托管服务,为毛自己还要受累搞一个呢?

其实我也不想,谁让现在风声越来越紧,好多自己搞的东西都上不了这些平台的:比如逆了个向的微信、钉钉


开搞

其实,苹果是有专门的文档说明怎么安装企业内部应用的。大致的流程就是:

  • 创建一个 plist 文件,通常命名为 manifest.plist(名字就随意了)

    这个 plist 文件的作用就是配置内部应用的相关信息,比如 ipa 包的地址(苹果会通过这个地址去下载 ipa,而且必须要支持 HTTPS)、APP 的 icon URL、APP 的名字等

  • 最后是整个链接,当用户进入链接或者点击下载什么的,能够发起 itms-services://?action=download-manifest&url=https://example.com/manifest.plist 的加载就行了,也可以写个标签按钮触发

需要注意的是,ipa 的下载地址一定要支持 HTTPS。由于七牛现在申请个 HTTPS 还挺麻烦,就只好把 ipa 包托管到 coding 上了。

由于自己也不会写 html,就用 markdown 简单写了个下载样式,导出 html 存放在七牛上就完成了下载页面的支持了,哈哈


自动化

由于前期总是会调试,每次 build 打包然后上传修改 html 啥的还真是麻烦,决定写个脚本来解决这些重复的工作

首先,用自带的 xcbuild 完成 build 工作,并且把 .app 文件打包为 ipa 包

# build APP
function build() {
echo "** Start build **\n"
if [ $type == $debug ]; then
xcodebuild -project $workspaceName -target $projectName -configuration $configuration
else
xcodebuild -project $workspaceName -target $projectName -configuration $configuration
fi
if [ $? -ne 0 ]; then
echo "** Failed to build app 💔 **"
exit 2
else
echo "** Build sucess 👏👏👏 **\n\n"
fi
}
# 生成 ipa 包
# 主要就是拷贝 .app 文件到新建的 Payload 文件夹,然后压缩为 .ipa 文件就好了
function createIpa() {
echo "** Start create ipa! **\n"
mkdir -p "${currentpath}/Payload"
cp -r $appPath "${currentpath}/Payload/${projectName}.app"
rm -rf "${basepath}/build" # delete build dir
cd "${currentpath}"
zip -r "${projectName}.ipa" Payload && rm -rf "${currentpath}/Payload"
mv -f "${projectName}.ipa" $ipaPath
if [ $? -ne 0 ]; then
echo "** Failed to create ipa 💔 **"
exit 2
else
echo "** Create ipa sucess! ✅ **"
return 0
fi
}

有了脚本打包 ipa 后,就解决了一大问题。之后就想 plist 文件、markdown 转 html 也能支持自动化就好了,所以又写了个脚本自动创建 plist、markdown 文件。至于 markdown 转 html 这里我用了 Pandoc

# create md file
function createMarkdown() {
cat>$markdownName<<EOF
### 微信『Joanne』安装
[点击安装](${downloadURL}) 之后请按 HOME 键查看安装情况😉
EOF
}
# create html by using the md
function createHTML() {
pandoc -s -c http://api.error408.com/html/macdown.css $markdownName -o $htmlName
}
# create manifest plist
function createPlist() {
cat>$plistName<<EOF
<?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>${ipaURL}</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>${iconURL57}</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>https://www.example.com/image.512x512.jpg</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.roylee.xin</string>
<key>bundle-version</key>
<string>1.0</string>
<key>kind</key>
<string>software</string>
<key>subtitle</key>
<string>Apple</string>
<key>title</key>
<string>${appName}</string>
</dict>
</dict>
</array>
</dict>
</plist>
EOF
}

最后,将 html 上传七牛也写了个脚本,利用七牛的 Ruby API 实现文件的自动上传

require 'pathname'
require 'qiniu'
require "http" # https://github.com/httprb/http
# keys
AK = 'LBtk5yzMuaZcnGSi__MREcoc3XVuti2nJgvvb4dF'
SK = 'fQPUjOgnuZ6DGyDaezd2pEEukO-Mfwd1L-vgqRqn'
# 构建鉴权对象
Qiniu.establish_connection! access_key: AK,
secret_key: SK
# 空间名
bucket = 'app-data'
# Host
host = 'http://api.error408.com'
# 上传到七牛后保存的文件名
prefixs = ['html', 'html']
keys = ['download.html', 'macdown.css']
keys.each do |key|
# 拼接上传的文件名
uploadkey = "#{prefixs[keys.index(key)]}/#{key}"
# 构建上传策略,上传策略的更多参数请参照 http://developer.qiniu.com/article/developer/security/put-policy.html
put_policy = Qiniu::Auth::PutPolicy.new(
bucket, # 存储空间
uploadkey, # 指定上传的资源名,如果传入 nil,就表示不指定资源名,将使用默认的资源名
3600 # token 过期时间,默认为 3600 秒,即 1 小时
)
# 生成上传 Token
uptoken = Qiniu::Auth.generate_uptoken(put_policy)
# 获取当前的文件的目录,而不是 ruby 执行的工作目录
currentPath = Pathname.new(File.dirname(__FILE__)).realpath
# 要上传文件的本地路径
filePath = "#{currentPath}/#{key}"
# 调用 upload_with_token_2 方法上传
code, result, response_headers = Qiniu::Storage.upload_with_token_2(
uptoken,
filePath,
uploadkey,
nil, # 可以接受一个 Hash 作为自定义变量,请参照 http://developer.qiniu.com/article/kodo/kodo-developer/up/vars.html#xvar
bucket: bucket
)
#打印上传返回的信息
puts code
puts result
# 刷新七牛 CDN 缓存 https://developer.qiniu.com/fusion/api/1229/cache-refresh
# 分两步:
#
# 第一步生成token,如下(执行 shell 命令, strip 去除生成 token 的\n):
refresh_token = `echo "/v2/tune/refresh" |openssl dgst -binary -hmac #{SK} -sha1 |base64 | tr + - | tr / _`.strip
# 第二步请求数据:s
url = "#{host}/#{uploadkey}"
auth = "QBox #{AK}:#{refresh_token}"
headers = {
"Content-Type" => "application/json",
"Authorization" => auth
}
res = HTTP.headers(headers).post('http://fusion.qiniuapi.com/v2/tune/refresh', :json => { :urls => [url] })
puts res.to_s
end

最后,写了三个脚本分别用来生成 ipa、自动创建 html、上传七牛等。问了方便使用,最后又添加了 make 的支持,只需要执行以下三个命令就可以完成操作

  • make create_ipa or make
  • make files
  • make qiniu
# make 文件
create_ipa:
./AutoCreate/autocreate.sh -j D
files:
./AutoCreate/generator.sh
qiniu:
# upload data to qiniu
ruby ./AutoCreate/qiniu_upload.rb

总结

自己搭建内测平台确实方便不少,想想脚本也真是强大的东西

引用

搭建自己的iOS内测分发平台

苹果官方文档

七牛文件上传