Skip to content

Commit

Permalink
Added expand-xip-inplace flag to prevent expanding on a temporal di…
Browse files Browse the repository at this point in the history
…rectory

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).
  • Loading branch information
juanjonol committed Feb 6, 2022
1 parent f1163aa commit 6fe3a3e
Show file tree
Hide file tree
Showing 5 changed files with 28 additions and 25 deletions.
9 changes: 5 additions & 4 deletions Sources/XcodesKit/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,11 @@ public struct Files {
return try temporalDirectory(URL)
}

public var xcodeExpansionDirectory: (URL, URL) -> URL = { (try? Current.files.temporalDirectory(for: $1)) ?? $0.deletingLastPathComponent() }

public func xcodeExpansionDirectory(archiveURL: URL, xcodeURL: URL) -> URL {
return xcodeExpansionDirectory(archiveURL, xcodeURL)
public func xcodeExpansionDirectory(archiveURL: URL, xcodeURL: URL, shouldExpandInplace: Bool) -> URL {
if shouldExpandInplace {
return archiveURL.deletingLastPathComponent()
}
return (try? Current.files.temporalDirectory(for: xcodeURL)) ?? archiveURL.deletingLastPathComponent()
}

public var installedXcodes = XcodesKit.installedXcodes
Expand Down
18 changes: 9 additions & 9 deletions Sources/XcodesKit/XcodeInstaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,22 +155,22 @@ public final class XcodeInstaller {
case aria2(Path)
}

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

private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, attemptNumber: Int) -> Promise<InstalledXcode> {
private func install(_ installationType: InstallationType, dataSource: DataSource, downloader: Downloader, destination: Path, shouldExpandXipInplace: Bool, attemptNumber: Int) -> Promise<InstalledXcode> {
return firstly { () -> Promise<(Xcode, URL)> in
return self.getXcodeArchive(installationType, dataSource: dataSource, downloader: downloader, destination: destination, willInstall: true)
}
.then { xcode, url -> Promise<InstalledXcode> in
return self.installArchivedXcode(xcode, at: url, to: destination)
return self.installArchivedXcode(xcode, at: url, to: destination, shouldExpandXipInplace: shouldExpandXipInplace)
}
.recover { error -> Promise<InstalledXcode> in
switch error {
Expand All @@ -187,7 +187,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)
return self.install(installationType, dataSource: dataSource, downloader: downloader, destination: destination, shouldExpandXipInplace: shouldExpandXipInplace, attemptNumber: attemptNumber + 1)
}
}
default:
Expand Down Expand Up @@ -520,7 +520,7 @@ public final class XcodeInstaller {
}
}

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

func unarchiveAndMoveXIP(at source: URL, to destination: URL) -> Promise<URL> {
let xcodeExpansionDirectory = Current.files.xcodeExpansionDirectory(archiveURL: source, xcodeURL: destination)
func unarchiveAndMoveXIP(at source: URL, to destination: URL, shouldExpandXipInplace: Bool) -> Promise<URL> {
let xcodeExpansionDirectory = Current.files.xcodeExpansionDirectory(archiveURL: source, xcodeURL: destination, shouldExpandInplace: shouldExpandXipInplace)
return firstly { () -> Promise<ProcessOutput> in
Current.logging.log(InstallationStep.unarchiving.description)
return Current.shell.unxip(source, xcodeExpansionDirectory)
Expand Down
5 changes: 4 additions & 1 deletion Sources/xcodes/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ struct Xcodes: ParsableCommand {
completion: .directory)
var directory: String?

@Flag(help: "Expands (decompress) Xcode .xip on the same directory it's downloaded, instead of using a temporal directory.")
var expandXipInplace: Bool = false

@OptionGroup
var globalDataSource: GlobalDataSourceOption

Expand Down Expand Up @@ -218,7 +221,7 @@ struct Xcodes: ParsableCommand {

let destination = getDirectory(possibleDirectory: directory)

installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination)
installer.install(installation, dataSource: globalDataSource.dataSource, downloader: downloader, destination: destination, shouldExpandXipInplace: expandXipInplace)
.done { Install.exit() }
.catch { error in
Install.processDownloadOrInstall(error: error)
Expand Down
1 change: 0 additions & 1 deletion Tests/XcodesKitTests/Environment+Mock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ extension Files {
createFile: { _, _, _ in return true },
createDirectory: { _, _, _ in },
temporalDirectory: { _ in return URL(fileURLWithPath: NSTemporaryDirectory()) },
xcodeExpansionDirectory: { _, _ in return URL(fileURLWithPath: NSTemporaryDirectory()) },
installedXcodes: { _ in [] }
)
}
Expand Down
20 changes: 10 additions & 10 deletions Tests/XcodesKitTests/XcodesKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,23 +86,23 @@ 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"))
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.failedSecurityAssessment(xcode: installedXcode, output: "")) }
}

func test_InstallArchivedXcode_VerifySigningCertificateFails_Throws() {
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"))
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.codesignVerifyFailed(output: "")) }
}

func test_InstallArchivedXcode_VerifySigningCertificateDoesntMatch_Throws() {
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"))
installer.installArchivedXcode(xcode, at: URL(fileURLWithPath: "/Xcode-0.0.0.xip"), to: Path.root.join("Applications"), shouldExpandXipInplace: true)
.catch { error in XCTAssertEqual(error as! XcodeInstaller.Error, XcodeInstaller.Error.unexpectedCodeSigningIdentity(identifier: "", certificateAuthority: [])) }
}

Expand All @@ -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"))
installer.installArchivedXcode(xcode, at: xipURL, to: Path.root.join("Applications"), shouldExpandXipInplace: true)
.ensure { XCTAssertEqual(trashedItemAtURL, xipURL) }
.cauterize()
}
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
.ensure {
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath", withExtension: "txt", subdirectory: "Fixtures")!
XCTAssertEqual(log, try! String(contentsOf: url))
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
.ensure {
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NoColor", withExtension: "txt", subdirectory: "Fixtures")!
XCTAssertEqual(log, try! String(contentsOf: url))
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
.ensure {
let url = Bundle.module.url(forResource: "LogOutput-FullHappyPath-NonInteractiveTerminal", withExtension: "txt", subdirectory: "Fixtures")!
XCTAssertEqual(log, try! String(contentsOf: url))
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.home.join("Xcode"), shouldExpandXipInplace: true)
.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)
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
.ensure {
let url = Bundle.module.url(forResource: "LogOutput-IncorrectSavedPassword", withExtension: "txt", subdirectory: "Fixtures")!
XCTAssertEqual(log, try! String(contentsOf: url))
Expand Down Expand Up @@ -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"))
installer.install(.version("0.0.0"), dataSource: .apple, downloader: .urlSession, destination: Path.root.join("Applications"), shouldExpandXipInplace: true)
.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)
Expand Down

0 comments on commit 6fe3a3e

Please sign in to comment.