友情链接
完成源码在底部
xbuild.py文件所在路径(需要与 .xcodeproj 文件同级)
普通工程pod组件化开发脚本路径
确定所需全局变量
################### 打包配置 ##################
# boundleId
bundleid = ' 项目的 bundle identifier '
# 打包配置文件名称
profile_name = '项目的 profile 证书'
# 打包证书的 teamID
teamid = '打包证书的 teamId ,就是证书信息上有个括号里边的ID'
################## 所需全局变量 #################
# 参数解析器
parser = argparse.ArgumentParser()
#判断是否有 workspace
has_workspace = False
# archive 整个路径
archive_full_path = ''
# .xcodeproj 文件名称,带后缀
project_full_name = ''
# .xcworkspace 文件名称,带后缀
workspace_full_name = ''
# 导出的 ipa 完成 path
ipa_path = ''
初始化全局变量,添加参数,输出变量及参数信息
# 初始化参数
def init_parameter():
print('\n=========== xcode base info ===========\n')
os.system('xcodebuild -list')
print('\n')
build_list = os.popen('xcodebuild -list')
# 获取 scheme 和 target
schemes_name = ''
target_name = ''
for i,line in enumerate(build_list):
if line.find('Schemes:') != -1:
temp = build_list.readline().strip(' ')
schemes_name = temp.strip('\n')
elif line.find('Targets:') != -1:
temp = build_list.readline().strip(' ')
target_name = temp.strip('\n')
# 增加 scheme 和 target 参数
parser.add_argument('-scheme', default = schemes_name,
help='none input scheme will use fist scheme of "xcodebuild -list" command resualt')
parser.add_argument('-target', default = target_name,
help='none input target will use fist target of "xcodebuild -list" command resualt')
# 增加 环境 参数
parser.add_argument('-env', default='Release',
help='none input will use "Release"')
######### 路径相关 ##########
global archive_full_path
global has_workspace
global project_full_name
global workspace_full_name
# 获取当前目录下的所有文件名称
list_file_name = os.listdir(os.getcwd())
for file_name in list_file_name:
if file_name.endswith('.xcodeproj'):
name_split = file_name.split('.')
project_full_name = file_name
elif file_name.endswith('.xcworkspace'):
has_workspace = True
workspace_full_name = file_name
else:
pass
parser.add_argument('-project', default=project_full_name,help='none input project will use the ".xcodeproj" file prefix name')
user_root_pass = os.path.expanduser('~')
archive_path = os.path.join(user_root_pass, 'Library/Developer/Xcode/Archives/%s' %
(time.strftime('%Y-%m-%d', time.localtime())))
temp_parser = parser.parse_args()
temp_time = time.strftime('%Y-%m-%d %H.%M %Ss', time.localtime())
export_archive_name = '%s %s.xcarchive' % (temp_parser.target, temp_time)
archive_full_path = os.path.join(archive_path, export_archive_name)
print('\n************* 相关全局参数 ***************\n')
print('scheme = %s \ntarget = %s' %(temp_parser.scheme, temp_parser.target))
print('workspace_full_name : %s' % (workspace_full_name))
print('project_full_name : %s' % (project_full_name))
print('archive_full_path = %s' % (archive_full_path))
print('has_workspace = %s' % (has_workspace))
print('\n****************************')
clean 工程(可有可无)
# clean 工程
def xcode_clean():
print('\n============ clean build ==========\n')
clear_command = 'xcodebuild clean'
os.system(clear_command)
生成 archive
# 归档工程,生成 .xcarchive 文件
def xcode_archive():
print('\n============ xcode archive ==========\n')
temp_parser = parser.parse_args()
print(archive_full_path)
archive_command = 'xcodebuild archive -project "%s" -scheme "%s" -configuration %s -archivePath "%s"' % (
project_full_name, temp_parser.scheme,temp_parser.env, archive_full_path)
if has_workspace == True:
archive_command = 'xcodebuild archive -workspace "%s" -scheme "%s" -configuration %s -archivePath "%s"' % (
workspace_full_name, temp_parser.scheme, temp_parser.env, archive_full_path)
print(archive_command)
os.system(archive_command)
这里的archive 路径没有更改,依然放在了系统指定目录下,为了方便与系统同步,但是因为系统的命名规则问题,我们无法找到系统路径下的 archive
文件,所以对 archive
进行了重新命名,方便后边导出时使用archive
导出 IPA 包
# 导出 IPA 包
def xcode_export_ipa():
print('\n============ xcode export ipa ==========\n')
global ipa_path
user_path = os.path.expanduser('~')
temp_time = time.strftime('%Y-%m-%d %H%M %Ss',time.localtime())
ipa_path = os.path.join(user_path, 'Downloads/PYXCODE-IPA', temp_time)
if os.path.exists(ipa_path) == False:
os.system('mkdir -p "%s"' % (ipa_path))
plist_path = creat_config_plist(ipa_path)
if plist_path != '':
export_command = 'xcodebuild -exportArchive -archivePath "%s" -target app.ipa -exportPath "%s" -exportOptionsPlist "%s"' % (
archive_full_path, ipa_path, plist_path)
os.system(export_command)
导出时是需要一个 .plist 文件的,这个文件我们经常能看到,就是我们平时使用xcode导出 ipa 的时候,ipa 所在的文件夹里有个 ExportOptions.plist文件,就是我们需要的文件。
我们在使用脚本的时候,肯定不能再弄一个 plist 文件和脚本捆绑使用,那就太麻烦了,所以我们自动去生成这个文件
生成 .plist 文件
# 创建用于导出的 plist 配置文件
def creat_config_plist(plist_path):
"""
关键参数
method = ad-hoc
<dict>
<key > com.framework.BStar < /key >
<string > BStar Framework ADHoc < /string >
</dict>
teamID = 'xxx'
"""
config_plist_content = """
<?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>
<true/>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>ad-hoc</string>
<key>provisioningProfiles</key>
<dict>
<key>%s</key>
<string>%s</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>%s</string>
<key>thinning</key>
<string><none></string>
</dict>
</plist>
""" %(bundleid,profile_name,teamid)
plist_full_path = os.path.join(plist_path, 'ExportOptions.plist')
with open(plist_full_path, 'w') as plist:
plist.write(config_plist_content)
return plist_full_path
return ''
这个plist文件有几个点是需要注意的,只有几个关键值是必须要对的,其他的大部分写对写错无所谓
- boundleId (项目的 bundle identifier)
- profile_name (证书的配置文件 profile )
- teamId (证书的 所属 teamID )
导出后,上传IPA到蒲公英上
# 上传到蒲公英
def upload_to_pyger():
url = 'https://www.pgyer.com/apiv2/app/upload'
api_key = '530d2e8f9e50012acff783689e3af26d'
buildInstallType = 2
buildPassword = '111111'
buildUpdateDescription = '版本更新 version = %s' % (time.strftime('%Y-%m-%d %H%M', time.localtime()))
temp_parser = parser.parse_args()
file_path = '%s/%s.ipa' % (ipa_path, temp_parser.target)
try:
print('\n------------ 上传应用到 蒲公英 -------------\n')
print('\nfilepath = %s\n' % (file_path))
file = open(file_path, 'rb')
data = {
'_api_key': api_key,
'buildInstallType': buildInstallType,
'buildPassword': buildPassword,
'buildUpdateDescription': buildUpdateDescription,
}
print('\nparams = %s\n' % (data))
rsp = requests.post(url, params=data, files={'file': file})
file.close()
print ('%s\n%s'%(rsp,rsp.text))
except IOError as identifier:
print('****** 读取文件失败 ******')
except requests.ConnectionError as cerror:
print('****** 链接不到服务器 ******')
程序入口
if __name__ == '__main__':
init_parameter()
xcode_clean()
xcode_archive()
xcode_export_ipa()
upload_to_pyger()
重要说明
如果要上传到 fir 需要自行修改对应api
plist 生成 本来想自动获取 bundle id 和 证书名称 和 teamId 的,奈何没找到相关技术,如果有老哥懂得,麻烦告知一声
后续会使用Python制作桌面小工具代替命令行执行Python脚本,实现一键打包发布(还未开启计划)
没有研究打 App Store 版本,个人认为 并不适合App Store版本的打包发布
如果含有多个 .xcworkspace 或者 .xcodeproj 文件,打包可能会有问题(手头没这种项目,所以没试)
如果
target
和scheme
没有指定,都是取的xcodebuild -list
返回对应数据的第一个target
和scheme
,对于含有多个target
和scheme
尽量在执行.py
文件时 传入, 示例python xxx.py -scheme xxx target xxx
完整代码
#!/usr/bin/python3
# -*- coding:utf-8 -*-
import os,sys,time
import requests
import argparse
################### 打包配置 ##################
# boundleId
bundleid = ' 需要手动填写 '
# 打包配置文件名称
profile_name = ' 需要手动填写 '
# 打包证书的 teamID
teamid = ' 需要手动填写 '
################## 打包配置 #################
# 参数解析器
parser = argparse.ArgumentParser()
#判断是否有 workspace
has_workspace = False
# archive 整个路径
archive_full_path = ''
# .xcodeproj 文件名称,带后缀
project_full_name = ''
# .xcworkspace 文件名称,带后缀
workspace_full_name = ''
# 导出的 ipa 完成 path
ipa_path = ''
# 初始化参数
def init_parameter():
print('\n=========== xcode base info ===========\n')
os.system('xcodebuild -list')
print('\n')
build_list = os.popen('xcodebuild -list')
# 获取 scheme 和 target
schemes_name = ''
target_name = ''
for i,line in enumerate(build_list):
if line.find('Schemes:') != -1:
temp = build_list.readline().strip(' ')
schemes_name = temp.strip('\n')
elif line.find('Targets:') != -1:
temp = build_list.readline().strip(' ')
target_name = temp.strip('\n')
# 增加 scheme 和 target 参数
parser.add_argument('-scheme', default = schemes_name,
help='none input scheme will use fist scheme of "xcodebuild -list" command resualt')
parser.add_argument('-target', default = target_name,
help='none input target will use fist target of "xcodebuild -list" command resualt')
# 增加 环境 参数
parser.add_argument('-env', default='Release',
help='none input will use "Release"')
######### 路径相关 ##########
global archive_full_path
global has_workspace
global project_full_name
global workspace_full_name
# 获取当前目录下的所有文件名称
list_file_name = os.listdir(os.getcwd())
for file_name in list_file_name:
if file_name.endswith('.xcodeproj'):
name_split = file_name.split('.')
project_full_name = file_name
elif file_name.endswith('.xcworkspace'):
has_workspace = True
workspace_full_name = file_name
else:
pass
parser.add_argument('-project', default=project_full_name,help='none input project will use the ".xcodeproj" file prefix name')
user_root_pass = os.path.expanduser('~')
archive_path = os.path.join(user_root_pass, 'Library/Developer/Xcode/Archives/%s' %
(time.strftime('%Y-%m-%d', time.localtime())))
temp_parser = parser.parse_args()
temp_time = time.strftime('%Y-%m-%d %H.%M %Ss', time.localtime())
export_archive_name = '%s %s.xcarchive' % (temp_parser.target, temp_time)
archive_full_path = os.path.join(archive_path, export_archive_name)
print('\n************* 相关全局参数 ***************\n')
print('scheme = %s \ntarget = %s' %(temp_parser.scheme, temp_parser.target))
print('workspace_full_name : %s' % (workspace_full_name))
print('project_full_name : %s' % (project_full_name))
print('archive_full_path = %s' % (archive_full_path))
print('has_workspace = %s' % (has_workspace))
print('\n****************************')
# clean 工程
def xcode_clean():
print('\n============ clean build ==========\n')
clear_command = 'xcodebuild clean'
os.system(clear_command)
# 归档工程,生成 .xcarchive 文件
def xcode_archive():
print('\n============ xcode archive ==========\n')
temp_parser = parser.parse_args()
print(archive_full_path)
archive_command = 'xcodebuild archive -project "%s" -scheme "%s" -archivePath "%s"' % (
project_full_name, temp_parser.scheme, archive_full_path)
if has_workspace == True:
archive_command = 'xcodebuild archive -workspace "%s" -scheme "%s" -archivePath "%s"' % (
workspace_full_name, temp_parser.scheme, archive_full_path)
print(archive_command)
os.system(archive_command)
# 导出 IPA 包
def xcode_export_ipa():
print('\n============ xcode export ipa ==========\n')
global ipa_path
user_path = os.path.expanduser('~')
temp_time = time.strftime('%Y-%m-%d %H%M %Ss',time.localtime())
ipa_path = os.path.join(user_path, 'Downloads/PYXCODE-IPA', temp_time)
if os.path.exists(ipa_path) == False:
os.system('mkdir -p "%s"' % (ipa_path))
plist_path = creat_config_plist(ipa_path)
if plist_path != '':
export_command = 'xcodebuild -exportArchive -archivePath "%s" -target app.ipa -exportPath "%s" -exportOptionsPlist "%s"' % (
archive_full_path, ipa_path, plist_path)
os.system(export_command)
# 创建用于导出的 plist 配置文件
def creat_config_plist(plist_path):
"""
关键参数
method = ad-hoc
<dict>
<key > com.framework.BStar < /key >
<string > BStar Framework ADHoc < /string >
</dict>
teamID = 'xxx'
"""
config_plist_content = """
<?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>
<true/>
<key>destination</key>
<string>export</string>
<key>method</key>
<string>ad-hoc</string>
<key>provisioningProfiles</key>
<dict>
<key>%s</key>
<string>%s</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>%s</string>
<key>thinning</key>
<string><none></string>
</dict>
</plist>
""" %(bundleid,profile_name,teamid)
plist_full_path = os.path.join(plist_path, 'ExportOptions.plist')
with open(plist_full_path, 'w') as plist:
plist.write(config_plist_content)
return plist_full_path
return ''
# 上传到蒲公英
def upload_to_pyger():
url = 'https://www.pgyer.com/apiv2/app/upload'
api_key = '530d2e8f9e50012acff783689e3af26d'
buildInstallType = 2
buildPassword = '111111'
buildUpdateDescription = '版本更新 version = %s' % (time.strftime('%Y-%m-%d %H%M', time.localtime()))
temp_parser = parser.parse_args()
file_path = '%s/%s.ipa' % (ipa_path, temp_parser.target)
try:
print('\n------------ 上传应用到 蒲公英 -------------\n')
print('\nfilepath = %s\n' % (file_path))
file = open(file_path, 'rb')
data = {
'_api_key': api_key,
'buildInstallType': buildInstallType,
'buildPassword': buildPassword,
'buildUpdateDescription': buildUpdateDescription,
}
print('\nparams = %s\n' % (data))
rsp = requests.post(url, params=data, files={'file': file})
file.close()
print ('%s\n%s'%(rsp,rsp.text))
except IOError as identifier:
print('****** 读取文件失败 ******')
except requests.ConnectionError as cerror:
print('****** 链接不到服务器 ******')
if __name__ == '__main__':
init_parameter()
xcode_clean()
xcode_archive()
xcode_export_ipa()
upload_to_pyger()
网友评论