Xcode9.3 xcodebuild 自动化打包发布到蒲公英并发送邮件通知

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lvchenqiang_/article/details/79916585

前言

从事iOS最经常遇到的莫过于 测试同学过来 通知你 赶紧发个包 ~ ~ 。。然后一顿操作。作为一个有着多年iOS开发经验的程序猿,肯定不能再走寻常路,遂决定亲自写个脚本 ps: 其实是python忘的差不多了(本来也不咋的) 赶紧练练手 复习复习

相关配置

前提:mac配置python3
IDE工具:Pycharm
网络请求工具:Requests
脚本工具: Xcode

代码实现

编译并打包生成ipa文件

清理工程(command+shift+k)

  def cleanProject(self):
        global isWorkSpace
        if isWorkSpace:
            os.system(
                'cd %s;xcodebuild -workspace %s.xcworkspace -scheme %s clean' % (mainPath,  targetName, targetName))
        else:
            os.system('cd %s;xcodebuild -target %s clean' % (mainPath, targetName))
        return

编译工程

  def buildProject(self):

        if isWorkSpace:
            os.system(
                "cd %s;xcodebuild -workspace %s.xcworkspace -scheme %s -configuration Release  -archivePath %s/%s.xcarchive clean archive" % (
                    mainPath, targetName, targetName, archivePath, targetName))
        else:
            os.system(
                "cd %s;xcodebuild -project %s.xcodeproj -scheme %s -configuration Release  -archivePath %s/%s.xcarchive clean archive" % (
                    mainPath, targetName, targetName, archivePath, targetName))
        return

导出并生成IPA文件

        def cerateIPA(self):
        if isDev:
          os.system("cd %s; xcodebuild -exportArchive -archivePath %s/%s.xcarchive -exportPath %s/%s -exportOptionsPlist %s/DevExportOptions.plist -allowProvisioningUpdates -quiet" % (
                mainPath, archivePath, targetName, archivePath, targetName, archivePath))
        else:
            os.system(
                "cd %s; xcodebuild -exportArchive -archivePath %s/%s.xcarchive -exportPath %s/%s -exportOptionsPlist %s/DisExportOptions.plist -allowProvisioningUpdates -quiet" % (
                    mainPath, archivePath, targetName, archivePath, targetName, archivePath))
        return
上传到蒲公英

调用蒲公英API,将ipa文件上传至自己账户

   def uploadToPGY(self):
        print("上传到蒲公英")
        path = "%s/%s.ipa/%s.ipa" % (archivePath, targetName, targetName)
        f_op = open(path, 'rb')
        print(path)
        print(f_op.readlines())

        if os.path.exists(path):
            print("找到ipa文件")
            # 请求参数字典
            params = {
                'uKey': PGY_UKey,
                '_api_key': PGY_APIKey,
                'installType': PGY_INSTALL_type,
                'password': PGY_PW,
                'updateDescription': PGY_DES
            }

            response = requests.post(PGY_URL, files={"file": open(path, 'rb')}, data=params)


        else:
            print("没有找到ipa文件")
上传给Itunes Connect
  """
          altoolPath="/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
          ${altoolPath} --validate-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml >>
          ${altoolPath} --upload-app -f ${ipaPath} -u xxxxxx -p xxxxxx -t ios --output-format xml

          --validate-app 您要验证指定的 App
          --upload-app   您要上传指定的 App
          -f file        正在验证或上传的 App 的路径和文件名。
          -u username    您的用户名
          -p password    您的用户密码
          --output-format [xml | normal]   您想让 Application Loader 以结构化的 XML 格式还是非结构化的文本格式返回输出信息。默认情况下,Application Loader 以文本格式返回输出信息


          success-message
          product-errors

          """
    def uploadToItunesConnect(self):
        print("上传到ItunesConnect")
        altoolPath = "/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/altool"
        validateresult = os.popen("%s --validate-app -f %s/ -u %s -p %s -t ios --output-format xml" % (altoolPath, ipaPath, DeveloperAccountName, DeveloperAccountPWD)).read()
        uploadresult = os.popen("%s --upload-app -f %s -u %s -p %s -t ios --output-format xml" % (altoolPath, ipaPath, DeveloperAccountName, DeveloperAccountPWD)).read()

        # 处理plist文件
        pl = plistlib.readPlistFromBytes(str(uploadresult).encode())

        print(pl["product-errors"])
        print("输出成功")

        fp = open("1.plist", 'w')
        fp.write(str(uploadresult))
        fp.close()

        if("success-message" in str(uploadresult)):
             print("上传成功")
        elif("product-errors" in str(uploadresult)):
             print("上传失败 %s", uploadresult)
        else:
             print("未知失败原因")
        return
        self.sendMail({"appName": targetName,
                        "appVersion": "appVersion",
                        "appShortcutUrl": "暂无",
                        "appUpdated": "暂无"
                       })
