Python的iOS自動化打包

lxiaok發表於2018-11-15
Python的iOS自動化打包

前言

這段時間剛剛學習了一段時間的Python,加上自己是做iOS開發的,就想著用Python來做一個自動化打包,可以自動完成打包,上傳到蒲公英,並且傳送郵箱給測試人員.

一是可以減少打包功夫,二來可以練練手,結合自己的工作來輸出一點東西.廢話不多說,直接上程式碼...

原理

就是使用xcodebuild來控制Xcode進行一系列的操作,從而完成打包的操作.

Python的iOS自動化打包

為什麼要做這個?

在我們日常開發的時候,特別是在內部測試的時間,有可能需要頻繁的打包,打包的工作比較繁瑣,需要等待點選下一步,選擇之類,影響了開發的節奏.(開玩笑,我能有啥節奏...), 為什麼不能直接執行,然後完成所有的操作呢?

思路:

從網上查詢了一些關於xcodebuild來打包的資料,從而得到:

  1. 找到對應的專案
  2. clean專案
  3. archive專案
  4. export IPA
  5. 上傳蒲公英
  6. 傳送郵件
  7. 收工

思路有了,動手起來.

執行環境

Python, Xcode

這些需要大家直接去搭建好環境...

準備工作

  1. 下載安裝pycharm(這只是我開發Python的工具而已,大家可以根據自己喜歡的來選擇)
  2. 註冊並認證蒲公英(不認證的話,是不能上傳的)
  3. 郵箱開啟POP3/SMTP服務(我使用的是QQ郵箱),記錄下16位授權碼
  4. 一個ExportOptions.plist檔案, 這個下面會解釋為什麼需要還有怎麼生成!
  5. 一份iOS專案程式碼→_→

完整程式碼

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time    : 2018/11/14 11:04 AM
# @Author  : liangk
# @Site    :
# @File    : auto_archive_ios.py
# @Software: PyCharm


import os
import requests
import webbrowser
import subprocess
import time
import smtplib
from email.mime.text import MIMEText
from email import encoders
from email.header import Header
from email.utils import parseaddr, formataddr

project_name = 'TestArchive'    # 專案名稱
archive_workspace_path = '/Users/使用者/Desktop/TestArchive'    # 專案路徑
export_directory = 'archive'    # 輸出的資料夾
ipa_download_url = 'https://www.pgyer.com/XXX' #蒲公英的APP地址

# 蒲公英賬號USER_KEY、API_KEY
USER_KEY = 'XXXXXXXXXXXXXXXXXXXX'
API_KEY = 'XXXXXXXXXXXXXXXXXXXX'

from_address = 'XXXXXXXXXXXXXXXXXXXX@qq.com'    # 傳送人的地址
password = 'XXXXXXXXXXXXXXXXXXXX'  # 郵箱密碼換成他提供的16位授權碼
to_address = 'XXXXXXXXXXXXXXXXXXXX@qq.com'    # 收件人地址,可以是多個的
smtp_server = 'smtp.qq.com'    # 因為我是使用QQ郵箱..


class AutoArchive(object):
    """自動打包並上傳到蒲公英,發郵件通知"""

    def __init__(self):
        pass

    def clean(self):
        print("\n\n===========開始clean操作===========")
        start = time.time()
        clean_command = 'xcodebuild clean -workspace %s/%s.xcworkspace -scheme %s -configuration Release' % (
            archive_workspace_path, project_name, project_name)
        clean_command_run = subprocess.Popen(clean_command, shell=True)
        clean_command_run.wait()
        end = time.time()
        # Code碼
        clean_result_code = clean_command_run.returncode
        if clean_result_code != 0:
            print("=======clean失敗,用時:%.2f秒=======" % (end - start))
        else:
            print("=======clean成功,用時:%.2f秒=======" % (end - start))
            self.archive()

    def archive(self):
        print("\n\n===========開始archive操作===========")

        # 刪除之前的檔案
        subprocess.call(['rm', '-rf', '%s/%s' % (archive_workspace_path, export_directory)])
        time.sleep(1)
        # 建立資料夾存放打包檔案
        subprocess.call(['mkdir', '-p', '%s/%s' % (archive_workspace_path, export_directory)])
        time.sleep(1)

        start = time.time()
        archive_command = 'xcodebuild archive -workspace %s/%s.xcworkspace -scheme %s -configuration Release -archivePath %s/%s' % (
            archive_workspace_path, project_name, project_name, archive_workspace_path, export_directory)
        archive_command_run = subprocess.Popen(archive_command, shell=True)
        archive_command_run.wait()
        end = time.time()
        # Code碼
        archive_result_code = archive_command_run.returncode
        if archive_result_code != 0:
            print("=======archive失敗,用時:%.2f秒=======" % (end - start))
        else:
            print("=======archive成功,用時:%.2f秒=======" % (end - start))
            # 匯出IPA
            self.export()

    def export(self):
        print("\n\n===========開始export操作===========")
        print("\n\n==========請你耐心等待一會~===========")
        start = time.time()
        # export_command = 'xcodebuild -exportArchive -archivePath /Users/liangk/Desktop/TestArchive/myArchivePath.xcarchive -exportPath /Users/liangk/Desktop/TestArchive/out -exportOptionsPlist /Users/liangk/Desktop/TestArchive/ExportOptions.plist'
        export_command = 'xcodebuild -exportArchive -archivePath %s/%s.xcarchive -exportPath %s/%s -exportOptionsPlist %s/ExportOptions.plist' % (
            archive_workspace_path, export_directory, archive_workspace_path, export_directory, archive_workspace_path)
        export_command_run = subprocess.Popen(export_command, shell=True)
        export_command_run.wait()
        end = time.time()
        # Code碼
        export_result_code = export_command_run.returncode
        if export_result_code != 0:
            print("=======匯出IPA失敗,用時:%.2f秒=======" % (end - start))
        else:
            print("=======匯出IPA成功,用時:%.2f秒=======" % (end - start))
            # 刪除archive.xcarchive檔案
            subprocess.call(['rm', '-rf', '%s/%s.xcarchive' % (archive_workspace_path, export_directory)])
            self.upload('%s/%s/%s.ipa' % (archive_workspace_path, export_directory, project_name))

    def upload(self, ipa_path):
        print("\n\n===========開始上傳蒲公英操作===========")
        if ipa_path:
            # https://www.pgyer.com/doc/api 具體引數大家可以進去裡面檢視,
            url = 'http://www.pgyer.com/apiv1/app/upload'
            data = {
                'uKey': USER_KEY,
                '_api_key': API_KEY,
                'installType': '1',
                'updateDescription': description
            }
            files = {'file': open(ipa_path, 'rb')}
            r = requests.post(url, data=data, files=files)
            if r.status_code == 200:
            	# 是否需要開啟瀏覽器
                # self.open_browser(self)
                self.send_email()
        else:
            print("\n\n===========沒有找到對應的ipa===========")
            return

    @staticmethod
    def open_browser(self):
        webbrowser.open(ipa_download_url, new=1, autoraise=True)

    @staticmethod
    def _format_address(self, s):
        name, address = parseaddr(s)
        return formataddr((Header(name, 'utf-8').encode(), address))

    def send_email(self):
        # https://www.pgyer.com/XXX app地址
        # 只是單純的發了一個文字郵箱,具體的發附件和圖片大家可以自己去補充
        msg = MIMEText('<html><body><h1>Hello</h1>' +
                       '<p>╮(╯_╰)╭<a href="https://www.pgyer.com/XXX">應用已更新,請下載測試</a>╮(╯_╰)╭</p>' +
                       '<p>蒲公英的更新會有延遲,具體版本時間以郵件時間為準</p>' +
                       '</body></html>', 'html', 'utf-8')
        msg['From'] = self._format_address(self, 'iOS開發團隊 <%s>' % from_address)
        msg['Subject'] = Header('來自iOS開發團隊的問候……', 'utf-8').encode()
        server = smtplib.SMTP(smtp_server, 25)  # SMTP協議預設埠是25
        server.set_debuglevel(1)
        server.login(from_address, password)
        server.sendmail(from_address, [to_address], msg.as_string())
        server.quit()
        print("===========郵件傳送成功===========")


