iOS开发

iOS 提高生产力(1) - Python 打包APP并发布蒲公

2020-11-03  本文已影响0人  BlackStar暗星

友情链接

iOS 提高生产力 - Python发布 pod 组件


完成源码在底部

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>&lt;none&gt;</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文件有几个点是需要注意的,只有几个关键值是必须要对的,其他的大部分写对写错无所谓

导出后,上传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 文件,打包可能会有问题(手头没这种项目,所以没试)

  • 如果 targetscheme 没有指定,都是取的 xcodebuild -list 返回对应数据的第一个 targetscheme ,对于含有多个 targetscheme 尽量在执行 .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>&lt;none&gt;</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()

上一篇下一篇

猜你喜欢

热点阅读