mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
199 lines
8.3 KiB
Swift
199 lines
8.3 KiB
Swift
/*
|
|
* Copyright 2019 Google
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import Foundation
|
|
|
|
/// Convenience function for calling functions in the Shell. This should be used sparingly and only
|
|
/// when interacting with tools that can't be accessed directly in Swift (i.e. CocoaPods,
|
|
/// xcodebuild, etc). Intentionally empty, this enum is used as a namespace.
|
|
public enum Shell {}
|
|
|
|
public extension Shell {
|
|
/// A type to represent the result of running a shell command.
|
|
enum Result {
|
|
/// The command was successfully run (based on the output code), with the output string as the
|
|
/// associated value.
|
|
case success(output: String)
|
|
|
|
/// The command failed with a given exit code and output.
|
|
case error(code: Int32, output: String)
|
|
}
|
|
|
|
/// Log without executing the shell commands.
|
|
static var logOnly = false
|
|
static func setLogOnly() {
|
|
logOnly = true
|
|
}
|
|
|
|
/// Execute a command in the user's shell. This creates a temporary shell script and runs the
|
|
/// command from there, instead of calling via `Process()` directly in order to include the
|
|
/// appropriate environment variables. This is mostly for CocoaPods commands, but doesn't hurt
|
|
/// other commands.
|
|
///
|
|
/// - Parameters:
|
|
/// - command: The command to run in the shell.
|
|
/// - outputToConsole: A flag if the command output should be written to the console as well.
|
|
/// - workingDir: An optional working directory to run the shell command in.
|
|
/// - Returns: A Result containing output information from the command.
|
|
static func executeCommandFromScript(_ command: String,
|
|
outputToConsole: Bool = true,
|
|
workingDir: URL? = nil) -> Result {
|
|
let scriptPath: URL
|
|
do {
|
|
let tempScriptsDir = FileManager.default.temporaryDirectory(withName: "temp_scripts")
|
|
try FileManager.default.createDirectory(at: tempScriptsDir,
|
|
withIntermediateDirectories: true,
|
|
attributes: nil)
|
|
scriptPath = tempScriptsDir.appendingPathComponent("wrapper.sh")
|
|
|
|
// Write the temporary script contents to the script's path. CocoaPods complains when LANG
|
|
// isn't set in the environment, so explicitly set it here. The `/usr/local/git/current/bin`
|
|
// is to allow the `sso` protocol if it's there.
|
|
let contents = """
|
|
export PATH="/usr/local/bin:/usr/local/git/current/bin:$PATH"
|
|
export LANG="en_US.UTF-8"
|
|
source ~/.bash_profile
|
|
\(command)
|
|
"""
|
|
try contents.write(to: scriptPath, atomically: true, encoding: .utf8)
|
|
} catch let FileManager.FileError.failedToCreateDirectory(path, error) {
|
|
fatalError("Could not execute shell command: \(command) - could not create temporary " +
|
|
"script directory at \(path). \(error)")
|
|
} catch {
|
|
fatalError("Could not execute shell command: \(command) - unexpected error. \(error)")
|
|
}
|
|
|
|
// Remove the temporary script at the end of this function. If it fails, it's not a big deal
|
|
// since it will be over-written next time and won't affect the Zip file, so we can ignore
|
|
// any failure.
|
|
defer { try? FileManager.default.removeItem(at: scriptPath) }
|
|
|
|
// Let the process call directly into the temporary shell script we created.
|
|
let task = Process()
|
|
task.arguments = [scriptPath.path]
|
|
|
|
if #available(OSX 10.13, *) {
|
|
if let workingDir = workingDir {
|
|
task.currentDirectoryURL = workingDir
|
|
}
|
|
|
|
// Explicitly use `/bin/bash`. Investigate whether or not we can use `/usr/local/env`
|
|
task.executableURL = URL(fileURLWithPath: "/bin/bash")
|
|
} else {
|
|
// Assign the old `currentDirectoryPath` property if `currentDirectoryURL` isn't available.
|
|
if let workingDir = workingDir {
|
|
task.currentDirectoryPath = workingDir.path
|
|
}
|
|
task.launchPath = "/bin/bash"
|
|
}
|
|
|
|
// Assign a pipe to read as soon as data is available, log it to the console if requested, but
|
|
// also keep an array of the output in memory so we can pass it back to functions.
|
|
// Assign a pipe to grab the output, and handle it differently if we want to stream the results
|
|
// to the console or not.
|
|
let pipe = Pipe()
|
|
task.standardOutput = pipe
|
|
let outHandle = pipe.fileHandleForReading
|
|
var output: [String] = []
|
|
|
|
// If we want to output to the console, create a readabilityHandler and save each line along the
|
|
// way. Otherwise, we can just read the pipe at the end. By disabling outputToConsole, some
|
|
// commands (such as any xcodebuild) can run much, much faster.
|
|
if outputToConsole {
|
|
outHandle.readabilityHandler = { pipe in
|
|
// This will be run any time data is sent to the pipe. We want to print it and store it for
|
|
// later. Ignore any non-valid Strings.
|
|
guard let line = String(data: pipe.availableData, encoding: .utf8) else {
|
|
print("Could not get data from pipe for command \(command): \(pipe.availableData)")
|
|
return
|
|
}
|
|
if line != "" {
|
|
output.append(line)
|
|
}
|
|
print(line)
|
|
}
|
|
// Also set the termination handler on the task in order to stop the readabilityHandler from
|
|
// parsing any more data from the task.
|
|
task.terminationHandler = { t in
|
|
guard let stdOut = t.standardOutput as? Pipe else { return }
|
|
|
|
stdOut.fileHandleForReading.readabilityHandler = nil
|
|
}
|
|
}
|
|
|
|
// Launch the task and wait for it to exit. This will trigger the above readabilityHandler
|
|
// method and will redirect the command output back to the console for quick feedback.
|
|
if outputToConsole {
|
|
print("Running command: \(command).")
|
|
print("----------------- COMMAND OUTPUT -----------------")
|
|
}
|
|
task.launch()
|
|
task.waitUntilExit()
|
|
if outputToConsole { print("----------------- END COMMAND OUTPUT -----------------") }
|
|
|
|
let fullOutput: String
|
|
if outputToConsole {
|
|
fullOutput = output.joined(separator: "\n")
|
|
} else {
|
|
let outData = outHandle.readDataToEndOfFile()
|
|
// Force unwrapping since we know it's UTF8 coming from the console.
|
|
fullOutput = String(data: outData, encoding: .utf8)!
|
|
}
|
|
|
|
// Check if the task succeeded or not, and return the failure code if it didn't.
|
|
guard task.terminationStatus == 0 else {
|
|
return Result.error(code: task.terminationStatus, output: fullOutput)
|
|
}
|
|
|
|
// The command was successful, return the output.
|
|
return Result.success(output: fullOutput)
|
|
}
|
|
|
|
/// Execute a command in the user's shell. This creates a temporary shell script and runs the
|
|
/// command from there, instead of calling via `Process()` directly in order to include the
|
|
/// appropriate environment variables. This is mostly for CocoaPods commands, but doesn't hurt
|
|
/// other commands.
|
|
///
|
|
/// This is a variation of `executeCommandFromScript` that also does error handling internally.
|
|
///
|
|
/// - Parameters:
|
|
/// - command: The command to run in the shell.
|
|
/// - outputToConsole: A flag if the command output should be written to the console as well.
|
|
/// - workingDir: An optional working directory to run the shell command in.
|
|
/// - Returns: A Result containing output information from the command.
|
|
static func executeCommand(_ command: String,
|
|
outputToConsole: Bool = true,
|
|
workingDir: URL? = nil) {
|
|
if logOnly {
|
|
print(command)
|
|
return
|
|
}
|
|
let result = Shell.executeCommandFromScript(command, workingDir: workingDir)
|
|
switch result {
|
|
case let .error(code, output):
|
|
fatalError("""
|
|
`\(command)` failed with exit code \(code) while trying to install pods:
|
|
|
|
Output from `\(command)`:
|
|
\(output)
|
|
""")
|
|
case let .success(output):
|
|
// Print the output to the console and return the information for all installed pods.
|
|
print(output)
|
|
}
|
|
}
|
|
}
|