diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65a669c..2bfdb38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,10 @@ name: CI on: [push, pull_request] jobs: test: - runs-on: macOS-10.15 + runs-on: macOS-11 steps: - uses: actions/checkout@v3 - name: Run tests env: - DEVELOPER_DIR: /Applications/Xcode_12.3.app + DEVELOPER_DIR: /Applications/Xcode_13.2.1.app run: swift test diff --git a/Sources/Unxip/Unxip.swift b/Sources/Unxip/Unxip.swift index a078bd6..b15a761 100644 --- a/Sources/Unxip/Unxip.swift +++ b/Sources/Unxip/Unxip.swift @@ -133,8 +133,9 @@ struct File { compressionStream.addTask { try Task.checkCancellation() let position = _position - let data = [UInt8](unsafeUninitializedCapacity: blockSize + blockSize / 16) { buffer, count in - data[position..(of path: S) -> S.SubSequence { - return path[.. URLRequest { + var components = URLComponents(url: .downloadADCAuth, resolvingAgainstBaseURL: false)! + components.queryItems = [URLQueryItem(name: "path", value: path)] + var request = URLRequest(url: components.url!) + request.allHTTPHeaderFields = request.allHTTPHeaderFields ?? [:] + request.allHTTPHeaderFields?["Accept"] = "*/*" + return request + } } diff --git a/Sources/XcodesKit/Version.swift b/Sources/XcodesKit/Version.swift index e722549..23a5691 100644 --- a/Sources/XcodesKit/Version.swift +++ b/Sources/XcodesKit/Version.swift @@ -1,3 +1,3 @@ import Version -public let version = Version("0.20.0")! +public let version = Version("1.0.0")! diff --git a/Sources/XcodesKit/XcodeInstaller.swift b/Sources/XcodesKit/XcodeInstaller.swift index 6b54ec4..d0ce78a 100644 --- a/Sources/XcodesKit/XcodeInstaller.swift +++ b/Sources/XcodesKit/XcodeInstaller.swift @@ -82,7 +82,7 @@ public final class XcodeInstaller { case downloading(version: String, progress: String?, willInstall: Bool) case unarchiving(experimentalUnxip: Bool) case moving(destination: String) - case trashingArchive(archiveName: String) + case cleaningArchive(archiveName: String, shouldDelete: Bool) case checkingSecurity case finishing @@ -114,7 +114,10 @@ public final class XcodeInstaller { """ case .moving(let destination): return "Moving Xcode to \(destination)" - case .trashingArchive(let archiveName): + case .cleaningArchive(let archiveName, let shouldDelete): + if shouldDelete { + return "Deleting Xcode archive \(archiveName)" + } return "Moving Xcode archive \(archiveName) to the Trash" case .checkingSecurity: return "Checking security assessment and code signing" @@ -128,7 +131,7 @@ public final class XcodeInstaller { case .downloading: return 1 case .unarchiving: return 2 case .moving: return 3 - case .trashingArchive: return 4 + case .cleaningArchive: return 4 case .checkingSecurity: return 5 case .finishing: return 6 } @@ -163,9 +166,9 @@ public final class XcodeInstaller { case aria2(Path) } - public func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, experimentalUnxip: Bool = false, shouldExpandXipInplace: Bool) -> Promise { + public func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, experimentalUnxip: Bool = false, shouldExpandXipInplace: Bool, emptyTrash: Bool, noSuperuser: Bool) -> Promise { return firstly { () -> Promise in - return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace) + return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash: emptyTrash, noSuperuser: noSuperuser) } .done { xcode in Current.logging.log("\nXcode \(xcode.version.descriptionWithoutBuildMetadata) has been installed to \(xcode.path.string)".green) @@ -173,12 +176,12 @@ public final class XcodeInstaller { } } - private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, attemptNumber: Int, experimentalUnxip: Bool, shouldExpandXipInplace: Bool) -> Promise { + private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, attemptNumber: Int, experimentalUnxip: Bool, shouldExpandXipInplace: Bool, emptyTrash: Bool, noSuperuser: Bool) -> Promise { return firstly { () -> Promise<(Xcode, URL)> in return self.getXcodeArchive(installationType, dataSource: dataSource, downloader: downloader, destination: destination, willInstall: true) } .then { xcode, url -> Promise in - return self.installArchivedXcode(xcode, at: url, to: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace) + return self.installArchivedXcode(xcode, at: url, to: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash: emptyTrash, noSuperuser: noSuperuser) } .recover { error -> Promise in switch error { @@ -195,7 +198,7 @@ public final class XcodeInstaller { Current.logging.log(error.legibleLocalizedDescription.red) Current.logging.log("Removing damaged XIP and re-attempting installation.\n") try Current.files.removeItem(at: damagedXIPURL) - return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: attemptNumber + 1, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace) + return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: attemptNumber + 1, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: shouldExpandXipInplace, emptyTrash: emptyTrash, noSuperuser: noSuperuser) } } default: @@ -287,7 +290,15 @@ public final class XcodeInstaller { private func downloadXcode(version: Version, dataSource: DataSource, downloader: Downloader, willInstall: Bool) -> Promise<(Xcode, URL)> { return firstly { () -> Promise in - loginIfNeeded().map { version } + if dataSource == .apple { + return loginIfNeeded().map { version } + } else { + guard let xcode = self.xcodeList.availableXcodes.first(withVersion: version) else { + throw Error.unavailableVersion(version) + } + + return validateADCSession(path: xcode.downloadPath).map { version } + } } .then { version -> Promise in if self.xcodeList.shouldUpdate { @@ -297,14 +308,6 @@ public final class XcodeInstaller { return Promise.value(version) } } - .then { version -> Promise in - // This request would've already been made if the Apple data source were being used. - // That's not the case for the Xcode Releases data source. - // We need the cookies from its response in order to download Xcodes though, - // so perform it here first just to be sure. - Current.network.dataTask(with: URLRequest.downloads) - .map { _ in version } - } .then { version -> Promise<(Xcode, URL)> in guard let xcode = self.xcodeList.availableXcodes.first(withVersion: version) else { throw Error.unavailableVersion(version) @@ -334,7 +337,11 @@ public final class XcodeInstaller { .map { return (xcode, $0) } } } - + + func validateADCSession(path: String) -> Promise { + return Current.network.dataTask(with: URLRequest.downloadADCAuth(path: path)).asVoid() + } + func loginIfNeeded(withUsername providedUsername: String? = nil, shouldPromptForPassword: Bool = false) -> Promise { return firstly { () -> Promise in return Current.network.validateSession() @@ -528,15 +535,7 @@ public final class XcodeInstaller { } } - public func installArchivedXcode(_ xcode: Xcode, at archiveURL: URL, to destination: Path, experimentalUnxip: Bool = false, shouldExpandXipInplace: Bool) -> Promise { - let passwordInput = { - Promise { seal in - Current.logging.log("xcodes requires superuser privileges in order to finish installation.") - guard let password = Current.shell.readSecureLine(prompt: "macOS User Password: ") else { seal.reject(Error.missingSudoerPassword); return } - seal.fulfill(password + "\n") - } - } - + public func installArchivedXcode(_ xcode: Xcode, at archiveURL: URL, to destination: Path, experimentalUnxip: Bool = false, shouldExpandXipInplace: Bool, emptyTrash: Bool, noSuperuser: Bool) -> Promise { return firstly { () -> Promise in let destinationURL = destination.join("Xcode-\(xcode.version.descriptionWithoutBuildMetadata).app").url switch archiveURL.pathExtension { @@ -556,8 +555,13 @@ public final class XcodeInstaller { } } .then { xcode -> Promise in - Current.logging.log(InstallationStep.trashingArchive(archiveName: archiveURL.lastPathComponent).description) - try Current.files.trashItem(at: archiveURL) + Current.logging.log(InstallationStep.cleaningArchive(archiveName: archiveURL.lastPathComponent, shouldDelete: emptyTrash).description) + if emptyTrash { + try Current.files.removeItem(at: archiveURL) + } + else { + try Current.files.trashItem(at: archiveURL) + } Current.logging.log(InstallationStep.checkingSecurity.description) return when(fulfilled: self.verifySecurityAssessment(of: xcode), @@ -565,6 +569,24 @@ public final class XcodeInstaller { .map { xcode } } .then { xcode -> Promise in + if noSuperuser { + Current.logging.log(InstallationStep.finishing.description) + Current.logging.log("Skipping asking for superuser privileges.") + return Promise.value(xcode) + } + return self.postInstallXcode(xcode) + } + } + + public func postInstallXcode(_ xcode: InstalledXcode) -> Promise { + let passwordInput = { + Promise { seal in + Current.logging.log("xcodes requires superuser privileges in order to finish installation.") + guard let password = Current.shell.readSecureLine(prompt: "macOS User Password: ") else { seal.reject(Error.missingSudoerPassword); return } + seal.fulfill(password + "\n") + } + } + return firstly { () -> Promise in Current.logging.log(InstallationStep.finishing.description) return self.enableDeveloperMode(passwordInput: passwordInput).map { xcode } @@ -577,7 +599,7 @@ public final class XcodeInstaller { } } - public func uninstallXcode(_ versionString: String, directory: Path) -> Promise { + public func uninstallXcode(_ versionString: String, directory: Path, emptyTrash: Bool) -> Promise { return firstly { () -> Promise in guard let version = Version(xcodeVersion: versionString) else { Current.logging.log(Error.invalidVersion(versionString).legibleLocalizedDescription) @@ -591,11 +613,17 @@ public final class XcodeInstaller { return Promise.value(installedXcode) } - .map { ($0, try Current.files.trashItem(at: $0.path.url)) } - .then { (installedXcode, trashURL) -> Promise<(InstalledXcode, URL)> in + .map { installedXcode -> (InstalledXcode, URL?) in + if emptyTrash { + try Current.files.removeItem(at: installedXcode.path.url) + return (installedXcode, nil) + } + return (installedXcode, try Current.files.trashItem(at: installedXcode.path.url)) + } + .then { (installedXcode, trashURL) -> Promise<(InstalledXcode, URL?)> in // If we just uninstalled the selected Xcode, try to select the latest installed version so things don't accidentally break Current.shell.xcodeSelectPrintPath() - .then { output -> Promise<(InstalledXcode, URL)> in + .then { output -> Promise<(InstalledXcode, URL?)> in if output.out.hasPrefix(installedXcode.path.string), let latestInstalledXcode = Current.files.installedXcodes(directory).sorted(by: { $0.version < $1.version }).last { return selectXcodeAtPath(latestInstalledXcode.path.string) @@ -610,17 +638,26 @@ public final class XcodeInstaller { } } .done { (installedXcode, trashURL) in - Current.logging.log("Xcode \(installedXcode.version.appleDescription) moved to Trash: \(trashURL.path)".green) + if let trashURL = trashURL { + Current.logging.log("Xcode \(installedXcode.version.appleDescription) moved to Trash: \(trashURL.path)".green) + } + else { + Current.logging.log("Xcode \(installedXcode.version.appleDescription) deleted".green) + } Current.shell.exit(0) } } func update(dataSource: DataSource) -> Promise<[Xcode]> { - return firstly { () -> Promise in - loginIfNeeded() - } - .then { () -> Promise<[Xcode]> in - self.xcodeList.update(dataSource: dataSource) + if dataSource == .apple { + return firstly { () -> Promise in + loginIfNeeded() + } + .then { () -> Promise<[Xcode]> in + self.xcodeList.update(dataSource: dataSource) + } + } else { + return self.xcodeList.update(dataSource: dataSource) } } diff --git a/Sources/xcodes/main.swift b/Sources/xcodes/main.swift index f37c646..acd199b 100644 --- a/Sources/xcodes/main.swift +++ b/Sources/xcodes/main.swift @@ -185,6 +185,12 @@ struct Xcodes: ParsableCommand { @Flag(help: "Use the experimental unxip functionality. May speed up unarchiving by up to 2-3x.") var experimentalUnxip: Bool = false + + @Flag(help: "Don't ask for superuser (root) permission. Some optional steps of the installation will be skipped.") + var noSuperuser: Bool = false + + @Flag(help: "Completely delete Xcode .xip after installation, instead of keeping it on the user's Trash.") + var emptyTrash: Bool = false @Option(help: "The directory to install Xcode into. Defaults to /Applications.", completion: .directory) @@ -224,7 +230,7 @@ struct Xcodes: ParsableCommand { let destination = getDirectory(possibleDirectory: directory) - installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: expandXipInplace) + installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, experimentalUnxip: experimentalUnxip, shouldExpandXipInplace: expandXipInplace, emptyTrash: emptyTrash, noSuperuser: noSuperuser) .done { Install.exit() } .catch { error in Install.processDownloadOrInstall(error: error) @@ -348,6 +354,9 @@ struct Xcodes: ParsableCommand { completion: .custom { _ in Current.files.installedXcodes(getDirectory(possibleDirectory: nil)).sorted { $0.version < $1.version }.map { $0.version.appleDescription } }) var version: [String] = [] + @Flag(help: "Completely delete Xcode, instead of keeping it on the user's Trash.") + var emptyTrash: Bool = false + @OptionGroup var globalDirectory: GlobalDirectoryOption @@ -359,7 +368,7 @@ struct Xcodes: ParsableCommand { let directory = getDirectory(possibleDirectory: globalDirectory.directory) - installer.uninstallXcode(version.joined(separator: " "), directory: directory) + installer.uninstallXcode(version.joined(separator: " "), directory: directory, emptyTrash: emptyTrash) .done { Uninstall.exit() } .catch { error in Uninstall.exit(withLegibleError: error) } diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-AlternativeDirectory.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-AlternativeDirectory.txt index 1a462e0..7e0d0f7 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-AlternativeDirectory.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-AlternativeDirectory.txt @@ -1,6 +1,8 @@ Apple ID: Apple ID Password: +Downloading with urlSession - for faster downloads install aria2 (`brew install aria2`) + (1/6) Downloading Xcode 0.0.0: 1% (1/6) Downloading Xcode 0.0.0: 2% (1/6) Downloading Xcode 0.0.0: 3% @@ -102,6 +104,7 @@ Apple ID Password: (1/6) Downloading Xcode 0.0.0: 99% (1/6) Downloading Xcode 0.0.0: 100% (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Users/brandon/Xcode/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-DamagedXIP.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-DamagedXIP.txt index e7f7d0b..c3a0e12 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-DamagedXIP.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-DamagedXIP.txt @@ -3,10 +3,13 @@ Apple ID Password: (1/6) Found existing archive that will be used for installation at /Users/brandon/Library/Application Support/com.robotsandpencils.xcodes/Xcode-0.0.0.xip. (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process The archive "Xcode-0.0.0.xip" is damaged and can't be expanded. Removing damaged XIP and re-attempting installation. +Downloading with urlSession - for faster downloads install aria2 (`brew install aria2`) + (1/6) Downloading Xcode 0.0.0: 1% (1/6) Downloading Xcode 0.0.0: 2% (1/6) Downloading Xcode 0.0.0: 3% @@ -108,6 +111,7 @@ Removing damaged XIP and re-attempting installation. (1/6) Downloading Xcode 0.0.0: 99% (1/6) Downloading Xcode 0.0.0: 100% (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Applications/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NoColor.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NoColor.txt index f562fab..c2c1d68 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NoColor.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NoColor.txt @@ -1,6 +1,8 @@ Apple ID: Apple ID Password: +Downloading with urlSession - for faster downloads install aria2 (`brew install aria2`) + (1/6) Downloading Xcode 0.0.0: 1% (1/6) Downloading Xcode 0.0.0: 2% (1/6) Downloading Xcode 0.0.0: 3% @@ -102,6 +104,7 @@ Apple ID Password: (1/6) Downloading Xcode 0.0.0: 99% (1/6) Downloading Xcode 0.0.0: 100% (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Applications/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NonInteractiveTerminal.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NonInteractiveTerminal.txt index eaa8932..e43b59a 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NonInteractiveTerminal.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath-NonInteractiveTerminal.txt @@ -2,6 +2,7 @@ Apple ID: Apple ID Password: (1/6) Downloading Xcode 0.0.0 (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Applications/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath.txt index 579622b..190aca5 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-FullHappyPath.txt @@ -1,6 +1,8 @@ Apple ID: Apple ID Password: +Downloading with urlSession - for faster downloads install aria2 (`brew install aria2`) + (1/6) Downloading Xcode 0.0.0: 1% (1/6) Downloading Xcode 0.0.0: 2% (1/6) Downloading Xcode 0.0.0: 3% @@ -102,6 +104,7 @@ Apple ID Password: (1/6) Downloading Xcode 0.0.0: 99% (1/6) Downloading Xcode 0.0.0: 100% (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Applications/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/Fixtures/LogOutput-IncorrectSavedPassword.txt b/Tests/XcodesKitTests/Fixtures/LogOutput-IncorrectSavedPassword.txt index 7f8aff9..b3b59f4 100644 --- a/Tests/XcodesKitTests/Fixtures/LogOutput-IncorrectSavedPassword.txt +++ b/Tests/XcodesKitTests/Fixtures/LogOutput-IncorrectSavedPassword.txt @@ -3,6 +3,8 @@ Invalid username and password combination. Attempted to sign in with username te Try entering your password again Apple ID Password (test@example.com): +Downloading with urlSession - for faster downloads install aria2 (`brew install aria2`) + (1/6) Downloading Xcode 0.0.0: 1% (1/6) Downloading Xcode 0.0.0: 2% (1/6) Downloading Xcode 0.0.0: 3% @@ -104,6 +106,7 @@ Apple ID Password (test@example.com): (1/6) Downloading Xcode 0.0.0: 99% (1/6) Downloading Xcode 0.0.0: 100% (2/6) Unarchiving Xcode (This can take a while) +Using regular unxip. Try passing `--experimental-unxip` for a faster unxip process (3/6) Moving Xcode to /Applications/Xcode-0.0.0.app (4/6) Moving Xcode archive Xcode-0.0.0.xip to the Trash (5/6) Checking security assessment and code signing diff --git a/Tests/XcodesKitTests/XcodesKitTests.swift b/Tests/XcodesKitTests/XcodesKitTests.swift index 2ba5993..d6b2fed 100644 --- a/Tests/XcodesKitTests/XcodesKitTests.swift +++ b/Tests/XcodesKitTests/XcodesKitTests.swift @@ -86,7 +86,7 @@ final class XcodesKitTests: XCTestCase { let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil) let installedXcode = InstalledXcode(path: Path("/Applications/Xcode-0.0.0.app")!)! - installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.failedSecurityAssessment(xcode: installedXcode, output: "")) } } @@ -94,7 +94,7 @@ final class XcodesKitTests: XCTestCase { Current.shell.codesignVerify = { _ in return Promise(error: Process.PMKError.execution(process: Process(), standardOutput: nil, standardError: nil)) } let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil) - installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.codesignVerifyFailed(output: "")) } } @@ -102,7 +102,7 @@ final class XcodesKitTests: XCTestCase { Current.shell.codesignVerify = { _ in return Promise.value((0, "", "")) } let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil) - installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.unexpectedCodeSigningIdentity(identifier: "", certificateAuthority: [])) } } @@ -115,7 +115,7 @@ final class XcodesKitTests: XCTestCase { let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil) let xipURL = URL(fileURLWithPath: "/Xcode-0.0.0.xip") - installer.installArchivedXcode(xcode, at: xipURL, to: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.installArchivedXcode(xcode, at: xipURL, to: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { XCTAssertEqual(trashedItemAtURL, xipURL) } .cauterize() } @@ -203,7 +203,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath", withExtension: "txt", subdirectory: "Fixtures")! XCTAssertEqual(log, try! String(contentsOf: url)) @@ -296,7 +296,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NoColor", withExtension: "txt", subdirectory: "Fixtures")! XCTAssertEqual(log, try! String(contentsOf: url)) @@ -393,7 +393,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NonInteractiveTerminal", withExtension: "txt", subdirectory: "Fixtures")! XCTAssertEqual(log, try! String(contentsOf: url)) @@ -486,7 +486,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.home.join("Xcode"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.home.join("Xcode"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-AlternativeDirectory", withExtension: "txt", subdirectory: "Fixtures")! let expectedText = try! String(contentsOf: url).replacingOccurrences(of: "/Users/brandon", with: Path.home.string) @@ -600,7 +600,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-IncorrectSavedPassword", withExtension: "txt", subdirectory: "Fixtures")! XCTAssertEqual(log, try! String(contentsOf: url)) @@ -718,7 +718,7 @@ final class XcodesKitTests: XCTestCase { let expectation = self.expectation(description: "Finished") - installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true) + installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true, emptyTrash: false, noSuperuser: false) .ensure { let url = Bundle.module.url(forResource: "LogOutput-DamagedXIP", withExtension: "txt", subdirectory: "Fixtures")! let expectedText = try! String(contentsOf: url).replacingOccurrences(of: "/Users/brandon", with: Path.home.string) @@ -778,7 +778,7 @@ final class XcodesKitTests: XCTestCase { return Promise.value((status: 0, out: "", err: "")) } - installer.uninstallXcode("0.0.0", directory: Path.root.join("Applications")) + installer.uninstallXcode("0.0.0", directory: Path.root.join("Applications"), emptyTrash: false) .ensure { XCTAssertEqual(selectedPaths, ["/Applications/Xcode-2.0.1.app"]) XCTAssertEqual(trashedItemAtURL, installedXcodes[0].path.url) @@ -823,7 +823,7 @@ final class XcodesKitTests: XCTestCase { return URL(fileURLWithPath: "\(NSHomeDirectory())/.Trash/\(itemURL.lastPathComponent)") } - installer.uninstallXcode("999", directory: Path.root.join("Applications")) + installer.uninstallXcode("999", directory: Path.root.join("Applications"), emptyTrash: false) .ensure { XCTAssertEqual(trashedItemAtURL, installedXcodes[0].path.url) }