diff --git a/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate b/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate deleted file mode 100644 index d97fd9f..0000000 Binary files a/Launchd Package Creator.xcodeproj/project.xcworkspace/xcuserdata/ryan_ball.xcuserdatad/UserInterfaceState.xcuserstate and /dev/null differ diff --git a/Launchd Package Creator/AppDelegate.swift b/Launchd Package Creator/AppDelegate.swift index 3f9689f..ec2d625 100644 --- a/Launchd Package Creator/AppDelegate.swift +++ b/Launchd Package Creator/AppDelegate.swift @@ -1,6 +1,6 @@ // // AppDelegate.swift -// simple-launchdaemon-creator +// launchd-package-creator // // Created by Ryan Ball on 4/11/19. // Copyright © 2019 Ryan Ball. All rights reserved. diff --git a/Launchd Package Creator/Base.lproj/Main.storyboard b/Launchd Package Creator/Base.lproj/Main.storyboard index 000404e..c291861 100644 --- a/Launchd Package Creator/Base.lproj/Main.storyboard +++ b/Launchd Package Creator/Base.lproj/Main.storyboard @@ -441,11 +441,11 @@ - + - + @@ -454,7 +454,7 @@ - + @@ -463,7 +463,7 @@ - + @@ -472,7 +472,7 @@ - + @@ -481,7 +481,7 @@ - + @@ -490,7 +490,7 @@ - + @@ -499,7 +499,7 @@ - + @@ -508,7 +508,7 @@ - + - + - + @@ -555,7 +555,7 @@ - + @@ -564,7 +564,7 @@ - + @@ -573,7 +573,7 @@ - + @@ -582,7 +582,7 @@ - + @@ -646,7 +646,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -665,7 +711,10 @@ + + + @@ -677,7 +726,7 @@ - + diff --git a/Launchd Package Creator/ViewController.swift b/Launchd Package Creator/ViewController.swift index ccc6494..e602894 100644 --- a/Launchd Package Creator/ViewController.swift +++ b/Launchd Package Creator/ViewController.swift @@ -1,6 +1,6 @@ // // ViewController.swift -// simple-launchdaemon-creator +// launchd-package-creator // // Created by Ryan Ball on 4/11/19. // Copyright © 2019 Ryan Ball. All rights reserved. @@ -8,6 +8,7 @@ import Cocoa import Foundation +import AppKit var globalIdentifier: String = "" var globalProgramArgs: String = "" @@ -24,7 +25,9 @@ var globalDaemonFolderName: String = "" var globalStandardOutPath: String? = nil var globalStandardErrorPath: String? = nil var globalPkgTempLocation: String = "" -var optionalRequiredFields = [String]() +var globalSessionType: String? = nil +var emptyRequiredFields = [String]() +var usingApp: Bool = false class ViewController: NSViewController { @@ -42,16 +45,22 @@ class ViewController: NSViewController { @IBOutlet weak var standardErrorPathCheck: NSButton! @IBOutlet weak var standardErrorPathField: NSTextField! @IBOutlet weak var clearButton: NSButton! + @IBOutlet weak var sessionTypeOptions: NSPopUpButton! + @IBOutlet weak var sessionTypeCheck: NSButton! + @IBOutlet weak var programArgsOptLabel: NSTextField! // Create arrays of fields/buttons to clear or disable when using the "Clear" button lazy var fieldsToClear: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.programArgs, self.targetPath, self.startIntervalSeconds, self.standardOutPathField, self.standardErrorPathField] - lazy var buttonsToClear: [NSButton] = [self.runAtLoad, self.startInterval, self.standardOutPathCheck, self.standardErrorPathCheck] + lazy var buttonsToClear: [NSButton] = [self.runAtLoad, self.startInterval, self.standardOutPathCheck, self.standardErrorPathCheck, self.sessionTypeCheck, self.sessionTypeOptions] lazy var fieldsToDisable: [NSTextField] = [self.startIntervalSeconds, self.standardOutPathField, self.standardErrorPathField] - lazy var requiredFields: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.targetPath] + lazy var alwaysRequiredFields: [NSTextField] = [self.daemonIdentifier, self.daemonVersion, self.targetPath] + var sessionTypes: Array = [""] + + // Function to display a dialog with an OK button func dialogOK(title: String, message: String) -> Void { let dialogOK: NSAlert = NSAlert() dialogOK.messageText = title @@ -62,38 +71,68 @@ class ViewController: NSViewController { return } + func fileSaveDialog(title: String, allowedFileTypes: Array, source: String) { + // Open the File Save dialog box + let fileSaveDialog = NSSavePanel(); + fileSaveDialog.message = title; + fileSaveDialog.showsResizeIndicator = true; + fileSaveDialog.showsHiddenFiles = false; + fileSaveDialog.showsTagField = false; + fileSaveDialog.canCreateDirectories = true; + fileSaveDialog.allowedFileTypes = allowedFileTypes; + fileSaveDialog.allowsOtherFileTypes = false; + fileSaveDialog.beginSheetModal(for: self.view.window!) { (result) in + if result == NSApplication.ModalResponse.OK { + let result = fileSaveDialog.url // Pathname of the file + + if (result != nil) { + let selectedPath = result!.path + //let destination = path + _ = extras().copyFile(source: source, destination: selectedPath) + } else { + // User clicked on "Cancel" + return + } + } + } + } + // Function that checks for empty required fields func checkRequiredFields() -> Bool { - optionalRequiredFields = [] + emptyRequiredFields = [] - // Determine our required fields - for field in requiredFields { + // Determine which of our required fields are empty, if so add to our emptyRequiredFields array + for field in alwaysRequiredFields { if field.stringValue.isEmpty { - optionalRequiredFields.append(field.toolTip!) + emptyRequiredFields.append(field.toolTip!) } } - // If there are options selected, add their values as required + // If options are selected, make sure their associated values are populated, if not add to our emptyRequiredFields array if startInterval.state == NSControl.StateValue.on && startIntervalSeconds.stringValue.isEmpty { - optionalRequiredFields.append(startIntervalSeconds.toolTip!) + emptyRequiredFields.append(startIntervalSeconds.toolTip!) } if standardOutPathCheck.state == NSControl.StateValue.on && standardOutPathField.stringValue.isEmpty { - optionalRequiredFields.append(standardOutPathField.toolTip!) + emptyRequiredFields.append(standardOutPathField.toolTip!) } if standardErrorPathCheck.state == NSControl.StateValue.on && standardErrorPathField.stringValue.isEmpty { - optionalRequiredFields.append(standardErrorPathField.toolTip!) + emptyRequiredFields.append(standardErrorPathField.toolTip!) + } + + if programArgs.isEnabled == true && programArgs.stringValue.isEmpty { + emptyRequiredFields.append(programArgs.toolTip!) } let dialogText = """ Please complete all of the required fields to continue: - \(optionalRequiredFields.joined(separator: "\n")) + \(emptyRequiredFields.joined(separator: "\n")) """ - if !optionalRequiredFields.isEmpty { + if !emptyRequiredFields.isEmpty { dialogOK(title: "Fields Empty", message: dialogText) return true } else { @@ -113,28 +152,38 @@ class ViewController: NSViewController { func determineProgramArgs (userSelectedTarget:String) -> String { let targetExtension = NSURL(fileURLWithPath: userSelectedTarget).pathExtension + // Make sure the program args field is enabled initially + programArgs.isEnabled = true if targetExtension == "" { + // We asume the target is a binary (no extension) so disable the program args field programArgs.isEnabled = false programArgs.stringValue = "" + programArgsOptLabel.isHidden = false } else if targetExtension == "app" { - optionalRequiredFields.append(programArgs.toolTip!) + // Target has a .app extensin and use set /usr/bin/open as the program args programArgs.stringValue = String("/usr/bin/open"); + usingApp = true + programArgsOptLabel.isHidden = true } else { do { - optionalRequiredFields.append(programArgs.toolTip!) + programArgsOptLabel.isHidden = true + // We attempt to look at the shebang of the file to determine program args let targetContents = try String(contentsOfFile: userSelectedTarget) let linesOfTarget = targetContents.components(separatedBy: "\n") let shebang = String(linesOfTarget[0]); if shebang.contains ("bash") { + // shebang contains bash programArgs.stringValue = String("/bin/bash"); } else if shebang.contains ("sh") { + // shebang contains sh programArgs.stringValue = String("/bin/sh"); } else if shebang.contains ("python") { + // shebang contains python programArgs.stringValue = String("/usr/bin/python"); } else { - dialogOK(title: "Missing shebang", message: "Your script does not have a proper shebang as the first line. Either add a shebang into the script or manually configure the Program Arguments.") - targetPath.stringValue = "" + // Either no shebang or we don't know how to deal with the shebang in the file + dialogOK(title: "Missing shebang", message: "Your script does not have a proper shebang as the first line. Either add a shebang into the script and try again or manually configure the Program Arguments.") } } catch { // bad things happened @@ -143,11 +192,17 @@ class ViewController: NSViewController { return programArgs.stringValue } - @IBAction func daemonRadioAction(_ sender: Any) { if daemonButton.state == NSControl.StateValue.on { agentButton.state = NSControl.StateValue.off globalDaemonType = "daemon" + + // Populate LimitLoadToSessionType PopUp Button + let sessionTypes: Array = ["System"] + sessionTypeOptions.removeAllItems() + for sessionType in sessionTypes { + sessionTypeOptions.addItem(withTitle: sessionType) + } } } @@ -155,6 +210,13 @@ class ViewController: NSViewController { if agentButton.state == NSControl.StateValue.on { daemonButton.state = NSControl.StateValue.off globalDaemonType = "agent" + + // Populate LimitLoadToSessionType PopUp Button + let sessionTypes: Array = ["Aqua", "Background", "LoginWindow", "StandardIO"] + sessionTypeOptions.removeAllItems() + for sessionType in sessionTypes { + sessionTypeOptions.addItem(withTitle: sessionType) + } } } @@ -214,6 +276,19 @@ class ViewController: NSViewController { } } + @IBAction func sessionTypeCheckAction(_ sender: Any) { + if sessionTypeCheck.state == NSControl.StateValue.on { + sessionTypeOptions.isEnabled = true + } else { + sessionTypeOptions.isEnabled = false + sessionTypeOptions.selectItem(at: 0) + } + } + + @IBAction func viewPlistButtonAction(_ sender: Any) { + mostOfTheDaemonStuff(buildType: "Plist") + } + // Actions to take when the "Clear" button is pressed @IBAction func clearButtonAction(_ sender: Any) { @@ -222,42 +297,71 @@ class ViewController: NSViewController { for field in self.fieldsToDisable { field.isEnabled = false } daemonButton.state = NSControl.StateValue.on agentButton.state = NSControl.StateValue.off + sessionTypeOptions.isEnabled = false + sessionTypeOptions.selectItem(at: 0) + programArgsOptLabel.isHidden = true + + // Populate LimitLoadToSessionType PopUp Button + let sessionTypes: Array = ["System"] + sessionTypeOptions.removeAllItems() + for sessionType in sessionTypes { + sessionTypeOptions.addItem(withTitle: sessionType) + } + self.daemonIdentifier.becomeFirstResponder() } - @IBAction func createDaemon(_ sender: AnyObject) { - + func mostOfTheDaemonStuff(buildType: String) { let anyRequredFieldsEmpty = checkRequiredFields() let startIntervalFieldIsValid = verifyStartInterval(value: startIntervalSeconds.stringValue) - if anyRequredFieldsEmpty == false || startIntervalFieldIsValid == true { + // If no required fields are empty and the start interval field is valid then continue + if anyRequredFieldsEmpty == false && startIntervalFieldIsValid == true { globalIdentifier = daemonIdentifier.stringValue globalVersion = daemonVersion.stringValue globalProgramArgs = programArgs.stringValue globalTargetPath = targetPath.stringValue + + // Determine state of runAtLoad Checkbox, populate value for plist if runAtLoad.state == NSControl.StateValue.on { globalRunAtLoad = true } else { globalRunAtLoad = nil } + + // Determine state of startInterval checkbox, populate value for plist if startInterval.state == NSControl.StateValue.on { globalStartInterval = startIntervalSeconds.integerValue } else { globalStartInterval = nil } + + // Determine if user has selected LaunchDaemon or LaunchAgent, set a global type so we know which directory to place the plist if globalDaemonType == "agent" { globalDaemonFolderName = "LaunchAgents" } else { globalDaemonFolderName = "LaunchDaemons" } + + // Determine state of standardOutPath checkbox, populate value for plist if standardOutPathCheck.state == NSControl.StateValue.on { globalStandardOutPath = standardOutPathField.stringValue } + + // Determine state of standardErrorPath checkbox, populate value for plist if standardErrorPathCheck.state == NSControl.StateValue.on { globalStandardErrorPath = standardErrorPathField.stringValue } + // Determine state of sessionType checkbox, populate value for plist + if sessionTypeCheck.state == NSControl.StateValue.on { + globalSessionType = sessionTypeOptions.titleOfSelectedItem + } else { + globalSessionType = nil + } + + // Determine the filename of the target app/script, build the program args array for plist globalTargetPathFileName = (globalTargetPath as NSString).lastPathComponent if programArgs.stringValue != "" { globalProgramArgsFull = [globalProgramArgs, "/Library/Scripts/\(globalTargetPathFileName)"] @@ -265,40 +369,76 @@ class ViewController: NSViewController { globalProgramArgsFull = ["/Library/Scripts/\(globalTargetPathFileName)"] } - create_daemon().build() - - let fileSaveDialog = NSSavePanel(); - - fileSaveDialog.message = "Choose a save location for the packaged \(globalDaemonType)."; - fileSaveDialog.showsResizeIndicator = true; - fileSaveDialog.showsHiddenFiles = false; - fileSaveDialog.showsTagField = false; - fileSaveDialog.canCreateDirectories = true; - fileSaveDialog.allowedFileTypes = ["pkg"]; - fileSaveDialog.allowsOtherFileTypes = false; - fileSaveDialog.beginSheetModal(for: self.view.window!) { (result) in - if result == NSApplication.ModalResponse.OK { - let result = fileSaveDialog.url // Pathname of the file - - if (result != nil) { - let path = result!.path - globalDestinationPath = path - _ = extras().copyFile(source: globalPkgTempLocation, destination: globalDestinationPath) - } else { - // User clicked on "Cancel" + // Let the user know LaunchAgent/LimitLoadToSessionType: Aqua is preferred for .apps + if (usingApp == true) && (globalDaemonType == "agent") && (globalSessionType != "Aqua") || (usingApp == true) && (globalDaemonType == "daemon") { + + // Display the alert + let dialogCustomButton = NSAlert() + dialogCustomButton.messageText = "Are you sure?" + dialogCustomButton.informativeText = "When targeting a GUI application it is recommended to use a LaunchAgent with LimitLoadToSessionType: Aqua" + dialogCustomButton.alertStyle = .warning + dialogCustomButton.addButton(withTitle: "Create \(buildType) Anyway") + dialogCustomButton.addButton(withTitle: "Cancel") + // Include "Do not show this message again" option in future release + // dialogCustomButton.showsSuppressionButton = true + dialogCustomButton.beginSheetModal(for: self.view.window!, completionHandler: { (NSModalResponse) -> Void in + if (NSModalResponse == NSApplication.ModalResponse.alertSecondButtonReturn) { + // User clicked the cancel button return + } else if (NSModalResponse == NSApplication.ModalResponse.alertFirstButtonReturn) { + // User clicked the continue button + create_daemon().build(buildType: buildType) + // If the intention is to build a PKG, then show the save dialog + //if buildType == "PKG" { _ = self.fileSaveDialog() } + //if buildType == "Plist" { NSWorkspace.shared.openFile(preferencesURL.path, withApplication: "TextEdit") } + if buildType == "Plist" { + self.fileSaveDialog(title: "Choose a save location for the launchd plist.", allowedFileTypes: ["plist"], source: preferencesURL.path) + } + if buildType == "PKG" { + self.fileSaveDialog(title: "Choose a save location for the packaged \(globalDaemonType).", allowedFileTypes: ["pkg"], source: globalPkgTempLocation) + } } + }) + } else { + create_daemon().build(buildType: buildType) + // If the intention is to build a PKG, then show the save dialog + //if buildType == "PKG" { _ = self.fileSaveDialog() } + //if buildType == "Plist" { NSWorkspace.shared.openFile(preferencesURL.path, withApplication: "TextEdit") } + if buildType == "Plist" { + self.fileSaveDialog(title: "Choose a save location for the launchd plist.", allowedFileTypes: ["plist"], source: preferencesURL.path) + } + if buildType == "PKG" { + self.fileSaveDialog(title: "Choose a save location for the packaged \(globalDaemonType).", allowedFileTypes: ["pkg"], source: globalPkgTempLocation) } } + } else { + return } } + @IBAction func createDaemon(_ sender: AnyObject) { + // This will build the PKG and prompt for a save location + mostOfTheDaemonStuff(buildType: "PKG") + } + override func viewDidLoad() { super.viewDidLoad() globalDaemonType = "daemon" + programArgsOptLabel.isHidden = true startIntervalSeconds.isEnabled = false standardOutPathField.isEnabled = false standardErrorPathField.isEnabled = false + sessionTypeOptions.isEnabled = false + + // Populate LimitLoadToSessionType PopUp Button + let sessionTypes: Array = ["System"] + sessionTypeOptions.autoenablesItems = false + sessionTypeOptions.removeAllItems() + for sessionType in sessionTypes { + sessionTypeOptions.addItem(withTitle: sessionType) + } + + // Set focus to Identifier field self.daemonIdentifier.becomeFirstResponder() } @@ -307,5 +447,4 @@ class ViewController: NSViewController { // Update the view, if already loaded. } } - } diff --git a/Launchd Package Creator/create_daemon.swift b/Launchd Package Creator/create_daemon.swift index 5e9f13c..0a2e99d 100644 --- a/Launchd Package Creator/create_daemon.swift +++ b/Launchd Package Creator/create_daemon.swift @@ -1,14 +1,15 @@ // // create_daemon.swift -// simple-launchdaemon-creator +// launchd-package-creator // // Created by Ryan Ball on 4/23/19. // Copyright © 2019 Ryan Ball. All rights reserved. // -// import Cocoa import Foundation +var preferencesURL: URL! + struct Preferences: Codable { // Items that are required in the plist var Label: String @@ -19,21 +20,36 @@ struct Preferences: Codable { var StartInterval: Int? var StandardOutPath: String? var StandardErrorPath: String? + var LimitLoadToSessionType: String? +} + +struct ComponentPlistRoot : Codable { + let array : [ComponentPlist] } -public class create_daemon { +struct ComponentPlist: Codable { + var BundleIsRelocatable: Bool + var BundleIsVersionChecked: Bool + var BundleOverwriteAction: String + var RootRelativeBundlePath: String +} + +public class create_daemon: NSObject { var uuid: String = "" var tempBuildDir: String = "" var baseTempDir: URL! var sessionTempDir: URL! + var componentPlistURL: URL! - public func build() { + public func build(buildType: String) { uuid = NSUUID().uuidString tempBuildDir = "com.github.ryangball.launchd-package-creator/\(uuid)" baseTempDir = URL(fileURLWithPath: NSTemporaryDirectory()) sessionTempDir = baseTempDir.appendingPathComponent(tempBuildDir, isDirectory: true) + preferencesURL = sessionTempDir.appendingPathComponent("/root/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist") + componentPlistURL = sessionTempDir.appendingPathComponent("/build/component.plist") let subPaths = [ "root/Library/\(globalDaemonFolderName)", @@ -53,21 +69,20 @@ public class create_daemon { } // Create the pkg postinstall script - let postInstallText = """ - #!/bin/bash - - # Set permissions on LaunchDaemon and Script - chown root:wheel "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist" - chmod 644 "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist" - chown -R root:wheel "/Library/Scripts/\(globalTargetPathFileName)" - chmod -R 755 "/Library/Scripts/\(globalTargetPathFileName)" - - exit 0 - """ - - do { - // get the documents folder url - if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { + func createPostinstall() { + let postInstallText = """ + #!/bin/bash + + # Set permissions on LaunchDaemon and Script + chown root:wheel "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist" + chmod 644 "/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist" + chown -R root:wheel "/Library/Scripts/\(globalTargetPathFileName)" + chmod -R 755 "/Library/Scripts/\(globalTargetPathFileName)" + + exit 0 + """ + + do { // create the destination url for the text file to be saved let fileURL = sessionTempDir.appendingPathComponent("scripts/postinstall") @@ -79,54 +94,90 @@ public class create_daemon { attributes[.posixPermissions] = 0o755 do { try FileManager.default.setAttributes(attributes, ofItemAtPath: fileURL.path) - }catch let error { + } catch let error { print("Permissions error: ", error) } - + } catch { + print("error:", error) } - } catch { - print("error:", error) } - // Copy the target script/app - let destinationURL = sessionTempDir.appendingPathComponent("root/Library/Scripts/\(globalTargetPathFileName)") - do { - try FileManager.default.copyItem(atPath: globalTargetPath, toPath: destinationURL.path) +// func encodePlist(PlistData: String, Destination: URL) { +// let preferencesToEncode = ComponentPlist(BundleIsRelocatable: false, BundleIsVersionChecked: false, BundleOverwriteAction: "upgrade", RootRelativeBundlePath: "/Library/Scripts/\(globalTargetPathFileName)") +// let encoder = PropertyListEncoder() +// encoder.outputFormat = .xml +// do { +// let data = try encoder.encode(preferencesToEncode) +// try data.write(to: Destination) +// } catch { +// // Handle error +// print(error) +// } +// // Code goes here +// } + + func createComponentPlist() { + let preferencesToEncode = ComponentPlist(BundleIsRelocatable: false, BundleIsVersionChecked: false, BundleOverwriteAction: "upgrade", RootRelativeBundlePath: "/Library/Scripts/\(globalTargetPathFileName)") + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + do { + let data = try encoder.encode([preferencesToEncode].self) + try data.write(to: componentPlistURL) + } catch { + // Handle error + print(error) + } } - catch let error as NSError { - print("Ooops! Something went wrong: \(error)") + + // Copy the target script/app + func copyTarget() { + let destinationURL = sessionTempDir.appendingPathComponent("root/Library/Scripts/\(globalTargetPathFileName)") + _ = extras().copyFile(source: globalTargetPath, destination: destinationURL.path) } // Create the plist - let preferencesURL = sessionTempDir.appendingPathComponent("/root/Library/\(globalDaemonFolderName)/\(globalIdentifier).plist") - let preferencesToEncode = Preferences(Label: globalIdentifier, ProgramArguments: globalProgramArgsFull, RunAtLoad: globalRunAtLoad, StartInterval: globalStartInterval, StandardOutPath: globalStandardOutPath, StandardErrorPath: globalStandardErrorPath) - let encoder = PropertyListEncoder() - encoder.outputFormat = .xml - do { - let data = try encoder.encode(preferencesToEncode) - try data.write(to: preferencesURL) - } catch { - // Handle error - print(error) + func createPlist() { + let preferencesToEncode = Preferences(Label: globalIdentifier, ProgramArguments: globalProgramArgsFull, RunAtLoad: globalRunAtLoad, StartInterval: globalStartInterval, StandardOutPath: globalStandardOutPath, StandardErrorPath: globalStandardErrorPath, LimitLoadToSessionType: globalSessionType) + let encoder = PropertyListEncoder() + encoder.outputFormat = .xml + do { + let data = try encoder.encode(preferencesToEncode) + try data.write(to: preferencesURL) + } catch { + // Handle error + print(error) + } } - // Create the .pkg - @discardableResult - func shell(_ args: String...) -> Int32 { - let task = Process() - task.launchPath = "/usr/bin/env" - task.arguments = args - task.launch() - task.waitUntilExit() - return task.terminationStatus + // Create the .pkg using shell commands + func createPKG() { + @discardableResult + func shell(_ args: String...) -> Int32 { + let task = Process() + task.launchPath = "/usr/bin/env" + task.arguments = args + task.launch() + task.waitUntilExit() + return task.terminationStatus + } + + let pkgRoot = sessionTempDir.appendingPathComponent("/root/") + let pkgScripts = sessionTempDir.appendingPathComponent("/scripts/") + let pkgBuildDir = sessionTempDir.appendingPathComponent("build/") + globalPkgTempLocation = "\(pkgBuildDir.path)/hello.pkg" + + shell("/usr/bin/pkgbuild", "--quiet", "--root", "\(pkgRoot.path)", "--install-location", "/", "--scripts", "\(pkgScripts.path)", "--identifier", "\(globalIdentifier)", "--version", "\(globalVersion)", "--ownership", "recommended", "--component-plist", "\(componentPlistURL.path)", "\(globalPkgTempLocation)") } - let pkgRoot = sessionTempDir.appendingPathComponent("/root/") - let pkgScripts = sessionTempDir.appendingPathComponent("/scripts/") - let pkgBuildDir = sessionTempDir.appendingPathComponent("build/") - globalPkgTempLocation = "\(pkgBuildDir.path)/hello.pkg" - + if buildType == "PKG" { + createPostinstall() + createComponentPlist() + copyTarget() + createPlist() + createPKG() + } else if buildType == "Plist" { + createPlist() + } - shell("/usr/bin/pkgbuild", "--quiet", "--root", "\(pkgRoot.path)", "--install-location", "/", "--scripts", "\(pkgScripts.path)", "--identifier", "\(globalIdentifier)", "--version", "\(globalVersion)", "--ownership", "recommended", "\(globalPkgTempLocation)") } } diff --git a/Launchd Package Creator/extras.swift b/Launchd Package Creator/extras.swift index fa467da..e1e56be 100644 --- a/Launchd Package Creator/extras.swift +++ b/Launchd Package Creator/extras.swift @@ -1,6 +1,6 @@ // // dialog.swift -// testing +// launchd-package-creator // // Created by Ryan Ball on 4/15/19. // Copyright © 2019 Ryan Ball. All rights reserved. diff --git a/images/main_window.png b/images/main_window.png index 8e9cca4..fc96ae3 100644 Binary files a/images/main_window.png and b/images/main_window.png differ