美文网首页iOS开发
iOS 提高生产力(1) - Python 打包APP并发布蒲公

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

作者: BlackStar暗星 | 来源:发表于2020-11-03 17:23 被阅读0次

    友情链接

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

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

    • 如果 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()
    
    

    相关文章

      网友评论

        本文标题:iOS 提高生产力(1) - Python 打包APP并发布蒲公

        本文链接:https://www.haomeiwen.com/subject/rfymvktx.html