diff --git a/Sources/Scipio/main.swift b/Sources/Scipio/main.swift index c320083..0a9f006 100644 --- a/Sources/Scipio/main.swift +++ b/Sources/Scipio/main.swift @@ -10,7 +10,7 @@ extension Command { .init( commandName: "Scipio", abstract: "A program to pre-build and cache Swift packages", - version: "0.1.11", + version: "0.1.12", subcommands: [ Command.Build.self, Command.Upload.self, diff --git a/Sources/ScipioKit/Dependency Processors/CocoaPodProcessor.swift b/Sources/ScipioKit/Dependency Processors/CocoaPodProcessor.swift index c3ab816..3406d11 100644 --- a/Sources/ScipioKit/Dependency Processors/CocoaPodProcessor.swift +++ b/Sources/ScipioKit/Dependency Processors/CocoaPodProcessor.swift @@ -120,26 +120,24 @@ project '\(projectPath.string)' } private func installPods(in path: Path) throws -> [CocoaPodDescriptor] { - do { - try sh("which", "pod") - .waitUntilExit() - } catch { + let ruby = try Ruby() + var podCommandPath = try? which("pod") + + if podCommandPath == nil { log.info("🍫 Installing CocoaPods...") - try sh("gem", "install", "cocoapods", asAdministrator: true) - .logOutput() - .waitUntilExit() + try ruby.installGem("cocoapods") + + podCommandPath = try which("pod") } log.info("🍫 Installing Pods...") let sandboxPath = path + "Pods" let manifestPath = path + "Pods/Manifest.lock" - let podBinaryPath = try sh("which", "pod") - .waitForOutputString() try path.chdir { - try sh(podBinaryPath, "install") + try sh(podCommandPath!, "install") .logOutput() .waitUntilExit() } diff --git a/Sources/ScipioKit/Helpers/Ruby.swift b/Sources/ScipioKit/Helpers/Ruby.swift new file mode 100644 index 0000000..4362b90 --- /dev/null +++ b/Sources/ScipioKit/Helpers/Ruby.swift @@ -0,0 +1,38 @@ +import Foundation +import PathKit + +public enum RubyError: LocalizedError { + case missingRuby + + public var errorDescription: String? { + switch self { + case .missingRuby: + return "A Ruby installation that is not provided by the system is required to use CocoaPods dependencies. Please install Ruby via rbenv, rvm, or Homebrew." + } + } +} + +struct Ruby { + + private let rubyPath: Path + private let gemPath: Path + + init() throws { + do { + rubyPath = try which("ruby") + gemPath = try which("gem") + } catch ShellError.commandNotFound { + throw RubyError.missingRuby + } + } + + func installGem(_ gem: String) throws { + try sh(gemPath, "install", gem) + .logOutput() + .waitUntilExit() + } + + func commandExists(_ command: String) -> Bool { + return (try? which(command)) != nil + } +} diff --git a/Sources/ScipioKit/Helpers/ShellCommand.swift b/Sources/ScipioKit/Helpers/ShellCommand.swift index 2374506..a27095b 100644 --- a/Sources/ScipioKit/Helpers/ShellCommand.swift +++ b/Sources/ScipioKit/Helpers/ShellCommand.swift @@ -1,19 +1,46 @@ import Foundation import PathKit -func sh(_ command: String, _ arguments: String..., asAdministrator: Bool = false) -> ShellCommand { - ShellCommand.sh(command: command, arguments: arguments, asAdministrator: asAdministrator) +func sh(_ command: Path, _ arguments: String..., asAdministrator: Bool = false, passEnvironment: Bool = false) -> ShellCommand { + ShellCommand.sh(command: command.string, arguments: arguments, asAdministrator: asAdministrator, passEnvironment: passEnvironment) } -func sh(_ command: String, _ arguments: [String], asAdministrator: Bool = false) -> ShellCommand { - ShellCommand.sh(command: command, arguments: arguments, asAdministrator: asAdministrator) +func sh(_ command: String, _ arguments: String..., asAdministrator: Bool = false, passEnvironment: Bool = false) -> ShellCommand { + ShellCommand.sh(command: command, arguments: arguments, asAdministrator: asAdministrator, passEnvironment: passEnvironment) +} + +func sh(_ command: String, _ arguments: [String], asAdministrator: Bool = false, passEnvironment: Bool = false) -> ShellCommand { + ShellCommand.sh(command: command, arguments: arguments, asAdministrator: asAdministrator, passEnvironment: passEnvironment) +} + +func which(_ command: String) throws -> Path { + do { + let output = try sh("/usr/bin/which", command, passEnvironment: true) + .waitForOutputString() + .trimmingCharacters(in: .whitespacesAndNewlines) + + return Path(output) + } catch { + throw ShellError.commandNotFound(command) + } +} + +public enum ShellError: LocalizedError { + case commandNotFound(String) + + public var errorDescription: String? { + switch self { + case .commandNotFound(let command): + return "Command '\(command)' could not be found." + } + } } struct ShellCommand { - static func sh(command: String, arguments: [String], asAdministrator: Bool = false) -> ShellCommand { + static func sh(command: String, arguments: [String], asAdministrator: Bool = false, passEnvironment: Bool = false) -> ShellCommand { let shell = ShellCommand(command: command, arguments: arguments) - shell.run(asAdministrator: asAdministrator) + shell.run(asAdministrator: asAdministrator, passEnvironment: passEnvironment) return shell } @@ -26,11 +53,15 @@ struct ShellCommand { private let task = Process() - func run(asAdministrator: Bool = false) { + func run(asAdministrator: Bool = false, passEnvironment: Bool = false) { task.standardOutput = outputPipe task.standardError = errorPipe - if asAdministrator { + if passEnvironment { + task.environment = ProcessInfo.processInfo.environment + } + + if asAdministrator { let launch: () -> Void = { task.arguments = ["-S"] + [command] + arguments task.launchPath = "/usr/bin/sudo" @@ -68,14 +99,17 @@ struct ShellCommand { } } - func waitForOutput() throws -> Data { + func waitForOutput(stdout: Bool = true, stderr: Bool = false) throws -> Data { try waitUntilExit() - return outputPipe.fileHandleForReading.readDataToEndOfFile() + let out = stdout ? outputPipe.fileHandleForReading.readDataToEndOfFile() : Data() + let err = stderr ? errorPipe.fileHandleForReading.readDataToEndOfFile() : Data() + + return out + err } - func waitForOutputString() throws -> String { - return String(data: try waitForOutput(), encoding: .utf8) ?? "" + func waitForOutputString(stdout: Bool = true, stderr: Bool = false) throws -> String { + return String(data: try waitForOutput(stdout: stdout, stderr: stderr), encoding: .utf8) ?? "" } @discardableResult diff --git a/Sources/ScipioKit/Models/Config.swift b/Sources/ScipioKit/Models/Config.swift index e2bcd48..b028460 100644 --- a/Sources/ScipioKit/Models/Config.swift +++ b/Sources/ScipioKit/Models/Config.swift @@ -22,7 +22,7 @@ public struct Config: Decodable, Equatable { if let buildDirectory = buildDirectory { return Path(buildDirectory) } else { - return Path.current + ".scipio" + return directory + ".scipio" } }