diff --git a/Xcodes/Backend/AppState+Install.swift b/Xcodes/Backend/AppState+Install.swift index 2d07bb2..215b445 100644 --- a/Xcodes/Backend/AppState+Install.swift +++ b/Xcodes/Backend/AppState+Install.swift @@ -206,10 +206,15 @@ extension AppState { return Current.shell.unxip(source) .catch { error -> AnyPublisher in - if let executionError = error as? ProcessExecutionError, - executionError.standardError.contains("damaged and can’t be expanded") { + if let executionError = error as? ProcessExecutionError { + if executionError.standardError.contains("damaged and can’t be expanded") { return Fail(error: InstallationError.damagedXIP(url: source)) .eraseToAnyPublisher() + } else if executionError.standardError.contains("can’t be expanded because the selected volume doesn’t have enough free space.") { + return Fail(error: InstallationError.notEnoughFreeSpaceToExpandArchive(archivePath: Path(url: source)!, + version: availableXcode.version)) + .eraseToAnyPublisher() + } } return Fail(error: error) .eraseToAnyPublisher() @@ -365,6 +370,7 @@ extension AppState { public enum InstallationError: LocalizedError, Equatable { case damagedXIP(url: URL) + case notEnoughFreeSpaceToExpandArchive(archivePath: Path, version: Version) case failedToMoveXcodeToApplications case failedSecurityAssessment(xcode: InstalledXcode, output: String) case codesignVerifyFailed(output: String) @@ -383,6 +389,12 @@ public enum InstallationError: LocalizedError, Equatable { switch self { case .damagedXIP(let url): return "The archive \"\(url.lastPathComponent)\" is damaged and can't be expanded." + case let .notEnoughFreeSpaceToExpandArchive(archivePath, version): + return """ + The archive “\(archivePath.basename())” can’t be expanded because the current volume doesn’t have enough free space. + + Make more space available to expand the archive and then install Xcode \(version.appleDescription) again to start installation from where it left off. + """ case .failedToMoveXcodeToApplications: return "Failed to move Xcode to the /Applications directory." case .failedSecurityAssessment(let xcode, let output): diff --git a/XcodesTests/AppStateTests.swift b/XcodesTests/AppStateTests.swift index b5f7dc3..294bb12 100644 --- a/XcodesTests/AppStateTests.swift +++ b/XcodesTests/AppStateTests.swift @@ -280,4 +280,39 @@ class AppStateTests: XCTestCase { ) } + func test_Install_NotEnoughFreeSpace() throws { + Current.shell.unxip = { _ in + Fail(error: ProcessExecutionError( + process: Process(), + standardOutput: "xip: signing certificate was \"Development Update\" (validation not attempted)", + standardError: "xip: error: The archive “Xcode-12.4.0-Release.Candidate+12D4e.xip” can’t be expanded because the selected volume doesn’t have enough free space." + )) + .eraseToAnyPublisher() + } + let archiveURL = URL(fileURLWithPath: "/Users/user/Library/Application Support/Xcode-0.0.0.xip") + + let recorder = subject.unarchiveAndMoveXIP( + availableXcode: AvailableXcode( + version: Version("0.0.0")!, + url: URL(string: "https://developer.apple.com")!, + filename: "Xcode-0.0.0.xip", + releaseDate: nil + ), + at: archiveURL, + to: URL(string: "/Applications/Xcode-0.0.0.app")! + ).record() + + let completion = try wait(for: recorder.completion, timeout: 1, description: "Completion") + + if case let .failure(error as InstallationError) = completion { + XCTAssertEqual( + error, + InstallationError.notEnoughFreeSpaceToExpandArchive(archivePath: Path(url: archiveURL)!, + version: Version("0.0.0")!) + ) + } + else { + XCTFail() + } + } }