diff --git a/Package.swift b/Package.swift index e3647405..74d8da0f 100644 --- a/Package.swift +++ b/Package.swift @@ -77,7 +77,8 @@ let package = Package( .target(name: "AppKitBackend", dependencies: ["SwiftCrossUI"]), .target( name: "QtBackend", - dependencies: ["SwiftCrossUI", .product(name: "Qlift", package: "qlift")] + dependencies: ["SwiftCrossUI", .product(name: "Qlift", package: "qlift")], + plugins: ["DependencyCheckingPlugin"] ), .target( name: "CursesBackend", @@ -91,7 +92,11 @@ let package = Package( .product(name: "CLVGL", package: "LVGLSwift"), ] ), - .target(name: "GtkBackend", dependencies: ["SwiftCrossUI", "Gtk", "CGtk"]), + .target( + name: "GtkBackend", + dependencies: ["SwiftCrossUI", "Gtk", "CGtk"], + plugins: ["DependencyCheckingPlugin"] + ), .systemLibrary( name: "CGtk", pkgConfig: "gtk4", @@ -112,6 +117,10 @@ let package = Package( "XMLCoder", .product(name: "SwiftSyntaxBuilder", package: "swift-syntax"), ] ), + .plugin( + name: "DependencyCheckingPlugin", + capability: .buildTool() + ), ] ) diff --git a/Plugins/DependencyCheckingPlugin/DependencyCheckingPlugin.swift b/Plugins/DependencyCheckingPlugin/DependencyCheckingPlugin.swift new file mode 100644 index 00000000..4bbcffd6 --- /dev/null +++ b/Plugins/DependencyCheckingPlugin/DependencyCheckingPlugin.swift @@ -0,0 +1,98 @@ +import Foundation +import PackagePlugin + +enum DependencyError: Error, CustomStringConvertible { + case missingSystemDependency(String) + + var description: String { + switch self { + case .missingSystemDependency(let name): + Diagnostics.error("Missing '\(name)' system dependency.") + + #if os(Windows) + return "No installation instructions for Windows." + #else + let installationInstructions: String? + let packageManager: String + #if os(macOS) + packageManager = "brew" + switch name { + case "CGtk": + installationInstructions = "brew install gtk4" + case "Qt5Widgets": + installationInstructions = "brew install qt@5 && brew link qt@5" + default: + installationInstructions = nil + } + #elseif os(Linux) + packageManager = "apt" + switch name { + case "CGtk": + installationInstructions = "apt install libgtk-4-dev clang" + case "Qt5Widgets": + installationInstructions = "apt install qt5-default" + default: + installationInstructions = nil + } + #endif + + let instructions = + installationInstructions + ?? "Missing installation instructions for '\(name)', please open an issue at https://github.com/stackotter/swift-cross-ui" + return "To install with \(packageManager): \(instructions)" + #endif + } + } +} + +@main +struct DependencyCheckingPlugin: BuildToolPlugin { + func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] { + if ProcessInfo.processInfo.environment["SWIFTCROSSUI_SKIP_DEP_CHECK"] == "1" { + return [] + } + + #if os(Windows) + Diagnostics.warning( + "Dependency checking not implemented on Windows. Set SWIFTCROSSUI_SKIP_DEP_CHECK=1 to disable this warning" + ) + return [] + #endif + + for dependency in target.recursiveTargetDependencies { + try check(dependency) + } + + return [] + } + + func check(_ target: Target) throws { + // TODO: Add hardcoded checks for curses and lvgl + if let systemDependency = target as? SystemLibraryTarget { + guard let pkgConfigName = systemDependency.pkgConfig else { + Diagnostics.warning( + "Dependency checking not implemented for system libraries without pkgConfig names, manually implement a check for '\(systemDependency.name)'" + ) + return + } + + try checkPkgConfig(pkgConfigName) + } + } + + func checkPkgConfig(_ name: String) throws { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/env") + process.arguments = [ + "pkg-config", + name, + ] + + try process.run() + process.waitUntilExit() + + if process.terminationStatus != 0 { + throw DependencyError.missingSystemDependency(name) + } + } +} diff --git a/format_and_lint.sh b/format_and_lint.sh index 67792ae3..936a159a 100755 --- a/format_and_lint.sh +++ b/format_and_lint.sh @@ -1,2 +1,2 @@ -swift format format --in-place --recursive --configuration .swift-format Sources Examples +swift format format --in-place --recursive --configuration .swift-format Sources Examples/Sources Plugins swiftlint lint --quiet