马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
iOS 脚本实现自动构建打包(xcodebuild)—Python版
在软件开发过程中,尤其是iOS应用的发布,打包是至关重要的环节。为了简化打包的流程,将用Python实现自动构建打包功能。
当然,要想学会自动化打包,就要先相识自动化打包所必要用到的工具及打包过程。
⭐️⭐️⭐️AutoBuildIPA项目地点: https://gitee.com/miniaj/auto-build-ipa.git
相识xcodebuild
1.脚本相干命令
- // 1.清除编译过程生成文件
- xcodebuild clean -workspace <xxx.workspace> -scheme <schemeName> -configuration <Debug|Release>
- // 2.编译项目,保证验证项目正常
- xcodebuild build -workspace <xxx.workspace> -scheme <schemeName> -configuration <Debug|Release>
- // 3.编译并生成.xcarchive包
- xcodebuild archive -archivePath <archivePath> -workspace <XXX.xcworkspace> -scheme <schemeNmae> -configuration <Debug|Release>
- // 4.将生成的.archive包导出成ipa文件
- xcodebuild -exportArchive -archivePath <archivePath> -exportPath <exportPath> -exportOptionsPlist <exportOptionsPlistPath>
复制代码 2.其他命令
- // 1.可以看到工程的Target ,scheme,configuration配置参数
- cd xxx项目路径下
- xcodebuild -list
- // 2.可以查看本机有哪些描述文件
- open ~/Library/MobileDevice/Provisioning\ Profiles
- // 3.可以查看描述文件具体信息
- /usr/bin/security cms -D -i xxx.mobileprovision
复制代码 相识Python详细实现打包流程
文章会涉及部分工具类,详细实现可以参照源码,本文章不做解释了。1
导入相干py库
- import datetime
- import plistlib
- import re
- import shutil
- import subprocess
- import time
- import os
- from mobileprovision import MobileProvisionModel
- def executeCmd(cmd):
- """
- 指定终端指令
- :param cmd: 指令字符串
- :return: 返回输出字符串, 判断是否报错
- """
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
- output, stderrOutput = p.communicate()
- output = output.decode()
- esultCode = p.returncode
- return output, resultCode != 0
-
- def dealPrintMsg(self, msg, status=1):
- """
- 处理打印行为
- :param status: 状态
- :param msg: 打印信息
- :return: 无返回值
- """
- print("{状态: %s, 打印信息: %s}" % (str(status), msg))
复制代码 1.查抄项目基础信息,并复制暂时项目进行操纵
- def start(self):
- if not os.path.exists(self.projectPath):
- self.dealPrintMsg("项目路径为空, 请检查后重试", 2)
- return
- projectSuffixName = os.path.splitext(self.projectPath)[1]
- # 判断是处理项目空间还是项目
- self.isXcworkspace = projectSuffixName == '.xcworkspace'
- # 获取项目名称
- oldProjectName = FileToolController.backFileNameAndFolderName(self.projectPath, 3)
- if self.projectName == "" or not self.isXcworkspace:
- # 项目名称
- self.projectName = oldProjectName
- self.projectFolder = os.path.dirname(self.projectPath)
- # 拷贝临时项目
- newProjectFolderPath = os.path.join(os.path.dirname(self.projectFolder),
- os.path.basename(self.projectFolder) + "_temp" + str(int(time.time())))
- shutil.copytree(os.path.dirname(self.projectPath), newProjectFolderPath)
- self.projectFolder = newProjectFolderPath
- # 操作项目路径
- self.projectPath = os.path.join(newProjectFolderPath, oldProjectName + projectSuffixName)
- # 输出的路径
- self.exportDirectory = self.projectFolder + '/auto_archive'
复制代码 2.设置配置信息
- def setConfigAppInfo(self):
- newProjectPath = self.projectPath
- if self.isXcworkspace:
- # 如果是Xcworkspace项目,需要找到指定的编译项目路径
- for root, dirs, files in os.walk(self.projectFolder):
- for dirName in dirs:
- if dirName == (self.projectName + ".xcodeproj"):
- newProjectPath = os.path.join(root, dirName)
- break
- pbxprojPath = os.path.join(newProjectPath, "project.pbxproj")
- if not os.path.exists(pbxprojPath):
- self.dealPrintMsg("project.pbxproj文件不存在,无法配置项目,跳过")
- else:
- self.dealPrintMsg("===========开始配置应用信息操作===========")
- newProjectFolder = os.path.dirname(newProjectPath)
- newProjectFolderPath = os.path.join(newProjectFolder, self.projectName)
- infoFilePath = os.path.join(newProjectFolderPath, "info.plist")
-
- with open(pbxprojPath, 'r') as file:
- fileTempData = file.read()
- # 获取证书信息
- mpModel = MobileProvisionModel(self.mobileprovisionPath)
- # mpModel.team_identifier 证书团队ID
- # mpModel.name 证书名称
- targetDic = {"DEVELOPMENT_TEAM": str(mpModel.team_identifier),
- "PROVISIONING_PROFILE_SPECIFIER": str(mpModel.name)}
-
- # 替换项目相关信息
- for key, value in targetDic.items():
- targetPattern = re.compile(r'(%s.*=.(.*?);)' % key)
- targetResult = list(set(targetPattern.findall(fileTempData)))
- for targetArr in targetResult:
- if len(targetArr) >= 2:
- oldTeamStr = str(targetArr[0])
- replaceStr = str(targetArr[1])
- if replaceStr == """" or replaceStr == value:
- continue
- newTeamStr = oldTeamStr.replace(replaceStr, "%s" % value)
- fileTempData = fileTempData.replace(oldTeamStr, newTeamStr)
- fileData = fileTempData
- # 写入project.pbxproj文件
- with open(pbxprojPath, 'w', encoding='utf-8') as file:
- file.write(fileData)
复制代码 3.整理编译文件
- def clean(self):
- self.dealPrintMsg("===========开始clean操作===========")
- start = time.time()
- getXcworkspaceStr = "-workspace" if self.isXcworkspace else "-project"
- cleanOpt = ('xcodebuild clean "%s" "%s" -scheme "%s" -configuration Release' %
- (getXcworkspaceStr, self.projectPath, self.projectName))
- result, isError = self.executeCmd(cleanOpt)
- end = time.time()
- # clean 结果
- if isError:
- self.dealPrintMsg("%s===========clean失败,用时:%.2f秒===========" % (result, end - start), 2)
- else:
- self.dealPrintMsg(result, 4)
- self.dealPrintMsg("===========clean成功,用时:%.2f秒===========" % (end - start))
复制代码 4.编译项目,保证验证项目正常
- def buildProj(self):
- self.dealPrintMsg("===========开始build操作===========")
- start = time.time()
- buildOpt = ('xcodebuild build "%s" "%s" -scheme "%s" -configuration Release -destination "generic/platform=iOS"'
- % (self.getXcworkspaceStr(), self.projectPath, self.projectName))
- result, isError = self.executeCmd(buildOpt)
- end = time.time()
- # build 结果
- if isError:
- self.dealPrintMsg("%s===========build失败,用时:%.2f秒===========" % (result, end - start), 2)
- else:
- self.dealPrintMsg(result, 4)
- self.dealPrintMsg("===========build成功,用时:%.2f秒===========" % (end - start))
- buildPath = self.projectFolder + "/build"
- if os.path.exists(self.projectFolder + "/build"):
- shutil.rmtree(buildPath)
复制代码 5.编译并生成.xcarchive包
- def archive(self):
- self.dealPrintMsg("===========开始archive操作===========")
- subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
- time.sleep(1)
- subprocess.call(['mkdir', '-p', '%s' % self.exportDirectory])
- time.sleep(1)
- start = time.time()
- getXcworkspaceStr = "-workspace" if self.isXcworkspace else "-project"
- archiveOpt = ('xcodebuild archive "%s" "%s" -scheme "%s" -configuration Release -archivePath "%s/%s" '
- '-destination "generic/platform=iOS"' %
- (getXcworkspaceStr, self.projectPath, self.projectName, self.exportDirectory,
- self.projectName))
- result, isError = self.executeCmd(archiveOpt)
- end = time.time()
- # archive 结果
- if isError:
- subprocess.call(['rm', '-rf', '%s' % self.exportDirectory])
- self.dealPrintMsg("%s===========archive失败,用时:%.2f秒===========" % (result, end - start), 0)
- else:
- self.dealPrintMsg(result, 4)
- self.dealPrintMsg("===========archive成功,用时:%.2f秒===========" % (end - start))
复制代码 6.处理plist文件
- def createExportOptions(self):
- self.dealPrintMsg("===========开始处理plist文件操作===========")
- mpModel = MobileProvisionModel(self.mobileprovisionPath)
- # app-store,ad-hoc,enterprise, development
- isDev = mpModel.entitlements.get('aps-environment') != "production"
- methodStr = "development" if isDev else ("app-store-connect" if self.isAppStore else "ad-hoc")
- # print(methodStr)
- signingCertificateStr = "Apple Development" if isDev else "Apple Distribution"
- fileStr = '''<?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>destination</key>
- <string>export</string>
- <key>manageAppVersionAndBuildNumber</key>
- <false/>
- <key>method</key>
- <string>%s</string>
- <key>provisioningProfiles</key>
- <dict>
- <key>%s</key>
- <string>%s</string>
- </dict>
- <key>signingCertificate</key>
- <string>%s</string>
- <key>signingStyle</key>
- <string>manual</string>
- <key>stripSwiftSymbols</key>
- <true/>
- <key>teamID</key>
- <string>%s</string>
- <key>uploadSymbols</key>
- <false/>
- <key>generateAppStoreInformation</key>
- <true/>
- </dict>
- </plist>
- ''' % (methodStr, self.bundleId, mpModel.name, signingCertificateStr, mpModel.app_id_prefix)
- with open(self.exportDirectory + "/ExportOptions.plist", 'w+') as file:
- file.write(fileStr)
- self.dealPrintMsg("===========处理plist文件操作完成===========")
复制代码 7.导出成ipa文件
- def export(self):
- self.dealPrintMsg("===========开始export操作===========")
- start = time.time()
- exportIpaFileName = self.projectName + " " + datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
- if self.outputPath == "":
- exportIpaPath = os.path.join(os.path.dirname(self.projectFolder), exportIpaFileName)
- else:
- if not os.path.exists(self.outputPath):
- os.makedirs(self.outputPath)
- exportIpaPath = os.path.join(self.outputPath, exportIpaFileName)
- exportOpt = (('xcodebuild -exportArchive -archivePath "%s/%s.xcarchive" -exportPath "%s" '
- '-exportOptionsPlist "%s/ExportOptions.plist"') %
- (self.exportDirectory, self.projectName, exportIpaPath, self.exportDirectory))
- result, isError = self.executeCmd(exportOpt)
- end = time.time()
- # ipa导出结果
- if isError:
- self.dealPrintMsg("%s===========导出IPA失败,用时:%.2f秒===========" % (result, end - start), 2)
- else:
- self.dealPrintMsg(result, 4)
- self.dealPrintMsg("===========导出IPA成功,用时:%.2f秒===========" % (end - start))
- self.dealPrintMsg("导出IPA文件路径为 === %s" % exportIpaPath)
复制代码 8.运行脚本
- if __name__ == '__main__':
- projectPath1 = "xxx/Test/Test.xcodeproj" # 项目路径
- mobileprovisionPath1 = "xxx/xxx.mobileprovision" # 证书路径
- projectName1 = "Test" # 编译项目名称
- archive = AutoBuildIPA(projectPath1, mobileprovisionPath1, isAppStore=False, projectName=projectName1)
- archive.start()
复制代码 ⭐️如果对你有效的话,希望可以点点赞,感谢了⭐️
欢迎学习交流。
- 文章会涉及部分工具类,详细实现可以参照源码,本文章不做解释了 ↩︎
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |