iOS 持续集成

简介: iOS 持续集成,打包到fir.im 或者直接上传到testflight

最近这两天生完Xcode发现云可以用了,但是碍于把公司代码直接放到公网不太合适。但是也是一个机会偷偷自动化一下打包逻辑,毕竟人为操作的话,有几个时间节点需要等,年纪也大了,一打断就忘记接着干嘛了。还是能机器一气呵成的就机器来吧。
之前的打包逻辑主要有以下几个步骤。

  1. 修改版本号、build号、项目名称等信息;
  2. 使用xcode执行build archive;
  3. 在organizer中导出包,手动上传到 fir.im;
  4. 在organizer中导出包,手动上传到testflight;

按照这个思路逐步把逻辑脚本化,脚本的话当然还是用python比较好,人生苦短。

文件结构

主要有以下几个文件及文件结构:

- cicd.py # 主要执行脚本
- cicd # 放打包产物及配置等信息文件夹
   |- changelog.txt # 版本修改信息,可以在fir.im中查看
   |- ExportOptions.plist # 测试版本自动打包配置
   |- ReleaseExportOptions.plist # 正式版本自动打包配置
   |- build # 编译产物
   |- output # ipa包

修改版本号、build号、项目名称等信息

使用到命令行工具 agvtool
详细原理及配置参考链接https://blog.csdn.net/xo19882011/article/details/121204208
配置好相关项目之后转到命令上来:

agvtool new-marketing-version version # 相应的发布版本
agvtool new-version -all buildnum # 相应的buildnum

这样打版本的准备就做好了。

打版本

使用到命令行工具 xcodebuild,这个命令的详细介绍还是看帮助文档比较好,然后再结合不太理解的参数搜搜,也参考了一下别人的博客

xcodebuild archive \
    -workspace xxx.xcworkspace \ # 项目里边一般都有cocospod,所以一般都是 xcworkspace
    -scheme xxx \ # debug 跟 release 不同,使用的时候采用不同的scheme
    -destination generic/platform=iOS \
    -configuration xxx \ # build setting中配置的configuration,一般来说可能是 Debug / Release
    -archivePath cicd/build/xxx.xcarchive \
    -allowProvisioningUpdates \
    -allowProvisioningDeviceRegistration

最后可能会报错,但是只要是产物在一般来说没啥问题。

导出ipa

使用到命令行工具 xcodebuild

xcodebuild -exportArchive \
    -archivePath cicd/build/debug/xxx.xcarchive \
    -exportOptionsPlist cicd/ExportOptions.plist \
    -exportPath cicd/output/debug/

其中 ExportOptions.plist文件内容如下,也是对应着xcode导出包的时候每个步骤对应的配置,当前用到的是自动签名。

<?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>compileBitcode</key>
    <false/>
    <key>method</key>
    <string>development</string>
    <key>signingStyle</key>
    <string>automatic</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>xxxx</string>
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

上传ipa到fir.im

结合 fir.im 的文档 https://www.betaqr.com/docs,使用到发布应用那项。代码如下:

print(f'\033[36m--- 上传 ---\033[0m')
res = requests.post('http://api.bq04.com/apps', json={
  'type': 'ios',
  'bundle_id': 'com.xxxx', # bunldid
  'api_token': 'xxxx' # 相应的 fir.im token
})
print('token is ', res.text)
res = json.loads(res.text)
binary_key = res['cert']['binary']['key']
binary_token = res['cert']['binary']['token']
binary_upload_url = res['cert']['binary']['upload_url']
icon_key = res['cert']['icon']['key']
icon_token = res['cert']['icon']['token']
icon_upload_url = res['cert']['icon']['upload_url']
res = requests.post(icon_upload_url, data={
  'key': icon_key,
  'token': icon_token,
}, files={'file': open('图标地址', 'rb')})
print('upload icon res ', res.content)
res = requests.post(binary_upload_url, data={
  'key': binary_key,
  'token': binary_token,
  'x:name': 'xxxx',
  'x:version': args.version,
  'x:build': buildnum,
  'x:changelog': open('cicd/changelog.txt', 'r').read(),
}, files={'file': open('cicd/output/debug/xxxx.ipa', 'rb')})
print('upload binary res ', res.content)

changelog记录一下版本更新内容,方便测试人员确认信息。

上传 Test Flight

使用到命令行工具 xcrun altool

  xcrun altool \
    --upload-app \
    -f cicd/output/release/xxx.ipa \
    -t ios \
    --apiKey xxx \
    --apiIssuer xxx \
    --verbose 

