Skip to content

Commit

Permalink
Moved XIP expansion to a temporal directory
Browse files Browse the repository at this point in the history
Instead of expanding XIPs on its current directory, a temporal directory is used. The main advantage of this approach is that, because the temporal directory is created on the same volume where Xcode will be installed, the installation is vastly improved if the XIP is on a different volume.

- https://nshipster.com/temporary-files/
- The implementation of the `temporalDirectory` function and variable are based on the similar `trashItem`, but without the `@discardableResult` annotation (I cannot think of any case where this URL could to be ignored).

This closes XcodesOrg#178.
  • Loading branch information
juanjonol committed Jan 23, 2022
1 parent 9fa7794 commit 276531b
Show file tree
Hide file tree
Showing 4 changed files with 14 additions and 6 deletions.
8 changes: 7 additions & 1 deletion Sources/XcodesKit/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct Environment {
public var Current = Environment()

public struct Shell {
public var unxip: (URL) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.bin.xip, workingDirectory: $0.deletingLastPathComponent(), "--expand", "\($0.path)") }
public var unxip: (URL, URL) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.bin.xip, workingDirectory: $1, "--expand", "\($0.path)") }
public var spctlAssess: (URL) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.sbin.spctl, "--assess", "--verbose", "--type", "execute", "\($0.path)") }
public var codesignVerify: (URL) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.bin.codesign, "-vv", "-d", "\($0.path)") }
public var devToolsSecurityEnable: (String?) -> Promise<ProcessOutput> = { Process.sudo(password: $0, Path.root.usr.sbin.DevToolsSecurity, "-enable") }
Expand Down Expand Up @@ -238,6 +238,12 @@ public struct Files {
try createDirectory(url, createIntermediates, attributes)
}

public var temporalDirectory: (URL) throws -> URL = { try FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: $0, create: true) }

public func temporalDirectory(for URL: URL) throws -> URL {
return try temporalDirectory(URL)
}

public var installedXcodes = XcodesKit.installedXcodes
}
private func installedXcodes(directory: Path) -> [InstalledXcode] {
Expand Down
7 changes: 4 additions & 3 deletions Sources/XcodesKit/XcodeInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -708,9 +708,10 @@ public final class XcodeInstaller {
}

func unarchiveAndMoveXIP(at source: URL, to destination: URL) -> Promise<URL> {
let xcodeExpansionDirectory = (try? Current.files.temporalDirectory(for: source)) ?? source.deletingLastPathComponent()
return firstly { () -> Promise<ProcessOutput> in
Current.logging.log(InstallationStep.unarchiving.description)
return Current.shell.unxip(source)
return Current.shell.unxip(source, xcodeExpansionDirectory)
.recover { (error) throws -> Promise<ProcessOutput> in
if case Process.PMKError.execution(_, _, let standardError) = error,
standardError?.contains("damaged and can’t be expanded") == true {
Expand All @@ -722,8 +723,8 @@ public final class XcodeInstaller {
.map { output -> URL in
Current.logging.log(InstallationStep.moving(destination: destination.path).description)

let xcodeURL = source.deletingLastPathComponent().appendingPathComponent("Xcode.app")
let xcodeBetaURL = source.deletingLastPathComponent().appendingPathComponent("Xcode-beta.app")
let xcodeURL = xcodeExpansionDirectory.appendingPathComponent("Xcode.app")
let xcodeBetaURL = xcodeExpansionDirectory.appendingPathComponent("Xcode-beta.app")
if Current.files.fileExists(atPath: xcodeURL.path) {
try Current.files.moveItem(at: xcodeURL, to: destination)
}
Expand Down
3 changes: 2 additions & 1 deletion Tests/XcodesKitTests/Environment+Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension Shell {
static var processOutputMock: ProcessOutput = (0, "", "")

static var mock = Shell(
unxip: { _ in return Promise.value(Shell.processOutputMock) },
unxip: { _, _ in return Promise.value(Shell.processOutputMock) },
spctlAssess: { _ in return Promise.value(Shell.processOutputMock) },
codesignVerify: { _ in return Promise.value(Shell.processOutputMock) },
devToolsSecurityEnable: { _ in return Promise.value(Shell.processOutputMock) },
Expand Down Expand Up @@ -60,6 +60,7 @@ extension Files {
trashItem: { _ in return URL(fileURLWithPath: "\(NSHomeDirectory())/.Trash") },
createFile: { _, _, _ in return true },
createDirectory: { _, _, _ in },
temporalDirectory: { _ in return URL(fileURLWithPath: NSTemporaryDirectory()) },
installedXcodes: { _ in [] }
)
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/XcodesKitTests/XcodesKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ final class XcodesKitTests: XCTestCase {
XcodesKit.Current.logging.log(prompt)
return "asdf"
}
Current.shell.unxip = { _ in
Current.shell.unxip = { _, _ in
unxipCallCount += 1
if unxipCallCount == 1 {
return Promise(error: Process.PMKError.execution(process: Process(), standardOutput: nil, standardError: "The file \"Xcode-0.0.0.xip\" is damaged and can’t be expanded."))
Expand Down

0 comments on commit 276531b

Please sign in to comment.