iOS - 实现25秒完成测试包出包
# 一、背景
在日常研发中,被提 bug
是家常便饭,当 bug
修复完成后便提交代码,然后触发构建机打出测试包,自动上传至蒲公英后提供测试,但是我们的构建机打一个包的时间近 25分钟
,而且有时还会莫名其妙的出现失败,效率偏低。那什么办法减少这个时间呢?
经过下面的方案调整后,我将时间测试包的出包时间降低至 20秒
左右,极速出包 😃
# 二、思考
我们在编译运行时,xcode
就已经打出了后缀为 app
的包,并且是经过描述文件签名的,所以可安装于那些 UDID
有记录于描述文件中的测试机,该 app
包存放于 DerivedData
,其具体路径如下:
/Users/lxf/Library/Developer/Xcode/DerivedData/LXFCardsLayout-xxx/Build/Products/Debug-iphoneos/LXFCardsLayout.app
所以这从这里开始,我们需要思考的是如何得到该 app
包的路径,以及拿到 app
包后要如何处理才能安装到测试机
# 三、解决方案
# 1、app
转 ipa
上面提到的两个问题中,第二个很好处理,我们只要新建一个名为 Payload
的文件夹,将 LXFCardsLayout.app
放入其中,接着进行 zip
压缩,将 zip
后缀改成 ipa
即可。
这里需要注意的是:该 app
包是根据当前真机的架构打包而来,像我们的测试机是 arm64
架构的,所以打出来的是仅包含 arm64
架构,armv7
的设备就不可使用,但我们的测试机都是 arm64
架构的,所以问题不大。在使用的过程中大家请注意自己的设备架构~
# 2、获取 app
包路径
在编译的过程中,有一个环境变量 BUILD_DIR
,可以取到如下路径
/Users/lxf/Library/Developer/Xcode/DerivedData/LXFCardsLayout-xxx/Build/Products
不过在 Archive
的时候,得到的路径如下:
/Users/lxf/Library/Developer/Xcode/DerivedData/LXFCardsLayout-xxx/Build/Intermediates.noindex/ArchiveIntermediates/LXFCardsLayout-Swift/BuildProductsPath
在 shell
环境中,我们可以使用 %
取到 LXFCardsLayout-xxx
为至的路径,即把 Build/
及后边的内容全部删掉
${BUILD_DIR%Build/*}
# 四、实践
# 1、保存 app
路径
在 iOS
项目下新建一个 script
目录,存放 python
脚本 save_build_config.py
# -*- coding: UTF-8 -*-
# -*- author: LinXunFeng -*-
import os
from configparser import ConfigParser
def handle_build_config():
"""保存编译时的一些配置"""
build_dir_path = os.getenv("BUILD_DIR") # 编译地址
if build_dir_path is None:
return
build_str_index = build_dir_path.find('Build/')
if build_str_index is not None:
build_dir_path = build_dir_path[0:build_str_index]
print(build_dir_path)
save_config('build_dir_path', build_dir_path)
def save_config(key, value):
"""
保存配置
:param key: 键
:param value: 值
:return:
"""
section_name = 'project'
config_file_name = 'build_time_conf.ini'
config = ConfigParser()
config.read(config_file_name)
if not config.has_section(section_name):
config.add_section(section_name)
config.set(section_name, key, value)
with open(config_file_name, 'w') as f:
config.write(f)
if __name__ == '__main__':
handle_build_config()
注:脚本中的 section_name = 'project'
,可以调整成其它名称,如:项目名,但需要与下文另一脚本中的 section_name
保持一致!
新建 Run Script
填写如下内容
# LinXunFeng 项目其它配置
cd script
python3 save_build_config.py # 记录编译时配置
该操作的用意:在编译的过程中,将 app
包所在路径保持至 script
目录的 build_time_conf.ini
文件中,并使用 build_dir_path
做为其 key
。
注:鉴于多人协作下,该 build_time_conf.ini
文件必定不可能相同,所以建议将该 build_time_conf.ini
文件添加至 .gitignore
中
# 2、处理 app
包并上传至蒲公英
push_dev_ipa.py
# -*- coding: utf-8 -*-
# -*- author: LinXunFeng -*-
import getopt, os, sys, shutil, time, json
from utils import file_util as FileUtil
from utils import upload_pgyer as PgyerUtil
from configparser import ConfigParser
from enum import Enum
class AppackSetKey(Enum):
"""appack_set的键"""
PGYER_API_KEY = "pgyer_api_key"
PGYER_USER_KEY = "pgyer_user_key"
PGYER_PASSWORD_KEY = "pgyer_api_password"
def get_build_dir_path(config_ini_path):
"""获取项目的编译目录路径"""
section_name = 'project'
config = ConfigParser()
config.read(config_ini_path)
if not config.has_section(section_name):
return ""
else:
return config.get(section_name, 'build_dir_path')
def get_build_config_ini_path(project_path):
"""获取build_conf.ini文件路径"""
return os.path.join(project_path, 'script', 'build_time_conf.ini')
def get_pgyer_config(project_path):
"""获取蒲公英的相关配置"""
config_set_json = os.path.join(project_path, 'fastlane', 'appack_set.json')
json_data = json.loads(FileUtil.read_file(config_set_json))
# print(json_data)
pgyer_api_key = json_data[AppackSetKey.PGYER_API_KEY.value]
pgyer_user_key = json_data[AppackSetKey.PGYER_USER_KEY.value]
pgyer_password = json_data[AppackSetKey.PGYER_PASSWORD_KEY.value]
return pgyer_api_key, pgyer_user_key, pgyer_password
def handle(project_path, target_name):
app_name = target_name + '.app'
config_ini_path = get_build_config_ini_path(project_path)
build_dir_path = get_build_dir_path(config_ini_path)
print('build_dir_path -- ', build_dir_path)
app_path = os.path.join(build_dir_path, 'Build/Products/Debug-iphoneos', app_name)
# print(app_path)
# cur_path = os.path.abspath('.')
script_path = os.path.join(project_path, 'script')
temp_path = os.path.join(script_path, 'temp')
payload_path = os.path.join(temp_path, 'Payload')
payload_app_path = os.path.join(payload_path, app_name)
if os.path.exists(temp_path):
shutil.rmtree(temp_path) # 移除Payload
time.sleep(1) # 等删除完
os.makedirs(payload_path) # 创建Payload
new_path = shutil.copytree(app_path, payload_app_path)
# print(new_path)
ipa_path = shutil.make_archive(payload_path, 'zip', temp_path)
ipa_path = shutil.move(ipa_path, os.path.join(temp_path, target_name + '.ipa'))
print(ipa_path)
# 上传至蒲公英
def payer_upload_callback():
shutil.rmtree(temp_path) # 删除temp目录
pgyer_api_key, pgyer_user_key, pgyer_password = get_pgyer_config(project_path)
PgyerUtil.upload_to_pgyer(ipa_path, pgyer_api_key, pgyer_user_key, password=pgyer_password, callbcak=payer_upload_callback)
if __name__ == "__main__":
argv = sys.argv[1:]
project_path = "" # 项目路径
target_name = "" # target名称
try:
opts, args = getopt.getopt(argv, "p:t:", ["path=", "target_name="])
except getopt.GetoptError:
print('push_dev_ipa.py -p "项目路径" -t "target名"')
sys.exit(2)
print(opts)
for opt, arg in opts:
if opt in ["-p", "--path"]:
project_path = arg
if len(project_path) == 0:
print('请输入项目的地址')
sys.exit('请输入项目的地址')
if opt in ["-t", "--target_name"]:
target_name = arg
# print(project_path)
handle(project_path, target_name)
配置说明:
config_set_json = os.path.join(project_path, 'fastlane', 'appack_set.json')
我们项目中与蒲公英配置相关的内容存入于 项目/fastlane/appack_set.json
,可以根据自身实际情况进行调整
{
...
"pgyer_api_key": "api_key_xxx",
"pgyer_user_key": "user_key_xxx",
"pgyer_api_password": "api_password_xxx"
...
}
在使用该脚本前请根据 【Mac上pyenv的安装与使用】 配置虚拟环境 env383_ScriptBox
,完成后激活该虚拟环境,按如下命令安装依赖包
pip install -r requirements.txt
使用:
python push_dev_ipa.py -p "项目路径" -t "target名"
# 五、实现一键
使用 Shuttle (opens new window) 将该命令保存起来,实现在编译完成后,一键打包上传至蒲公英
{
"更新测试包": [
{
"cmd": "cd /Users/lxf/Desktop/github/script_box; python push_dev_ipa.py -p '/Users/lxf/Desktop/github/LXFCardsLayout/'",
"inTerminal": "new",
"name": "上传测试包至蒲公英",
"title": "上传测试包至蒲公英"
}
]
}
此处说明一下 cd
命令的用意:
cd /Users/lxf/Desktop/github/script_box
在 script_box
下存有一个名为 .python-version
的文件,里面仅写了 env383_ScriptBox
,这是一个 python
虚拟环境,其作用是:当进入包含该文件所在目录时,会自动切换至该 python
虚拟环境。
我使用的是 pyenv
来实现 python
虚拟环境,详细说明可以看我的另一篇文章:【Mac上pyenv的安装与使用】
# 六、其它
Shuttle
虽然好用,但是在后续使用过程中,你一定会遇到多处配置相同的情况,如果你想解决该问题,不妨看我的解决方案:【Jsonnet - json数据模板语言】
脚本链接:LinXunFeng/script_box: 脚本工具箱 (github.com) (opens new window)
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08