iOS 脚本实现自动构建打包(xcodebuild)---Python版

打印 上一主题 下一主题

主题 1007|帖子 1007|积分 3025

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
iOS 脚本实现自动构建打包(xcodebuild)—Python版

在软件开发过程中,尤其是iOS应用的发布,打包是至关重要的环节。为了简化打包的流程,将用Python实现自动构建打包功能。
当然,要想学会自动化打包,就要先相识自动化打包所必要用到的工具及打包过程。
⭐️⭐️⭐️AutoBuildIPA项目地点: https://gitee.com/miniaj/auto-build-ipa.git
相识xcodebuild

1.脚本相干命令

  1. // 1.清除编译过程生成文件
  2. xcodebuild clean -workspace <xxx.workspace> -scheme <schemeName> -configuration <Debug|Release>
  3. // 2.编译项目,保证验证项目正常
  4. xcodebuild build -workspace <xxx.workspace> -scheme <schemeName> -configuration <Debug|Release>
  5. // 3.编译并生成.xcarchive包
  6. xcodebuild archive -archivePath <archivePath> -workspace <XXX.xcworkspace> -scheme <schemeNmae> -configuration <Debug|Release>
  7. // 4.将生成的.archive包导出成ipa文件
  8. xcodebuild  -exportArchive -archivePath <archivePath> -exportPath <exportPath> -exportOptionsPlist <exportOptionsPlistPath>
复制代码
2.其他命令

  1. // 1.可以看到工程的Target ,scheme,configuration配置参数
  2. cd xxx项目路径下
  3. xcodebuild -list
  4. // 2.可以查看本机有哪些描述文件
  5. open ~/Library/MobileDevice/Provisioning\ Profiles
  6. // 3.可以查看描述文件具体信息
  7. /usr/bin/security cms -D -i xxx.mobileprovision
复制代码
相识Python详细实现打包流程

文章会涉及部分工具类,详细实现可以参照源码,本文章不做解释了。1
导入相干py库
  1. import datetime
  2. import plistlib
  3. import re
  4. import shutil
  5. import subprocess
  6. import time
  7. import os
  8. from mobileprovision import MobileProvisionModel
  9. def executeCmd(cmd):
  10.         """
  11.     指定终端指令
  12.     :param cmd: 指令字符串
  13.     :return: 返回输出字符串, 判断是否报错
  14.     """
  15.     p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
  16.     output, stderrOutput = p.communicate()
  17.         output = output.decode()
  18.         esultCode = p.returncode
  19.         return output, resultCode != 0
  20.        
  21. def dealPrintMsg(self, msg, status=1):
  22.         """
  23.         处理打印行为
  24.         :param status: 状态
  25.         :param msg: 打印信息
  26.         :return: 无返回值
  27.         """
  28.         print("{状态: %s, 打印信息: %s}" % (str(status), msg))
复制代码
1.查抄项目基础信息,并复制暂时项目进行操纵

  1.     def start(self):
  2.         if not os.path.exists(self.projectPath):
  3.             self.dealPrintMsg("项目路径为空, 请检查后重试", 2)
  4.             return
  5.         projectSuffixName = os.path.splitext(self.projectPath)[1]
  6.         # 判断是处理项目空间还是项目
  7.         self.isXcworkspace = projectSuffixName == '.xcworkspace'
  8.         # 获取项目名称
  9.         oldProjectName = FileToolController.backFileNameAndFolderName(self.projectPath, 3)
  10.         if self.projectName == "" or not self.isXcworkspace:
  11.             # 项目名称
  12.             self.projectName = oldProjectName
  13.         self.projectFolder = os.path.dirname(self.projectPath)
  14.         # 拷贝临时项目
  15.         newProjectFolderPath = os.path.join(os.path.dirname(self.projectFolder),
  16.                                             os.path.basename(self.projectFolder) + "_temp" + str(int(time.time())))
  17.         shutil.copytree(os.path.dirname(self.projectPath), newProjectFolderPath)
  18.         self.projectFolder = newProjectFolderPath
  19.         # 操作项目路径
  20.         self.projectPath = os.path.join(newProjectFolderPath, oldProjectName + projectSuffixName)
  21.                 # 输出的路径
  22.         self.exportDirectory = self.projectFolder + '/auto_archive'  
