Skip to content

Commit 6fe3a3e

Browse files
committed
Added expand-xip-inplace flag to prevent expanding on a temporal directory
As suggested by @MattKiazyk, to allow the user to revert to the old behaviour if needed. - For testing this flag is set to `true`, to use a well-known location when testing. - The `xcodeExpansionDirectory` function cannot be easily expressed on a single line anymore, so removed the variable added on the previous commit (which wasn’t used anyway).
1 parent f1163aa commit 6fe3a3e

File tree

5 files changed

+28
-25
lines changed

5 files changed

+28
-25
lines changed

Sources/XcodesKit/Environment.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,11 @@ public struct Files {
244244
return try temporalDirectory(URL)
245245
}
246246

247-
public var xcodeExpansionDirectory: (URL, URL) -> URL = { (try? Current.files.temporalDirectory(for: $1)) ?? $0.deletingLastPathComponent() }
248-
249-
public func xcodeExpansionDirectory(archiveURL: URL, xcodeURL: URL) -> URL {
250-
return xcodeExpansionDirectory(archiveURL, xcodeURL)
247+
public func xcodeExpansionDirectory(archiveURL: URL, xcodeURL: URL, shouldExpandInplace: Bool) -> URL {
248+
if shouldExpandInplace {
249+
return archiveURL.deletingLastPathComponent()
250+
}
251+
return (try? Current.files.temporalDirectory(for: xcodeURL)) ?? archiveURL.deletingLastPathComponent()
251252
}
252253

253254
public var installedXcodes = XcodesKit.installedXcodes

Sources/XcodesKit/XcodeInstaller.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,22 +155,22 @@ public final class XcodeInstaller {
155155
case aria2(Path)
156156
}
157157

158-
public func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path) -> Promise<Void> {
158+
public func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, shouldExpandXipInplace: Bool) -> Promise<Void> {
159159
return firstly { () -> Promise<InstalledXcode> in
160-
return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: 0)
160+
return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, shouldExpandXipInplace: shouldExpandXipInplace, attemptNumber: 0)
161161
}
162162
.done { xcode in
163163
Current.logging.log("\nXcode \(xcode.version.descriptionWithoutBuildMetadata) has been installed to \(xcode.path.string)".green)
164164
Current.shell.exit(0)
165165
}
166166
}
167167

168-
private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, attemptNumber: Int) -> Promise<InstalledXcode> {
168+
private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, shouldExpandXipInplace: Bool, attemptNumber: Int) -> Promise<InstalledXcode> {
169169
return firstly { () -> Promise<(Xcode, URL)> in
170170
return self.getXcodeArchive(installationType, dataSource: dataSource, downloader: downloader, destination: destination, willInstall: true)
171171
}
172172
.then { xcode, url -> Promise<InstalledXcode> in
173-
return self.installArchivedXcode(xcode, at: url, to: destination)
173+
return self.installArchivedXcode(xcode, at: url, to: destination, shouldExpandXipInplace: shouldExpandXipInplace)
174174
}
175175
.recover { error -> Promise<InstalledXcode> in
176176
switch error {
@@ -187,7 +187,7 @@ public final class XcodeInstaller {
187187
Current.logging.log(error.legibleLocalizedDescription.red)
188188
Current.logging.log("Removing damaged XIP and re-attempting installation.\n")
189189
try Current.files.removeItem(at: damagedXIPURL)
190-
return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, attemptNumber: attemptNumber + 1)
190+
return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, shouldExpandXipInplace: shouldExpandXipInplace, attemptNumber: attemptNumber + 1)
191191
}
192192
}
193193
default:
@@ -520,7 +520,7 @@ public final class XcodeInstaller {
520520
}
521521
}
522522

