用Swift写一个自动打包ipa,并上传蒲公英

用Swift写一个自动打包ipa,并上传蒲公英在项目中看到以前同事写的自动打包并上传蒲公英脚本,就萌发了用原生swift或者OC可不可以编写脚本的想法。查阅相关资料后发现是可行的。 1、Process是一个可以执行终端命令的类 我们给Proces

在项目中看到以前同事写的自动打包并上传蒲公英脚本,就萌发了用原生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命令

  1. Clean

    xcodebuild clean
               -workspace <workspaceName>
               -scheme <schemeName>
               -configuration <Debug|Release>
    
  2. Archive

    xcodebuild archive 
               -archivePath <archivePath>
               -project <projectName>
               -workspace <workspaceName>
               -scheme <schemeName> 
               -configuration <Debug|Release>
    
  3. 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。

  1. 封装一个打包上传相关的工具

    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()))
     }
    }
    
  2. 封装执行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))
        }
    }
    
  3. 准备工作完成,我们现在来编写打包代码

    do{
        let ipaTool = try IpaTool(projectPath: "项目路径",
                              configuration: .debug,
                              exportOptionsPlist: "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建议先手动打一次包来获取

  4. 运行结果

    IpaTool(scheme: "xxx", configuration: SwiftCommandLineTool.IpaTool.Configuration.debug, exportOptionsPlist: "/Users/xxx/Desktop/xxx_ipa/2021-06-03 09:48:40/ExportOptions.plist", exportIpaPath: "/Users/xxx/Desktop/xxx_ipa/2021-06-07 13:59:21", projectPath: "/Users/xxx/xxx_ios", projectName: "xxx", packageDirectory: "/Users/xxx/Desktop/xxx_ipa", pgyerKey: "51895949ad44dcc3934f47c17aa0c0e5")
    执行clean
    执行archive
    执行exportArchive
    导出ipa成功/Users/xxx/Desktop/xxx_ipa/2021-06-07 13:59:21
    Program ended with exit code: 0
    

4、把ipa包上传蒲公英

我这里上传文件使用Alamofire,如果你们熟悉URLSession用它也行

  1. 在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"]),
        ]
    )
    
    
  2. 给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

1623050858375.jpg

6、一般我们的项目中用了CocoaPods我们打包的时候要执行一下pod相关的命令

 // pod install
    func podInstall()->Output{
        var environment = [String:String]()
        /* 添加环境变量LANG = en_US.UTF-8 否则这个错误 WARNING: 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/xxx/.rvm/bin 终端运行 echo $PATH 获取 否则这个错误 Traceback (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!': [!] Unable to locate the executable `git` (Pod::Informative) */
        environment["PATH"] = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/Users/xxx/.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)
    }

最后附上 项目地址

今天的文章用Swift写一个自动打包ipa,并上传蒲公英分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18881.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注