打包出的app運行以下圖,使用磁盤壓縮成dmg,直接打開package.dmg便可git
配置完畢後點擊start運行打包腳本,生成ipa到指定目錄
該項目用swift開發,項目和dmg保存在
https://github.com/gwh111/tes...github
概述整個流程就是,經過recoverAndSet()函數恢復以前保存數據,start()檢查路徑後會替換內部package.sh的動態路徑,而後起一個線程建立Process(),經過Pipe()監控腳本執行輸出,捕獲異常macos
經過UserDefaults簡單地記住上次打包的路徑,下次寫了新代碼後便可點擊start當即打包
恢復時把值傳給控件swift
func recoverAndSet() { let objs:[Any]=[projectPath,projectName,exportOptionsPath,ipaPath] let names:[NSString]=["projectPath","projectName","exportOptionsPath","ipaPath"] for i in 0...3{ print(i) let key=names[i] let obj=objs[i] as! NSTextField let v=UserDefaults.standard.value(forKey: key as String) if (v == nil){ continue } obj.stringValue=(v as? String)! } let ps=UserDefaults.standard.value(forKey: "projectName" as String) if (ps==nil){ }else{ projectName.stringValue=(ps as? String)!; } let dr=UserDefaults.standard.value(forKey: "debugRelease") if (dr==nil){ }else{ debugRelease.selectedSegment=dr as! Int; } debugRelease.action = #selector(segmentControlChanged(segmentControl:)) }
經過NSOpenPanel()建立打開文檔面板對象,選擇文件目錄,而不是手動輸入
一般項目路徑名和項目名稱是一致的,這裏使用了path.components(separatedBy:"/")將路徑分割自動取工程名xcode
@IBAction func selectPath(_ sender: NSButton) { let tag=sender.tag print(tag) // 1. 建立打開文檔面板對象 let openPanel = NSOpenPanel() // 2. 設置確認按鈕文字 openPanel.prompt = "Select" // 3. 設置禁止選擇文件 openPanel.canChooseFiles = true if tag==0||tag==2 { openPanel.canChooseFiles = false } // 4. 設置能夠選擇目錄 openPanel.canChooseDirectories = true if tag==1 { openPanel.canChooseDirectories = false openPanel.allowedFileTypes=["plist"] } // 5. 彈出面板框 openPanel.beginSheetModal(for: self.view.window!) { (result) in // 6. 選擇確認按鈕 if result == NSApplication.ModalResponse.OK { // 7. 獲取選擇的路徑 let path=openPanel.urls[0].absoluteString.removingPercentEncoding! if tag==0 { self.projectPath.stringValue=path let array=path.components(separatedBy:"/") if array.count>1{ let name=array[array.count-2] print(array) print(name as Any) self.projectName.stringValue=name } }else if tag==1 { self.exportOptionsPath.stringValue=path }else{ self.ipaPath.stringValue=path } let names:[NSString]=["projectPath","exportOptionsPath","ipaPath"] UserDefaults.standard.setValue(openPanel.url?.path, forKey: names[tag] as String) UserDefaults.standard.setValue(self.projectName.stringValue, forKey: "projectName") UserDefaults.standard.synchronize() // self.savePath.stringValue = (openPanel.directoryURL?.path)! // // 8. 保存用戶選擇路徑(爲了能夠在其餘地方有權限訪問這個路徑,須要對用戶選擇的路徑進行保存) // UserDefaults.standard.setValue(openPanel.url?.path, forKey: kSelectedFilePath) // UserDefaults.standard.synchronize() } // 9. 恢復按鈕狀態 // sender.state = NSOffState } }
經過str.replacingOccurrences(of: "file://", with: "")將路徑和sh裏的路徑替換
經過DispatchQueue.global(qos: .default).async獲取Concurrent Dispatch Queue並開啓Process()
在處理完的terminationHandler裏回到主線程更新UIbash
@IBAction func start(_ sender: Any) { guard projectPath.stringValue != "" else { self.logTextField.stringValue="工程目錄不能爲空"; return } guard projectName.stringValue != "" else { self.logTextField.stringValue="工程名不能爲空"; return } guard exportOptionsPath.stringValue != "" else { self.logTextField.stringValue="exportOptions不能爲空 xcode生成ipa文件夾中包含"; return } guard ipaPath.stringValue != "" else { self.logTextField.stringValue="輸出ipa目錄不能爲空"; return } var str1="abc" let str2="abc" if str1==str2{ print("same") } //save let objs:[Any]=[projectPath,exportOptionsPath,ipaPath] let names:[NSString]=["projectPath","exportOptionsPath","ipaPath"] for i in 0...2{ let obj=objs[i] as! NSTextField UserDefaults.standard.setValue(obj.stringValue, forKey: names[i] as String) } UserDefaults.standard.setValue(self.projectName.stringValue, forKey: "projectName") UserDefaults.standard.setValue(self.debugRelease.selectedSegment, forKey: "debugRelease") UserDefaults.standard.synchronize() // self.showInfoTextView.string="abc"; if isLoadingRepo { self.logTextField.stringValue="正在執行上一個任務"; return }// 若是正在執行,則返回 isLoadingRepo = true // 設置正在執行標記 let projectStr=self.projectPath.stringValue let nameStr=self.projectName.stringValue let plistStr=self.exportOptionsPath.stringValue let ipaStr=self.ipaPath.stringValue let returnData = Bundle.main.path(forResource: "package", ofType: "sh") let data = NSData.init(contentsOfFile: returnData!) var str = NSString(data:data! as Data, encoding: String.Encoding.utf8.rawValue)! as String if debugRelease.selectedSegment==0 { str = str.replacingOccurrences(of: "DEBUG_RELEASE", with: "debug") }else{ str = str.replacingOccurrences(of: "DEBUG_RELEASE", with: "release") } str = str.replacingOccurrences(of: "NAME_PROJECT", with: nameStr) str = str.replacingOccurrences(of: "PATH_PROJECT", with: projectStr) str = str.replacingOccurrences(of: "PATH_PLIST", with: plistStr) str = str.replacingOccurrences(of: "PATH_IPA", with: ipaStr) str = str.replacingOccurrences(of: "file://", with: "") print("返回的數據:\(str)"); self.logTextField.stringValue="執行中。。。"; DispatchQueue.global(qos: .default).async { // str="aaaabc" // str = str.replacingOccurrences(of: "ab", with: "dd") // print(self.projectPath.stringValue) // print(self.exportOptionsPath.stringValue) // print(self.ipaPath.stringValue) let task = Process() // 建立NSTask對象 // 設置task task.launchPath = "/bin/bash" // 執行路徑(這裏是須要執行命令的絕對路徑) // 設置執行的具體命令 task.arguments = ["-c",str] task.terminationHandler = { proce in // 執行結束的閉包(回調) self.isLoadingRepo = false // 恢復執行標記 //5. 在主線程處理UI DispatchQueue.main.async(execute: { self.logTextField.stringValue="執行完畢"; }) } self.captureStandardOutputAndRouteToTextView(task) task.launch() // 開啓執行 task.waitUntilExit() // 阻塞直到執行完畢 } }
對執行腳本的日誌監控
爲了看到腳本報錯或執行成功提示,使用Pipe()監控 NSPipe通常是兩個線程之間進行通訊使用的閉包
在osx 系統中 ,沙盒有個規則:在App運行期間經過NSOpenPanel用戶手動打開的任意位置的文件,把這個這個路徑保存下來,後面都是能夠直接用這個路徑繼續訪問文件,但當App退出後再次運行,這個路徑默認是不能夠訪問的
fileprivate func captureStandardOutputAndRouteToTextView(_ task:Process) { //1. 設置標準輸出管道 outputPipe = Pipe() task.standardOutput = outputPipe //2. 在後臺線程等待數據和通知 outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() //3. 接受到通知消息 observe=NotificationCenter.default.addObserver(forName: NSNotification.Name.NSFileHandleDataAvailable, object: outputPipe.fileHandleForReading , queue: nil) { notification in //4. 獲取管道數據 轉爲字符串 let output = self.outputPipe.fileHandleForReading.availableData let outputString = String(data: output, encoding: String.Encoding.utf8) ?? "" if outputString != ""{ //5. 在主線程處理UI DispatchQueue.main.async { if self.isLoadingRepo == false { let previousOutput = self.showInfoTextView.string let nextOutput = previousOutput + "\n" + outputString self.showInfoTextView.string = nextOutput // 滾動到可視位置 let range = NSRange(location:nextOutput.utf8CString.count,length:0) self.showInfoTextView.scrollRangeToVisible(range) if self.observe==nil { return } NotificationCenter.default.removeObserver(self.observe!) return }else{ let previousOutput = self.showInfoTextView.string var nextOutput = previousOutput + "\n" + outputString as String if nextOutput.count>5000 { nextOutput=String(nextOutput.suffix(1000)); } // 滾動到可視位置 let range = NSRange(location:nextOutput.utf8CString.count,length:0) self.showInfoTextView.scrollRangeToVisible(range) self.showInfoTextView.string = nextOutput } } } if self.isLoadingRepo == false { return } //6. 繼續等待新數據和通知 self.outputPipe.fileHandleForReading.waitForDataInBackgroundAndNotify() } }
compileBitcodeapp
embedOnDemandResourcesAssetPacksInBundleless
methodasync
teamID
thinning
uploadBitcode
uploadSymbols