523-
public func installArchivedXcode(_ xcode: Xcode, at archiveURL: URL, to destination: Path) -> Promise<InstalledXcode> {
523+
public func installArchivedXcode(_ xcode: Xcode, at archiveURL: URL, to destination: Path, shouldExpandXipInplace: Bool) -> Promise<InstalledXcode> {
524524
let passwordInput = {
525525
Promise<String> { seal in
526526
Current.logging.log("xcodes requires superuser privileges in order to finish installation.")
@@ -533,7 +533,7 @@ public final class XcodeInstaller {
533533
let destinationURL = destination.join("Xcode-\(xcode.version.descriptionWithoutBuildMetadata).app").url
534534
switch archiveURL.pathExtension {
535535
case "xip":
536-
return unarchiveAndMoveXIP(at: archiveURL, to: destinationURL).map { xcodeURL in
536+
return unarchiveAndMoveXIP(at: archiveURL, to: destinationURL, shouldExpandXipInplace: shouldExpandXipInplace).map { xcodeURL in
537537
guard
538538
let path = Path(url: xcodeURL),
539539
Current.files.fileExists(atPath: path.string),
@@ -717,8 +717,8 @@ public final class XcodeInstaller {
717717
}
718718
}
719719

720-
func unarchiveAndMoveXIP(at source: URL, to destination: URL) -> Promise<URL> {
721-
let xcodeExpansionDirectory = Current.files.xcodeExpansionDirectory(archiveURL: source, xcodeURL: destination)
720+
func unarchiveAndMoveXIP(at source: URL, to destination: URL, shouldExpandXipInplace: Bool) -> Promise<URL> {
721+
let xcodeExpansionDirectory = Current.files.xcodeExpansionDirectory(archiveURL: source, xcodeURL: destination, shouldExpandInplace: shouldExpandXipInplace)
722722
return firstly { () -> Promise<ProcessOutput> in
723723
Current.logging.log(InstallationStep.unarchiving.description)
724724
return Current.shell.unxip(source, xcodeExpansionDirectory)

Sources/xcodes/main.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ struct Xcodes: ParsableCommand {
187187
completion: .directory)
188188
var directory: String?
189189

190+
@Flag(help: "Expands (decompress) Xcode .xip on the same directory it's downloaded, instead of using a temporal directory.")
191+
var expandXipInplace: Bool = false
192+
190193
@OptionGroup
191194
var globalDataSource: GlobalDataSourceOption
192195

@@ -218,7 +221,7 @@ struct Xcodes: ParsableCommand {
218221

219222
let destination = getDirectory(possibleDirectory: directory)
220223

221-
installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination)
224+
installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, shouldExpandXipInplace: expandXipInplace)
222225
.done { Install.exit() }
223226
.catch { error in
224227
Install.processDownloadOrInstall(error: error)

Tests/XcodesKitTests/Environment+Mock.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ extension Files {
6161
createFile: { _, _, _ in return true },
6262
createDirectory: { _, _, _ in },
6363
temporalDirectory: { _ in return URL(fileURLWithPath: NSTemporaryDirectory()) },
64-
xcodeExpansionDirectory: { _, _ in return URL(fileURLWithPath: NSTemporaryDirectory()) },
6564
installedXcodes: { _ in [] }
6665
)
6766
}

Tests/XcodesKitTests/XcodesKitTests.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,23 +86,23 @@ final class XcodesKitTests: XCTestCase {
8686

8787
let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil)
8888
let installedXcode = InstalledXcode(path: Path("/Applications/Xcode-0.0.0.app")!)!
89-
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"))
89+
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
9090
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.failedSecurityAssessment(xcode: installedXcode, output: "")) }
9191
}
9292

9393
func test_InstallArchivedXcode_VerifySigningCertificateFails_Throws() {
9494
Current.shell.codesignVerify = { _ in return Promise(error: Process.PMKError.execution(process: Process(), standardOutput: nil, standardError: nil)) }
9595

9696
let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil)
97-
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"))
97+
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
9898
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.codesignVerifyFailed(output: "")) }
9999
}
100100

101101
func test_InstallArchivedXcode_VerifySigningCertificateDoesntMatch_Throws() {
102102
Current.shell.codesignVerify = { _ in return Promise.value((0, "", "")) }
103103

104104
let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil)
105-
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"))
105+
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
106106
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.unexpectedCodeSigningIdentity(identifier: "", certificateAuthority: [])) }
107107
}
108108

@@ -115,7 +115,7 @@ final class XcodesKitTests: XCTestCase {
115115

116116
let xcode = Xcode(version: Version("0.0.0")!, url: URL(fileURLWithPath: "/"), filename: "mock", releaseDate: nil)
117117
let xipURL = URL(fileURLWithPath: "/Xcode-0.0.0.xip")
118-
installer.installArchivedXcode(xcode, at: xipURL, to: Path.root.join("Applications"))
118+
installer.installArchivedXcode(xcode, at: xipURL, to: Path.root.join("Applications"), shouldExpandXipInplace: true)
119119
.ensure { XCTAssertEqual(trashedItemAtURL, xipURL) }
120120
.cauterize()
121121
}
@@ -203,7 +203,7 @@ final class XcodesKitTests: XCTestCase {
203203

204204
let expectation = self.expectation(description: "Finished")
205205

206-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"))
206+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
207207
.ensure {
208208
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath", withExtension: "txt", subdirectory: "Fixtures")!
209209
XCTAssertEqual(log, try! String(contentsOf: url))
@@ -296,7 +296,7 @@ final class XcodesKitTests: XCTestCase {
296296

297297
let expectation = self.expectation(description: "Finished")
298298

299-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"))
299+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
300300
.ensure {
301301
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NoColor", withExtension: "txt", subdirectory: "Fixtures")!
302302
XCTAssertEqual(log, try! String(contentsOf: url))
@@ -393,7 +393,7 @@ final class XcodesKitTests: XCTestCase {
393393

394394
let expectation = self.expectation(description: "Finished")
395395

396-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"))
396+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
397397
.ensure {
398398
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NonInteractiveTerminal", withExtension: "txt", subdirectory: "Fixtures")!
399399
XCTAssertEqual(log, try! String(contentsOf: url))
@@ -486,7 +486,7 @@ final class XcodesKitTests: XCTestCase {
486486

487487
let expectation = self.expectation(description: "Finished")
488488

489-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.home.join("Xcode"))
489+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.home.join("Xcode"), shouldExpandXipInplace: true)
490490
.ensure {
491491
let url = Bundle.module.url(forResource: "LogOutput-AlternativeDirectory", withExtension: "txt", subdirectory: "Fixtures")!
492492
let expectedText = try! String(contentsOf: url).replacingOccurrences(of: "/Users/brandon", with: Path.home.string)
@@ -600,7 +600,7 @@ final class XcodesKitTests: XCTestCase {
600600

601601
let expectation = self.expectation(description: "Finished")
602602

603-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"))
603+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
604604
.ensure {
605605
let url = Bundle.module.url(forResource: "LogOutput-IncorrectSavedPassword", withExtension: "txt", subdirectory: "Fixtures")!
606606
XCTAssertEqual(log, try! String(contentsOf: url))
@@ -718,7 +718,7 @@ final class XcodesKitTests: XCTestCase {
718718

719719
let expectation = self.expectation(description: "Finished")
720720

721-
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"))
721+
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
722722
.ensure {
723723
let url = Bundle.module.url(forResource: "LogOutput-DamagedXIP", withExtension: "txt", subdirectory: "Fixtures")!
724724
let expectedText = try! String(contentsOf: url).replacingOccurrences(of: "/Users/brandon", with: Path.home.string)

0 commit comments

Comments
 (0)