其中apiKey及apiIssuer在在 app store connect 的用户和访问 -> 密钥中新建及查看。
新建一个密钥,记录密钥 ID,并下载相应的私钥。把密钥放在以下目录其中之一中

'./private_keys'
'~/private_keys'
'~/.private_keys'
'~/.appstoreconnect/private_keys'

apiKey => 密钥ID
apiIssuer => Issuer ID

之后没啥问题就能直接上传了。

完整脚本

# coding = utf8
import os
import sys
import argparse
import requests
import json
import datetime

WORKSPACE = 'xxxx.xcworkspace'
BUNDLEID = 'com.xxxx'
FIRIMTOKEN = 'xxxx'
ICONPATH = 'Images.xcassets/AppIcon.appiconset/iphone1024.png'
DISPLAYNAME = 'xxxx'
TARGETNAME = 'xxxx'
API_KEY = 'xxxx'
API_ISSUER = 'xxxx'

DEBUG_SCHEME = 'xxxx'
DEBUG_CONFIGURE = 'Debug'
DEBUG_ARCHIVEPATH = 'cicd/build/debug/xxxx.xcarchive'
DEBUG_exportPath = 'cicd/output/debug/'
DEBUG_exportOptionsPlist = 'cicd/ExportOptions.plist'
DEBUG_archivePath = 'cicd/build/debug/xxxx.xcarchive'

RELEASE_SCHEME = 'xxxxDistribute'
RELEASE_CONFIGURE = 'xxxxDistribute'
RELEASE_ARCHIVEPATH = 'cicd/build/release/xxxxRelease.xcarchive'
RELEASE_exportPath = 'cicd/output/release/'
RELEASE_exportOptionsPlist = 'cicd/ReleaseExportOptions.plist'
RELEASE_archivePath = 'cicd/build/release/xxxxRelease.xcarchive'

def clean():
  print(f'\033[36m--- 清理文档 ---\033[0m')
  os.system('rm -rf cicd/build/*')
  os.system('rm -rf cicd/output/*')

def configuration(version, buildnum):
  print(f'\033[36m--- 设置版本号 ---\033[0m')
  os.system('agvtool new-marketing-version {}'.format(version))
  os.system('agvtool new-version -all {}'.format(buildnum))

def build(mode):
  print(f'\033[36m--- 打版本 ---\033[0m')
  scheme = ''
  configure = ''
  archivePath = ''
  if mode == 'debug':
    scheme = DEBUG_SCHEME
    configure = DEBUG_CONFIGURE
    archivePath = DEBUG_ARCHIVEPATH
  else:
    scheme = RELEASE_SCHEME
    configure = RELEASE_CONFIGURE
    archivePath = RELEASE_ARCHIVEPATH

  os.system('''
    xcodebuild archive \
    -workspace {} \
    -scheme {} \
    -destination generic/platform=iOS \
    -configuration {} \
    -archivePath {} \
    -allowProvisioningUpdates \
    -allowProvisioningDeviceRegistration
  '''.format(WORKSPACE, scheme, configure, archivePath))

def export(mode):
  print(f'\033[36m--- 打ipa版本 ---\033[0m')
  exportPath = ''
  exportOptionsPlist = ''
  archivePath = ''
  if mode == 'debug':
    exportPath = DEBUG_exportPath
    exportOptionsPlist = DEBUG_exportOptionsPlist
    archivePath = DEBUG_archivePath
  else:
    exportPath = RELEASE_exportPath
    exportOptionsPlist = RELEASE_exportOptionsPlist
    archivePath = RELEASE_archivePath

  os.system('''
    xcodebuild -exportArchive \
    -archivePath {} \
    -exportOptionsPlist {} \
    -exportPath {}
  '''.format(archivePath, exportOptionsPlist, exportPath))

def uploadFim(buildnum):
  print(f'\033[36m--- 上传 ---\033[0m')
  res = requests.post('http://api.bq04.com/apps', json={
    'type': 'ios',
    'bundle_id': BUNDLEID,
    'api_token': FIRIMTOKEN
  })
  print('token is ', res.text)
  res = json.loads(res.text)
  binary_key = res['cert']['binary']['key']
  binary_token = res['cert']['binary']['token']
  binary_upload_url = res['cert']['binary']['upload_url']
  icon_key = res['cert']['icon']['key']
  icon_token = res['cert']['icon']['token']
  icon_upload_url = res['cert']['icon']['upload_url']
  res = requests.post(icon_upload_url, data={
    'key': icon_key,
    'token': icon_token,
  }, files={'file': open(ICONPATH, 'rb')})
  print('upload icon res ', res.content)
  res = requests.post(binary_upload_url, data={
    'key': binary_key,
    'token': binary_token,
    'x:name': DISPLAYNAME,
    'x:version': args.version,
    'x:build': buildnum,
    'x:changelog': open('cicd/changelog.txt', 'r').read(),
  }, files={'file': open('cicd/output/debug/{}.ipa'.format(TARGETNAME), 'rb')})
  print('upload binary res ', res.content)

