1
0
Fork 0
gobject-generator/Sources/SwiftGtkGenCommandPlugin/CommandPlugin.swift
Brendan Szymanski 1330f2bd25 Initial commit: SwiftGtkGen GIR-to-Swift binding generator
Extracted from gtk-swift monorepo. Previous history
contains 38 commits across the full Phase 1 development
including GIR XML parsing, IR model, analysis engine,
code generation, CLI, plugins, and documentation.
2026-07-01 03:27:41 -04:00

92 lines
3.4 KiB
Swift

import PackagePlugin
import Foundation
/// SwiftPM command plugin for manual GIR binding regeneration.
///
/// Invoked via `swift package generate-gir`. Iterates over all package
/// targets, checks for a `Config.swift` and `.gir` files, and runs the
/// `swift-gtk-gen` executable to produce or update generated Swift sources in
/// each target's `Generated/` directory.
@main
struct SwiftGtkGenCommandPlugin: CommandPlugin {
/// Performs GIR code generation for all eligible targets in the package.
///
/// For each target that has a `Config.swift` and at least one `.gir` file,
/// launches `swift-gtk-gen` as a subprocess with arguments derived from the
/// target's directory and metadata. Generated files are written to
/// `<target>/Generated/`. Throws if any subprocess fails.
///
/// - Parameters:
/// - context: The plugin context providing access to tools and package targets.
/// - arguments: Unused command-line arguments passed to the plugin.
func performCommand(context: PluginContext, arguments: [String]) async throws {
let executable = try context.tool(named: "swift-gtk-gen")
for target in context.package.targets {
let targetDir = target.directoryURL
let configURL = targetDir.appending(path: "Config.swift")
guard FileManager.default.fileExists(atPath: configURL.path()) else {
continue
}
let girFiles = findGIRFiles(in: targetDir)
guard !girFiles.isEmpty else { continue }
let outputDir = targetDir.appending(path: "Generated")
let process = Process()
process.executableURL = executable.url
process.arguments = [
"--config", configURL.path(),
"--library", target.name,
"--version", "4.0",
"--output", outputDir.path(),
]
try process.run()
process.waitUntilExit()
guard process.terminationStatus == 0 else {
throw SwiftGtkGenPluginError.generationFailed(target.name)
}
}
}
/// Recursively finds all `.gir` files in the given directory.
///
/// Uses a directory enumerator to walk the file tree and collect URLs
/// whose path extension is `"gir"`.
///
/// - Parameter directory: The root directory to search.
/// - Returns: An array of URLs pointing to discovered `.gir` files.
private func findGIRFiles(in directory: URL) -> [URL] {
guard let enumerator = FileManager.default.enumerator(
at: directory, includingPropertiesForKeys: [.isRegularFileKey]
) else {
return []
}
var girFiles: [URL] = []
for case let fileURL as URL in enumerator {
if fileURL.pathExtension == "gir" {
girFiles.append(fileURL)
}
}
return girFiles
}
}
/// Errors that can occur during command-plugin GIR generation.
enum SwiftGtkGenPluginError: Error {
/// Generation failed for the specified target name.
case generationFailed(String)
}
extension SwiftGtkGenPluginError: CustomStringConvertible {
/// A human-readable description of the error.
var description: String {
switch self {
case .generationFailed(let target):
return "GIR generation failed for target: \(target)"
}
}
}