复制代码
2.设置配置信息

  1.     def setConfigAppInfo(self):
  2.         newProjectPath = self.projectPath
  3.         if self.isXcworkspace:
  4.                 # 如果是Xcworkspace项目,需要找到指定的编译项目路径
  5.             for root, dirs, files in os.walk(self.projectFolder):
  6.                 for dirName in dirs:
  7.                     if dirName == (self.projectName + ".xcodeproj"):
  8.                         newProjectPath = os.path.join(root, dirName)
  9.                         break
  10.         pbxprojPath = os.path.join(newProjectPath, "project.pbxproj")
  11.         if not os.path.exists(pbxprojPath):
  12.             self.dealPrintMsg("project.pbxproj文件不存在,无法配置项目,跳过")
  13.         else:
  14.             self.dealPrintMsg("===========开始配置应用信息操作===========")
  15.             newProjectFolder = os.path.dirname(newProjectPath)
  16.             newProjectFolderPath = os.path.join(newProjectFolder, self.projectName)
  17.             infoFilePath = os.path.join(newProjectFolderPath, "info.plist")
  18.             
  19.             with open(pbxprojPath, 'r') as file:
  20.                 fileTempData = file.read()
  21.                 # 获取证书信息
  22.                 mpModel = MobileProvisionModel(self.mobileprovisionPath)
  23.                                 # mpModel.team_identifier 证书团队ID
  24.                                 # mpModel.name 证书名称
  25.                 targetDic = {"DEVELOPMENT_TEAM": str(mpModel.team_identifier),
  26.                              "PROVISIONING_PROFILE_SPECIFIER": str(mpModel.name)}
  27.                
  28.                 # 替换项目相关信息
  29.                 for key, value in targetDic.items():
  30.                     targetPattern = re.compile(r'(%s.*=.(.*?);)' % key)
  31.                     targetResult = list(set(targetPattern.findall(fileTempData)))
  32.                     for targetArr in targetResult:
  33.                         if len(targetArr) >= 2:
  34.                             oldTeamStr = str(targetArr[0])
  35.                             replaceStr = str(targetArr[1])
  36.                             if replaceStr == """" or replaceStr == value:
  37.                                 continue
  38.                             newTeamStr = oldTeamStr.replace(replaceStr, "%s" % value)
  39.                         fileTempData = fileTempData.replace(oldTeamStr, newTeamStr)
  40.                 fileData = fileTempData
  41.             # 写入project.pbxproj文件
  42.             with open(pbxprojPath, 'w', encoding='utf-8') as file:
  43.                 file.write(fileData)
复制代码
3.整理编译文件

  1.     def clean(self):
  2.         self.dealPrintMsg("===========开始clean操作===========")
  3.         start = time.time()
  4.         getXcworkspaceStr = "-workspace" if self.isXcworkspace else "-project"
  5.         cleanOpt = ('xcodebuild clean "%s" "%s" -scheme "%s" -configuration Release' %
  6.                     (getXcworkspaceStr, self.projectPath, self.projectName))
  7.         result, isError = self.executeCmd(cleanOpt)
  8.         end = time.time()
  9.         # clean 结果
  10.         if isError:
  11.             self.dealPrintMsg("%s===========clean失败,用时:%.2f秒===========" % (result, end - start), 2)
  12.         else:
  13.             self.dealPrintMsg(result, 4)
  14.             self.dealPrintMsg("===========clean成功,用时:%.2f秒===========" % (end - start))
复制代码
4.编译项目,保证验证项目正常

  1.     def buildProj(self):
  2.         self.dealPrintMsg("===========开始build操作===========")
  3.         start = time.time()
  4.         buildOpt = ('xcodebuild build "%s" "%s" -scheme "%s" -configuration Release -destination "generic/platform=iOS"'
  5.                     % (self.getXcworkspaceStr(), self.projectPath, self.projectName))
  6.         result, isError = self.executeCmd(buildOpt)
  7.         end = time.time()
  8.         # build 结果
  9.         if isError:
  10.             self.dealPrintMsg("%s===========build失败,用时:%.2f秒===========" % (result, end - start), 2)
  11.         else:
  12.             self.dealPrintMsg(result, 4)
  13.             self.dealPrintMsg("===========build成功,用时:%.2f秒===========" % (end - start))
  14.             buildPath = self.projectFolder + "/build"
  15.             if os.path.exists(self.projectFolder + "/build"):
  16.                 shutil.rmtree(buildPath)
复制代码
5.编译并生成.xcarchive包

  1.     def archive(self):
  2.         self.dealPrintMsg("===========开始archive操作===========")
  3.         subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
  4.         time.sleep(1)
  5.         subprocess.call(['mkdir', '-p', '%s' % self.exportDirectory])
  6.         time.sleep(1)
  7.         start = time.time()
  8.         getXcworkspaceStr = "-workspace" if self.isXcworkspace else "-project"
  9.         archiveOpt = ('xcodebuild archive "%s" "%s" -scheme "%s" -configuration Release -archivePath "%s/%s" '
  10.                       '-destination "generic/platform=iOS"' %
  11.                       (getXcworkspaceStr, self.projectPath, self.projectName, self.exportDirectory,
  12.                        self.projectName))
  13.         result, isError = self.executeCmd(archiveOpt)
  14.         end = time.time()
  15.         # archive 结果
  16.         if isError:
  17.             subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
  18.             self.dealPrintMsg("%s===========archive失败,用时:%.2f秒===========" % (result, end - start), 0)
  19.         else:
  20.             self.dealPrintMsg(result, 4)
  21.             self.dealPrintMsg("===========archive成功,用时:%.2f秒===========" % (end - start))
复制代码
6.处理plist文件

  1.     def createExportOptions(self):
  2.         self.dealPrintMsg("===========开始处理plist文件操作===========")
  3.         mpModel = MobileProvisionModel(self.mobileprovisionPath)
  4.         # app-store,ad-hoc,enterprise, development
  5.         isDev = mpModel.entitlements.get('aps-environment') != "production"
  6.         methodStr = "development" if isDev else ("app-store-connect" if self.isAppStore else "ad-hoc")
  7.         # print(methodStr)
  8.         signingCertificateStr = "Apple Development" if isDev else "Apple Distribution"
  9.         fileStr = '''<?xml version="1.0" encoding="UTF-8"?>
  10. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  11. <plist version="1.0">
  12. <dict>
  13.     <key>destination</key>
  14.     <string>export</string>
  15.     <key>manageAppVersionAndBuildNumber</key>
  16.     <false/>
  17.     <key>method</key>
  18.     <string>%s</string>
  19.     <key>provisioningProfiles</key>
  20.     <dict>
  21.         <key>%s</key>
  22.         <string>%s</string>
  23.     </dict>
  24.     <key>signingCertificate</key>
  25.     <string>%s</string>
  26.     <key>signingStyle</key>
  27.     <string>manual</string>
  28.     <key>stripSwiftSymbols</key>
  29.     <true/>
  30.     <key>teamID</key>
  31.     <string>%s</string>
  32.     <key>uploadSymbols</key>
  33.     <false/>
  34.     <key>generateAppStoreInformation</key>
  35.     <true/>
  36. </dict>
  37. </plist>
  38.         ''' % (methodStr, self.bundleId, mpModel.name, signingCertificateStr, mpModel.app_id_prefix)
  39.         with open(self.exportDirectory + "/ExportOptions.plist", 'w+') as file:
  40.             file.write(fileStr)
  41.         self.dealPrintMsg("===========处理plist文件操作完成===========")
复制代码
7.导出成ipa文件

  1.     def export(self):
  2.         self.dealPrintMsg("===========开始export操作===========")
  3.         start = time.time()
  4.         exportIpaFileName = self.projectName + " " + datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
  5.         if self.outputPath == "":
  6.             exportIpaPath = os.path.join(os.path.dirname(self.projectFolder), exportIpaFileName)
  7.         else:
  8.             if not os.path.exists(self.outputPath):
  9.                 os.makedirs(self.outputPath)
  10.             exportIpaPath = os.path.join(self.outputPath, exportIpaFileName)
  11.         exportOpt = (('xcodebuild -exportArchive -archivePath "%s/%s.xcarchive" -exportPath "%s" '
  12.                       '-exportOptionsPlist "%s/ExportOptions.plist"') %
  13.                      (self.exportDirectory, self.projectName, exportIpaPath, self.exportDirectory))
  14.         result, isError = self.executeCmd(exportOpt)
  15.         end = time.time()
  16.         # ipa导出结果
  17.         if isError:
  18.             self.dealPrintMsg("%s===========导出IPA失败,用时:%.2f秒===========" % (result, end - start), 2)
  19.         else:
  20.             self.dealPrintMsg(result, 4)
  21.             self.dealPrintMsg("===========导出IPA成功,用时:%.2f秒===========" % (end - start))
  22.         self.dealPrintMsg("导出IPA文件路径为 === %s" % exportIpaPath)
复制代码
8.运行脚本

  1. if __name__ == '__main__':
  2.     projectPath1 = "xxx/Test/Test.xcodeproj"  # 项目路径
  3.     mobileprovisionPath1 = "xxx/xxx.mobileprovision" # 证书路径
  4.     projectName1 = "Test" # 编译项目名称
  5.     archive = AutoBuildIPA(projectPath1, mobileprovisionPath1, isAppStore=False, projectName=projectName1)
  6.     archive.start()
复制代码
⭐️如果对你有效的话,希望可以点点赞,感谢了⭐️
欢迎学习交流。

   

  • 文章会涉及部分工具类,详细实现可以参照源码,本文章不做解释了 ↩︎

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

刘俊凯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表