def uploadTestFlight():
  print(f'\033[36m--- 上传 Test Flight ---\033[0m')
  os.system('''
  xcrun altool \
    --upload-app \
    -f cicd/output/release/{}.ipa \
    -t ios \
    --apiKey {} \
    --apiIssuer {} \
    --verbose 
  '''.format(TARGETNAME, API_KEY, API_ISSUER))

if __name__ == '__main__':
  parser = argparse.ArgumentParser(description='cicd parser')
  parser.add_argument('-v', type=str, dest='version', metavar='版本号', default='', required=True)
  parser.add_argument('-m', type=str, dest='mode', metavar='模式', default='debug')
  args = parser.parse_args()
  if args.version == '':
    exit(0)

  buildnum = datetime.datetime.strftime(datetime.datetime.now(), '%Y%m%d%H%M')
  print('build num ', buildnum)

  # 清理
  clean()
  # 配置
  configuration(args.version, buildnum)
  # build
  build(args.mode)
  # export
  export(args.mode)
  
  if args.mode == 'debug':
    # 上传
    uploadFim(buildnum)
  else:
    # 上传testflight
    uploadTestFlight()
相关文章
|
7天前
|
iOS开发 Perl
IOS集成flutter_boost 3.0常见问题
IOS集成flutter_boost 3.0常见问题
14 0
|
2月前
|
人工智能 数据安全/隐私保护 iOS开发
苹果在WWDC24上宣布的所有内容:Apple Intelligence、集成ChatGPT的Siri、iOS 18
苹果在WWDC24上宣布的所有内容:Apple Intelligence、集成ChatGPT的Siri、iOS 18
|
2月前
|
机器学习/深度学习 定位技术 开发工具
必知的技术知识:ios个推推送集成
必知的技术知识:ios个推推送集成
16 0
|
3月前
|
安全 Android开发 iOS开发
构建未来:安卓与iOS的无缝集成技术探索
【5月更文挑战第20天】随着智能设备的普及和技术的不断进步,安卓和iOS两大操作系统之间的界限正在逐渐模糊。本文将深入探讨如何通过最新的API、框架和工具实现安卓与iOS应用的无缝集成,以及这一趋势对开发者和用户的潜在影响。我们将从技术可行性、安全性挑战、用户体验优化等角度出发,分析当前的发展状况,并展望未来可能的技术融合路径。
|
3月前
|
存储 监控 安全
打造高效移动办公环境:Android与iOS平台的集成策略
【5月更文挑战第15天】 在数字化时代,移动办公不再是一种奢望,而是日常工作的必需。随着智能手机和平板电脑的性能提升,Android与iOS设备已成为职场人士的重要工具。本文深入探讨了如何通过技术整合,提高两大移动平台在企业环境中的协同工作能力,重点分析了各自平台上的系统集成策略、安全性考虑以及跨平台协作工具的应用。通过对现有技术的剖析与案例研究,旨在为读者提供一套实用的移动办公解决方案。
|
3月前
|
机器学习/深度学习 PyTorch TensorFlow
iOS设备功能和框架: 什么是 Core ML?如何在应用中集成机器学习模型?
iOS设备功能和框架: 什么是 Core ML?如何在应用中集成机器学习模型?
106 0
|
11月前
|
iOS开发 开发者 Perl
使用mPaaS iOS通过Pod集成"mPaaS_TinyApp"时遇到了错误
使用mPaaS iOS通过Pod集成"mPaaS_TinyApp"时遇到了错误
115 2
|
11月前
|
小程序 JavaScript API
支付宝小程序集成mqtt兼容IOS和安卓
支付宝小程序集成mqtt兼容IOS和安卓
176 0
|
API 开发工具 iOS开发
一点就通,社交源码IOS客户端开发集成SDK
所谓SDK,全称是SoftwaredevelopmentKit,翻译成软件开发工具包。SDK用助开发某种软件,今天给大家简单讲解下如何在社交源码IOS客户端上开发集成 SDK。
|
API 开发工具 iOS开发
一点就通,社交源码IOS客户端开发集成SDK
所谓SDK,全称是SoftwaredevelopmentKit,翻译成软件开发工具包。SDK用助开发某种软件,今天给大家简单讲解下如何在社交源码IOS客户端上开发集成 SDK。