Open a field's visualizer from its tree row, and draw each kind in the render tests.
253 lines
9.3 KiB
Swift
253 lines
9.3 KiB
Swift
// swift-tools-version: 6.0
|
|
|
|
import Foundation
|
|
import PackageDescription
|
|
|
|
let lumaGtkTargetDir = URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent()
|
|
.appendingPathComponent("Sources/LumaGtk", isDirectory: true).path
|
|
|
|
let lumaGtkExcludes: [String] = ["luma.rc", "luma.res"].filter { name in
|
|
FileManager.default.fileExists(atPath: lumaGtkTargetDir + "/" + name)
|
|
}
|
|
|
|
#if os(Windows)
|
|
let lumaExecutableIconResource = compileWindowsExecutableIcon()
|
|
#endif
|
|
|
|
#if os(macOS)
|
|
let cLumaSources: [String] = ["shim_gtk.c", "svg_paintable.c"]
|
|
let cLumaCSettings: [CSetting] = [
|
|
.unsafeFlags(pkgConfigFlags(["gtk4", "libadwaita-1", "epoxy", "librsvg-2.0"])),
|
|
]
|
|
let cLumaCxxSettings: [CXXSetting] = []
|
|
let cLumaLinkerSettings: [LinkerSetting] = [
|
|
.linkedFramework("CoreGraphics"),
|
|
.unsafeFlags(pkgConfigFlags(["libadwaita-1", "epoxy", "librsvg-2.0"], libs: true)),
|
|
]
|
|
let lumaGtkLinkerSettings: [LinkerSetting] = [
|
|
.unsafeFlags(["-Xlinker", "-export_dynamic"]),
|
|
]
|
|
#elseif os(Windows)
|
|
let cLumaSources: [String] = ["shim_gtk.c", "svg_paintable.c"]
|
|
let cLumaCSettings: [CSetting] = [
|
|
.unsafeFlags(pkgConfigFlags(["gtk4", "epoxy", "librsvg-2.0"])),
|
|
]
|
|
let cLumaCxxSettings: [CXXSetting] = []
|
|
let cLumaLinkerSettings: [LinkerSetting] = [
|
|
.linkedLibrary("user32"),
|
|
.linkedLibrary("epoxy"),
|
|
.linkedLibrary("rsvg-2"),
|
|
]
|
|
// Windows: produce a GUI app (no console window). Swift's runtime
|
|
// still calls main(), so redirect the linker entry to the C runtime's
|
|
// main-compatible start routine rather than WinMain. Tool binaries
|
|
// built by SwiftPM plugins (gir2swift-tool, Yams-tool, ...) need the
|
|
// default console subsystem, so keep these target-scoped.
|
|
//
|
|
// The /ignore:* warning filters also apply to those tool binaries and
|
|
// can't be set on targets outside this package — they live on the
|
|
// swift build command line (see scripts/windows/build.ps1 and the
|
|
// Windows CI job).
|
|
let windowsGuiLinkerFlags = ["-Xlinker", "/SUBSYSTEM:WINDOWS", "-Xlinker", "/ENTRY:mainCRTStartup"]
|
|
let lumaGtkLinkerSettings: [LinkerSetting] = [
|
|
.unsafeFlags(
|
|
windowsGuiLinkerFlags
|
|
+ pharoBridgeExports().flatMap { ["-Xlinker", "/EXPORT:\($0)"] }
|
|
+ (lumaExecutableIconResource.map { [$0] } ?? [])
|
|
)
|
|
]
|
|
#else
|
|
let cLumaSources: [String] = ["shim_gtk.c", "svg_paintable.c"]
|
|
let cLumaCSettings: [CSetting] = [
|
|
.unsafeFlags(pkgConfigFlags(["gtk4", "epoxy", "librsvg-2.0"])),
|
|
]
|
|
let cLumaCxxSettings: [CXXSetting] = []
|
|
let cLumaLinkerSettings: [LinkerSetting] = [
|
|
.linkedLibrary("X11"),
|
|
.linkedLibrary("epoxy"),
|
|
.linkedLibrary("rsvg-2"),
|
|
]
|
|
let lumaGtkLinkerSettings: [LinkerSetting] = [
|
|
// Fedora's Swift 6.2 ships libswiftObservation.so with an unresolved
|
|
// reference to swift::threading::fatal that no other shipped library
|
|
// exports. Until upstream fixes the packaging, allow the dynamic
|
|
// linker to defer the symbol — it's only ever called on a fatal
|
|
// assertion path inside Observation.
|
|
.unsafeFlags(["-Xlinker", "--allow-shlib-undefined"]),
|
|
.unsafeFlags(["-Xlinker", "--export-dynamic"]),
|
|
]
|
|
#endif
|
|
|
|
let package = Package(
|
|
name: "LumaGtk",
|
|
platforms: [
|
|
.macOS(.v15),
|
|
],
|
|
dependencies: [
|
|
.package(name: "luma", path: ".."),
|
|
.package(url: "https://github.com/frida/SwiftGtk.git", branch: "gtk4-development"),
|
|
.package(url: "https://github.com/frida/SwiftAdw.git", branch: "development"),
|
|
.package(url: "https://github.com/frida/SwiftGtkSourceView.git", branch: "development"),
|
|
],
|
|
targets: [
|
|
.target(
|
|
name: "CLuma",
|
|
path: "Sources/CLuma",
|
|
sources: cLumaSources,
|
|
publicHeadersPath: "include",
|
|
cSettings: cLumaCSettings,
|
|
cxxSettings: cLumaCxxSettings,
|
|
linkerSettings: cLumaLinkerSettings
|
|
),
|
|
.executableTarget(
|
|
name: "LumaGtk",
|
|
dependencies: [
|
|
.product(name: "LumaCore", package: "luma"),
|
|
.product(name: "Gtk", package: "SwiftGtk"),
|
|
.product(name: "Adw", package: "SwiftAdw"),
|
|
.product(name: "GtkSource", package: "SwiftGtkSourceView"),
|
|
"CLuma",
|
|
],
|
|
path: "Sources/LumaGtk",
|
|
exclude: lumaGtkExcludes,
|
|
resources: [
|
|
.copy("Resources/nowsecure-logo.svg"),
|
|
.copy("Resources/pharo"),
|
|
],
|
|
swiftSettings: [
|
|
.swiftLanguageMode(.v6),
|
|
// The GL widget is Swift, so CLuma's headers reach epoxy.
|
|
.unsafeFlags(pkgConfigFlags(["epoxy"]).flatMap { ["-Xcc", $0] }),
|
|
] + adwaitaFeatureDefines(),
|
|
linkerSettings: lumaGtkLinkerSettings,
|
|
plugins: [
|
|
.plugin(name: "EmbedLumaVersion"),
|
|
]
|
|
),
|
|
.executableTarget(
|
|
name: "LumaVersionWriter",
|
|
path: "Sources/LumaVersionWriter",
|
|
swiftSettings: [
|
|
.swiftLanguageMode(.v6),
|
|
]
|
|
),
|
|
.plugin(
|
|
name: "EmbedLumaVersion",
|
|
capability: .buildTool(),
|
|
dependencies: ["LumaVersionWriter"],
|
|
path: "Plugins/EmbedLumaVersion"
|
|
),
|
|
]
|
|
)
|
|
|
|
#if os(Windows)
|
|
func compileWindowsExecutableIcon() -> String? {
|
|
let pkg = URL(fileURLWithPath: #filePath).deletingLastPathComponent().path
|
|
let rcFile = pkg + "\\Sources\\LumaGtk\\luma.rc"
|
|
let resFile = pkg + "\\Sources\\LumaGtk\\luma.res"
|
|
guard FileManager.default.fileExists(atPath: rcFile) else { return nil }
|
|
if let rc = findOnPath("rc") {
|
|
let proc = Process()
|
|
proc.executableURL = URL(fileURLWithPath: rc)
|
|
proc.arguments = ["/nologo", "/fo", resFile, rcFile]
|
|
proc.standardOutput = FileHandle.nullDevice
|
|
proc.standardError = FileHandle.nullDevice
|
|
try? proc.run()
|
|
proc.waitUntilExit()
|
|
if proc.terminationStatus == 0 { return resFile }
|
|
}
|
|
return FileManager.default.fileExists(atPath: resFile) ? resFile : nil
|
|
}
|
|
#endif
|
|
|
|
func adwaitaFeatureDefines() -> [SwiftSetting] {
|
|
var defines: [SwiftSetting] = []
|
|
if pkgConfigAtLeast("libadwaita-1", "1.6") {
|
|
defines.append(.define("HAS_ADW_SPINNER"))
|
|
}
|
|
if pkgConfigAtLeast("libadwaita-1", "1.8") {
|
|
defines.append(.define("HAS_ADW_SHORTCUTS_DIALOG"))
|
|
}
|
|
if pkgConfigAtLeast("gtk4", "4.16") {
|
|
defines.append(.define("HAS_GDK_MEMORY_TEXTURE_BUILDER"))
|
|
}
|
|
if pkgConfigAtLeast("gtk4", "4.20") {
|
|
defines.append(.define("HAS_GTK_CALENDAR_SET_DATE"))
|
|
}
|
|
return defines
|
|
}
|
|
|
|
// The image looks these up by name and nothing in Swift calls them, so on
|
|
// Windows they reach the export table only by being named here.
|
|
func pharoBridgeExports() -> [String] {
|
|
let bridgeDir = URL(fileURLWithPath: #filePath)
|
|
.deletingLastPathComponent()
|
|
.deletingLastPathComponent()
|
|
.appendingPathComponent("Sources/LumaCore/Pharo", isDirectory: true)
|
|
let sources = (try? FileManager.default.contentsOfDirectory(at: bridgeDir, includingPropertiesForKeys: nil)) ?? []
|
|
return sources.filter { $0.pathExtension == "swift" }
|
|
.flatMap { source -> [String] in
|
|
let text = (try? String(contentsOf: source, encoding: .utf8)) ?? ""
|
|
return text.components(separatedBy: "@_cdecl(\"").dropFirst().map {
|
|
String($0.prefix(while: { $0 != "\"" }))
|
|
}
|
|
}
|
|
.sorted()
|
|
}
|
|
|
|
func pkgConfigFlags(_ packages: [String], libs: Bool = false) -> [String] {
|
|
guard let pkgConfigPath = findOnPath("pkg-config") else { return [] }
|
|
let proc = Process()
|
|
proc.executableURL = URL(fileURLWithPath: pkgConfigPath)
|
|
proc.arguments = (libs ? ["--libs"] : ["--cflags"]) + packages
|
|
let pipe = Pipe()
|
|
proc.standardOutput = pipe
|
|
proc.standardError = FileHandle.nullDevice
|
|
try? proc.run()
|
|
proc.waitUntilExit()
|
|
guard proc.terminationStatus == 0 else { return [] }
|
|
let data = pipe.fileHandleForReading.readDataToEndOfFile()
|
|
let tokens = String(data: data, encoding: .utf8)?
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
.split(separator: " ")
|
|
.map(String.init) ?? []
|
|
var seen = Set<String>()
|
|
return tokens.filter { seen.insert($0).inserted }
|
|
}
|
|
|
|
func pkgConfigAtLeast(_ package: String, _ version: String) -> Bool {
|
|
guard let pkgConfigPath = findOnPath("pkg-config") else { return false }
|
|
let proc = Process()
|
|
proc.executableURL = URL(fileURLWithPath: pkgConfigPath)
|
|
proc.arguments = ["--atleast-version=\(version)", package]
|
|
proc.standardOutput = FileHandle.nullDevice
|
|
proc.standardError = FileHandle.nullDevice
|
|
try? proc.run()
|
|
proc.waitUntilExit()
|
|
return proc.terminationStatus == 0
|
|
}
|
|
|
|
func findOnPath(_ name: String) -> String? {
|
|
#if os(Windows)
|
|
let separator: Character = ";"
|
|
let extensions = ["", ".exe", ".cmd", ".bat"]
|
|
let pathSep = "\\"
|
|
#else
|
|
let separator: Character = ":"
|
|
let extensions = [""]
|
|
let pathSep = "/"
|
|
#endif
|
|
let env = ProcessInfo.processInfo.environment
|
|
guard let pathValue = env["PATH"] ?? env["Path"] else { return nil }
|
|
for dir in pathValue.split(separator: separator).map(String.init) where !dir.isEmpty {
|
|
for ext in extensions {
|
|
let candidate = dir + pathSep + name + ext
|
|
if FileManager.default.fileExists(atPath: candidate) {
|
|
return candidate
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|