发送邮件给相关测试人员
    def sendMail(self, responseResult):
        print("发送邮件")

        # if not os.path.exists("%s/%s.ipa/%s.ipa" % (archivePath, targetName, targetName)):
        #     print("发送邮件 没有找到ipa文件")
        #     return
        msgInfo = "<html><body><h1>您好,新版本信息如下</h1><table><tr><th>应用名称</th><th>%s</th></tr><tr><th>应用版本</th><th>%s</th></tr><tr><th>应用地址</th><th>%s</th></tr><tr><th>应用更新时间</th><th>%s</th></tr></table></body></html>" % (
                      responseResult["appName"], str(responseResult["appVersion"]), str(responseResult["appQRCodeURL"]), str(responseResult["appUpdated"]))
        print(msgInfo)
        msg = MIMEText(msgInfo, 'html', 'utf-8')
        msg["Subject"] = MAIL_SUBJECT
        msg['From'] = self._format_addr('客户端 <%s>' % MAIL_FROM)
        msg['To'] = self._format_addr('测试同学 <%s>' % MAIL_TO)
        try:
            # // 创建一个SMTP对象
            server = smtplib.SMTP()
            print(server)
            # // 通过connect方法链接到smtp主机
            server.connect(MAIL_HOST, "25")
            server.set_debuglevel(1)
            # // 启动安全传输模式
            # server.starttls()
            # // 登录邮箱
            # 校验用户,密码
            server.login(MAIL_FROM, Mail_PassWord)
            # // 发送邮件
            server.sendmail(MAIL_FROM, [MAIL_TO], msg.as_string())
            server.quit()
            # 发送成功并打印
            print("邮件发送成功 \n发送人:%s\n发送内容:\n%s接收者:%s " % (MAIL_FROM, msg, MAIL_TO))

        except Exception as e:
            print("邮件发送失败:" + str(e))

备注

1、自动化打包方式的选择

 自动化打包方式
 1、xcodebuild + xcrun(不推荐)
 2、 arhive+exportArchive

之前的打包方式大多都是采用第一种 适用于Xcode8之前的版本。 本人之前用的shell 也是找的网上大佬 用第一种方式实现 的。 本人在实践中,最后一只过不去,遂直接选择用第二种。实验证明可行。

2、Pycharm破解

感谢网上的大佬,我使用的是pycharm 2018.也是从网上找的激活方式。

3、最后附上完整的源码

"""
 1、找到工程 并打包生成ipa文件
 2、ipa文件上传
 3、发送邮件给测试人员

 自动化打包方式
 xcodebuild + xcrun(不推荐)
 arhive+exportArchive

  """

import os
import getpass
import requests
import smtplib
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
from email.header import Header

# 蒲公英相关参数设置
PGY_URL = "http://www.pgyer.com/apiv1/app/upload" //蒲公英的的接口
PGY_UKey = "************"                /// 蒲公英账户下的用户key
PGY_APIKey = "*********"                 /// 蒲公英账户下的接口key
PGY_PW = ""                              ///应用安装密码 (选填)
PGY_INSTALL_type = 1                    /// 应用安装方式 值为(1,2,3)。1:公开,2:密码安装,3:邀请安装。默认为1公开
PGY_DES = ""                            /// 应用更新描述信息
# 项目参数相关设置
isWorkSpace = False                     /// 是否是WorkSpace

