Skip to content

Commit

Permalink
Merge pull request #85 from interstateone/34-improve-logging
Browse files Browse the repository at this point in the history
Refactor to test and improve logging
  • Loading branch information
interstateone authored Dec 18, 2019
2 parents 9b7bb01 + 060b5ea commit aa11a6b
Show file tree
Hide file tree
Showing 12 changed files with 724 additions and 355 deletions.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ let package = Package(
.target(
name: "xcodes",
dependencies: [
"Guaka", "XcodesKit", "KeychainAccess"
"Guaka", "XcodesKit"
]),
.testTarget(
name: "xcodesTests",
dependencies: ["xcodes"]),
.target(
name: "XcodesKit",
dependencies: [
"AppleAPI", "Path", "Version", "PromiseKit", "PMKFoundation", "SwiftSoup", "LegibleError"
"AppleAPI", "Path", "Version", "PromiseKit", "PMKFoundation", "SwiftSoup", "LegibleError", "KeychainAccess"
]),
.testTarget(
name: "XcodesKitTests",
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ If that occurs, it means you need to select a version of Xcode. You can do this

## Usage

Install a specific version of Xcode using a command like one of these:

```
xcodes install 10.2.1
xcodes install 11 Beta 7
Expand All @@ -66,6 +68,21 @@ You'll then be prompted to enter your Apple ID username and password. You can al

After successfully authenticating, xcodes will save your Apple ID password into the keychain and will remember your Apple ID for future use. If you need to use a different Apple ID than the one that's remembered, set the `XCODES_USERNAME` environment variable.

xcodes will download and install the version you asked for so that it's ready to use.

```
(1/6) Downloading Xcode 11.2.0: 100%
(2/6) Unarchiving Xcode (This can take a while)
(3/6) Moving Xcode to /Applications/Xcode-11.2.0.app
(4/6) Moving Xcode archive Xcode-11.2.0.xip to the Trash
(5/6) Checking security assessment and code signing
(6/6) Finishing installation
xcodes requires superuser privileges in order to finish installation.
macOS User Password:
Xcode 11.2.0 has been installed to /Applications/Xcode-11.2.0.app
```

### Commands

- `install <version>`: Download and install a specific version of Xcode
Expand Down
8 changes: 3 additions & 5 deletions Sources/XcodesKit/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ public struct Configuration: Codable {
}

public mutating func load() throws {
let data = try Data(contentsOf: Path.configurationFile.url)
guard let data = Current.files.contents(atPath: Path.configurationFile.string) else { return }
self = try JSONDecoder().decode(Configuration.self, from: data)
}

public func save() throws {
let data = try JSONEncoder().encode(self)
try FileManager.default.createDirectory(at: Path.configurationFile.url.deletingLastPathComponent(),
withIntermediateDirectories: true)
try data.write(to: Path.configurationFile.url)
try Current.files.createDirectory(at: Path.configurationFile.url.deletingLastPathComponent(), withIntermediateDirectories: true)
Current.files.createFile(atPath: Path.configurationFile.string, contents: data)
}
}

77 changes: 76 additions & 1 deletion Sources/XcodesKit/Environment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Foundation
import PromiseKit
import PMKFoundation
import Path
import AppleAPI
import KeychainAccess

/**
Lightweight dependency injection using global mutable state :P
Expand All @@ -14,6 +16,8 @@ public struct Environment {
public var shell = Shell()
public var files = Files()
public var network = Network()
public var logging = Logging()
public var keychain = Keychain()
}

public var Current = Environment()
Expand All @@ -31,6 +35,23 @@ public struct Shell {
public var xcodeBuildVersion: (InstalledXcode) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.libexec.PlistBuddy, "-c", "Print :ProductBuildVersion", "\($0.path.string)/Contents/version.plist") }
public var getUserCacheDir: () -> Promise<ProcessOutput> = { Process.run(Path.root.usr.bin.getconf, "DARWIN_USER_CACHE_DIR") }
public var touchInstallCheck: (String, String, String) -> Promise<ProcessOutput> = { Process.run(Path.root.usr.bin/"touch", "\($0)com.apple.dt.Xcode.InstallCheckCache_\($1)_\($2)") }

public var readLine: (String) -> String? = { _ in return nil }
public func readLine(prompt: String) -> String? {
readLine(prompt)
}

public var readSecureLine: (String) -> String? = { _ in return nil }
public func readSecureLine(prompt: String) -> String? {
readSecureLine(prompt)
}

public var env: (String) -> String? = { _ in nil }
public func env(_ key: String) -> String? {
env(key)
}

public var exit: (Int32) -> Void = { Darwin.exit($0) }
}

public struct Files {
Expand Down Expand Up @@ -71,12 +92,66 @@ public struct Files {
public func createFile(atPath path: String, contents data: Data?, attributes attr: [FileAttributeKey : Any]? = nil) -> Bool {
return createFile(path, data, attr)
}

public var createDirectory: (URL, Bool, [FileAttributeKey : Any]?) throws -> Void = FileManager.default.createDirectory(at:withIntermediateDirectories:attributes:)
public func createDirectory(at url: URL, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey : Any]? = nil) throws {
try createDirectory(url, createIntermediates, attributes)
}

public var installedXcodes = XcodesKit.installedXcodes
}
private func installedXcodes() -> [InstalledXcode] {
let results = try! Path.root.join("Applications").ls().filter { entry in
guard entry.kind == .directory && entry.path.extension == "app" && !entry.path.isSymlink else { return false }
let infoPlistPath = entry.path.join("Contents").join("Info.plist")
let infoPlist = try! PropertyListDecoder().decode(InfoPlist.self, from: try! Data(contentsOf: infoPlistPath.url))
return infoPlist.bundleID == "com.apple.dt.Xcode"
}
let installedXcodes = results.map { $0.path }.compactMap(InstalledXcode.init)
return installedXcodes
}

public struct Network {
public var downloadTask: (URLRequestConvertible, URL, Data?) -> (Progress, Promise<(saveLocation: URL, response: URLResponse)>) = { URLSession.shared.downloadTask(with: $0, to: $1, resumingWith: $2) }
private static let client = AppleAPI.Client()

public var dataTask: (URLRequestConvertible) -> Promise<(data: Data, response: URLResponse)> = { client.session.dataTask(.promise, with: $0) }
public func dataTask(with convertible: URLRequestConvertible) -> Promise<(data: Data, response: URLResponse)> {
dataTask(convertible)
}

public var downloadTask: (URLRequestConvertible, URL, Data?) -> (Progress, Promise<(saveLocation: URL, response: URLResponse)>) = { client.session.downloadTask(with: $0, to: $1, resumingWith: $2) }

public func downloadTask(with convertible: URLRequestConvertible, to saveLocation: URL, resumingWith resumeData: Data?) -> (progress: Progress, promise: Promise<(saveLocation: URL, response: URLResponse)>) {
return downloadTask(convertible, saveLocation, resumeData)
}

public var validateSession: () -> Promise<Void> = client.validateSession

public var login: (String, String) -> Promise<Void> = client.login(accountName:password:)
public func login(accountName: String, password: String) -> Promise<Void> {
login(accountName, password)
}
}

public struct Logging {
public var log: (String) -> Void = { print($0) }
}

public struct Keychain {
private static let keychain = KeychainAccess.Keychain(service: "com.robotsandpencils.xcodes")

public var getString: (String) throws -> String? = keychain.getString(_:)
public func getString(_ key: String) throws -> String? {
try getString(key)
}

public var set: (String, String) throws -> Void = keychain.set(_:key:)
public func set(_ value: String, key: String) throws {
try set(value, key)
}

public var remove: (String) throws -> Void = keychain.remove(_:)
public func remove(_ key: String) throws -> Void {
try remove(key)
}
}
17 changes: 17 additions & 0 deletions Sources/XcodesKit/Migration.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Path

/// Migrates any application support files from Xcodes < v0.4 if application support files from >= v0.4 don't exist
public func migrateApplicationSupportFiles() {
if Current.files.fileExistsAtPath(Path.oldXcodesApplicationSupport.string) {
if Current.files.fileExistsAtPath(Path.xcodesApplicationSupport.string) {
Current.logging.log("Removing old support files...")
try? Current.files.removeItem(Path.oldXcodesApplicationSupport.url)
Current.logging.log("Done")
}
else {
Current.logging.log("Migrating old support files...")
try? Current.files.moveItem(Path.oldXcodesApplicationSupport.url, Path.xcodesApplicationSupport.url)
Current.logging.log("Done")
}
}
}
8 changes: 6 additions & 2 deletions Sources/XcodesKit/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ public struct Xcode: Codable {
}
}

public struct Download: Decodable {
struct Downloads: Codable {
let downloads: [Download]
}

public struct Download: Codable {
public let name: String
public let files: [File]

public struct File: Decodable {
public struct File: Codable {
public let remotePath: String
}
}
Expand Down
Loading

0 comments on commit aa11a6b

Please sign in to comment.