60 lines
3 KiB
Swift
60 lines
3 KiB
Swift
import Foundation
|
|
import Vapor
|
|
|
|
final class PreBuildRunner {
|
|
static func run(hooks: [PreBuildHook], session: BuildSession, logger: Logger) async -> Bool {
|
|
for hook in hooks {
|
|
session.broadcast(BuildLogMessage(type: .stdout, text: "\n⚙️ Hook: \(hook.name)\n", exitCode: nil))
|
|
let ok = await runHook(hook, session: session)
|
|
if !ok {
|
|
session.broadcast(BuildLogMessage(type: .stderr, text: "❌ Hook '\(hook.name)' failed.", exitCode: nil))
|
|
if !hook.continueOnFailure { return false }
|
|
} else {
|
|
session.broadcast(BuildLogMessage(type: .stdout, text: "✅ \(hook.name) done.\n", exitCode: nil))
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
private static func runHook(_ hook: PreBuildHook, session: BuildSession) async -> Bool {
|
|
await withCheckedContinuation { cont in
|
|
let p = Process()
|
|
p.executableURL = URL(fileURLWithPath: "/bin/zsh")
|
|
p.arguments = ["-l", "-c", hook.command]
|
|
p.currentDirectoryURL = URL(fileURLWithPath: hook.workingDirectory)
|
|
var env = ProcessInfo.processInfo.environment
|
|
env["PATH"] = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:" + (env["PATH"] ?? "")
|
|
p.environment = env
|
|
let out = Pipe(); let err = Pipe()
|
|
p.standardOutput = out; p.standardError = err
|
|
var timedOut = false
|
|
let wd = DispatchWorkItem { timedOut = true; p.terminate()
|
|
session.broadcast(BuildLogMessage(type: .stderr, text: "⏱ Hook '\(hook.name)' timed out.", exitCode: nil))
|
|
}
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + .seconds(hook.timeoutSeconds), execute: wd)
|
|
let g = DispatchGroup()
|
|
g.enter()
|
|
out.fileHandleForReading.readabilityHandler = { h in
|
|
let d = h.availableData
|
|
if d.isEmpty { out.fileHandleForReading.readabilityHandler = nil; g.leave(); return }
|
|
if let t = String(data: d, encoding: .utf8) { session.broadcast(BuildLogMessage(type: .stdout, text: t, exitCode: nil)) }
|
|
}
|
|
g.enter()
|
|
err.fileHandleForReading.readabilityHandler = { h in
|
|
let d = h.availableData
|
|
if d.isEmpty { err.fileHandleForReading.readabilityHandler = nil; g.leave(); return }
|
|
if let t = String(data: d, encoding: .utf8) { session.broadcast(BuildLogMessage(type: .stderr, text: t, exitCode: nil)) }
|
|
}
|
|
p.terminationHandler = { proc in
|
|
wd.cancel()
|
|
g.notify(queue: .global()) { cont.resume(returning: proc.terminationStatus == 0 && !timedOut) }
|
|
}
|
|
do { try p.run() }
|
|
catch {
|
|
wd.cancel()
|
|
session.broadcast(BuildLogMessage(type: .stderr, text: "Launch failed: \(error)", exitCode: nil))
|
|
cont.resume(returning: false)
|
|
}
|
|
}
|
|
}
|
|
}
|