if __name__ == '__main__':
    description = input("請輸入內容:")
    archive = AutoArchive()
    archive.clean()

複製程式碼

關於ExportOptions.plist檔案

因為 Xcode 9+ 預設不允許訪問鑰匙串的內容,必須要設定 allowProvisioningUpdates 才會允許,Python的Xcode外掛目前無法支援此項完成打包流程。

解決步驟如下:

1、手動Xcode10打包,匯出ExportOptions.plist檔案;

2、編輯ExportOptions.plist檔案,配置 provisioningProfiles 對應填入Bundle identifier及證照關聯配置檔案(打包時自動匹配或手動填入證照,provisioningProfiles需配置的必填資訊可自動生成);

3、提供ExportOptions.plist檔案路徑供Python指令碼呼叫(詳請參看Python指令碼程式碼)。

具體的內容

<?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>//是否編譯bitcode
    <true/>
    <key>method</key>
    <string>ad-hoc</string>/
    <key>provisioningProfiles</key>
    <dict>
        <key>檔案bundle id</key>
        <string>Adhoc_ID</string>
    </dict>
    <key>signingCertificate</key>//證照籤名
    <string>這裡填證照籤名</string>
    <key>signingStyle</key>
    <string>manual</string>
    <key>stripSwiftSymbols</key>
    <true/>
    <key>teamID</key>
    <string>AANCCUK4M3</string>//TeamID
    <key>thinning</key>
    <string>&lt;none&gt;</string>
</dict>
</plist>

複製程式碼

分析

xcodebuild archive -workspace XXX.xcworkspace -scheme XXX -configuration Release -archivePath XXX CONFIGURATION_BUILD_DIR ./dir ODE_SIGN_IDENTITY=證照 PROVISIONING_PROFILE=描述檔案UUID
複製程式碼
檔案 說明
-workspace XXX.xcworkspace XXX.xcworkspace需要編譯工程的工作空間名稱,如果工程不是.xcworkspace的,可以不需要-workspace XXX.xcworkspace這段話
-scheme XXX XXX是工程名稱,-scheme XXX是指定構建工程的名稱
-configuration Release 填入打包的方式是Debug或Release,就跟在Xcode中編譯前需要在Edit scheme的Build configuration中選擇打出來的包是Debug還是Release包一樣,-configuration就是配置編譯的Build configuration
-archivePath XXX 配置生成.xcarchive的路徑,
ODE_SIGN_IDENTITY=證照 配置打包的指定證照,如果該工程的Xcode已經配置好了證照,那麼不加入這段話也可以,打包出來的證照就是Xcode中配置好的。
PROVISIONING_PROFILE=描述檔案UUID 配置打包的描述檔案,同上,Xcode已經配置好了就不用在填入這段話了
CONFIGURATION_BUILD_DIR 配置編譯檔案的輸出路徑,如果需要用到.xcarchive檔案內部的dSYM等檔案,可以使用改欄位指定輸出路徑。

問題一

Python的iOS自動化打包

配置一下compileBicode=NO即可

Python的iOS自動化打包

感謝樹下敲程式碼的超人

相關文章