| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- /*
- * Copyright 2025 Google LLC
- *
- * 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 Dispatch
- import Foundation
- public extension Process {
- /// Creates a new `Process` instance without running it.
- ///
- /// - Parameters:
- /// - exe: The executable to run.
- /// - args: An array of arguments to pass to the executable.
- /// - env: A map of environment variables to set for the process.
- /// - inheritEnvironment: When enabled, the parent process' environvment will also be applied
- /// to this process. Effectively, this means that any environvment variables declared within the
- /// parent process will propogate down to this new process.
- convenience init(_ exe: String,
- _ args: [String] = [],
- env: [String: String] = [:],
- inheritEnvironment: Bool = false) {
- self.init()
- executableURL = URL(filePath: "/usr/bin/env")
- arguments = [exe] + args
- environment = env
- if inheritEnvironment {
- mergeEnvironment(ProcessInfo.processInfo.environment)
- }
- }
- /// Merges the provided environment variables with this process' existing environment variables.
- ///
- /// If an environment variable is already set, then it will **NOT** be overwritten. Only
- /// environment variables not currently set on the process will be applied.
- ///
- /// - Parameters:
- /// - env: The environment variables to merge with this process.
- func mergeEnvironment(_ env: [String: String]) {
- guard environment != nil else {
- // if this process doesn't have an environment, we can just set it instead of merging
- environment = env
- return
- }
- environment = environment?.merging(env) { current, _ in current }
- }
- /// Run the process with signals from the parent process.
- ///
- /// The signals `SIGINT` and `SIGTERM` will both be propogated
- /// down to the process from the parent process.
- ///
- /// This function will not return until the process is done running.
- ///
- /// - Parameters:
- /// - args: Optionally provide an array of arguments to run the process with.
- ///
- /// - Returns: The exit code that the process completed with.
- @discardableResult
- func runWithSignals(_ args: [String]? = nil) throws -> Int32 {
- if let args {
- arguments = (arguments ?? []) + args
- }
- let sigint = bindSignal(signal: SIGINT) {
- if self.isRunning {
- self.interrupt()
- }
- }
- let sigterm = bindSignal(signal: SIGTERM) {
- if self.isRunning {
- self.terminate()
- }
- }
- sigint.resume()
- sigterm.resume()
- try run()
- waitUntilExit()
- return terminationStatus
- }
- }
- /// Binds a callback to a signal from the parent process.
- ///
- /// ```swift
- /// bindSignal(SIGINT) {
- /// print("SIGINT was triggered")
- /// }
- /// ```
- ///
- /// - Parameters:
- /// - signal: The signal to listen for.
- /// - callback: The function to invoke when the signal is received.
- func bindSignal(signal value: Int32,
- callback: @escaping DispatchSourceProtocol
- .DispatchSourceHandler) -> any DispatchSourceSignal {
- // allow the process to survive long enough to trigger the callback
- signal(value, SIG_IGN)
- let dispatch = DispatchSource.makeSignalSource(signal: value, queue: .main)
- dispatch.setEventHandler(handler: callback)
- return dispatch
- }
|