# 项目的相关配置
mainPath = "/Users/xxxxxx/xxxxx/xxxx/xxxxxxx"    /// 项目的主路径
targetName = "xxxxxxx"                          ///项目的名称 类似xxxxx.xcodeproj
certificateName = "xxxxxxxxxxxx"                 ///证书名 Automatic 模式下不需要
archivePath = "/Users/xxxxxxxx/xxxxx/xxxxxx/"   /// 生成的解压缩文件路径

# 邮件的相关参数设置
MAIL_HOST = "smtp.xxxxx.com"                  /// 邮箱host        
# //邮件标题
MAIL_SUBJECT = "测试版本已经发布"               /// 邮箱的标题
# //收件人
MAIL_TO = "xxxxx@xxxxx.cn"                   /// 收件人邮箱地址 可传数组
# //发件人MAIL_
MAIL_FROM = "xxxxx@xxxxx.cn"                 /// 发件人邮箱地址
Mail_PassWord = "************"               /// 发件人邮箱登录密码


class IPAHelper(object):
    def __init__(self):
        print("工具初始化")


     # clean工程
    def cleanProject(self):
        global isWorkSpace
        if isWorkSpace:
            os.system(
                'cd %s;xcodebuild -workspace %s.xcworkspace -scheme %s clean' % (mainPath, targetName, targetName))
        else:
            os.system('cd %s;xcodebuild -target %s clean' % (mainPath, targetName))
        return
    # build工程
    def buildProject(self):

        if isWorkSpace:
            os.system(
                "cd %s;xcodebuild -workspace %s.xcworkspace -scheme %s -configuration Release  -archivePath %s/%s.xcarchive clean archive" % (
                    mainPath, targetName, targetName, archivePath, targetName))
        else:
            os.system(
                "cd %s;xcodebuild -project %s.xcodeproj -scheme %s -configuration Release  -archivePath %s/%s.xcarchive clean archive" % (
                    mainPath, targetName, targetName, archivePath, targetName))
        return
    #生成ipa文件
    def cerateIPA(self):
        os.system(
            "cd %s; xcodebuild -exportArchive -archivePath %s/%s.xcarchive -exportPath %s/%s.ipa -exportOptionsPlist %s/IPA.plist -allowProvisioningUpdates -quiet" % (
                mainPath, archivePath, targetName, archivePath, targetName, archivePath))

        return
    # 上传到蒲公英
    @property
    def uploadToPGY(self):
        print("上传到蒲公英")
        path = "%s/%s.ipa/%s.ipa" % (archivePath, targetName, targetName)
        f_op = open(path, 'rb')
        print(path)
        print(f_op.readlines())

        if os.path.exists(path):
            print("找到ipa文件")
            # 请求参数字典
            params = {
                'uKey': PGY_UKey,
                '_api_key': PGY_APIKey,
                'installType': PGY_INSTALL_type,
                'password': PGY_PW,
                'updateDescription': PGY_DES
            }

            response = requests.post(PGY_URL, files={"file": open(path, 'rb')}, data=params)

            if str(response.json()["code"]) == "0":
                self.sendMail(response.json()["data"])


        else:
            print("没有找到ipa文件")

    def _format_addr(self, s):
        name, addr = parseaddr(s)
        return formataddr((Header(name, 'utf-8').encode(), addr))
    #发送邮件给测试人员
    def sendMail(self, responseResult):
        print("发送邮件")

        # if not os.path.exists("%s/%s.ipa/%s.ipa" % (archivePath, targetName, targetName)):
        #     print("发送邮件 没有找到ipa文件")
        #     return
        msgInfo = "<html><body><table><tr><th>应用名称</th><th>%s</th></tr><tr><th>应用版本</th><th>%s</th></tr><tr><th>应用地址</th><th>%s</th></tr><tr><th>应用更新时间</th><th>%s</th></tr></table></body></html>" % (
                      responseResult["appName"], str(responseResult["appVersion"]), str(responseResult["appQRCodeURL"]), str(responseResult["appUpdated"]))
        print(msgInfo)
        msg = MIMEText(msgInfo, 'html', 'utf-8')
        msg["Subject"] = MAIL_SUBJECT
        msg['From'] = self._format_addr('iOS开发 <%s>' % MAIL_FROM)
        msg['To'] = self._format_addr('白天不懂夜的黑 <%s>' % MAIL_TO)
        try:
            # // 创建一个SMTP对象
            server = smtplib.SMTP()
            print(server)
            # // 通过connect方法链接到smtp主机
            server.connect(MAIL_HOST, "25")
            #server.set_debuglevel(1)
            # // 启动安全传输模式
            # server.starttls()
            # // 登录邮箱
            # 校验用户,密码
            server.login(MAIL_FROM, Mail_PassWord)
            # // 发送邮件
            server.sendmail(MAIL_FROM, [MAIL_TO], msg.as_string())
            server.quit()
            # 发送成功并打印
            print("邮件发送成功 \n发送人:%s\n发送内容:\n%s接收者:%s " % (MAIL_FROM, msg, MAIL_TO))

        except Exception as e:
            print("邮件发送失败:" + str(e))


if __name__ == '__main__':
    helper = IPAHelper()

    # clean工程
    helper.cleanProject()
    # build app
    helper.buildProject()
    # 生成ipa
    helper.cerateIPA()
    # 上传到蒲公英
    helper.uploadToPGY()
    # 发送邮件给测试人员
    helper.sendMail();

4、xcodebuild tools 简介
在上面的代码中,我们使用到了一些 xcodebuild 的一下简单命令 接下来 我们就解释一下

clean 指令

  # -target {工程的名字}
  os.system('cd %s;xcodebuild -target %s clean' % (mainPath, targetName))

build指令

# -project {项目名称.xcodeproj}  
# -scheme {项目名称}
# -configuration  {构建版本(Debug or Release)}
# -archivePath  {archive文件的生成路径}
# clean archive  清理
 os.system("cd %s;xcodebuild -project %s.xcodeproj -scheme %s -configuration Release  -archivePath %s/%s.xcarchive clean archive" % (
                    mainPath, targetName, targetName, archivePath, targetName))

exportArchive生成ipa指令

#-archivePath {之前生成archive文件的路径}
#-exportPath {到处ipa的路径}
# -exportOptionsPlist exportOptionsPlist文件路径 

# -allowProvisioningUpdates 允许配置更新
os.system("cd %s; xcodebuild -exportArchive -archivePath %s/%s.xcarchive -exportPath %s/%s.ipa -exportOptionsPlist %s/IPA.plist -allowProvisioningUpdates -quiet"  % (mainPath, archivePath, targetName, archivePath, targetName, archivePath))

exportOptionsPlist文件格式 如果不想编辑 也可以从之前的ipa包中找到

这里写图片描述

# uploadSymbols
# uploadBitcode
# compileBitcode
# method 打包方式(development、app-store、ad-hoc、enterprise)
<?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>uploadSymbols</key>
    <true/>
    <key>uploadBitcode</key>
    <true/>
    <key>method</key>
    <string>development</string>
    <key>compileBitcode</key>
    <true/>
</dict>
</plist>

altool打包到Itunes Connect

您可以使用 Application Loader 的命令行工具 altool,验证 App 二进制文件并将其上传至 App Store。

若要在将有效构建版本上传或自动上传至 App Store 之前验证构建版本,您可在持续集成系统中包含 altool。altool 位于以下文件夹:Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/。

若要运行 altool,请在命令行指定以下一项操作:

$ altool --validate-app -f file -u username [-p password] [--output-format xml]
$ altool --upload-app -f file -u username [-p password] [--output-format xml]

 --validate-app 您要验证指定的 App
 --upload-app   您要上传指定的 App
 -f file        正在验证或上传的 App 的路径和文件名。
 -u username    您的用户名
 -p password    您的用户密码
 --output-format [xml | normal]   您想让 Application Loader 以结构化的 XML 格式还是非结构化的文本格式返回输出信息。默认情况下,Application Loader 以文本格式返回输出信息


 success-message   成功标识
 product-errors    失败标识

最后 发现了一个看起来比较好用的工具:nomad-cli

阅读更多

没有更多推荐了,返回首页