在项目中看到以前同事写的自动打包并上传蒲公英脚本,就萌发了用原生swift或者OC可不可以编写脚本的想法。查阅相关资料后发现是可行的。
1、Process是一个可以执行终端命令的类
我们给Process
扩展一个便捷方法执行终端命令
extension Process {
/// 执行命令
/// - Parameters:
/// - launchPath: 命令路径
/// - arguments: 命令参数
/// - currentDirectoryPath: 命令执行目录
/// - environment: 环境变量
/// - Returns: 返回执行结果
static func executable(launchPath:String,
arguments:[String],
currentDirectoryPath:String? = nil,
environment:[String:String]? = nil)->Pipe{
let process = Process()
process.launchPath = launchPath
process.arguments = arguments
if let environment = environment {
process.environment = environment
}
if let currentDirectoryPath = currentDirectoryPath {
process.currentDirectoryPath = currentDirectoryPath
}
let pipe = Pipe()
process.standardOutput = pipe
process.launch()
return pipe
}
}
复制代码
2、xcodebuild命令
-
Clean
xcodebuild clean -workspace <workspaceName> -scheme <schemeName> -configuration <Debug|Release> 复制代码
-
Archive
xcodebuild archive -archivePath <archivePath> -project <projectName> -workspace <workspaceName> -scheme <schemeName> -configuration <Debug|Release> 复制代码
-
Export
xcodebuild -exportArchive -archivePath <xcarchivepath> -exportPath <destinationpath> -exportOptionsPlist <plistpath> 复制代码
参考文章:xcodebuild命令介绍
3、使用 SPM 搭建开发或者直接使用xcode的创建一个命令行程序项目
$ mkdir SwiftCommandLineTool
$ cd SwiftCommandLineTool
$ swift package init --type executable
复制代码
最后一行的 type executable
参数将告诉 SPM,我们想创建一个 CLI,而不是一个 Framework。
-
封装一个打包上传相关的工具
extension String{ func appPath(_ value:String) -> String { if self.hasSuffix("/") { return self + value } return self + "/" + value } } struct IpaTool { struct Output { var pipe:Pipe var readData:String init(pipe:Pipe) { self.pipe = pipe self.readData = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8) ?? "" } } enum Configuration:String { case debug = "Debug" case release = "Release" } var workspace:String{ projectPath.appPath("\(projectName).xcworkspace") } ///scheme var scheme:String ///Debug|Release var configuration:Configuration ///编译产物路径 var xcarchive:String{ exportIpaPath.appPath("\(projectName).xcarchive") } ///配置文件路径 var exportOptionsPlist:String ///导出ipa包的路径 var exportIpaPath:String ///项目路径 let projectPath:String ///项目名称 let projectName:String ///存放打包目录 let packageDirectory:String ///蒲公英_api_key let pgyerKey:String /// /// - Parameters: /// - projectPath: 项目路径 /// - configuration: Debug|Release /// - exportOptionsPlist: 配置文件Plist的路径 /// - pgyerKey: 上传蒲公英的key /// - Throws: 抛出错误 init(projectPath:String, configuration:Configuration, exportOptionsPlist:String, pgyerKey:String) throws { self.projectPath = projectPath self.configuration = configuration self.exportOptionsPlist = exportOptionsPlist self.pgyerKey = pgyerKey let manager = FileManager.default var allFiles = try manager.contentsOfDirectory(atPath: projectPath) projectName = allFiles.first(where: { $0.hasSuffix(".xcodeproj") })?.components(separatedBy: ".").first ?? "" packageDirectory = NSHomeDirectory() .appPath("Desktop/\(projectName)_ipa") allFiles = try manager.contentsOfDirectory(atPath: projectPath.appPath("\(projectName).xcodeproj/xcshareddata/xcschemes") ) scheme = allFiles[0].components(separatedBy: ".").first ?? "" let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" exportIpaPath = packageDirectory.appPath(formatter.string(from: Date())) } } 复制代码
-
封装执行clean,archive,exportArchive
extension IpaTool{ /// 执行 xcodebuild clean func clean()->Output{ let arguments = ["clean", "-workspace", workspace, "-scheme", scheme, "-configuration", configuration.rawValue, "-quiet", ] return Output(pipe: Process.executable(launchPath: "/usr/bin/xcodebuild", arguments: arguments)) } /// 执行 xcodebuild archive func archive()->Output{ let arguments = ["archive", "-workspace", workspace, "-scheme", scheme, "-configuration", configuration.rawValue, "-archivePath", xcarchive, "-quiet", ] return Output(pipe: Process.executable(launchPath: "/usr/bin/xcodebuild", arguments: arguments)) } /// 执行 xcodebuild exportArchive func exportArchive()->Output{ let arguments = ["-exportArchive", "-archivePath", xcarchive, "-configuration", configuration.rawValue, "-exportPath", exportIpaPath, "-exportOptionsPlist", exportOptionsPlist, "-quiet", ] return Output(pipe: Process.executable(launchPath: "/usr/bin/xcodebuild", arguments: arguments)) } } 复制代码
-
准备工作完成,我们现在来编写打包代码
do{ let ipaTool = try IpaTool(projectPath: "/Users/gree/gmall_ios", configuration: .debug, exportOptionsPlist: "/Users/gree/Desktop/greeMall_ipa/2021-06-03 09:48:40/ExportOptions.plist", pgyerKey: "xxxxxx") print(ipaTool) print("执行clean") var output = ipaTool.clean() if output.readData.count > 0 { print("执行失败clean error = \(output.readData)") exit(-1) } print("执行archive") output = ipaTool.archive() if !FileManager.default.fileExists(atPath: ipaTool.xcarchive) { print("执行失败archive error = \(output.readData)") exit(-1) } print("执行exportArchive") output = ipaTool.exportArchive() if !FileManager.default.fileExists(atPath: ipaTool.exportIpaPath.appPath("\(ipaTool.scheme).ipa")) { print("执行失败exportArchive error =\(output.readData)") exit(-1) } print("导出ipa成功\(ipaTool.exportIpaPath)") }catch{ print(error) } 复制代码
注意projectPath使用自己项目路径,exportOptionsPlist建议先手动打一次包来获取
-
运行结果
IpaTool(scheme: "greeMall", configuration: SwiftCommandLineTool.IpaTool.Configuration.debug, exportOptionsPlist: "/Users/gree/Desktop/greeMall_ipa/2021-06-03 09:48:40/ExportOptions.plist", exportIpaPath: "/Users/gree/Desktop/greeMall_ipa/2021-06-07 13:59:21", projectPath: "/Users/gree/gmall_ios", projectName: "greeMall", packageDirectory: "/Users/gree/Desktop/greeMall_ipa", pgyerKey: "51895949ad44dcc3934f47c17aa0c0e5") 执行clean 执行archive 执行exportArchive 导出ipa成功/Users/gree/Desktop/greeMall_ipa/2021-06-07 13:59:21 Program ended with exit code: 0 复制代码
4、把ipa包上传蒲公英
我这里上传文件使用Alamofire
,如果你们熟悉URLSession
用它也行
-
在Package.swift
let package = Package( name: "SwiftCommandLineTool", platforms: [.macOS("10.12")], dependencies: [ .package(name: "Alamofire", url: "https://github.com/Alamofire/Alamofire.git", from: "5.4.3") ], targets: [ .target( name: "SwiftCommandLineTool", dependencies: ["Alamofire"]), .testTarget( name: "SwiftCommandLineToolTests", dependencies: ["SwiftCommandLineTool","Alamofire"]), ] ) 复制代码
-
给IpaTool添加一个上传ipa的函数
//上传蒲公英 func update(){ let ipaPath = exportIpaPath.appPath("\(scheme).ipa") let upload = AF.upload(multipartFormData: { formdata in formdata.append(pgyerKey.data(using: .utf8)!, withName: "_api_key") formdata.append(URL(fileURLWithPath: ipaPath), withName: "file") }, to: URL(string: "https://www.pgyer.com/apiv2/app/upload")!) var isExit = true let queue = DispatchQueue(label: "queue") upload.uploadProgress(queue: queue) { progress in let p = Int((Double(progress.completedUnitCount) / Double(progress.totalUnitCount)) * 100) print("上传进度:\(p)%") } upload.responseData(queue:queue) { dataResponse in switch dataResponse.result { case .success(let data): let result = String(data: data, encoding: .utf8) ?? "" print("上传成功:\(result)") case .failure(let error): print("上传失败: \(error)") } isExit = false } //使用循环换保证命令行程序,不会死掉 while isExit { Thread.sleep(forTimeInterval: 1) } } 复制代码
print("导出ipa成功\(ipaTool.exportIpaPath)") print("开始上传蒲公英") ipaTool.update() 复制代码
上传文件的时候使用DispatchQueue.main命令行程序还是会死掉,所以加了一个while循环来保证程序不死。大家有其他方法告送我一下。
5、打代码打包成一个CLl工具(命令行程序)
我这里就不提供教程了,大家可以参考这篇文章:使用 Swift 编写 CLI 工具的入门教程
我不喜欢使用终端所以使用SwiftUI写了一个简单的macOS App
6、一般我们的项目中用了CocoaPods
我们打包的时候要执行一下pod相关的命令
// pod install
func podInstall()->Output{
var environment = [String:String]()
/*
添加环境变量LANG = en_US.UTF-8
否则这个错误
[33mWARNING: CocoaPods requires your terminal to be using UTF-8 encoding.
Consider adding the following to ~/.profile:
export LANG=en_US.UTF-8
*/
environment["LANG"] = "en_US.UTF-8"
/*
添加环境变量PATH = /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/gree/.rvm/bin
终端运行 echo $PATH 获取
否则这个错误
[1mTraceback[m (most recent call last):
9: from /usr/local/bin/pod:23:in `<main>'
8: from /usr/local/bin/pod:23:in `load'
7: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/bin/pod:55:in `<top (required)>'
6: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/command.rb:49:in `run'
5: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/command.rb:140:in `verify_minimum_git_version!'
4: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/command.rb:126:in `git_version'
3: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/executable.rb:143:in `capture_command'
2: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/executable.rb:117:in `which!'
1: from /Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/executable.rb:117:in `tap'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.10.1/lib/cocoapods/executable.rb:118:in `block in which!': [1m[31m[!] Unable to locate the executable `git`[0m ([1;4mPod::Informative[m[1m)[m
*/
environment["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/gree/.rvm/bin"
/*
添加环境变量CP_HOME_DIR = NSHomeDirectory().appending("/.cocoapods")
我的cocoapods安装在home目录所以使用这个,
你们可以在访达->前往文件夹...-> ~/.cocoapods,来获取路径
否则这个错误
Analyzing dependencies
Cloning spec repo `cocoapods` from `https://github.com/CocoaPods/Specs.git`
[!] Unable to add a source with url `https://github.com/CocoaPods/Specs.git` named `cocoapods`.
You can try adding it manually in `/var/root/.cocoapods/repos` or via `pod repo add`.
*/
environment["CP_HOME_DIR"] = NSHomeDirectory().appending("/.cocoapods")
let pipe = Process.executable(launchPath: "/usr/local/bin/pod",
arguments: ["install"],
currentDirectoryPath: projectPath,
environment: environment)
return Output(pipe: pipe)
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END