From 4481717636f530238e738b9795452ea6c663c62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Wed, 14 Oct 2020 13:39:18 +0200 Subject: [PATCH 01/18] [CI] Implemented experimental CI configuration --- .github/workflows/build-gtk.yml | 183 ++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 .github/workflows/build-gtk.yml diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml new file mode 100644 index 0000000..ee96f4b --- /dev/null +++ b/.github/workflows/build-gtk.yml @@ -0,0 +1,183 @@ +name: Build gir2swift and build SwiftGtk + +# Dependencies of Glib package +env: + MACOS_BREW: ${{ 'glib glib-networking gobject-introspection pkg-config libxml2' }} + UBUNTU_APT: ${{ 'libpango1.0-dev libglib2.0-dev libgdk-pixbuf2.0-dev gobject-introspection libcairo2-dev libatk1.0-dev glib-networking libgtk-3-dev libgirepository1.0-dev' }} + +on: + pull_request: + branches: [ master ] + +# There are multiple jobs. We want to test build on macOS, two latest LTS releases of Ubuntu and on Swift 5.3 and 5.2. +jobs: + + # # macOS tasks + # build-mac-swift-latest: + # runs-on: macos-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v2 + # - uses: maxim-lobanov/setup-xcode@v1 + # with: + # xcode-version: '12.0' + # - name: Print Swift version to confirm + # run: swift --version + # - name: Fetch dependencies for general repository + # run: brew install $MACOS_BREW + # - name: Build using scripts + # run: ./build.sh + # build-mac-swift-5_2: + # runs-on: macos-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v2 + # - uses: maxim-lobanov/setup-xcode@v1 + # with: + # xcode-version: '11.4' + # - name: Print Swift version to confirm + # run: swift --version + # - name: Fetch dependencies for general repository + # run: brew install $MACOS_BREW + # - name: Build using scripts + # run: ./build.sh + + # Ubuntu 20.04 tasks + build-ubuntu-20_04-swift-latest: + runs-on: ubuntu-20.04 + steps: + - name: Print Swift version to confirm + run: swift --version + - name: Fetch dependencies for general repository + run: sudo apt-get install $UBUNTU_APT + + - name: Checkout gir2swift + uses: actions/checkout@v2 + with: + path: gir2swift + - name: Checkout testing repo + uses: actions/checkout@v2 + with: + repository: rhx/SwiftGtk + path: swiftGtk + + - name: Build current gir2swift + run: | + cd gir2swift + ./build.sh + cd .. + - name: Export executable + run: | + echo "GIR2SWIFT_PATH=${PWD}/gir2swift/.build/release/" >> $GITHUB_ENV + echo "${PWD}/gir2swift/.build/release/" >> $GITHUB_PATH + - name: Test export success + run: gir2swift + + - name: Build gtk + run: | + cd swiftGtk + ./build.sh + cd .. + + - name: Remove unneeded files and archive artifacts + run: | + cd gir2swift + swift package clean + rm -rf .build/repositories + cd ../swiftGtk + swift package clean + rm -rf .build/repositories + cd .. + zip -r build.zip gir2swift swiftGtk + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: build-artifact-20.04-5.3 + path: build.zip + retention-days: 1 + + + build-ubuntu-20_04-swift-5_2: + runs-on: ubuntu-20.04 + steps: + - uses: sersoft-gmbh/SwiftyActions@v1 + with: + platform: ubuntu20.04 + release-version: 5.2.4 + - name: Print Swift version to confirm + run: swift --version + - name: Fetch dependencies for general repository + run: sudo apt-get install $UBUNTU_APT + + - name: Checkout gir2swift + uses: actions/checkout@v2 + with: + path: gir2swift + - name: Checkout testing repo + uses: actions/checkout@v2 + with: + repository: rhx/SwiftGtk + path: swiftGtk + + - name: Build current gir2swift + run: | + cd gir2swift + ./build.sh + cd .. + - name: Export executable + run: | + echo "GIR2SWIFT_PATH=${PWD}/gir2swift/.build/release/" >> $GITHUB_ENV + echo "${PWD}/gir2swift/.build/release/" >> $GITHUB_PATH + - name: Test export success + run: gir2swift + + - name: Build gtk + run: | + cd swiftGtk + ./build.sh + cd .. + + - name: Remove unneeded files, left over only source codes + run: | + cd gir2swift + swift package clean + rm -rf .build/repositories + cd ../swiftGtk + swift package clean + rm -rf .build/repositories + cd .. + zip -r build.zip gir2swift swiftGtk + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: build-artifact-20.04-5.2 + path: build.zip + retention-days: 1 + + # # Ubuntu 18.04 tasks + # build-ubuntu-18_04-swift-latest: + # runs-on: ubuntu-18.04 + # steps: + # - name: Checkout + # uses: actions/checkout@v2 + # - name: Print Swift version to confirm + # run: swift --version + # - name: Fetch dependencies for general repository + # run: sudo apt-get install $UBUNTU_APT + # - name: Build using scripts + # run: ./build.sh + # build-ubuntu-18_04-swift-5_2: + # runs-on: ubuntu-18.04 + # steps: + # - uses: sersoft-gmbh/SwiftyActions@v1 + # with: + # platform: ubuntu18.04 + # release-version: 5.2.2 + # - name: Print Swift version to confirm + # run: swift --version + # - name: Checkout + # uses: actions/checkout@v2 + # - name: Fetch dependencies for general repository + # run: sudo apt-get install $UBUNTU_APT + # - name: Build using scripts + # run: ./build.sh \ No newline at end of file From ce226f65e857b6228c0dc90d0edbae3f2bf2c190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Fri, 16 Oct 2020 16:37:37 +0200 Subject: [PATCH 02/18] [Signal] Implement concept of signal generation --- .gitignore | 1 + Sources/gir2swift/main.swift | 1 + Sources/libgir2swift/CodeBuilder.swift | 49 ++++ Sources/libgir2swift/gir+swift.swift | 341 +++++++++++++++++++++---- 4 files changed, 337 insertions(+), 55 deletions(-) create mode 100644 Sources/libgir2swift/CodeBuilder.swift diff --git a/.gitignore b/.gitignore index 2413ca6..1e86f68 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /Packages /Package.resolved /*.xcodeproj +/.swiftpm diff --git a/Sources/gir2swift/main.swift b/Sources/gir2swift/main.swift index b77c14c..beffdcf 100644 --- a/Sources/gir2swift/main.swift +++ b/Sources/gir2swift/main.swift @@ -107,6 +107,7 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect for type in types { let convert = ptrconvert(type.ptrName) let code = convert(type) + output += code + "\n\n" name = type.className guard let firstChar = name.first else { continue } diff --git a/Sources/libgir2swift/CodeBuilder.swift b/Sources/libgir2swift/CodeBuilder.swift new file mode 100644 index 0000000..2080294 --- /dev/null +++ b/Sources/libgir2swift/CodeBuilder.swift @@ -0,0 +1,49 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 16/10/2020. +// + +import Foundation + +@_functionBuilder +class CodeBuilder { + private static let ignoringEspace: String = "<%IGNORED%>" + + static func buildBlock( _ segments: String...) -> String { + segments.filter { $0 != CodeBuilder.ignoringEspace } .joined(separator: "\n") + } + + static func buildEither(first: String) -> String { first } + static func buildEither(second: String) -> String { second } + + static func buildOptional(_ component: String?) -> String { component ?? CodeBuilder.ignoringEspace } + static func buildIf(_ segment: String?) -> String { buildOptional(segment) } +} + +class Code { + static var defaultCodeIndentation: String = " " + + static func block(indentation: String? = defaultCodeIndentation, @CodeBuilder builder: ()->String) -> String { + let code = builder() + + if let indentation = indentation { + return indentation + (code.replacingOccurrences(of: "\n", with: "\n" + indentation)) + } + + return builder() + } + + static func line(@CodeBuilder builder: ()->String) -> String { + builder().components(separatedBy: "\n").joined() + } + + static func loop(over items: [T], @CodeBuilder builder: (T)->String) -> String { + items.map(builder).joined(separator: "\n") + } + + static func loopEnumerated(over items: [T], @CodeBuilder builder: (Int, T)->String) -> String { + items.enumerated().map(builder).joined(separator: "\n") + } +} diff --git a/Sources/libgir2swift/gir+swift.swift b/Sources/libgir2swift/gir+swift.swift index fc7b1e6..9e9cad6 100644 --- a/Sources/libgir2swift/gir+swift.swift +++ b/Sources/libgir2swift/gir+swift.swift @@ -10,29 +10,92 @@ import Foundation public extension GIR { /// code boiler plate var boilerPlate: String { - return """ - - extension gboolean { - private init(_ b: Bool) { self = b ? gboolean(1) : gboolean(0) } - } - - func asStringArray(_ param: UnsafePointer?>) -> [String] { - var ptr = param - var rv = [String]() - while ptr.pointee != nil { - rv.append(String(cString: ptr.pointee!)) - ptr = ptr.successor() - } - return rv - } - - func asStringArray(_ param: UnsafePointer?>, release: ((UnsafePointer?) -> Void)) -> [String] { - let rv = asStringArray(param) - param.withMemoryRebound(to: T.self, capacity: rv.count) { release(UnsafePointer($0)) } - return rv - } - - """ +""" + +final class Tmp__ClosureHolder { + public let call: (S) -> T + + @inlinable public init(_ closure: @escaping (S) -> T) { + self.call = closure + } +} + +final class Tmp__DualClosureHolder { + + public let call: (S, T) -> U + + public init(_ closure: @escaping (S, T) -> U) { + self.call = closure + } +} + +final class Tmp__Closure3Holder { + + public let call: (S, T, U) -> V + + public init(_ closure: @escaping (S, T, U) -> V) { + self.call = closure + } +} + +final class Tmp__Closure4Holder { + + public let call: (S, T, U, V) -> W + + public init(_ closure: @escaping (S, T, U, V) -> W) { + self.call = closure + } +} + +final class Tmp__Closure5Holder { + + public let call: (S, T, U, V, W) -> X + + public init(_ closure: @escaping (S, T, U, V, W) -> X) { + self.call = closure + } +} + +final class Tmp__Closure6Holder { + + public let call: (S, T, U, V, W, X) -> Y + + public init(_ closure: @escaping (S, T, U, V, W, X) -> Y) { + self.call = closure + } +} + +final class Tmp__Closure7Holder { + + public let call: (S, T, U, V, W, X, Y) -> Z + + public init(_ closure: @escaping (S, T, U, V, W, X, Y) -> Z) { + self.call = closure + } +} + + +extension gboolean { + private init(_ b: Bool) { self = b ? gboolean(1) : gboolean(0) } +} + +func asStringArray(_ param: UnsafePointer?>) -> [String] { + var ptr = param + var rv = [String]() + while ptr.pointee != nil { + rv.append(String(cString: ptr.pointee!)) + ptr = ptr.successor() + } + return rv +} + +func asStringArray(_ param: UnsafePointer?>, release: ((UnsafePointer?) -> Void)) -> [String] { + let rv = asStringArray(param) + param.withMemoryRebound(to: T.self, capacity: rv.count) { release(UnsafePointer($0)) } + return rv +} + +""" } } @@ -1529,41 +1592,209 @@ public func recordClassCode(_ e: GIR.Record, parent: String, indentation: String "@inlinable func set(property: \(className)PropertyName, value v: GLibObject.Value) {\n" + doubleIndentation + "g_object_set_property(ptr.assumingMemoryBound(to: GObject.self), property.rawValue, v.value_ptr)\n" + indentation + "}\n}\n\n")) - let code = code1 + code2 + code3 + (noSignals ? "// MARK: no \(className) signals\n" : "public enum \(className)SignalName: String, SignalNameProtocol {\n") + -// "public typealias Class = \(protocolName)\n") + - signals.map(scode).joined(separator: "\n") + "\n" + - properties.map(ncode).joined(separator: "\n") + "\n" + - (noSignals ? "" : ("}\n\npublic extension \(protocolName) {\n" + indentation + - "/// Connect a `\(className)SignalName` signal to a given signal handler.\n" + indentation + - "/// - Parameter signal: the signal to connect\n" + indentation + - "/// - Parameter flags: signal connection flags\n" + indentation + - "/// - Parameter handler: signal handler to use\n" + indentation + - "/// - Returns: positive handler ID, or a value less than or equal to `0` in case of an error\n" + indentation + - "@inlinable @discardableResult func connect(signal kind: \(className)SignalName, flags f: ConnectFlags = ConnectFlags(0), to handler: @escaping GLibObject.SignalHandler) -> Int {\n" + doubleIndentation + - "func _connect(signal name: UnsafePointer, flags: ConnectFlags, data: GLibObject.SignalHandlerClosureHolder, handler: @convention(c) @escaping (gpointer, gpointer) -> Void) -> Int {\n" + tripleIndentation + - "let holder = UnsafeMutableRawPointer(Unmanaged.passRetained(data).toOpaque())\n" + tripleIndentation + - "let callback = unsafeBitCast(handler, to: GLibObject.Callback.self)\n" + tripleIndentation + - "let rv = GLibObject.ObjectRef(raw: ptr).signalConnectData(detailedSignal: name, cHandler: callback, data: holder, destroyData: {\n" + tripleIndentation + indentation + - "if let swift = UnsafeRawPointer($0) {\n" + tripleIndentation + doubleIndentation + - "let holder = Unmanaged.fromOpaque(swift)\n" + tripleIndentation + doubleIndentation + - "holder.release()\n" + tripleIndentation + indentation + - "}\n" + tripleIndentation + indentation + - "let _ = $1\n" + tripleIndentation + - "}, connectFlags: flags)\n" + tripleIndentation + - "return rv\n" + doubleIndentation + - "}\n" + doubleIndentation + - "let rv = _connect(signal: kind.name, flags: f, data: ClosureHolder(handler)) {\n" + tripleIndentation + - "let ptr = UnsafeRawPointer($1)\n" + tripleIndentation + - "let holder = Unmanaged.fromOpaque(ptr).takeUnretainedValue()\n" + tripleIndentation + - "holder.call(())\n" + doubleIndentation + - "}\n" + doubleIndentation + - "return rv\n" + indentation + - "}\n" + - "}\n\n")) - return code + let legacySignals: String + var legacySignalExt: String = "" + if noSignals { + legacySignals = "// MARK: no \(className) signals\n" + } else { + legacySignals = "public enum \(className)SignalName: String, SignalNameProtocol {\n" + + signals.map(scode).joined(separator: "\n") + "\n" + + properties.map(ncode).joined(separator: "\n") + "\n" + + legacySignalExt = ("}\n\npublic extension \(protocolName) {\n" + indentation + + "/// Connect a `\(className)SignalName` signal to a given signal handler.\n" + indentation + + "/// - Parameter signal: the signal to connect\n" + indentation + + "/// - Parameter flags: signal connection flags\n" + indentation + + "/// - Parameter handler: signal handler to use\n" + indentation + + "/// - Returns: positive handler ID, or a value less than or equal to `0` in case of an error\n" + indentation + + "@inlinable @discardableResult func connect(signal kind: \(className)SignalName, flags f: ConnectFlags = ConnectFlags(0), to handler: @escaping GLibObject.SignalHandler) -> Int {\n" + doubleIndentation + + "func _connect(signal name: UnsafePointer, flags: ConnectFlags, data: GLibObject.SignalHandlerClosureHolder, handler: @convention(c) @escaping (gpointer, gpointer) -> Void) -> Int {\n" + tripleIndentation + + "let holder = UnsafeMutableRawPointer(Unmanaged.passRetained(data).toOpaque())\n" + tripleIndentation + + "let callback = unsafeBitCast(handler, to: GLibObject.Callback.self)\n" + tripleIndentation + + "let rv = GLibObject.ObjectRef(raw: ptr).signalConnectData(detailedSignal: name, cHandler: callback, data: holder, destroyData: {\n" + tripleIndentation + indentation + + "if let swift = UnsafeRawPointer($0) {\n" + tripleIndentation + doubleIndentation + + "let holder = Unmanaged.fromOpaque(swift)\n" + tripleIndentation + doubleIndentation + + "holder.release()\n" + tripleIndentation + indentation + + "}\n" + tripleIndentation + indentation + + "let _ = $1\n" + tripleIndentation + + "}, connectFlags: flags)\n" + tripleIndentation + + "return rv\n" + doubleIndentation + + "}\n" + doubleIndentation + + "let rv = _connect(signal: kind.name, flags: f, data: ClosureHolder(handler)) {\n" + tripleIndentation + + "let ptr = UnsafeRawPointer($1)\n" + tripleIndentation + + "let holder = Unmanaged.fromOpaque(ptr).takeUnretainedValue()\n" + tripleIndentation + + "holder.call(())\n" + doubleIndentation + + "}\n" + doubleIndentation + + "return rv\n" + indentation + + "}\n" + + "}\n\n") + } + let code = code1 + code2 + code3 + legacySignals + legacySignalExt + return code + buildSignalExtension(for: e) +} + +func buildSignalExtension(for record: GIR.Record) -> String { + if record.kind == "Interface" { + return "// MARK: Signals of \(record.kind) named \(record.name.swift) are dropped" + } + return Code.block(indentation: nil) { + if record.signals.isEmpty { + "// MARK: no \(record.name.swift) signals" + } else { + "// MARK: Signals of \(record.kind) named \(record.name.swift)" + "public extension \(record.name.swift) {" + Code.block { + Code.loop(over: record.signals) { signal in + if signal.args.contains(where: { $0.swiftClosureTypeName == "Void" }) { + "/// Warning: signal \(signal.name) is ignored because of Void argument" + } else { + + commentCode(signal) + "/// - Note: This function represents signal `\(signal.name)`" + "/// - Parameter flags: Flags" + + let returnComment = gtkDoc2SwiftDoc(signal.returns.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + if !returnComment.isEmpty { + "/// - Parameter handler: \(returnComment)" + } + + "/// - Parameter unownedSelf: Reference to instance of self" + Code.loop(over: signal.args) { argument in + let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" + } + + Code.line { + "public func _on\(signal.name.camelSignal.capitalised)(" + "flags: ConnectFlags = ConnectFlags(0), " + "handler: @escaping ( _ unownedSelf: \(record.structRef.type.swiftName)" + Code.loop(over: signal.args) { argument in + ", _ \(argument.prefixedArgumentName): \(argument.swiftClosureTypeName)" + } + ") -> " + signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name + ") -> Int {" + } + Code.block { + "typealias SwiftHandler = \(signalClosureHolderDecl(type: record.structRef.type.swiftName, args: signal.args.map { $0.swiftClosureTypeName }, returnType: signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name))" + "let swiftHandlerBoxed = Unmanaged.passRetained(SwiftHandler(handler)).toOpaque()" + Code.line { + "let cCallback: @convention(c) (" + "gpointer, " + Code.loop(over: signal.args) { argument in + "\(argument.swiftSignalCClosureName), " + } + "gpointer" + ") -> " + signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name + " = { " + } + Code.block { + "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" + Code.line { + "return holder.call(\(record.structRef.type.swiftName)(raw: $0)" + Code.loopEnumerated(over: signal.args) { index, argument in + ", \(argument.swiftSignalArgumentConversion(at: index + 1))" + } + ")" + } + } + "}" + "let __gCallback__ = unsafeBitCast(cCallback, to: GCallback.self)" + "let rv = signalConnectData(" + Code.block { + #"detailedSignal: "\#(signal.name)", "# + "cHandler: __gCallback__, " + "data: swiftHandlerBoxed, " + "destroyData: {" + Code.block { + "if let swift = $0 {" + Code.block { + "let holder = Unmanaged.fromOpaque(swift)" + "holder.release()" + } + "}" + "let _ = $1" + } + "}, " + "connectFlags: flags" + } + ")" + "return rv" + } + "}" + "\n" + } + } + } + "}" + } + "\n\n" + } } +extension GIR.Argument { + var swiftClosureTypeName: String { + switch self.knownType { + case is GIR.Record, is GIR.Class, is GIR.Interface: + return self.typeRef.type.swiftName + "Ref" + case is GIR.Bitfield: + return "UInt64" + default /* GIR.Bitfield, GIR.Enumeration */: + return self.callbackArgumentTypeName + } + } + + var swiftSignalCClosureName: String { + switch self.knownType { + case is GIR.Record, is GIR.Class, is GIR.Interface: + return "gpointer" + case is GIR.Bitfield: + return "UInt64" + default /* GIR.Bitfield, GIR.Enumeration */: + return self.callbackArgumentTypeName + } + } + + + func swiftSignalArgumentConversion(at index: Int) -> String { + switch self.knownType { + case is GIR.Record, is GIR.Class, is GIR.Interface: + return swiftClosureTypeName + "(raw: $\(index))" + default /* GIR.Bitfield, GIR.Enumeration */: + return "$\(index)" + } + } +} +func signalClosureHolderDecl(type: String, args: [String], returnType: String = "Void") -> String { + let holderType: String + switch args.count { + case 0: + holderType = "Tmp__ClosureHolder" + case 1: + holderType = "Tmp__DualClosureHolder" + case 2: + holderType = "Tmp__Closure3Holder" + case 3: + holderType = "Tmp__Closure4Holder" + case 4: + holderType = "Tmp__Closure5Holder" + case 5: + holderType = "Tmp__Closure6Holder" + case 6: + holderType = "Tmp__Closure7Holder" + default: + fatalError("Argument count \(args.count) exceeds number of allowed arguments (6)") + } + + return holderType + + "<\(type), " + + args.joined(separator: ", ") + + (args.isEmpty ? "" : ", ") + + returnType + + ">" +} // MARK: - Swift code for Record/Class methods From e2da0259a854495c8392b16ef7933e893c1f0873 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Thu, 12 Nov 2020 17:59:25 +0100 Subject: [PATCH 03/18] [Build] Implement external generation driver. --- .github/workflows/build-gtk.yml | 139 ++------------------------------ gir2swift-generation-driver.sh | 130 +++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 131 deletions(-) create mode 100755 gir2swift-generation-driver.sh diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index ee96f4b..5671479 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -2,46 +2,13 @@ name: Build gir2swift and build SwiftGtk # Dependencies of Glib package env: - MACOS_BREW: ${{ 'glib glib-networking gobject-introspection pkg-config libxml2' }} UBUNTU_APT: ${{ 'libpango1.0-dev libglib2.0-dev libgdk-pixbuf2.0-dev gobject-introspection libcairo2-dev libatk1.0-dev glib-networking libgtk-3-dev libgirepository1.0-dev' }} on: pull_request: branches: [ master ] - -# There are multiple jobs. We want to test build on macOS, two latest LTS releases of Ubuntu and on Swift 5.3 and 5.2. jobs: - # # macOS tasks - # build-mac-swift-latest: - # runs-on: macos-latest - # steps: - # - name: Checkout - # uses: actions/checkout@v2 - # - uses: maxim-lobanov/setup-xcode@v1 - # with: - # xcode-version: '12.0' - # - name: Print Swift version to confirm - # run: swift --version - # - name: Fetch dependencies for general repository - # run: brew install $MACOS_BREW - # - name: Build using scripts - # run: ./build.sh - # build-mac-swift-5_2: - # runs-on: macos-latest - # steps: - # - name: Checkout - # uses: actions/checkout@v2 - # - uses: maxim-lobanov/setup-xcode@v1 - # with: - # xcode-version: '11.4' - # - name: Print Swift version to confirm - # run: swift --version - # - name: Fetch dependencies for general repository - # run: brew install $MACOS_BREW - # - name: Build using scripts - # run: ./build.sh - # Ubuntu 20.04 tasks build-ubuntu-20_04-swift-latest: runs-on: ubuntu-20.04 @@ -58,33 +25,29 @@ jobs: - name: Checkout testing repo uses: actions/checkout@v2 with: - repository: rhx/SwiftGtk - path: swiftGtk + repository: mikolasstuchlik/SwiftGtk + path: SwiftGtk - name: Build current gir2swift run: | cd gir2swift ./build.sh + echo "GIR2SWIFT_PATH=${PWD}/.build/release/gir2swift" >> $GITHUB_ENV cd .. - - name: Export executable - run: | - echo "GIR2SWIFT_PATH=${PWD}/gir2swift/.build/release/" >> $GITHUB_ENV - echo "${PWD}/gir2swift/.build/release/" >> $GITHUB_PATH - - name: Test export success - run: gir2swift - name: Build gtk run: | - cd swiftGtk - ./build.sh + cd SwiftGtk + swift package update + ../gir2swift/gir2swift-generation-driver.sh "$PWD" "$GIR2SWIFT_PATH" cd .. - + - name: Remove unneeded files and archive artifacts run: | cd gir2swift swift package clean rm -rf .build/repositories - cd ../swiftGtk + cd ../SwiftGtk swift package clean rm -rf .build/repositories cd .. @@ -95,89 +58,3 @@ jobs: name: build-artifact-20.04-5.3 path: build.zip retention-days: 1 - - - build-ubuntu-20_04-swift-5_2: - runs-on: ubuntu-20.04 - steps: - - uses: sersoft-gmbh/SwiftyActions@v1 - with: - platform: ubuntu20.04 - release-version: 5.2.4 - - name: Print Swift version to confirm - run: swift --version - - name: Fetch dependencies for general repository - run: sudo apt-get install $UBUNTU_APT - - - name: Checkout gir2swift - uses: actions/checkout@v2 - with: - path: gir2swift - - name: Checkout testing repo - uses: actions/checkout@v2 - with: - repository: rhx/SwiftGtk - path: swiftGtk - - - name: Build current gir2swift - run: | - cd gir2swift - ./build.sh - cd .. - - name: Export executable - run: | - echo "GIR2SWIFT_PATH=${PWD}/gir2swift/.build/release/" >> $GITHUB_ENV - echo "${PWD}/gir2swift/.build/release/" >> $GITHUB_PATH - - name: Test export success - run: gir2swift - - - name: Build gtk - run: | - cd swiftGtk - ./build.sh - cd .. - - - name: Remove unneeded files, left over only source codes - run: | - cd gir2swift - swift package clean - rm -rf .build/repositories - cd ../swiftGtk - swift package clean - rm -rf .build/repositories - cd .. - zip -r build.zip gir2swift swiftGtk - - name: 'Upload Artifact' - uses: actions/upload-artifact@v2 - with: - name: build-artifact-20.04-5.2 - path: build.zip - retention-days: 1 - - # # Ubuntu 18.04 tasks - # build-ubuntu-18_04-swift-latest: - # runs-on: ubuntu-18.04 - # steps: - # - name: Checkout - # uses: actions/checkout@v2 - # - name: Print Swift version to confirm - # run: swift --version - # - name: Fetch dependencies for general repository - # run: sudo apt-get install $UBUNTU_APT - # - name: Build using scripts - # run: ./build.sh - # build-ubuntu-18_04-swift-5_2: - # runs-on: ubuntu-18.04 - # steps: - # - uses: sersoft-gmbh/SwiftyActions@v1 - # with: - # platform: ubuntu18.04 - # release-version: 5.2.2 - # - name: Print Swift version to confirm - # run: swift --version - # - name: Checkout - # uses: actions/checkout@v2 - # - name: Fetch dependencies for general repository - # run: sudo apt-get install $UBUNTU_APT - # - name: Build using scripts - # run: ./build.sh \ No newline at end of file diff --git a/gir2swift-generation-driver.sh b/gir2swift-generation-driver.sh new file mode 100755 index 0000000..7ba8b02 --- /dev/null +++ b/gir2swift-generation-driver.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +function is_processable_arg-path { + local PACKAGE_PATH=$1 + + local CALLER=$PWD + cd $PACKAGE_PATH + + local PACKAGE=`swift package dump-package` + local GENERATED=`jq -r '.dependencies | .[] | select(.name == "gir2swift") | .name' <<< $PACKAGE` + + cd $CALLER + + if [ $GENERATED ] + then + return 0 + else + return 1 + fi +} + +function gir_path { + # TODO: Add code to determine path + + echo "/usr/share/gir-1.0" +} + +function gir_2_swift_executable_arg-deps { + local DEPENDENCIES=$1 + + local G2S_PACKAGE_PATH=`jq -r 'first(recurse(.dependencies[]) | select(.name == "gir2swift")) | .path' <<< $DEPENDENCIES` + + local CALLER=$PWD + cd $G2S_PACKAGE_PATH + + ./distclean.sh > /dev/null + ./build.sh > /dev/null + + cd $CALLER + + echo "${G2S_PACKAGE_PATH}/.build/release/gir2swift" +} + +function get_processable_dependencies_arg-deps_arg-name { + local DEPENDENCIES=$1 + local PACKAGE_NAME=$2 + + local PACKAGE=`jq -r "first(recurse(.dependencies[]) | select(.name == \"$PACKAGE_NAME\"))" <<< $DEPENDENCIES` + + local ALL_DEPS=`jq -r "recurse(.dependencies[]) | select(.name != \"$PACKAGE_NAME\") | .path" <<< $PACKAGE | sort | uniq` + + for DEP in $ALL_DEPS + do + if $(is_processable_arg-path $DEP) + then + echo $DEP + fi + done +} + +function get_gir_names_arg-packages { + local PACKAGES=$1 + + for PACKAGE in $PACKAGES + do + bash -c "$PACKAGE/gir2swift-manifest.sh gir-name" + done +} + +function package_name { + local PACKAGE=`swift package dump-package` + local NAME=`jq -r '.name' <<< $PACKAGE` + + echo $NAME +} +export -f package_name + +function package_pkg_config_arguments { + local PACKAGE=`swift package dump-package` + local NAME=`jq -r '.targets[] | select(.pkgConfig != null) | .pkgConfig?' <<< $PACKAGE` + + echo $NAME +} +export -f package_pkg_config_arguments + +function package_name_arg-path { + local PACKAGE_PATH=$1 + + local CALLER=$PWD + cd $PACKAGE_PATH + + local PACKAGE=`swift package dump-package` + local NAME=`jq -r '.name' <<< $PACKAGE` + + cd $CALLER + + echo $NAME +} + + +# Building process +TOP_LEVEL_PACKAGE_PATH=$1 +OPTIONAL_ALTERNATIVE_G2S_PATH=$2 + +cd $TOP_LEVEL_PACKAGE_PATH +DEPENDENCIES=`swift package show-dependencies --format json` +GIR_PATH=$(gir_path) +PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") +if [ -z "$OPTIONAL_ALTERNATIVE_G2S_PATH" ] +then + G2S_PATH=$(gir_2_swift_executable_arg-deps "$DEPENDENCIES") +else + G2S_PATH=$OPTIONAL_ALTERNATIVE_G2S_PATH + echo "Using custom gir2swift executable at: $G2S_PATH" +fi + +for PACKAGE in $PROCESSABLE +do + PACKAGE_NAME=$(package_name_arg-path "$PACKAGE") + PACKAGE_DEPS=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$PACKAGE_NAME") + GIR_NAMES=$(get_gir_names_arg-packages "$PACKAGE_DEPS") + bash -c "$PACKAGE/gir2swift-manifest.sh generate \"$PACKAGE\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " +done + +if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") +then + GIR_NAMES=$(get_gir_names_arg-packages "$PROCESSABLE") + bash -c "$TOP_LEVEL_PACKAGE_PATH/gir2swift-manifest.sh generate \"$TOP_LEVEL_PACKAGE_PATH\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " +fi + From 48f3c9e4ae260c265783b686f4adf3e68c8136fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Tue, 17 Nov 2020 12:58:19 +0100 Subject: [PATCH 04/18] [gir2swift] Change project structure This commit splits long files to smaller ones, introduces subdirectories and removes some superfluous extensions --- Sources/gir2swift/main.swift | 12 +- Sources/libgir2swift/Sequence+Utilities.swift | 27 - Sources/libgir2swift/String+Lines.swift | 25 - .../{ => emmiting}/CodeBuilder.swift | 0 .../libgir2swift/{ => emmiting}/c2swift.swift | 2 +- .../libgir2swift/emmiting/emmit-signals.swift | 157 ++ .../{ => emmiting}/gir+swift.swift | 175 +- .../{ => emmiting}/girtypes+swift.swift | 0 .../{ => emmiting}/gtk2swiftdoc.swift | 0 Sources/libgir2swift/gir.swift | 1417 ----------------- .../models/ConversionContext.swift | 41 + .../Gir+Enums.swift} | 0 .../Gir+KnowTypeSets.swift} | 0 .../Gir+KnownTypes.swift} | 0 Sources/libgir2swift/models/Gir.swift | 331 ++++ .../{girtype.swift => models/GirType.swift} | 0 .../TypeConversion.swift} | 0 .../TypeReference.swift} | 0 .../models/gir elements/GirAlias.swift | 22 + .../models/gir elements/GirArgument.swift | 105 ++ .../models/gir elements/GirBitfield.swift | 41 + .../models/gir elements/GirCType.swift | 200 +++ .../models/gir elements/GirCallback.swift | 22 + .../models/gir elements/GirClass.swift | 50 + .../models/gir elements/GirConstant.swift | 53 + .../models/gir elements/GirDatatype.swift | 72 + .../models/gir elements/GirEnumeration.swift | 59 + .../models/gir elements/GirField.swift | 25 + .../models/gir elements/GirFunction.swift | 24 + .../models/gir elements/GirInterface.swift | 23 + .../models/gir elements/GirMethod.swift | 114 ++ .../models/gir elements/GirProperty.swift | 21 + .../models/gir elements/GirRecord.swift | 242 +++ .../models/gir elements/GirSignal.swift | 21 + .../models/gir elements/GirThing.swift | 119 ++ .../models/gir elements/GirUnion.swift | 22 + .../{ => models}/girtype+xml.swift | 0 .../Collection+Utilities.swift | 0 .../{ => utilities}/FileLen.swift | 0 .../String+ContentsOfFile.swift | 0 .../{ => utilities}/String+Substring.swift | 2 +- .../utilities/String+Utilities.swift | 54 + .../utilities/XML+Utilities.swift | 59 + .../{ => utilities}/basename.swift | 0 .../{ => utilities}/cstring.swift | 0 .../libgir2swift/{ => utilities}/getopt.swift | 0 .../libgir2swift/{ => utilities}/mmap.swift | 0 47 files changed, 1891 insertions(+), 1646 deletions(-) delete mode 100644 Sources/libgir2swift/Sequence+Utilities.swift delete mode 100644 Sources/libgir2swift/String+Lines.swift rename Sources/libgir2swift/{ => emmiting}/CodeBuilder.swift (100%) rename Sources/libgir2swift/{ => emmiting}/c2swift.swift (99%) create mode 100644 Sources/libgir2swift/emmiting/emmit-signals.swift rename Sources/libgir2swift/{ => emmiting}/gir+swift.swift (92%) rename Sources/libgir2swift/{ => emmiting}/girtypes+swift.swift (100%) rename Sources/libgir2swift/{ => emmiting}/gtk2swiftdoc.swift (100%) delete mode 100644 Sources/libgir2swift/gir.swift create mode 100644 Sources/libgir2swift/models/ConversionContext.swift rename Sources/libgir2swift/{girenums.swift => models/Gir+Enums.swift} (100%) rename Sources/libgir2swift/{girtypereplacements.swift => models/Gir+KnowTypeSets.swift} (100%) rename Sources/libgir2swift/{girknowntypes.swift => models/Gir+KnownTypes.swift} (100%) create mode 100644 Sources/libgir2swift/models/Gir.swift rename Sources/libgir2swift/{girtype.swift => models/GirType.swift} (100%) rename Sources/libgir2swift/{girtypeconversion.swift => models/TypeConversion.swift} (100%) rename Sources/libgir2swift/{girtypereference.swift => models/TypeReference.swift} (100%) create mode 100644 Sources/libgir2swift/models/gir elements/GirAlias.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirArgument.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirBitfield.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirCType.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirCallback.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirClass.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirConstant.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirDatatype.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirEnumeration.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirField.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirFunction.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirInterface.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirMethod.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirProperty.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirRecord.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirSignal.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirThing.swift create mode 100644 Sources/libgir2swift/models/gir elements/GirUnion.swift rename Sources/libgir2swift/{ => models}/girtype+xml.swift (100%) rename Sources/libgir2swift/{ => utilities}/Collection+Utilities.swift (100%) rename Sources/libgir2swift/{ => utilities}/FileLen.swift (100%) rename Sources/libgir2swift/{ => utilities}/String+ContentsOfFile.swift (100%) rename Sources/libgir2swift/{ => utilities}/String+Substring.swift (98%) create mode 100644 Sources/libgir2swift/utilities/String+Utilities.swift create mode 100644 Sources/libgir2swift/utilities/XML+Utilities.swift rename Sources/libgir2swift/{ => utilities}/basename.swift (100%) rename Sources/libgir2swift/{ => utilities}/cstring.swift (100%) rename Sources/libgir2swift/{ => utilities}/getopt.swift (100%) rename Sources/libgir2swift/{ => utilities}/mmap.swift (100%) diff --git a/Sources/gir2swift/main.swift b/Sources/gir2swift/main.swift index beffdcf..6be6080 100644 --- a/Sources/gir2swift/main.swift +++ b/Sources/gir2swift/main.swift @@ -49,7 +49,7 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect let base = file.baseName let node = base.stringByRemoving(suffix: ".gir") ?? base let wlfile = node + ".whitelist" - if let whitelist = String(contentsOfFile: wlfile, quiet: true)?.lines.asSet { + if let whitelist = String(contentsOfFile: wlfile, quiet: true).flatMap({ Set($0.components(separatedBy: "\n")) }) { for name in whitelist { GIR.knownDataTypes.removeValue(forKey: name) GIR.knownRecords.removeValue(forKey: name) @@ -57,11 +57,11 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect } } let escfile = node + ".callbackSuffixes" - GIR.callbackSuffixes = String(contentsOfFile: escfile, quiet: true)?.lines ?? [ + GIR.callbackSuffixes = String(contentsOfFile: escfile, quiet: true)?.components(separatedBy: "\n") ?? [ "Notify", "Func", "Marshaller", "Callback" ] let nsfile = node + ".namespaceReplacements" - if let ns = String(contentsOfFile: nsfile, quiet: true)?.lines.asSet { + if let ns = String(contentsOfFile: nsfile, quiet: true).flatMap({Set($0.components(separatedBy: "\n"))}) { for line in ns { let keyValues: [Substring] let tabbedKeyValues: [Substring] = line.split(separator: "\t") @@ -223,11 +223,11 @@ func processSpecialCases(_ gir: GIR, forFile node: String) { let preamble = node + ".preamble" gir.preamble = preamble.contents ?? "" let blacklist = node + ".blacklist" - GIR.blacklist = blacklist.contents?.lines.asSet ?? [] + GIR.blacklist = blacklist.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] let verbatimConstants = node + ".verbatim" - GIR.verbatimConstants = verbatimConstants.contents?.lines.asSet ?? [] + GIR.verbatimConstants = verbatimConstants.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] let overrideFile = node + ".override" - GIR.overrides = overrideFile.contents?.lines.asSet ?? [] + GIR.overrides = overrideFile.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] } let nTypesPrior = GIR.knownTypes.count diff --git a/Sources/libgir2swift/Sequence+Utilities.swift b/Sources/libgir2swift/Sequence+Utilities.swift deleted file mode 100644 index 4cf2661..0000000 --- a/Sources/libgir2swift/Sequence+Utilities.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Sequence+Utilities.swift -// gir2swift -// -// Created by Rene Hexel on 26/03/2016. -// Copyright © 2016, 2019 Rene Hexel. All rights reserved. -// -extension Sequence { - /// Returns the first element where the comparison function returns `true` - /// or `nil` if the comparisun functoin always returns `false`. - /// - /// - Complexity: O(`self.count`). - public func findFirstWhere(_ found: (Iterator.Element) -> Bool) -> Iterator.Element? { - for element in self { if found(element) { return element } } - return nil - } -} - - -extension Sequence where Iterator.Element: Hashable { - /// return a set containing the elements from the given sequence - @inlinable public var asSet: Set { - var set = Set() - self.forEach { set.insert($0) } - return set - } -} diff --git a/Sources/libgir2swift/String+Lines.swift b/Sources/libgir2swift/String+Lines.swift deleted file mode 100644 index d2c3bdd..0000000 --- a/Sources/libgir2swift/String+Lines.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// String+Lines.swift -// gir2swift -// -// Created by Rene Hexel on 13/05/2016. -// Copyright © 2016, 2019 Rene Hexel. All rights reserved. -// -#if os(Linux) - import Glibc -#else - import Darwin -#endif - - -public extension String { - /// Split a string into substrings separated by the given character - func split(separator s: Character = "\n") -> [String] { - let u = String(s).utf8.first! - let components = utf8.split(separator: u).map { String($0)! } - return components - } - - /// return the lines of the given string - var lines: [String] { return split() } -} diff --git a/Sources/libgir2swift/CodeBuilder.swift b/Sources/libgir2swift/emmiting/CodeBuilder.swift similarity index 100% rename from Sources/libgir2swift/CodeBuilder.swift rename to Sources/libgir2swift/emmiting/CodeBuilder.swift diff --git a/Sources/libgir2swift/c2swift.swift b/Sources/libgir2swift/emmiting/c2swift.swift similarity index 99% rename from Sources/libgir2swift/c2swift.swift rename to Sources/libgir2swift/emmiting/c2swift.swift index 50b7f3b..509a303 100644 --- a/Sources/libgir2swift/c2swift.swift +++ b/Sources/libgir2swift/emmiting/c2swift.swift @@ -121,7 +121,7 @@ private let typeNames: Set = reservedTypes.union(reversecast.keys) /// UnicodeScalars representing whitespaces and newlines private let wsnlScalars: Set = [ " ", "\t", "\n"] /// Set of whitespace and newline ASCII/UTF8 codes -private let wsnl = wsnlScalars.map { UInt8($0.value) }.asSet +private let wsnl = Set(wsnlScalars.map { UInt8($0.value) }) /// Swift keyword for `true` Boolean values private let trueS = "true" diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift new file mode 100644 index 0000000..1d349e9 --- /dev/null +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -0,0 +1,157 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 14.11.2020. +// + +import Foundation + +func signalCheck(_ signal: GIR.Signal) -> Bool { + signal.args.allSatisfy { $0.typeRef.type.name != "Void" } +} + +func buildSignalExtension(for record: GIR.Record) -> String { + // Check preconditions + if record.kind == "Interface" { + return "// MARK: Signals of \(record.kind) named \(record.name.swift) are dropped" + } + + if record.signals.isEmpty { + return "// MARK: no \(record.name.swift) signals" + } + + return Code.block(indentation: nil) { + + Code.block { + Code.loop(over: record.signals.filter({ !signalCheck($0) })) { signal in + "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" + } + } + + "// MARK: Signals of \(record.kind) named \(record.name.swift)" + "public extension \(record.name.swift) {" + Code.block { + Code.loop(over: record.signals.filter(signalCheck(_:))) { signal in + commentCode(signal) + "/// - Note: This function represents signal `\(signal.name)`" + "/// - Parameter flags: Flags" + + let returnComment = gtkDoc2SwiftDoc(signal.returns.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + if !returnComment.isEmpty { + "/// - Parameter handler: \(returnComment)" + } + + "/// - Parameter unownedSelf: Reference to instance of self" + Code.loop(over: signal.args) { argument in + let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" + } + + Code.line { + "public func _on\(signal.name.camelSignal.capitalised)(" + "flags: ConnectFlags = ConnectFlags(0), " + "handler: @escaping ( _ unownedSelf: \(record.structRef.fullSwiftTypeName)" + Code.loop(over: signal.args) { argument in + ", _ \(argument.prefixedArgumentName): \(argument.typeRef.fullSwiftTypeName)" + } + ") -> " + signal.returns.typeRef.fullSwiftTypeName + " ) -> Int {" + } + Code.block { + "typealias SwiftHandler = \(signalClosureHolderDecl(type: record.structRef.type.swiftName, args: signal.args.map { $0.typeRef.fullSwiftTypeName }, returnType: signal.returns.typeRef.fullSwiftTypeName))" + "let swiftHandlerBoxed = Unmanaged.passRetained(SwiftHandler(handler)).toOpaque()" + Code.line { + "let cCallback: @convention(c) (" + "gpointer, " + Code.loop(over: signal.args) { argument in + "\(argument.typeRef.fullTypeName), " + } + "gpointer" + ") -> " + signal.returns.typeRef.fullTypeName + " = { " + } + Code.block { + "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" + Code.line { + "let output = holder.call(\(record.structRef.type.swiftName)(raw: $0)" + Code.loopEnumerated(over: signal.args) { index, argument in + ", \(argument.swiftSignalArgumentConversion(at: index + 1))" + } + ")" + } + "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + } + "}" + "let __gCallback__ = unsafeBitCast(cCallback, to: GCallback.self)" + "let rv = signalConnectData(" + Code.block { + #"detailedSignal: "\#(signal.name)", "# + "cHandler: __gCallback__, " + "data: swiftHandlerBoxed, " + "destroyData: {" + Code.block { + "if let swift = $0 {" + Code.block { + "let holder = Unmanaged.fromOpaque(swift)" + "holder.release()" + } + "}" + "let _ = $1" + } + "}, " + "connectFlags: flags" + } + ")" + "return rv" + } + "}" + "\n" + } + } + "}" + "\n\n" + } +} + +extension GIR.Argument { + + func swiftSignalArgumentConversion(at index: Int) -> String { + if let type = self.knownType as? GIR.Record { + return type.structRef.fullSwiftTypeName + "(raw: $\(index))" + } + + return "$\(index)" + } +} + +func signalClosureHolderDecl(type: String, args: [String], returnType: String = "Void") -> String { + let holderType: String + switch args.count { + case 0: + holderType = "Tmp__ClosureHolder" + case 1: + holderType = "Tmp__DualClosureHolder" + case 2: + holderType = "Tmp__Closure3Holder" + case 3: + holderType = "Tmp__Closure4Holder" + case 4: + holderType = "Tmp__Closure5Holder" + case 5: + holderType = "Tmp__Closure6Holder" + case 6: + holderType = "Tmp__Closure7Holder" + default: + fatalError("Argument count \(args.count) exceeds number of allowed arguments (6)") + } + + return holderType + + "<\(type), " + + args.joined(separator: ", ") + + (args.isEmpty ? "" : ", ") + + returnType + + ">" +} diff --git a/Sources/libgir2swift/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift similarity index 92% rename from Sources/libgir2swift/gir+swift.swift rename to Sources/libgir2swift/emmiting/gir+swift.swift index 9e9cad6..c02cef6 100644 --- a/Sources/libgir2swift/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -195,7 +195,7 @@ public extension String { @inlinable var withoutDottedPrefix: String { guard !hasPrefix(GIR.dottedPrefix) else { - return split(separator: ".").last ?? self + return components(separatedBy: ".").last ?? self } return self } @@ -572,8 +572,8 @@ public func methodCode(_ indentation: String, initialIndentation: String? = nil, if instance { hadInstance = true } return !instance } - let templateTypes = arguments.compactMap(\.templateDecl).asSet.sorted().joined(separator: ", ") - let nonNullableTemplates = arguments.compactMap(\.nonNullableTemplateDecl).asSet.sorted().joined(separator: ", ") + let templateTypes = Set(arguments.compactMap(\.templateDecl)).sorted().joined(separator: ", ") + let nonNullableTemplates = Set(arguments.compactMap(\.nonNullableTemplateDecl)).sorted().joined(separator: ", ") let defaultArgsCode: String if templateTypes.isEmpty || nonNullableTemplates == templateTypes { // no need to create default arguments method @@ -583,7 +583,7 @@ public func methodCode(_ indentation: String, initialIndentation: String? = nil, let params = arguments.map(nullableRefParameterCode) let funcParam = params.joined(separator: ", ") let fname: String - if let firstParamName = params.first?.split(separator: " ").first?.split(separator: ":").first?.capitalised { + if let firstParamName = params.first?.components(separatedBy: " ").first?.components(separatedBy: ":").first?.capitalised { fname = name.stringByRemoving(suffix: firstParamName) ?? name } else { fname = name @@ -607,7 +607,7 @@ public func methodCode(_ indentation: String, initialIndentation: String? = nil, let params = arguments.map(templatedParameterCode) let funcParam = params.joined(separator: ", ") let fname: String - if let firstParamName = params.first?.split(separator: " ").first?.split(separator: ":").first?.capitalised { + if let firstParamName = params.first?.components(separatedBy: " ").first?.components(separatedBy: ":").first?.capitalised { fname = name.stringByRemoving(suffix: firstParamName) ?? name } else { fname = name @@ -841,7 +841,7 @@ public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: St // FIXME: as of Swift 5.3 beta, generating static class methods with va_list crashes the compiler return "\n\(indentation)// *** \(name)() is currently not available because \(method.cname) takes a va_list pointer!\n\n" } - let templateTypes = arguments.compactMap(\.templateDecl).asSet.sorted().joined(separator: ", ") + let templateTypes = Set(arguments.compactMap(\.templateDecl)).sorted().joined(separator: ", ") let templateDecl = templateTypes.isEmpty ? "" : ("<" + templateTypes + ">") let p: String? = consPrefix == firstArgName?.swift ? nil : consPrefix let fact = factory ? "static func \(fname.swift + templateDecl)(" : ("\(isOverride ? ovr : conv)init" + templateDecl + "(") @@ -1633,169 +1633,6 @@ public func recordClassCode(_ e: GIR.Record, parent: String, indentation: String return code + buildSignalExtension(for: e) } -func buildSignalExtension(for record: GIR.Record) -> String { - if record.kind == "Interface" { - return "// MARK: Signals of \(record.kind) named \(record.name.swift) are dropped" - } - return Code.block(indentation: nil) { - if record.signals.isEmpty { - "// MARK: no \(record.name.swift) signals" - } else { - "// MARK: Signals of \(record.kind) named \(record.name.swift)" - "public extension \(record.name.swift) {" - Code.block { - Code.loop(over: record.signals) { signal in - if signal.args.contains(where: { $0.swiftClosureTypeName == "Void" }) { - "/// Warning: signal \(signal.name) is ignored because of Void argument" - } else { - - commentCode(signal) - "/// - Note: This function represents signal `\(signal.name)`" - "/// - Parameter flags: Flags" - - let returnComment = gtkDoc2SwiftDoc(signal.returns.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") - if !returnComment.isEmpty { - "/// - Parameter handler: \(returnComment)" - } - - "/// - Parameter unownedSelf: Reference to instance of self" - Code.loop(over: signal.args) { argument in - let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") - "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" - } - - Code.line { - "public func _on\(signal.name.camelSignal.capitalised)(" - "flags: ConnectFlags = ConnectFlags(0), " - "handler: @escaping ( _ unownedSelf: \(record.structRef.type.swiftName)" - Code.loop(over: signal.args) { argument in - ", _ \(argument.prefixedArgumentName): \(argument.swiftClosureTypeName)" - } - ") -> " - signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name - ") -> Int {" - } - Code.block { - "typealias SwiftHandler = \(signalClosureHolderDecl(type: record.structRef.type.swiftName, args: signal.args.map { $0.swiftClosureTypeName }, returnType: signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name))" - "let swiftHandlerBoxed = Unmanaged.passRetained(SwiftHandler(handler)).toOpaque()" - Code.line { - "let cCallback: @convention(c) (" - "gpointer, " - Code.loop(over: signal.args) { argument in - "\(argument.swiftSignalCClosureName), " - } - "gpointer" - ") -> " - signal.returns.name.hasPrefix("Unknown") ? "Void" : signal.returns.name - " = { " - } - Code.block { - "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" - Code.line { - "return holder.call(\(record.structRef.type.swiftName)(raw: $0)" - Code.loopEnumerated(over: signal.args) { index, argument in - ", \(argument.swiftSignalArgumentConversion(at: index + 1))" - } - ")" - } - } - "}" - "let __gCallback__ = unsafeBitCast(cCallback, to: GCallback.self)" - "let rv = signalConnectData(" - Code.block { - #"detailedSignal: "\#(signal.name)", "# - "cHandler: __gCallback__, " - "data: swiftHandlerBoxed, " - "destroyData: {" - Code.block { - "if let swift = $0 {" - Code.block { - "let holder = Unmanaged.fromOpaque(swift)" - "holder.release()" - } - "}" - "let _ = $1" - } - "}, " - "connectFlags: flags" - } - ")" - "return rv" - } - "}" - "\n" - } - } - } - "}" - } - "\n\n" - } -} - -extension GIR.Argument { - var swiftClosureTypeName: String { - switch self.knownType { - case is GIR.Record, is GIR.Class, is GIR.Interface: - return self.typeRef.type.swiftName + "Ref" - case is GIR.Bitfield: - return "UInt64" - default /* GIR.Bitfield, GIR.Enumeration */: - return self.callbackArgumentTypeName - } - } - - var swiftSignalCClosureName: String { - switch self.knownType { - case is GIR.Record, is GIR.Class, is GIR.Interface: - return "gpointer" - case is GIR.Bitfield: - return "UInt64" - default /* GIR.Bitfield, GIR.Enumeration */: - return self.callbackArgumentTypeName - } - } - - - func swiftSignalArgumentConversion(at index: Int) -> String { - switch self.knownType { - case is GIR.Record, is GIR.Class, is GIR.Interface: - return swiftClosureTypeName + "(raw: $\(index))" - default /* GIR.Bitfield, GIR.Enumeration */: - return "$\(index)" - } - } -} - -func signalClosureHolderDecl(type: String, args: [String], returnType: String = "Void") -> String { - let holderType: String - switch args.count { - case 0: - holderType = "Tmp__ClosureHolder" - case 1: - holderType = "Tmp__DualClosureHolder" - case 2: - holderType = "Tmp__Closure3Holder" - case 3: - holderType = "Tmp__Closure4Holder" - case 4: - holderType = "Tmp__Closure5Holder" - case 5: - holderType = "Tmp__Closure6Holder" - case 6: - holderType = "Tmp__Closure7Holder" - default: - fatalError("Argument count \(args.count) exceeds number of allowed arguments (6)") - } - - return holderType - + "<\(type), " - + args.joined(separator: ", ") - + (args.isEmpty ? "" : ", ") - + returnType - + ">" -} - // MARK: - Swift code for Record/Class methods /// Swift code representation of a record diff --git a/Sources/libgir2swift/girtypes+swift.swift b/Sources/libgir2swift/emmiting/girtypes+swift.swift similarity index 100% rename from Sources/libgir2swift/girtypes+swift.swift rename to Sources/libgir2swift/emmiting/girtypes+swift.swift diff --git a/Sources/libgir2swift/gtk2swiftdoc.swift b/Sources/libgir2swift/emmiting/gtk2swiftdoc.swift similarity index 100% rename from Sources/libgir2swift/gtk2swiftdoc.swift rename to Sources/libgir2swift/emmiting/gtk2swiftdoc.swift diff --git a/Sources/libgir2swift/gir.swift b/Sources/libgir2swift/gir.swift deleted file mode 100644 index 8f8bbf0..0000000 --- a/Sources/libgir2swift/gir.swift +++ /dev/null @@ -1,1417 +0,0 @@ -// -// gir.swift -// gir2swift -// -// Created by Rene Hexel on 25/03/2016. -// Copyright © 2016, 2017, 2018, 2019, 2020 Rene Hexel. All rights reserved. -// -#if os(Linux) - import Glibc -#else - import Darwin -#endif -import SwiftLibXML - -public extension String { - /// Remove the name space and return the base name of the receiver - /// representing a fully qualified Swift type - var withoutNameSpace: String { - guard let dot = self.enumerated().filter({ $0.1 == "." }).last else { - return self - } - return String(self[index(startIndex, offsetBy: dot.offset+1)..(_ xml: XMLDocument, path: String, inNS namespaces: AnySequence, quiet: Bool, construct: (XMLElement, Int) -> T?, defaultPrefix prefix: String = "gir", check: (T) -> Bool = { _ in true }) -> [T] where T: GIR.Thing { - if let entries = xml.xpath(path, namespaces: namespaces, defaultPrefix: prefix) { - let things = entries.lazy.enumerated().map { construct($0.1, $0.0) }.filter { - guard let node = $0 else { return false } - guard check(node) else { - if !quiet { - fputs("Warning: duplicate type '\(node.name)' for \(path) ignored!\n", stderr) - } - return false - } - - return true - } - .map { $0! } - - return things - } - return [] -} - -/// Designated containers for types that can have associated methods -private let methodContainers: Set = [ "record", "class", "interface", "enumeration", "bitfield" ] - -/// Check whether a given XML element represents a free function -/// (as opposed to a method inside a type) -/// - Parameter function: XML element to be checked -func isFreeFunction(_ function: XMLElement) -> Bool { - let isContained = methodContainers.contains(function.parent.name) - return !isContained -} - -/// Comparator to check whether two `Thing`s are equal -/// - Parameters: -/// - lhs: `Thing` to compare -/// - rhs: `Thing` to compare with -public func ==(lhs: GIR.Thing, rhs: GIR.Thing) -> Bool { - return lhs.name == rhs.name -} - -/// Comparator to check the ordering of two `Thing`s -/// - Parameters: -/// - lhs: first `Thing` to compare -/// - rhs: second `Thing` to compare -public func <(lhs: GIR.Thing, rhs: GIR.Thing) -> Bool { - return lhs.name < rhs.name -} - -/// Representation of a GIR file -public final class GIR { - /// The parsed XML document represented by the receiver - public let xml: XMLDocument - /// Preample boilerplate to output before any generated code - public var preamble = "" - /// Namespace prefix defined by the receiver - public var prefix = "" { didSet { GIR.dottedPrefix = prefix + "." } } - /// Namespace prefix defined by the receiver with a trailing "." - public static var dottedPrefix = "" - /// Collection of identifier prefixes - public var identifierPrefixes = Array() - /// Collection of symbol prefixes - public var symbolPrefixes = Array() - /// Type-erased sequence of namespaces - public var namespaces: AnySequence = emptySequence() - /// Aliases defined by this GIR file - public var aliases: [Alias] = [] - /// Constants defined by this GIR file - public var constants: [Constant] = [] - /// Enums defined by this GIR file - public var enumerations: [Enumeration] = [] - /// Bitfields defined by this GIR file - public var bitfields: [Bitfield] = [] - /// Interfaces defined by this GIR file - public var interfaces: [Interface] = [] - /// Records defined by this GIR file - public var records: [Record] = [] - /// Unions defined by this GIR file - public var unions: [Union] = [] - /// Classes defined by this GIR file - public var classes: [Class] = [] - /// Free functions defined by this GIR file - public var functions: [Function] = [] - /// Callbacs defined by this GIR file - public var callbacks: [Callback] = [] - - /// names of black-listed identifiers - public static var blacklist: Set = [] - - /// names of constants to be taken verbatim - public static var verbatimConstants: Set = [] - - /// names of override initialisers - public static var overrides: Set = [] - - /// context of known types - public static var knownDataTypes: [ String : Datatype ] = [:] - /// context of known records - public static var knownRecords: [ String : Record ] = [:] - /// context of known records - public static var knownBitfields: [ String : Bitfield ] = [:] - /// context of known functions - public static var KnownFunctions: [ String : Function ] = [:] - /// suffixes for `@escaping` callback heuristics - public static var callbackSuffixes = [String]() - /// types to turn into force-unwrapped optionals - public static var forceUnwrapped: Set = ["gpointer", "gconstpointer"] - - /// Dotted namespace replacements - public static var namespaceReplacements: [ Substring : Substring ] = [ - "GObject." : "GLibObject.", "Gio." : "GIO.", "GdkPixbuf." : "", "cairo." : "Cairo." - ] - - /// designated constructor - public init(xmlDocument: XMLDocument, quiet: Bool = false) { - xml = xmlDocument - if let rp = xml.findFirstWhere({ $0.name == "repository" }) { - namespaces = rp.namespaces -// for n in namespaces { -// print("Got \(n.prefix) at \(n.href)") -// } - } - // - // set up name space prefix - // - if let ns = xml.xpath("//gir:namespace", namespaces: namespaces, defaultPrefix: "gir")?.makeIterator().next() { - if let name = ns.attribute(named: "name") { - prefix = name - GIR.dottedPrefix = name + "." - } - identifierPrefixes = ns.sortedSubAttributesFor(attr: "identifier-prefixes") - symbolPrefixes = ns.sortedSubAttributesFor(attr: "symbol-prefixes") - } - withUnsafeMutablePointer(to: &GIR.knownDataTypes) { (knownTypes: UnsafeMutablePointer<[ String : Datatype ]>) -> Void in - withUnsafeMutablePointer(to: &GIR.knownRecords) { (knownRecords: UnsafeMutablePointer<[ String : Record]>) -> Void in - withUnsafeMutablePointer(to: &GIR.knownBitfields) { (knownBitfields: UnsafeMutablePointer<[ String : Bitfield]>) -> Void in - let prefixed: (String) -> String = { $0.prefixed(with: self.prefix) } - - func setKnown(_ d: UnsafeMutablePointer<[ String : T]>) -> (String, T) -> Bool { - return { (name: String, type: T) -> Bool in - guard d.pointee[name] == nil || d.pointee[prefixed(name)] == nil else { return false } - let prefixedName = prefixed(name) - d.pointee[name] = type - d.pointee[prefixedName] = type - if GIR.namespaceReplacements[prefixedName.dottedPrefix] != nil { - let alternativelyPrefixed = prefixedName.withNormalisedPrefix - d.pointee[alternativelyPrefixed] = type - } - return true - } - } - let setKnownType = setKnown(knownTypes) - let setKnownRecord = setKnown(knownRecords) - let setKnownBitfield = setKnown(knownBitfields) - // - // get all type alias records - // - if let entries = xml.xpath("/*/*/gir:alias", namespaces: namespaces, defaultPrefix: "gir") { - aliases = entries.enumerated().map { Alias(node: $0.1, at: $0.0) }.filter { - let name = $0.name - guard setKnownType(name, $0) else { - if !quiet { fputs("Warning: duplicate type '\(name)' for alias ignored!\n", stderr) } - return false - } - return true - } - } - // closure for recording known types - func notKnownType(_ e: T) -> Bool where T: Datatype { - return setKnownType(e.name, e) - } - let notKnownRecord: (Record) -> Bool = { - guard notKnownType($0) else { return false } - return setKnownRecord($0.name, $0) - } - let notKnownBitfield: (Bitfield) -> Bool = { - guard notKnownType($0) else { return false } - return setKnownBitfield($0.name, $0) - } - let notKnownFunction: (Function) -> Bool = { - let name = $0.name - guard GIR.KnownFunctions[name] == nil else { return false } - GIR.KnownFunctions[name] = $0 - return true - } - - // - // get all constants, enumerations, records, classes, and functions - // - constants = enumerate(xml, path: "/*/*/gir:constant", inNS: namespaces, quiet: quiet, construct: { Constant(node: $0, at: $1) }, check: notKnownType) - enumerations = enumerate(xml, path: "/*/*/gir:enumeration", inNS: namespaces, quiet: quiet, construct: { Enumeration(node: $0, at: $1) }, check: notKnownType) - bitfields = enumerate(xml, path: "/*/*/gir:bitfield", inNS: namespaces, quiet: quiet, construct: { Bitfield(node: $0, at: $1) }, check: notKnownBitfield) - interfaces = enumerate(xml, path: "/*/*/gir:interface", inNS: namespaces, quiet: quiet, construct: { Interface(node: $0, at: $1) }, check: notKnownRecord) - records = enumerate(xml, path: "/*/*/gir:record", inNS: namespaces, quiet: quiet, construct: { Record(node: $0, at: $1) }, check: notKnownRecord) - classes = enumerate(xml, path: "/*/*/gir:class", inNS: namespaces, quiet: quiet, construct: { Class(node: $0, at: $1) }, check: notKnownRecord) - unions = enumerate(xml, path: "/*/*/gir:union", inNS: namespaces, quiet: quiet, construct: { Union(node: $0, at: $1) }, check: notKnownRecord) - callbacks = enumerate(xml, path: "/*/*/gir:callback", inNS: namespaces, quiet: quiet, construct: { Callback(node: $0, at: $1) }, check: notKnownType) - functions = enumerate(xml, path: "//gir:function", inNS: namespaces, quiet: quiet, construct: { - isFreeFunction($0) ? Function(node: $0, at: $1) : nil - }, check: notKnownFunction) - } - } - } - buildClassHierarchy() - buildConformanceGraph() - } - - /// convenience constructor to read a gir file - public convenience init?(fromFile name: String) { - guard let xml = XMLDocument(fromFile: name) else { return nil } - self.init(xmlDocument: xml) - } - - /// convenience constructor to read from memory - public convenience init?(buffer content: UnsafeBufferPointer, quiet q: Bool = false) { - guard let xml = XMLDocument(buffer: content) else { return nil } - self.init(xmlDocument: xml, quiet: q) - } - - /// Traverse all the classes and record their relationship in the type hierarchy - @inlinable - public func buildClassHierarchy() { - for cl in classes { - recordImplementedInterfaces(for: cl) - guard cl.typeRef.type.parent == nil else { continue } - if let parent = cl.parentType { - cl.typeRef.type.parent = parent.typeRef - } - } - } - - /// Traverse all the records and record all the interfaces implemented - @inlinable - public func buildConformanceGraph() { - records.forEach { recordImplementedInterfaces(for: $0) } - } - - /// Traverse all the records and record all the interfaces implemented - @discardableResult @inlinable - public func recordImplementedInterfaces(for record: Record) -> Set { - let t = record.typeRef.type - if let interfaces = GIR.implements[t] { return interfaces } - let implements = record.implements.compactMap { GIR.knownDataTypes[$0] } - var implementations = Set(implements.map(\.typeRef)) - let implementedRecords = implements.compactMap { $0 as? Record } - implementations.formUnion(implementedRecords.flatMap { recordImplementedInterfaces(for: $0) }) - if let parent = record.parentType { - implementations.formUnion(recordImplementedInterfaces(for: parent)) - } - GIR.implements[t] = implementations - if let ref = GIR.recordRefs[t] { GIR.implements[ref.type] = implementations } - if let pro = GIR.protocols[t] { GIR.implements[pro.type] = implementations } - return implementations - } - - /// GIR named thing class - public class Thing: Hashable, Comparable { - /// String representation of the kind of `Thing` represented by the receiver - public var kind: String { return "Thing" } - /// type name without namespace/prefix - public let name: String - /// documentation for the `Thing` - public let comment: String - /// Is this `Thing` introspectable? - public let introspectable: Bool - /// Is this `Thing` disguised? - public let disguised: Bool - /// Alternative to use if deprecated - public let deprecated: String? - /// Is this `Thing` explicitly marked as deprecated? - public let markedAsDeprecated: Bool - /// Version the receiver is available from - public let version: String? - - /// Hashes the essential components of this value by feeding them into the given hasher. - /// - /// This method is implemented to conform to the Hashable protocol. - /// Calls hasher.combine(_:) with the name component. - /// - Parameter hasher: The hasher to use when combining the components of the receiver. - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - } - - /// Memberwise initialiser - /// - Parameters: - /// - name: The name of the `Thing` to initialise - /// - comment: Documentation text for the `Thing` - /// - introspectable: Set to `true` if introspectable - /// - disguised: Set to `true` if disguised - /// - deprecated: Documentation on deprecation status if non-`nil` - /// - markedAsDeprecated: Set to `true` if deprecated - /// - version: The version this `Thing` is first available in - public init(name: String, comment: String, introspectable: Bool = true, disguised: Bool = false, deprecated: String? = nil, markedAsDeprecated: Bool = false, version: String? = nil) { - self.name = name - self.comment = comment - self.introspectable = introspectable - self.disguised = disguised - self.deprecated = deprecated - self.markedAsDeprecated = markedAsDeprecated - self.version = version - } - - /// XML Element initialser - /// - Parameters: - /// - node: `XMLElement` to construct this `Thing` from - /// - index: Index within the siblings of the `node` - /// - nameAttr: Key for the attribute to extract the `name` property from - public init(node: XMLElement, at index: Int, nameAttr: String = "name") { - name = node.attribute(named: nameAttr) ?? "Unknown\(index)" - let c = node.children.lazy - let depr = node.bool(named: "deprecated") - comment = GIR.docs(children: c) - markedAsDeprecated = depr - deprecated = GIR.deprecatedDocumentation(children: c) ?? ( depr ? "This method is deprecated." : nil ) - introspectable = (node.attribute(named: "introspectable") ?? "1") != "0" - disguised = node.bool(named: "disguised") - version = node.attribute(named: "version") - } - } - - - /// GIR type class - public class Datatype: Thing { - /// String representation of the `Datatype` thing - public override var kind: String { return "Datatype" } - /// The underlying type - public var typeRef: TypeReference - /// The identifier of this instance (e.g. C enum value type) - @inlinable public var identifier: String? { typeRef.identifier } - - /// A reference to the underlying C type - @inlinable public var underlyingCRef: TypeReference { - let type = typeRef.type - let nm = typeRef.fullCType - let tp = GIRType(name: nm, ctype: type.ctype, superType: type.parent, isAlias: type.isAlias, conversions: type.conversions) - let ref = TypeReference(type: tp, identifier: typeRef.identifier, isConst: typeRef.isConst, isOptional: typeRef.isOptional, isArray: typeRef.isArray, constPointers: typeRef.constPointers) - return ref - } - - /// Memberwise initialiser - /// - Parameters: - /// - name: The name of the `Datatype` to initialise - /// - type: The corresponding, underlying GIR type - /// - comment: Documentation text for the data type - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - /// - version: The version this data type is first available in - public init(name: String, type: TypeReference, comment: String, introspectable: Bool = false, deprecated: String? = nil) { - typeRef = type - super.init(name: name, comment: comment, introspectable: introspectable, deprecated: deprecated) - registerKnownType() - } - - /// XML Element initialser - /// - Parameters: - /// - node: `XMLElement` to construct this data type from - /// - index: Index within the siblings of the `node` - /// - type: Type reference for the data type (taken from XML if `nil`) - /// - nameAttr: Key for the attribute to extract the `name` property from - public init(node: XMLElement, at index: Int, with type: TypeReference? = nil, nameAttr: String = "name") { - typeRef = type ?? node.alias - super.init(node: node, at: index, nameAttr: nameAttr) - typeRef.isArray = node.name == "array" - registerKnownType() - } - - /// Register this type as an enumeration type - @inlinable - public func registerKnownType() { - } - - /// Returns `true` if the data type is `void` - public var isVoid: Bool { - return typeRef.isVoid - } - } - - - /// a type with an underlying C type entry - public class CType: Datatype { - /// String representation of the `CType` thing - public override var kind: String { return "CType" } - /// list of contained types - public let containedTypes: [CType] - /// reference scope - public let scope: String? - /// `true` if this is a readable element - public let isReadable: Bool - /// `true` if this is a writable element - public let isWritable: Bool - /// `true` if this is a private element - public let isPrivate: Bool - /// tuple size if non-`nil` - public let tupleSize: Int? - - /// Returns `true` if the data type is `void` - public override var isVoid: Bool { - return super.isVoid && (tupleSize ?? 0) == 0 - } - - /// Designated initialiser - /// - Parameters: - /// - name: The name of the `Datatype` to initialise - /// - type: The corresponding, underlying GIR type - /// - comment: Documentation text for the data type - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - /// - isWritable: Set to `true` if this is a writable type - /// - contains: Array of C types contained within this type - /// - tupleSize: Size of the given tuple if non-`nil` - /// - scope: The scope this type belongs in - public init(name: String, type: TypeReference, comment: String, introspectable: Bool = false, deprecated: String? = nil, isPrivate: Bool = false, isReadable: Bool = true, isWritable: Bool = false, contains: [CType] = [], tupleSize: Int? = nil, scope: String? = nil) { - self.isPrivate = isPrivate - self.isReadable = isReadable - self.isWritable = isWritable - self.containedTypes = contains - self.tupleSize = tupleSize - self.scope = scope - super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) - } - - /// XML Element initialser - /// - Parameters: - /// - node: `XMLElement` to construct this C type from - /// - index: Index within the siblings of the `node` - /// - nameAttr: Key for the attribute to extract the `name` property from - /// - privateAttr: Key for the attribute to extract the privacy status from - /// - readableAttr: Key for the attribute to extract the readbility status from - /// - writableAttr: Key for the attribute to extract the writability status from - /// - scopeAttr: Key for the attribute to extract the scope string from - public init(node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { - containedTypes = node.containedTypes - isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false - isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true - isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false - tupleSize = node.attribute(named: "fixed-size").flatMap(Int.init) - scope = node.attribute(named: scopeAttr) - super.init(node: node, at: index, nameAttr: nameAttr) - } - - /// Factory method to construct a C Type from XML with types taken from children - /// - Parameters: - /// - node: `XMLElement` whose descendants to construct this C type from - /// - index: Index within the siblings of the `node` - /// - nameAttr: Key for the attribute to extract the `name` property from - /// - typeAttr: Key for the attribute to extract the `type` property from - /// - scopeAttr: Key for the attribute to extract the scope string from - public init(fromChildrenOf node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { - let type: TypeReference - isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false - isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true - isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false - scope = node.attribute(named: scopeAttr) - if let array = node.children.filter({ $0.name == "array" }).first { - type = array.alias - tupleSize = array.attribute(named: "fixed-size").flatMap(Int.init) - containedTypes = array.containedTypes - } else { - containedTypes = [] - tupleSize = nil - type = GIR.typeOf(node: node) - } - super.init(node: node, at: index, with: type, nameAttr: nameAttr) - } - - /// return whether the type is an array - @inlinable - public var isArray: Bool { return !containedTypes.isEmpty } - - /// return whether the receiver is an instance of the given record (class) - /// - Parameter record: The record to test for - /// - Returns: `true` if `self` points to `record` - @inlinable - public func isInstanceOf(_ record: GIR.Record?) -> Bool { - if let r = record?.typeRef, (isGPointer && typeRef.type.name == record?.name) || typeRef.isDirectPointer(to: r) { - return true - } else { - return false - } - } - - /// return whether the type is a magical `gpointer` or related - /// - Note: This returns `false` if the indirection level is non-zero (e.g. for a `gpointer *`) - @inlinable - public var isGPointer: Bool { - guard typeRef.indirectionLevel == 0 else { return false } - let type = typeRef.type - let name = type.typeName - return name == GIR.gpointer || name == GIR.gconstpointer - } - - /// return whether the receiver is an instance of the given record (class) or any of its ancestors - @inlinable - public func isInstanceOfHierarchy(_ record: GIR.Record) -> Bool { - if isInstanceOf(record) { return true } - guard let parent = record.parentType else { return false } - return isInstanceOfHierarchy(parent) - } - - /// indicates whether the receiver is any known kind of pointer - @inlinable - public var isAnyKindOfPointer: Bool { - guard typeRef.indirectionLevel == 0 else { return true } - let type = typeRef.type - let name = type.name - return isGPointer || name.maybeCallback - } - - /// indicates whether the receiver is an array of scalar values - @inlinable - public var isScalarArray: Bool { return isArray && !isAnyKindOfPointer } - - /// return the Swift camel case name, quoted if necessary - @inlinable - public var camelQuoted: String { name.camelCase.swiftQuoted } - - /// return a non-clashing argument name - @inlinable - public var nonClashingName: String { - let sw = name.swift - let nt = sw + (sw.isKnownType ? "_" : "") - let type = typeRef.type - let ctype = type.ctype - let ct = ctype.innerCType.swiftType // swift name for C type - let st = ctype.innerCType.swift // corresponding Swift type - let nc = nt == ct ? nt + "_" : nt - let ns = nc == st ? nc + "_" : nc - let na = ns == type.swiftName ? ns + "_" : ns - return na - } - - //// return the known type of the argument (nil if not known) - @inlinable - public var knownType: GIR.Datatype? { return GIR.knownDataTypes[typeRef.type.name] } - - //// return the known class/record of the argument (nil if not known) - @inlinable - public var knownRecord: GIR.Record? { - typeRef.knownIndirectionLevel == 1 ? GIR.knownRecords[typeRef.type.name] : nil - } - - //// return the known bitfield the argument represents (nil if not known) - @inlinable - public var knownBitfield: GIR.Bitfield? { return GIR.knownBitfields[typeRef.type.name] } - - /// indicates whether the receiver is a known type - @inlinable - public var isKnownType: Bool { return knownType != nil } - - /// indicates whether the receiver is a known class or record - @inlinable - public var isKnownRecord: Bool { return knownRecord != nil } - - /// indicates whether the receiver is a known bit field - @inlinable - public var isKnownBitfield: Bool { return knownBitfield != nil } - - /// return the non-prefixed argument name - @inlinable - public var argumentName: String { return name.argumentSplit.arg.camelQuoted } - } - - /// a type alias is just a type with an underlying C type - public class Alias: CType { - /// String representation for an `Alias` - public override var kind: String { return "Alias" } - } - - - /// an entry for a constant - public class Constant: CType { - /// String representation of `Constant`s - public override var kind: String { return "Constant" } - /// raw value - public let value: Int - - /// Designated initialiser - /// - Parameters: - /// - name: The name of the `Constant` to initialise - /// - type: The type of the enum - /// - ctype: underlying C type - /// - value: the value of the constant - /// - comment: Documentation text for the constant - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - public init(name: String, type: TypeReference, value: Int, comment: String, introspectable: Bool = false, deprecated: String? = nil) { - self.value = value - super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) - } - - /// Initialiser to construct a constant from XML - /// - Parameters: - /// - node: `XMLElement` to construct this constant from - /// - index: Index within the siblings of the `node` - /// - nameAttr: Key for the attribute to extract the `name` property from - public init(node: XMLElement, at index: Int, nameAttr: String = "name") { - if let val = node.attribute(named: "value"), let v = Int(val) { - value = v - } else { - value = index - } - super.init(node: node, at: index, nameAttr: nameAttr) - } - } - - - /// an enumeration entry - public class Enumeration: Datatype { - /// String representation of `Enumeration`s - public override var kind: String { return "Enumeration" } - /// an enumeration value in C is a constant - public typealias Member = Constant - - /// enumeration values - public let members: [Member] - - /// Designated initialiser - /// - Parameters: - /// - name: The name of the `Enumeration` to initialise - /// - type: C typedef name of the enum - /// - members: the cases for this enum - /// - comment: Documentation text for the enum - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - public init(name: String, type: TypeReference, members: [Member], comment: String, introspectable: Bool = false, deprecated: String? = nil) { - self.members = members - super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) - } - - /// Initialiser to construct an enumeration from XML - /// - Parameters: - /// - node: `XMLElement` to construct this enum from - /// - index: Index within the siblings of the `node` - public init(node: XMLElement, at index: Int) { - let mem = node.children.lazy.filter { $0.name == "member" } - members = mem.enumerated().map { Member(node: $0.1, at: $0.0) } - super.init(node: node, at: index) - } - - /// Register this type as an enumeration type - @inlinable - override public func registerKnownType() { - if !GIR.enums.contains(typeRef.type) { - GIR.enums.insert(typeRef.type) - } - } - } - - /// a bitfield is defined akin to an enumeration - public class Bitfield: Enumeration { - /// String representation of `Bitfield`s - public override var kind: String { return "Bitfield" } - - /// Register this type as an enumeration type - @inlinable - override public func registerKnownType() { - let type = typeRef.type - let ctype = GIRType(name: type.typeName, typeName: type.typeName, ctype: type.ctype) - - if !GIR.bitfields.contains(ctype) { - let c = BitfieldTypeConversion(source: ctype, target: type) - type.conversions[ctype] = [c, c] - GIR.bitfields.insert(ctype) - } - - if !GIR.bitfields.contains(type) { - let c = BitfieldTypeConversion(source: type, target: ctype) - type.conversions[ctype] = [c, c] - GIR.bitfields.insert(type) - } - } - } - - - /// a data type record to create a protocol/struct/class for - public class Record: CType { - /// String representation of `Record`s - public override var kind: String { return "Record" } - /// C language symbol prefix - public let cprefix: String - /// C type getter function - public let typegetter: String - /// Methods associated with this record - public let methods: [Method] - /// Functions associated with this record - public let functions: [Function] - /// Constructors for this record - public let constructors: [Method] - /// Properties of this record - public let properties: [Property] - /// Fieldss of this record - public let fields: [Field] - /// List of signals for this record - public let signals: [Signal] - /// Type struct (e.g. class definition), typically nil for records - public var typeStruct: String? - /// Name of the function that returns the GType for this record (`nil` if unspecified) - public var parentType: Record? { return nil } - /// Root class (`nil` for plain records) - public var rootType: Record { return self } - /// Names of implemented interfaces - public var implements: [String] - /// records contained within this record - public var records: [Record] = [] - - /// return all functions, methods, and constructors - public var allMethods: [Method] { - return constructors + methods + functions - } - - /// return all functions, methods, and constructors inherited from ancestors - public var inheritedMethods: [Method] { - guard let parent = parentType else { return [] } - return parent.allMethods + parent.inheritedMethods - } - - /// return the typed pointer name - @inlinable public var ptrName: String { cprefix + "_ptr" } - - /// Designated initialiser - /// - Parameters: - /// - name: The name of the record to initialise - /// - type: C typedef name of the constant - /// - ctype: underlying C type - /// - cprefix: prefix used for C language free functions that implement methods for this record - /// - typegetter: C type getter function - /// - methods: Methods associated with this record - /// - functions: Functions associated with this record - /// - constructors: Constructors for this record - /// - properties: Properties of this record - /// - fields: Fields of this record - /// - signals: List of signals for this record - /// - interfaces: Interfaces implemented by this record - /// - comment: Documentation text for the constant - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - public init(name: String, type: TypeReference, cprefix: String, typegetter: String, methods: [Method] = [], functions: [Function] = [], constructors: [Method] = [], properties: [Property] = [], fields: [Field] = [], signals: [Signal] = [], interfaces: [String] = [], comment: String = "", introspectable: Bool = false, deprecated: String? = nil) { - self.cprefix = cprefix - self.typegetter = typegetter - self.methods = methods - self.functions = functions - self.constructors = constructors - self.properties = properties - self.fields = fields - self.signals = signals - self.implements = interfaces - super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) - } - - /// Initialiser to construct a record type from XML - /// - Parameters: - /// - node: `XMLElement` to construct this constant from - /// - index: Index within the siblings of the `node` - public init(node: XMLElement, at index: Int) { - cprefix = node.attribute(named: "symbol-prefix") ?? "" - typegetter = node.attribute(named: "get-type") ?? "" - typeStruct = node.attribute(named: "type-struct") - let children = node.children.lazy - let funcs = children.filter { $0.name == "function" } - functions = funcs.enumerated().map { Function(node: $0.1, at: $0.0) } - let meths = children.filter { $0.name == "method" } - methods = meths.enumerated().map { Method(node: $0.1, at: $0.0) } - let cons = children.filter { $0.name == "constructor" } - constructors = cons.enumerated().map { Method(node: $0.1, at: $0.0) } - let props = children.filter { $0.name == "property" } - properties = props.enumerated().map { Property(node: $0.1, at: $0.0) } - let fattrs = children.filter { $0.name == "field" } - fields = fattrs.enumerated().map { Field(node: $0.1, at: $0.0) } - let sigs = children.filter { $0.name == "signal" } - signals = sigs.enumerated().map { Signal(node: $0.1, at: $0.0) } - let interfaces = children.filter { $0.name == "implements" } - implements = interfaces.enumerated().compactMap { $0.1.attribute(named: "name") } - records = node.children.lazy.filter { $0.name == "record" }.enumerated().map { - Record(node: $0.element, at: $0.offset) - } - super.init(node: node, at: index) - } - - /// Register this type as a record type - @inlinable - override public func registerKnownType() { - let type = typeRef.type - let clsType = classType - let proType = protocolType - let refType = structType - let protocolRef = self.protocolRef - let clsRef = classRef - if type.parent == nil { type.parent = protocolRef } - if !GIR.recordTypes.contains(type) { - GIR.recordTypes.insert(type) - } - let ref = structRef - if GIR.protocols[type] == nil { GIR.protocols[type] = protocolRef } - if GIR.protocols[clsType] == nil { GIR.protocols[clsType] = clsRef } - if GIR.protocols[refType] == nil { GIR.protocols[refType] = protocolRef } - if GIR.recordRefs[type] == nil { GIR.recordRefs[type] = ref } - if GIR.recordRefs[clsType] == nil { GIR.recordRefs[clsType] = ref } - if GIR.recordRefs[refType] == nil { GIR.recordRefs[refType] = ref } - if GIR.recordRefs[proType] == nil { GIR.recordRefs[proType] = ref } - if GIR.refRecords[proType] == nil { GIR.refRecords[proType] = typeRef } - if GIR.refRecords[clsType] == nil { GIR.refRecords[clsType] = typeRef } - if GIR.refRecords[refType] == nil { GIR.refRecords[refType] = typeRef } - if GIR.refRecords[type] == nil { GIR.refRecords[type] = typeRef } - let prefixedType = type.prefixed - guard prefixedType !== type else { return } - let prefixedCls = clsType.prefixed - let prefixedRef = refType.prefixed - let prefixedPro = proType.prefixed - if GIR.protocols[prefixedType] == nil { GIR.protocols[prefixedType] = protocolRef } - if GIR.protocols[prefixedCls] == nil { GIR.protocols[prefixedCls] = clsRef } - if GIR.protocols[prefixedRef] == nil { GIR.protocols[prefixedRef] = protocolRef } - if GIR.recordRefs[prefixedType] == nil { GIR.recordRefs[prefixedType] = ref } - if GIR.recordRefs[prefixedCls] == nil { GIR.recordRefs[prefixedCls] = ref } - if GIR.recordRefs[prefixedRef] == nil { GIR.recordRefs[prefixedRef] = ref } - if GIR.recordRefs[prefixedPro] == nil { GIR.recordRefs[prefixedPro] = ref } - if GIR.refRecords[prefixedPro] == nil { GIR.refRecords[prefixedPro] = typeRef } - if GIR.refRecords[prefixedCls] == nil { GIR.refRecords[prefixedCls] = typeRef } - if GIR.refRecords[prefixedRef] == nil { GIR.refRecords[prefixedRef] = typeRef } - if GIR.refRecords[prefixedType] == nil { GIR.refRecords[prefixedType] = typeRef } - } - - /// Name of the Protocol for this record - @inlinable - public var protocolName: String { typeRef.type.swiftName.protocolName } - /// Name of the `Ref` struct for this record - @inlinable - public var structName: String { typeRef.type.swiftName + "Ref" } - - /// Type of the Class for this record - @inlinable public var classType: GIRRecordType { - let n = typeRef.type.swiftName.swift - return GIRRecordType(name: n, typeName: n, ctype: typeRef.type.ctype) - } - - /// Type of the Protocol for this record - @inlinable public var protocolType: GIRType { GIRType(name: protocolName, typeName: protocolName, ctype: "") } - - /// Protocol reference for this record - @inlinable public var protocolRef: TypeReference { TypeReference(type: protocolType) } - - /// Type of the `Ref` struct for this record - @inlinable - public var structType: GIRRecordType { GIRRecordType(name: structName, typeName: structName, ctype: "", superType: protocolRef) } - - /// Struct reference for this record - @inlinable public var structRef: TypeReference { TypeReference(type: structType) } - - /// Class reference for this record - @inlinable public var classRef: TypeReference { TypeReference(type: classType) } - - /// return the first method where the passed predicate closure returns `true` - public func methodMatching(_ predictate: (Method) -> Bool) -> Method? { - return allMethods.lazy.filter(predictate).first - } - - /// return the first inherited method where the passed predicate closure returns `true` - public func inheritedMethodMatching(_ predictate: (Method) -> Bool) -> Method? { - return inheritedMethods.lazy.filter(predictate).first - } - - /// return the first of my own or inherited methods where the passed predicate closure returns `true` - public func anyMethodMatching(_ predictate: (Method) -> Bool) -> Method? { - if let match = methodMatching(predictate) { return match } - return inheritedMethodMatching(predictate) - } - - /// return the `retain` (ref) method for the given record, if any - public var ref: Method? { return anyMethodMatching { $0.isRef && $0.args.first!.isInstanceOfHierarchy(self) } } - - /// return the `release` (unref) method for the given record, if any - public var unref: Method? { return anyMethodMatching { $0.isUnref && $0.args.first!.isInstanceOfHierarchy(self) } } - - /// return whether the record or one of its parents has a given property - public func has(property name: String) -> Bool { - guard properties.first(where: { $0.name == name }) == nil else { return true } - guard let parent = parentType else { return false } - return parent.has(property: name) - } - - /// return only the properties that are not derived - public var nonDerivedProperties: [Property] { - guard let parent = parentType else { return properties } - return properties.filter { !parent.has(property: $0.name) } - } - - /// return all properties, including the ones derived from ancestors - public var allProperties: [Property] { - guard let parent = parentType else { return properties } - let all = properties.asSet.union(parent.allProperties.asSet) - return all.sorted() - } - - /// return all signals, including the ones derived from ancestors - public var allSignals: [Signal] { - guard let parent = parentType else { return signals } - let all = signals.asSet.union(parent.allSignals.asSet) - return all.sorted() - } - } - - /// a union data type record - public class Union: Record { - /// String representation of `Union`s - public override var kind: String { return "Union" } - } - - /// a class data type record - public class Class: Record { - /// String representation of `Class`es - public override var kind: String { return "Class" } - /// parent class name - public let parent: String - - /// return the parent type of the given class - public override var parentType: Record? { - guard !parent.isEmpty else { return nil } - return GIR.knownDataTypes[parent] as? GIR.Record - } - - /// return the top level ancestor type of the given class - public override var rootType: Record { - guard parent != "" else { return self } - guard let p = GIR.knownDataTypes[parent] as? GIR.Record else { return self } - return p.rootType - } - - /// Initialiser to construct a class type from XML - /// - Parameters: - /// - node: `XMLElement` to construct this constant from - /// - index: Index within the siblings of the `node` - override init(node: XMLElement, at index: Int) { - var parent = node.attribute(named: "parent") ?? "" - if parent.isEmpty { - parent = node.children.lazy.filter { $0.name == "prerequisite" }.first?.attribute(named: "name") ?? "" - } - self.parent = parent - super.init(node: node, at: index) - } - } - - /// an inteface is similar to a class, - /// but can be part of a more complex type graph - public class Interface: Class { - /// String representation of `Interface`es - public override var kind: String { return "Interface" } - } - - /// data type representing a function/method - public class Method: Argument { // superclass type is return type - /// String representation of member `Method`s - public override var kind: String { return "Method" } - /// Original C function name - public let cname: String - /// Return type - public let returns: Argument - /// All associated arguments (parameters) in order - public let args: [Argument] - /// `true` if this method throws an error - public let throwsError: Bool - - /// Designated initialiser - /// - Parameters: - /// - name: The name of the method - /// - cname: C function name - /// - returns: return type - /// - args: Array of parameters - /// - comment: Documentation text for the method - /// - introspectable: Set to `true` if introspectable - /// - deprecated: Documentation on deprecation status if non-`nil` - /// - throwsAnError: Set to `true` if this method can throw an error - public init(name: String, cname: String, returns: Argument, args: [Argument] = [], comment: String = "", introspectable: Bool = false, deprecated: String? = nil, throwsAnError: Bool = false) { - self.cname = cname - self.returns = returns - self.args = args - throwsError = throwsAnError - super.init(name: name, type: returns.typeRef, instance: returns.instance, comment: comment, introspectable: introspectable, deprecated: deprecated) - } - - /// Initialiser to construct a method type from XML - /// - Parameters: - /// - node: `XMLElement` to construct this constant from - /// - index: Index within the siblings of the `node` - public init(node: XMLElement, at index: Int) { - cname = node.attribute(named: "identifier") ?? "" - let thrAttr = node.attribute(named: "throws") ?? "0" - throwsError = (Int(thrAttr) ?? 0) != 0 - let children = node.children.lazy - if let ret = children.findFirstWhere({ $0.name == "return-value"}) { - let arg = Argument(node: ret, at: -1) - returns = arg - } else { - returns = Argument(name: "", type: .void, instance: false, comment: "") - } - if let params = children.findFirstWhere({ $0.name == "parameters"}) { - let children = params.children.lazy - args = GIR.args(children: children) - } else { - args = GIR.args(children: children) - } - super.init(node: node, at: index, varargs: args.lazy.filter({$0.varargs}).first != nil) - } - - /// indicate whether this is an unref method - public var isUnref: Bool { - return args.count == 1 && name == "unref" - } - - /// indicate whether this is a ref method - public var isRef: Bool { - return args.count == 1 && name == "ref" - } - - /// indicate whether this is a getter method - public var isGetter: Bool { - return args.count == 1 && ( name.hasPrefix("get_") || name.hasPrefix("is_")) - } - - /// indicate whether this is a setter method - public var isSetter: Bool { - return args.count == 2 && name.hasPrefix("set_") - } - - /// indicate whether this is a setter method for the given getter - public func isSetterFor(getter: String) -> Bool { - guard args.count == 2 else { return false } - let u = getter.utf8 - let s = u.index(after: u.startIndex) - let e = u.endIndex - let v = u[s.. Bool { - guard args.count == 1 else { return false } - let u = setter.utf8 - let s = u.index(after: u.startIndex) - let e = u.endIndex - let v = u[s.. String] - /// Array of strings representing code to be output - var outputs: [String] = [] - - /// Designated initialiser - /// - Parameters: - /// - conversion: Dictionary of conversion functions/closures - /// - level: Level within the tree - /// - parent: Parent context (or `nil` if no parent) - /// - parentNode: Parent XML node (or `nil` if no parent) - init(_ conversion: [String : (XMLTree.Node) -> String] = [:], level: Int = 0, parent: ConversionContext? = nil, parentNode: XMLTree.Node? = nil) { - self.level = level - self.parent = parent - self.parentNode = parentNode - self.conversion = conversion - } - - /// push a context - func push(node: XMLTree.Node, _ fs: [String : (XMLTree.Node) -> String]) -> ConversionContext { - return ConversionContext(fs, level: node.level+1, parent: self, parentNode: node) - } -} - -/// Return a string of (leading) spaces preceding (and followed by) the given string -/// - Parameters: -/// - level: indentation level -/// - s: String to be indented -private func indent(level: Int, _ s: String = "") -> String { - return String(repeating: " ", count: level * 4) + s -} - -extension GIR { - /// - /// return the documentation for the given child nodes - /// - public class func docs(children: LazySequence>) -> String { - return documentation(name: "doc", children: children) - } - - /// - /// return the documentation for the given child nodes - /// - public class func deprecatedDocumentation(children: LazySequence>) -> String? { - let doc = documentation(name: "doc-deprecated", children: children) - guard !doc.isEmpty else { return nil } - return doc - } - - /// - /// return the documentation for the given child nodes - /// - public class func documentation(name: String, children: LazySequence>) -> String { - let docs = children.filter { $0.name == name } - let comments = docs.map { $0.content} - return comments.joined(separator: "\n") - } - - /// - /// return the method/function arguments for the given child nodes - /// - public class func args(children: LazySequence>) -> [Argument] { - let parameters = children.filter { $0.name.hasSuffix("parameter") } - let args = parameters.enumerated().map { Argument(node: $1, at: $0) } - return args - } - - /// - /// return the type information of an argument or return type node - /// - class func typeOf(node: XMLElement) -> TypeReference { - let t = node.type - if !t.isVoid { return t } - for child in node.children { - let t = child.type - if !t.isVoid { return t } - } - return .void - } - - /// - /// dump Swift code - /// - public func dumpSwift() -> String { - var context = ConversionContext([:]) - context = ConversionContext(["repository": { - let s = indent(level: $0.level, "// \($0.node.name) @ \($0.level)+\(context.level)") - context = context.push(node: $0, ["namespace": { - let s = indent(level: $0.level, "// \($0.node.name) @ \($0.level)+\(context.level)") - context = context.push(node: $0, ["alias": { - context = context.push(node: $0, ["type": { - if let type = $0.node.attribute(named: "name"), - let alias = context.parentNode.node.attribute(named: "name"), - !alias.isEmpty && !type.isEmpty { - context.outputs = ["public typealias \(alias) = \(type)"] - } else { - context.outputs = ["// error alias \(String(describing: $0.node.attribute(named: "name"))) = \(String(describing: context.parentNode.node.attribute(named: "name")))"] - } - return "" - }]) - return s - }, "function": { - let s: String - if let name = $0.node.attribute(named: "name"), !name.isEmpty { - s = "func \(name)(" - } else { s = "// empty function " } - context = context.push(node: $0, ["type": { - if let type = $0.node.attribute(named: "name"), - let alias = context.parentNode.node.attribute(named: "name"), - !alias.isEmpty && !type.isEmpty { - context.outputs = ["public typealias \(alias) = \(type)"] - } else { - context.outputs = ["// error alias \(String(describing: $0.node.attribute(named: "name"))) = \(String(describing: context.parentNode.node.attribute(named: "name")))"] - } - return "" - }]) - return s - }]) - return s - }]) - return s - }]) - return (xml.tree.map { (tn: XMLTree.Node) -> String in - if let f = context.conversion[tn.node.name] { return f(tn) } - while context.level > tn.level { - if let parent = context.parent { context = parent } - else { assert(context.level == 0) } - } - return indent(level: tn.level, "// unhandled: \(tn.node.name) @ \(tn.level)+\(context.level)") - }).reduce("") { (output: String, element: String) -> String in - output + "\(element)\n" - } - } -} - -extension XMLElement { - /// - /// return an attribute as a list of sub-attributeds split by a given character - /// and ordered with the longest attribute name first - /// - public func sortedSubAttributesFor(attr: String, splitBy char: Character = ",", orderedBy: (String, String) -> Bool = { $0.count > $1.count || ($0.count == $1.count && $0 < $1)}) -> [String] { - guard let attrs = (attribute(named: attr)?.split(separator: char))?.map({ String($0) }) else { return [] } - return attrs.sorted(by: orderedBy) - } - - /// - /// return the documentation for a given node - /// - public func docs() -> String { - return GIR.docs(children: children.lazy) - } -} - diff --git a/Sources/libgir2swift/models/ConversionContext.swift b/Sources/libgir2swift/models/ConversionContext.swift new file mode 100644 index 0000000..2b1f806 --- /dev/null +++ b/Sources/libgir2swift/models/ConversionContext.swift @@ -0,0 +1,41 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// + +import Foundation +import SwiftLibXML + +/// helper context class for tree traversal +class ConversionContext { + /// Tree/indentation level + let level: Int + /// Parent context + let parent: ConversionContext? + /// Parent node in the XML tree + let parentNode: XMLTree.Node! + /// Dictionary of conversion functions for named nodes + let conversion: [String : (XMLTree.Node) -> String] + /// Array of strings representing code to be output + var outputs: [String] = [] + + /// Designated initialiser + /// - Parameters: + /// - conversion: Dictionary of conversion functions/closures + /// - level: Level within the tree + /// - parent: Parent context (or `nil` if no parent) + /// - parentNode: Parent XML node (or `nil` if no parent) + init(_ conversion: [String : (XMLTree.Node) -> String] = [:], level: Int = 0, parent: ConversionContext? = nil, parentNode: XMLTree.Node? = nil) { + self.level = level + self.parent = parent + self.parentNode = parentNode + self.conversion = conversion + } + + /// push a context + func push(node: XMLTree.Node, _ fs: [String : (XMLTree.Node) -> String]) -> ConversionContext { + return ConversionContext(fs, level: node.level+1, parent: self, parentNode: node) + } +} diff --git a/Sources/libgir2swift/girenums.swift b/Sources/libgir2swift/models/Gir+Enums.swift similarity index 100% rename from Sources/libgir2swift/girenums.swift rename to Sources/libgir2swift/models/Gir+Enums.swift diff --git a/Sources/libgir2swift/girtypereplacements.swift b/Sources/libgir2swift/models/Gir+KnowTypeSets.swift similarity index 100% rename from Sources/libgir2swift/girtypereplacements.swift rename to Sources/libgir2swift/models/Gir+KnowTypeSets.swift diff --git a/Sources/libgir2swift/girknowntypes.swift b/Sources/libgir2swift/models/Gir+KnownTypes.swift similarity index 100% rename from Sources/libgir2swift/girknowntypes.swift rename to Sources/libgir2swift/models/Gir+KnownTypes.swift diff --git a/Sources/libgir2swift/models/Gir.swift b/Sources/libgir2swift/models/Gir.swift new file mode 100644 index 0000000..f0a5c0c --- /dev/null +++ b/Sources/libgir2swift/models/Gir.swift @@ -0,0 +1,331 @@ +// +// gir.swift +// gir2swift +// +// Created by Rene Hexel on 25/03/2016. +// Copyright © 2016, 2017, 2018, 2019, 2020 Rene Hexel. All rights reserved. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +/// Designated containers for types that can have associated methods +private let methodContainers: Set = [ "record", "class", "interface", "enumeration", "bitfield" ] + +/// Check whether a given XML element represents a free function +/// (as opposed to a method inside a type) +/// - Parameter function: XML element to be checked +func isFreeFunction(_ function: XMLElement) -> Bool { + let isContained = methodContainers.contains(function.parent.name) + return !isContained +} + +/// Representation of a GIR file +public final class GIR { + /// The parsed XML document represented by the receiver + public let xml: XMLDocument + /// Preample boilerplate to output before any generated code + public var preamble = "" + /// Namespace prefix defined by the receiver + public var prefix = "" { didSet { GIR.dottedPrefix = prefix + "." } } + /// Namespace prefix defined by the receiver with a trailing "." + public static var dottedPrefix = "" + /// Collection of identifier prefixes + public var identifierPrefixes = Array() + /// Collection of symbol prefixes + public var symbolPrefixes = Array() + /// Type-erased sequence of namespaces + public var namespaces: AnySequence = emptySequence() + /// Aliases defined by this GIR file + public var aliases: [Alias] = [] + /// Constants defined by this GIR file + public var constants: [Constant] = [] + /// Enums defined by this GIR file + public var enumerations: [Enumeration] = [] + /// Bitfields defined by this GIR file + public var bitfields: [Bitfield] = [] + /// Interfaces defined by this GIR file + public var interfaces: [Interface] = [] + /// Records defined by this GIR file + public var records: [Record] = [] + /// Unions defined by this GIR file + public var unions: [Union] = [] + /// Classes defined by this GIR file + public var classes: [Class] = [] + /// Free functions defined by this GIR file + public var functions: [Function] = [] + /// Callbacs defined by this GIR file + public var callbacks: [Callback] = [] + + /// names of black-listed identifiers + public static var blacklist: Set = [] + + /// names of constants to be taken verbatim + public static var verbatimConstants: Set = [] + + /// names of override initialisers + public static var overrides: Set = [] + + /// context of known types + public static var knownDataTypes: [ String : Datatype ] = [:] + /// context of known records + public static var knownRecords: [ String : Record ] = [:] + /// context of known records + public static var knownBitfields: [ String : Bitfield ] = [:] + /// context of known functions + public static var KnownFunctions: [ String : Function ] = [:] + /// suffixes for `@escaping` callback heuristics + public static var callbackSuffixes = [String]() + /// types to turn into force-unwrapped optionals + public static var forceUnwrapped: Set = ["gpointer", "gconstpointer"] + + /// Dotted namespace replacements + public static var namespaceReplacements: [ Substring : Substring ] = [ + "GObject." : "GLibObject.", "Gio." : "GIO.", "GdkPixbuf." : "", "cairo." : "Cairo." + ] + + /// designated constructor + public init(xmlDocument: XMLDocument, quiet: Bool = false) { + xml = xmlDocument + if let rp = xml.first(where: { $0.name == "repository" }) { + namespaces = rp.namespaces + } + // + // set up name space prefix + // + if let ns = xml.xpath("//gir:namespace", namespaces: namespaces, defaultPrefix: "gir")?.makeIterator().next() { + if let name = ns.attribute(named: "name") { + prefix = name + GIR.dottedPrefix = name + "." + } + identifierPrefixes = ns.sortedSubAttributesFor(attr: "identifier-prefixes") + symbolPrefixes = ns.sortedSubAttributesFor(attr: "symbol-prefixes") + } + withUnsafeMutablePointer(to: &GIR.knownDataTypes) { (knownTypes: UnsafeMutablePointer<[ String : Datatype ]>) -> Void in + withUnsafeMutablePointer(to: &GIR.knownRecords) { (knownRecords: UnsafeMutablePointer<[ String : Record]>) -> Void in + withUnsafeMutablePointer(to: &GIR.knownBitfields) { (knownBitfields: UnsafeMutablePointer<[ String : Bitfield]>) -> Void in + let prefixed: (String) -> String = { $0.prefixed(with: self.prefix) } + + func setKnown(_ d: UnsafeMutablePointer<[ String : T]>) -> (String, T) -> Bool { + return { (name: String, type: T) -> Bool in + guard d.pointee[name] == nil || d.pointee[prefixed(name)] == nil else { return false } + let prefixedName = prefixed(name) + d.pointee[name] = type + d.pointee[prefixedName] = type + if GIR.namespaceReplacements[prefixedName.dottedPrefix] != nil { + let alternativelyPrefixed = prefixedName.withNormalisedPrefix + d.pointee[alternativelyPrefixed] = type + } + return true + } + } + let setKnownType = setKnown(knownTypes) + let setKnownRecord = setKnown(knownRecords) + let setKnownBitfield = setKnown(knownBitfields) + // + // get all type alias records + // + if let entries = xml.xpath("/*/*/gir:alias", namespaces: namespaces, defaultPrefix: "gir") { + aliases = entries.enumerated().map { Alias(node: $0.1, at: $0.0) }.filter { + let name = $0.name + guard setKnownType(name, $0) else { + if !quiet { fputs("Warning: duplicate type '\(name)' for alias ignored!\n", stderr) } + return false + } + return true + } + } + // closure for recording known types + func notKnownType(_ e: T) -> Bool where T: Datatype { + return setKnownType(e.name, e) + } + let notKnownRecord: (Record) -> Bool = { + guard notKnownType($0) else { return false } + return setKnownRecord($0.name, $0) + } + let notKnownBitfield: (Bitfield) -> Bool = { + guard notKnownType($0) else { return false } + return setKnownBitfield($0.name, $0) + } + let notKnownFunction: (Function) -> Bool = { + let name = $0.name + guard GIR.KnownFunctions[name] == nil else { return false } + GIR.KnownFunctions[name] = $0 + return true + } + + // + // get all constants, enumerations, records, classes, and functions + // + constants = enumerate(xml, path: "/*/*/gir:constant", inNS: namespaces, quiet: quiet, construct: { Constant(node: $0, at: $1) }, check: notKnownType) + enumerations = enumerate(xml, path: "/*/*/gir:enumeration", inNS: namespaces, quiet: quiet, construct: { Enumeration(node: $0, at: $1) }, check: notKnownType) + bitfields = enumerate(xml, path: "/*/*/gir:bitfield", inNS: namespaces, quiet: quiet, construct: { Bitfield(node: $0, at: $1) }, check: notKnownBitfield) + interfaces = enumerate(xml, path: "/*/*/gir:interface", inNS: namespaces, quiet: quiet, construct: { Interface(node: $0, at: $1) }, check: notKnownRecord) + records = enumerate(xml, path: "/*/*/gir:record", inNS: namespaces, quiet: quiet, construct: { Record(node: $0, at: $1) }, check: notKnownRecord) + classes = enumerate(xml, path: "/*/*/gir:class", inNS: namespaces, quiet: quiet, construct: { Class(node: $0, at: $1) }, check: notKnownRecord) + unions = enumerate(xml, path: "/*/*/gir:union", inNS: namespaces, quiet: quiet, construct: { Union(node: $0, at: $1) }, check: notKnownRecord) + callbacks = enumerate(xml, path: "/*/*/gir:callback", inNS: namespaces, quiet: quiet, construct: { Callback(node: $0, at: $1) }, check: notKnownType) + functions = enumerate(xml, path: "//gir:function", inNS: namespaces, quiet: quiet, construct: { + isFreeFunction($0) ? Function(node: $0, at: $1) : nil + }, check: notKnownFunction) + } + } + } + buildClassHierarchy() + buildConformanceGraph() + } + + /// convenience constructor to read a gir file + public convenience init?(fromFile name: String) { + guard let xml = XMLDocument(fromFile: name) else { return nil } + self.init(xmlDocument: xml) + } + + /// convenience constructor to read from memory + public convenience init?(buffer content: UnsafeBufferPointer, quiet q: Bool = false) { + guard let xml = XMLDocument(buffer: content) else { return nil } + self.init(xmlDocument: xml, quiet: q) + } + + /// Traverse all the classes and record their relationship in the type hierarchy + @inlinable + public func buildClassHierarchy() { + for cl in classes { + recordImplementedInterfaces(for: cl) + guard cl.typeRef.type.parent == nil else { continue } + if let parent = cl.parentType { + cl.typeRef.type.parent = parent.typeRef + } + } + } + + /// Traverse all the records and record all the interfaces implemented + @inlinable + public func buildConformanceGraph() { + records.forEach { recordImplementedInterfaces(for: $0) } + } + + /// Traverse all the records and record all the interfaces implemented + @discardableResult @inlinable + public func recordImplementedInterfaces(for record: Record) -> Set { + let t = record.typeRef.type + if let interfaces = GIR.implements[t] { return interfaces } + let implements = record.implements.compactMap { GIR.knownDataTypes[$0] } + var implementations = Set(implements.map(\.typeRef)) + let implementedRecords = implements.compactMap { $0 as? Record } + implementations.formUnion(implementedRecords.flatMap { recordImplementedInterfaces(for: $0) }) + if let parent = record.parentType { + implementations.formUnion(recordImplementedInterfaces(for: parent)) + } + GIR.implements[t] = implementations + if let ref = GIR.recordRefs[t] { GIR.implements[ref.type] = implementations } + if let pro = GIR.protocols[t] { GIR.implements[pro.type] = implementations } + return implementations + } +} + +extension GIR { + /// + /// return the documentation for the given child nodes + /// + public class func docs(children: LazySequence>) -> String { + return documentation(name: "doc", children: children) + } + + /// + /// return the documentation for the given child nodes + /// + public class func deprecatedDocumentation(children: LazySequence>) -> String? { + let doc = documentation(name: "doc-deprecated", children: children) + guard !doc.isEmpty else { return nil } + return doc + } + + /// + /// return the documentation for the given child nodes + /// + public class func documentation(name: String, children: LazySequence>) -> String { + let docs = children.filter { $0.name == name } + let comments = docs.map { $0.content} + return comments.joined(separator: "\n") + } + + /// + /// return the method/function arguments for the given child nodes + /// + public class func args(children: LazySequence>) -> [Argument] { + let parameters = children.filter { $0.name.hasSuffix("parameter") } + let args = parameters.enumerated().map { Argument(node: $1, at: $0) } + return args + } + + /// + /// return the type information of an argument or return type node + /// + class func typeOf(node: XMLElement) -> TypeReference { + let t = node.type + if !t.isVoid { return t } + for child in node.children { + let t = child.type + if !t.isVoid { return t } + } + return .void + } + + /// + /// dump Swift code + /// + public func dumpSwift() -> String { + var context = ConversionContext([:]) + context = ConversionContext(["repository": { + let s = indent(level: $0.level, "// \($0.node.name) @ \($0.level)+\(context.level)") + context = context.push(node: $0, ["namespace": { + let s = indent(level: $0.level, "// \($0.node.name) @ \($0.level)+\(context.level)") + context = context.push(node: $0, ["alias": { + context = context.push(node: $0, ["type": { + if let type = $0.node.attribute(named: "name"), + let alias = context.parentNode.node.attribute(named: "name"), + !alias.isEmpty && !type.isEmpty { + context.outputs = ["public typealias \(alias) = \(type)"] + } else { + context.outputs = ["// error alias \(String(describing: $0.node.attribute(named: "name"))) = \(String(describing: context.parentNode.node.attribute(named: "name")))"] + } + return "" + }]) + return s + }, "function": { + let s: String + if let name = $0.node.attribute(named: "name"), !name.isEmpty { + s = "func \(name)(" + } else { s = "// empty function " } + context = context.push(node: $0, ["type": { + if let type = $0.node.attribute(named: "name"), + let alias = context.parentNode.node.attribute(named: "name"), + !alias.isEmpty && !type.isEmpty { + context.outputs = ["public typealias \(alias) = \(type)"] + } else { + context.outputs = ["// error alias \(String(describing: $0.node.attribute(named: "name"))) = \(String(describing: context.parentNode.node.attribute(named: "name")))"] + } + return "" + }]) + return s + }]) + return s + }]) + return s + }]) + return (xml.tree.map { (tn: XMLTree.Node) -> String in + if let f = context.conversion[tn.node.name] { return f(tn) } + while context.level > tn.level { + if let parent = context.parent { context = parent } + else { assert(context.level == 0) } + } + return indent(level: tn.level, "// unhandled: \(tn.node.name) @ \(tn.level)+\(context.level)") + }).reduce("") { (output: String, element: String) -> String in + output + "\(element)\n" + } + } +} diff --git a/Sources/libgir2swift/girtype.swift b/Sources/libgir2swift/models/GirType.swift similarity index 100% rename from Sources/libgir2swift/girtype.swift rename to Sources/libgir2swift/models/GirType.swift diff --git a/Sources/libgir2swift/girtypeconversion.swift b/Sources/libgir2swift/models/TypeConversion.swift similarity index 100% rename from Sources/libgir2swift/girtypeconversion.swift rename to Sources/libgir2swift/models/TypeConversion.swift diff --git a/Sources/libgir2swift/girtypereference.swift b/Sources/libgir2swift/models/TypeReference.swift similarity index 100% rename from Sources/libgir2swift/girtypereference.swift rename to Sources/libgir2swift/models/TypeReference.swift diff --git a/Sources/libgir2swift/models/gir elements/GirAlias.swift b/Sources/libgir2swift/models/gir elements/GirAlias.swift new file mode 100644 index 0000000..3428730 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirAlias.swift @@ -0,0 +1,22 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a type alias is just a type with an underlying C type + public class Alias: CType { + /// String representation for an `Alias` + public override var kind: String { return "Alias" } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirArgument.swift b/Sources/libgir2swift/models/gir elements/GirArgument.swift new file mode 100644 index 0000000..de55ce7 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirArgument.swift @@ -0,0 +1,105 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + /// data type representing a function/method argument or return type + public class Argument: CType { + public override var kind: String { return "Argument" } + public let instance: Bool ///< is this an instance parameter or return type? + public let _varargs: Bool ///< is this a varargs (...) parameter? + public let isNullable: Bool ///< is this a nullable parameter or return type? + public let allowNone: Bool ///< is this a parameter that can be ommitted? + public let isOptional: Bool ///< is this an optional (out) parameter? + public let callerAllocates: Bool ///< is this a caller-allocated (out) parameter? + public let ownershipTransfer: OwnershipTransfer ///< model of ownership transfer used + public let direction: ParameterDirection ///< whether this is an `in`, `out`, or `inout` parameter + + /// indicate whether the given parameter is varargs + public var varargs: Bool { + return _varargs || name.hasPrefix("...") + } + + /// default constructor + public init(name: String, type: TypeReference, instance: Bool, comment: String, introspectable: Bool = false, deprecated: String? = nil, varargs: Bool = false, isNullable: Bool = false, allowNone: Bool = false, isOptional: Bool = false, callerAllocates: Bool = false, ownershipTransfer: OwnershipTransfer = .none, direction: ParameterDirection = .in) { + self.instance = instance + _varargs = varargs + self.isNullable = isNullable + self.allowNone = allowNone + self.isOptional = isOptional + self.callerAllocates = callerAllocates + self.ownershipTransfer = ownershipTransfer + self.direction = direction + super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) + } + + /// XML constructor + public init(node: XMLElement, at index: Int, defaultDirection: ParameterDirection = .in) { + instance = node.name.hasPrefix("instance") + _varargs = node.children.lazy.first(where: { $0.name == "varargs"}) != nil + let allowNone = node.attribute(named: "allow-none") + if let allowNone = allowNone, !allowNone.isEmpty && allowNone != "0" && allowNone != "false" { + self.allowNone = true + } else { + self.allowNone = false + } + if let nullable = node.attribute(named: "nullable") ?? allowNone, !nullable.isEmpty && nullable != "0" && nullable != "false" { + isNullable = true + } else { + isNullable = false + } + if let optional = node.attribute(named: "optional") ?? allowNone, !optional.isEmpty && optional != "0" && optional != "false" { + isOptional = true + } else { + isOptional = false + } + if let callerAlloc = node.attribute(named: "caller-allocates"), !callerAlloc.isEmpty && callerAlloc != "0" && callerAlloc != "false" { + callerAllocates = true + } else { + callerAllocates = false + } + ownershipTransfer = node.attribute(named: "transfer-ownership").flatMap { OwnershipTransfer(rawValue: $0) } ?? .none + direction = node.attribute(named: "direction").flatMap { ParameterDirection(rawValue: $0) } ?? defaultDirection + super.init(fromChildrenOf: node, at: index) + } + + /// XML constructor for functions/methods/callbacks + public init(node: XMLElement, at index: Int, varargs: Bool, defaultDirection: ParameterDirection = .in) { + instance = node.name.hasPrefix("instance") + _varargs = varargs + let allowNone = node.attribute(named: "allow-none") + if let allowNone = allowNone, !allowNone.isEmpty && allowNone != "0" && allowNone != "false" { + self.allowNone = true + } else { + self.allowNone = false + } + if let nullable = node.attribute(named: "nullable") ?? allowNone, nullable != "0" && nullable != "false" { + isNullable = true + } else { + isNullable = false + } + if let optional = node.attribute(named: "optional") ?? allowNone, optional != "0" && optional != "false" { + isOptional = true + } else { + isOptional = false + } + if let callerAlloc = node.attribute(named: "caller-allocates"), callerAlloc != "0" && callerAlloc != "false" { + callerAllocates = true + } else { + callerAllocates = false + } + ownershipTransfer = node.attribute(named: "transfer-ownership").flatMap { OwnershipTransfer(rawValue: $0) } ?? .none + direction = node.attribute(named: "direction").flatMap { ParameterDirection(rawValue: $0) } ?? defaultDirection + super.init(node: node, at: index) + } + } +} diff --git a/Sources/libgir2swift/models/gir elements/GirBitfield.swift b/Sources/libgir2swift/models/gir elements/GirBitfield.swift new file mode 100644 index 0000000..b58322f --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirBitfield.swift @@ -0,0 +1,41 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a bitfield is defined akin to an enumeration + public class Bitfield: Enumeration { + /// String representation of `Bitfield`s + public override var kind: String { return "Bitfield" } + + /// Register this type as an enumeration type + @inlinable + override public func registerKnownType() { + let type = typeRef.type + let ctype = GIRType(name: type.typeName, typeName: type.typeName, ctype: type.ctype) + + if !GIR.bitfields.contains(ctype) { + let c = BitfieldTypeConversion(source: ctype, target: type) + type.conversions[ctype] = [c, c] + GIR.bitfields.insert(ctype) + } + + if !GIR.bitfields.contains(type) { + let c = BitfieldTypeConversion(source: type, target: ctype) + type.conversions[ctype] = [c, c] + GIR.bitfields.insert(type) + } + } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirCType.swift b/Sources/libgir2swift/models/gir elements/GirCType.swift new file mode 100644 index 0000000..6973c04 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirCType.swift @@ -0,0 +1,200 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// + +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a type with an underlying C type entry + public class CType: Datatype { + /// String representation of the `CType` thing + public override var kind: String { return "CType" } + /// list of contained types + public let containedTypes: [CType] + /// reference scope + public let scope: String? + /// `true` if this is a readable element + public let isReadable: Bool + /// `true` if this is a writable element + public let isWritable: Bool + /// `true` if this is a private element + public let isPrivate: Bool + /// tuple size if non-`nil` + public let tupleSize: Int? + + /// Returns `true` if the data type is `void` + public override var isVoid: Bool { + return super.isVoid && (tupleSize ?? 0) == 0 + } + + /// Designated initialiser + /// - Parameters: + /// - name: The name of the `Datatype` to initialise + /// - type: The corresponding, underlying GIR type + /// - comment: Documentation text for the data type + /// - introspectable: Set to `true` if introspectable + /// - deprecated: Documentation on deprecation status if non-`nil` + /// - isWritable: Set to `true` if this is a writable type + /// - contains: Array of C types contained within this type + /// - tupleSize: Size of the given tuple if non-`nil` + /// - scope: The scope this type belongs in + public init(name: String, type: TypeReference, comment: String, introspectable: Bool = false, deprecated: String? = nil, isPrivate: Bool = false, isReadable: Bool = true, isWritable: Bool = false, contains: [CType] = [], tupleSize: Int? = nil, scope: String? = nil) { + self.isPrivate = isPrivate + self.isReadable = isReadable + self.isWritable = isWritable + self.containedTypes = contains + self.tupleSize = tupleSize + self.scope = scope + super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) + } + + /// XML Element initialser + /// - Parameters: + /// - node: `XMLElement` to construct this C type from + /// - index: Index within the siblings of the `node` + /// - nameAttr: Key for the attribute to extract the `name` property from + /// - privateAttr: Key for the attribute to extract the privacy status from + /// - readableAttr: Key for the attribute to extract the readbility status from + /// - writableAttr: Key for the attribute to extract the writability status from + /// - scopeAttr: Key for the attribute to extract the scope string from + public init(node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { + containedTypes = node.containedTypes + isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true + isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + tupleSize = node.attribute(named: "fixed-size").flatMap(Int.init) + scope = node.attribute(named: scopeAttr) + super.init(node: node, at: index, nameAttr: nameAttr) + } + + /// Factory method to construct a C Type from XML with types taken from children + /// - Parameters: + /// - node: `XMLElement` whose descendants to construct this C type from + /// - index: Index within the siblings of the `node` + /// - nameAttr: Key for the attribute to extract the `name` property from + /// - typeAttr: Key for the attribute to extract the `type` property from + /// - scopeAttr: Key for the attribute to extract the scope string from + public init(fromChildrenOf node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { + let type: TypeReference + isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true + isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + scope = node.attribute(named: scopeAttr) + if let array = node.children.filter({ $0.name == "array" }).first { + type = array.alias + tupleSize = array.attribute(named: "fixed-size").flatMap(Int.init) + containedTypes = array.containedTypes + } else { + containedTypes = [] + tupleSize = nil + type = GIR.typeOf(node: node) + } + super.init(node: node, at: index, with: type, nameAttr: nameAttr) + } + + /// return whether the type is an array + @inlinable + public var isArray: Bool { return !containedTypes.isEmpty } + + /// return whether the receiver is an instance of the given record (class) + /// - Parameter record: The record to test for + /// - Returns: `true` if `self` points to `record` + @inlinable + public func isInstanceOf(_ record: GIR.Record?) -> Bool { + if let r = record?.typeRef, (isGPointer && typeRef.type.name == record?.name) || typeRef.isDirectPointer(to: r) { + return true + } else { + return false + } + } + + /// return whether the type is a magical `gpointer` or related + /// - Note: This returns `false` if the indirection level is non-zero (e.g. for a `gpointer *`) + @inlinable + public var isGPointer: Bool { + guard typeRef.indirectionLevel == 0 else { return false } + let type = typeRef.type + let name = type.typeName + return name == GIR.gpointer || name == GIR.gconstpointer + } + + /// return whether the receiver is an instance of the given record (class) or any of its ancestors + @inlinable + public func isInstanceOfHierarchy(_ record: GIR.Record) -> Bool { + if isInstanceOf(record) { return true } + guard let parent = record.parentType else { return false } + return isInstanceOfHierarchy(parent) + } + + /// indicates whether the receiver is any known kind of pointer + @inlinable + public var isAnyKindOfPointer: Bool { + guard typeRef.indirectionLevel == 0 else { return true } + let type = typeRef.type + let name = type.name + return isGPointer || name.maybeCallback + } + + /// indicates whether the receiver is an array of scalar values + @inlinable + public var isScalarArray: Bool { return isArray && !isAnyKindOfPointer } + + /// return the Swift camel case name, quoted if necessary + @inlinable + public var camelQuoted: String { name.camelCase.swiftQuoted } + + /// return a non-clashing argument name + @inlinable + public var nonClashingName: String { + let sw = name.swift + let nt = sw + (sw.isKnownType ? "_" : "") + let type = typeRef.type + let ctype = type.ctype + let ct = ctype.innerCType.swiftType // swift name for C type + let st = ctype.innerCType.swift // corresponding Swift type + let nc = nt == ct ? nt + "_" : nt + let ns = nc == st ? nc + "_" : nc + let na = ns == type.swiftName ? ns + "_" : ns + return na + } + + //// return the known type of the argument (nil if not known) + @inlinable + public var knownType: GIR.Datatype? { return GIR.knownDataTypes[typeRef.type.name] } + + //// return the known class/record of the argument (nil if not known) + @inlinable + public var knownRecord: GIR.Record? { + typeRef.knownIndirectionLevel == 1 ? GIR.knownRecords[typeRef.type.name] : nil + } + + //// return the known bitfield the argument represents (nil if not known) + @inlinable + public var knownBitfield: GIR.Bitfield? { return GIR.knownBitfields[typeRef.type.name] } + + /// indicates whether the receiver is a known type + @inlinable + public var isKnownType: Bool { return knownType != nil } + + /// indicates whether the receiver is a known class or record + @inlinable + public var isKnownRecord: Bool { return knownRecord != nil } + + /// indicates whether the receiver is a known bit field + @inlinable + public var isKnownBitfield: Bool { return knownBitfield != nil } + + /// return the non-prefixed argument name + @inlinable + public var argumentName: String { return name.argumentSplit.arg.camelQuoted } + } +} diff --git a/Sources/libgir2swift/models/gir elements/GirCallback.swift b/Sources/libgir2swift/models/gir elements/GirCallback.swift new file mode 100644 index 0000000..723dab7 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirCallback.swift @@ -0,0 +1,22 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a callback is the same as a function, + /// except that the type definition is a `@convention(c)` callback definition + public class Callback: Function { + public override var kind: String { return "Callback" } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirClass.swift b/Sources/libgir2swift/models/gir elements/GirClass.swift new file mode 100644 index 0000000..e024186 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirClass.swift @@ -0,0 +1,50 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a class data type record + public class Class: Record { + /// String representation of `Class`es + public override var kind: String { return "Class" } + /// parent class name + public let parent: String + + /// return the parent type of the given class + public override var parentType: Record? { + guard !parent.isEmpty else { return nil } + return GIR.knownDataTypes[parent] as? GIR.Record + } + + /// return the top level ancestor type of the given class + public override var rootType: Record { + guard parent != "" else { return self } + guard let p = GIR.knownDataTypes[parent] as? GIR.Record else { return self } + return p.rootType + } + + /// Initialiser to construct a class type from XML + /// - Parameters: + /// - node: `XMLElement` to construct this constant from + /// - index: Index within the siblings of the `node` + override init(node: XMLElement, at index: Int) { + var parent = node.attribute(named: "parent") ?? "" + if parent.isEmpty { + parent = node.children.lazy.filter { $0.name == "prerequisite" }.first?.attribute(named: "name") ?? "" + } + self.parent = parent + super.init(node: node, at: index) + } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirConstant.swift b/Sources/libgir2swift/models/gir elements/GirConstant.swift new file mode 100644 index 0000000..0a6998f --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirConstant.swift @@ -0,0 +1,53 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// an entry for a constant + public class Constant: CType { + /// String representation of `Constant`s + public override var kind: String { return "Constant" } + /// raw value + public let value: Int + + /// Designated initialiser + /// - Parameters: + /// - name: The name of the `Constant` to initialise + /// - type: The type of the enum + /// - ctype: underlying C type + /// - value: the value of the constant + /// - comment: Documentation text for the constant + /// - introspectable: Set to `true` if introspectable + /// - deprecated: Documentation on deprecation status if non-`nil` + public init(name: String, type: TypeReference, value: Int, comment: String, introspectable: Bool = false, deprecated: String? = nil) { + self.value = value + super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) + } + + /// Initialiser to construct a constant from XML + /// - Parameters: + /// - node: `XMLElement` to construct this constant from + /// - index: Index within the siblings of the `node` + /// - nameAttr: Key for the attribute to extract the `name` property from + public init(node: XMLElement, at index: Int, nameAttr: String = "name") { + if let val = node.attribute(named: "value"), let v = Int(val) { + value = v + } else { + value = index + } + super.init(node: node, at: index, nameAttr: nameAttr) + } + } + +} + diff --git a/Sources/libgir2swift/models/gir elements/GirDatatype.swift b/Sources/libgir2swift/models/gir elements/GirDatatype.swift new file mode 100644 index 0000000..7326386 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirDatatype.swift @@ -0,0 +1,72 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// + +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// GIR type class + public class Datatype: Thing { + /// String representation of the `Datatype` thing + public override var kind: String { return "Datatype" } + /// The underlying type + public var typeRef: TypeReference + /// The identifier of this instance (e.g. C enum value type) + @inlinable public var identifier: String? { typeRef.identifier } + + /// A reference to the underlying C type + @inlinable public var underlyingCRef: TypeReference { + let type = typeRef.type + let nm = typeRef.fullCType + let tp = GIRType(name: nm, ctype: type.ctype, superType: type.parent, isAlias: type.isAlias, conversions: type.conversions) + let ref = TypeReference(type: tp, identifier: typeRef.identifier, isConst: typeRef.isConst, isOptional: typeRef.isOptional, isArray: typeRef.isArray, constPointers: typeRef.constPointers) + return ref + } + + /// Memberwise initialiser + /// - Parameters: + /// - name: The name of the `Datatype` to initialise + /// - type: The corresponding, underlying GIR type + /// - comment: Documentation text for the data type + /// - introspectable: Set to `true` if introspectable + /// - deprecated: Documentation on deprecation status if non-`nil` + /// - version: The version this data type is first available in + public init(name: String, type: TypeReference, comment: String, introspectable: Bool = false, deprecated: String? = nil) { + typeRef = type + super.init(name: name, comment: comment, introspectable: introspectable, deprecated: deprecated) + registerKnownType() + } + + /// XML Element initialser + /// - Parameters: + /// - node: `XMLElement` to construct this data type from + /// - index: Index within the siblings of the `node` + /// - type: Type reference for the data type (taken from XML if `nil`) + /// - nameAttr: Key for the attribute to extract the `name` property from + public init(node: XMLElement, at index: Int, with type: TypeReference? = nil, nameAttr: String = "name") { + typeRef = type ?? node.alias + super.init(node: node, at: index, nameAttr: nameAttr) + typeRef.isArray = node.name == "array" + registerKnownType() + } + + /// Register this type as an enumeration type + @inlinable + public func registerKnownType() { + } + + /// Returns `true` if the data type is `void` + public var isVoid: Bool { + return typeRef.isVoid + } + } +} diff --git a/Sources/libgir2swift/models/gir elements/GirEnumeration.swift b/Sources/libgir2swift/models/gir elements/GirEnumeration.swift new file mode 100644 index 0000000..5ee80e0 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirEnumeration.swift @@ -0,0 +1,59 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// an enumeration entry + public class Enumeration: Datatype { + /// String representation of `Enumeration`s + public override var kind: String { return "Enumeration" } + /// an enumeration value in C is a constant + public typealias Member = Constant + + /// enumeration values + public let members: [Member] + + /// Designated initialiser + /// - Parameters: + /// - name: The name of the `Enumeration` to initialise + /// - type: C typedef name of the enum + /// - members: the cases for this enum + /// - comment: Documentation text for the enum + /// - introspectable: Set to `true` if introspectable + /// - deprecated: Documentation on deprecation status if non-`nil` + public init(name: String, type: TypeReference, members: [Member], comment: String, introspectable: Bool = false, deprecated: String? = nil) { + self.members = members + super.init(name: name, type: type, comment: comment, introspectable: introspectable, deprecated: deprecated) + } + + /// Initialiser to construct an enumeration from XML + /// - Parameters: + /// - node: `XMLElement` to construct this enum from + /// - index: Index within the siblings of the `node` + public init(node: XMLElement, at index: Int) { + let mem = node.children.lazy.filter { $0.name == "member" } + members = mem.enumerated().map { Member(node: $0.1, at: $0.0) } + super.init(node: node, at: index) + } + + /// Register this type as an enumeration type + @inlinable + override public func registerKnownType() { + if !GIR.enums.contains(typeRef.type) { + GIR.enums.insert(typeRef.type) + } + } + } + +} + diff --git a/Sources/libgir2swift/models/gir elements/GirField.swift b/Sources/libgir2swift/models/gir elements/GirField.swift new file mode 100644 index 0000000..e5b83f3 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirField.swift @@ -0,0 +1,25 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a field is a Property + public class Field: Property { + public override var kind: String { return "Field" } + + public init(node: XMLElement, at index: Int) { + super.init(fromChildrenOf: node, at: index) + } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirFunction.swift b/Sources/libgir2swift/models/gir elements/GirFunction.swift new file mode 100644 index 0000000..3a4e873 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirFunction.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + /// a function is the same as a method + public class Function: Method { + public override var kind: String { return "Function" } + + public override init(node: XMLElement, at index: Int) { + super.init(node: node, at: index) + } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirInterface.swift b/Sources/libgir2swift/models/gir elements/GirInterface.swift new file mode 100644 index 0000000..3dee2fd --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirInterface.swift @@ -0,0 +1,23 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// an inteface is similar to a class, + /// but can be part of a more complex type graph + public class Interface: Class { + /// String representation of `Interface`es + public override var kind: String { return "Interface" } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirMethod.swift b/Sources/libgir2swift/models/gir elements/GirMethod.swift new file mode 100644 index 0000000..c167ecc --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirMethod.swift @@ -0,0 +1,114 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// data type representing a function/method + public class Method: Argument { // superclass type is return type + /// String representation of member `Method`s + public override var kind: String { return "Method" } + /// Original C function name + public let cname: String + /// Return type + public let returns: Argument + /// All associated arguments (parameters) in order + public let args: [Argument] + /// `true` if this method throws an error + public let throwsError: Bool + + /// Designated initialiser + /// - Parameters: + /// - name: The name of the method + /// - cname: C function name + /// - returns: return type + /// - args: Array of parameters + /// - comment: Documentation text for the method + /// - introspectable: Set to `true` if introspectable + /// - deprecated: Documentation on deprecation status if non-`nil` + /// - throwsAnError: Set to `true` if this method can throw an error + public init(name: String, cname: String, returns: Argument, args: [Argument] = [], comment: String = "", introspectable: Bool = false, deprecated: String? = nil, throwsAnError: Bool = false) { + self.cname = cname + self.returns = returns + self.args = args + throwsError = throwsAnError + super.init(name: name, type: returns.typeRef, instance: returns.instance, comment: comment, introspectable: introspectable, deprecated: deprecated) + } + + /// Initialiser to construct a method type from XML + /// - Parameters: + /// - node: `XMLElement` to construct this constant from + /// - index: Index within the siblings of the `node` + public init(node: XMLElement, at index: Int) { + cname = node.attribute(named: "identifier") ?? "" + let thrAttr = node.attribute(named: "throws") ?? "0" + throwsError = (Int(thrAttr) ?? 0) != 0 + let children = node.children.lazy + if let ret = children.first(where: { $0.name == "return-value"}) { + let arg = Argument(node: ret, at: -1) + returns = arg + } else { + returns = Argument(name: "", type: .void, instance: false, comment: "") + } + if let params = children.first(where: { $0.name == "parameters"}) { + let children = params.children.lazy + args = GIR.args(children: children) + } else { + args = GIR.args(children: children) + } + super.init(node: node, at: index, varargs: args.lazy.filter({$0.varargs}).first != nil) + } + + /// indicate whether this is an unref method + public var isUnref: Bool { + return args.count == 1 && name == "unref" + } + + /// indicate whether this is a ref method + public var isRef: Bool { + return args.count == 1 && name == "ref" + } + + /// indicate whether this is a getter method + public var isGetter: Bool { + return args.count == 1 && ( name.hasPrefix("get_") || name.hasPrefix("is_")) + } + + /// indicate whether this is a setter method + public var isSetter: Bool { + return args.count == 2 && name.hasPrefix("set_") + } + + /// indicate whether this is a setter method for the given getter + public func isSetterFor(getter: String) -> Bool { + guard args.count == 2 else { return false } + let u = getter.utf8 + let s = u.index(after: u.startIndex) + let e = u.endIndex + let v = u[s.. Bool { + guard args.count == 1 else { return false } + let u = setter.utf8 + let s = u.index(after: u.startIndex) + let e = u.endIndex + let v = u[s.. Bool) -> Method? { + return allMethods.lazy.filter(predictate).first + } + + /// return the first inherited method where the passed predicate closure returns `true` + public func inheritedMethodMatching(_ predictate: (Method) -> Bool) -> Method? { + return inheritedMethods.lazy.filter(predictate).first + } + + /// return the first of my own or inherited methods where the passed predicate closure returns `true` + public func anyMethodMatching(_ predictate: (Method) -> Bool) -> Method? { + if let match = methodMatching(predictate) { return match } + return inheritedMethodMatching(predictate) + } + + /// return the `retain` (ref) method for the given record, if any + public var ref: Method? { return anyMethodMatching { $0.isRef && $0.args.first!.isInstanceOfHierarchy(self) } } + + /// return the `release` (unref) method for the given record, if any + public var unref: Method? { return anyMethodMatching { $0.isUnref && $0.args.first!.isInstanceOfHierarchy(self) } } + + /// return whether the record or one of its parents has a given property + public func has(property name: String) -> Bool { + guard properties.first(where: { $0.name == name }) == nil else { return true } + guard let parent = parentType else { return false } + return parent.has(property: name) + } + + /// return only the properties that are not derived + public var nonDerivedProperties: [Property] { + guard let parent = parentType else { return properties } + return properties.filter { !parent.has(property: $0.name) } + } + + /// return all properties, including the ones derived from ancestors + public var allProperties: [Property] { + guard let parent = parentType else { return properties } + let all = Set(properties).union(Set(parent.allProperties)) + return all.sorted() + } + + /// return all signals, including the ones derived from ancestors + public var allSignals: [Signal] { + guard let parent = parentType else { return signals } + let all = Set(signals).union(Set(parent.allSignals)) + return all.sorted() + } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirSignal.swift b/Sources/libgir2swift/models/gir elements/GirSignal.swift new file mode 100644 index 0000000..79504b2 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirSignal.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a signal is equivalent to a function + public class Signal: Function { + public override var kind: String { return "Signal" } + } + +} diff --git a/Sources/libgir2swift/models/gir elements/GirThing.swift b/Sources/libgir2swift/models/gir elements/GirThing.swift new file mode 100644 index 0000000..5ff39ce --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirThing.swift @@ -0,0 +1,119 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + /// GIR named thing class + public class Thing: Hashable, Comparable { + /// String representation of the kind of `Thing` represented by the receiver + public var kind: String { return "Thing" } + /// type name without namespace/prefix + public let name: String + /// documentation for the `Thing` + public let comment: String + /// Is this `Thing` introspectable? + public let introspectable: Bool + /// Is this `Thing` disguised? + public let disguised: Bool + /// Alternative to use if deprecated + public let deprecated: String? + /// Is this `Thing` explicitly marked as deprecated? + public let markedAsDeprecated: Bool + /// Version the receiver is available from + public let version: String? + + /// Hashes the essential components of this value by feeding them into the given hasher. + /// + /// This method is implemented to conform to the Hashable protocol. + /// Calls hasher.combine(_:) with the name component. + /// - Parameter hasher: The hasher to use when combining the components of the receiver. + public func hash(into hasher: inout Hasher) { + hasher.combine(name) + } + + + /// Comparator to check whether two `Thing`s are equal + /// - Parameters: + /// - lhs: `Thing` to compare + /// - rhs: `Thing` to compare with + public static func ==(lhs: GIR.Thing, rhs: GIR.Thing) -> Bool { + return lhs.name == rhs.name + } + + /// Comparator to check the ordering of two `Thing`s + /// - Parameters: + /// - lhs: first `Thing` to compare + /// - rhs: second `Thing` to compare + public static func <(lhs: GIR.Thing, rhs: GIR.Thing) -> Bool { + return lhs.name < rhs.name + } + + /// type name without 'Private' suffix (nil if public) + var priv: String? { + return name.stringByRemoving(suffix: "Private") + } + /// Type name without 'Class', 'Iface', etc. suffix + var node: String { + let nodeName: String + let privateSuffix: String + if let p = priv { + nodeName = p + privateSuffix = "Private" + } else { + nodeName = name + privateSuffix = "" + } + for s in ["Class", "Iface"] { + if let n = nodeName.stringByRemoving(suffix: s) { + return n + privateSuffix + } + } + return name + } + + /// Memberwise initialiser + /// - Parameters: + /// - name: The name of the `Thing` to initialise + /// - comment: Documentation text for the `Thing` + /// - introspectable: Set to `true` if introspectable + /// - disguised: Set to `true` if disguised + /// - deprecated: Documentation on deprecation status if non-`nil` + /// - markedAsDeprecated: Set to `true` if deprecated + /// - version: The version this `Thing` is first available in + public init(name: String, comment: String, introspectable: Bool = true, disguised: Bool = false, deprecated: String? = nil, markedAsDeprecated: Bool = false, version: String? = nil) { + self.name = name + self.comment = comment + self.introspectable = introspectable + self.disguised = disguised + self.deprecated = deprecated + self.markedAsDeprecated = markedAsDeprecated + self.version = version + } + + /// XML Element initialser + /// - Parameters: + /// - node: `XMLElement` to construct this `Thing` from + /// - index: Index within the siblings of the `node` + /// - nameAttr: Key for the attribute to extract the `name` property from + public init(node: XMLElement, at index: Int, nameAttr: String = "name") { + name = node.attribute(named: nameAttr) ?? "Unknown\(index)" + let c = node.children.lazy + let depr = node.bool(named: "deprecated") + comment = GIR.docs(children: c) + markedAsDeprecated = depr + deprecated = GIR.deprecatedDocumentation(children: c) ?? ( depr ? "This method is deprecated." : nil ) + introspectable = (node.attribute(named: "introspectable") ?? "1") != "0" + disguised = node.bool(named: "disguised") + version = node.attribute(named: "version") + } + } +} diff --git a/Sources/libgir2swift/models/gir elements/GirUnion.swift b/Sources/libgir2swift/models/gir elements/GirUnion.swift new file mode 100644 index 0000000..86ab496 --- /dev/null +++ b/Sources/libgir2swift/models/gir elements/GirUnion.swift @@ -0,0 +1,22 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +extension GIR { + + /// a union data type record + public class Union: Record { + /// String representation of `Union`s + public override var kind: String { return "Union" } + } + +} diff --git a/Sources/libgir2swift/girtype+xml.swift b/Sources/libgir2swift/models/girtype+xml.swift similarity index 100% rename from Sources/libgir2swift/girtype+xml.swift rename to Sources/libgir2swift/models/girtype+xml.swift diff --git a/Sources/libgir2swift/Collection+Utilities.swift b/Sources/libgir2swift/utilities/Collection+Utilities.swift similarity index 100% rename from Sources/libgir2swift/Collection+Utilities.swift rename to Sources/libgir2swift/utilities/Collection+Utilities.swift diff --git a/Sources/libgir2swift/FileLen.swift b/Sources/libgir2swift/utilities/FileLen.swift similarity index 100% rename from Sources/libgir2swift/FileLen.swift rename to Sources/libgir2swift/utilities/FileLen.swift diff --git a/Sources/libgir2swift/String+ContentsOfFile.swift b/Sources/libgir2swift/utilities/String+ContentsOfFile.swift similarity index 100% rename from Sources/libgir2swift/String+ContentsOfFile.swift rename to Sources/libgir2swift/utilities/String+ContentsOfFile.swift diff --git a/Sources/libgir2swift/String+Substring.swift b/Sources/libgir2swift/utilities/String+Substring.swift similarity index 98% rename from Sources/libgir2swift/String+Substring.swift rename to Sources/libgir2swift/utilities/String+Substring.swift index 81def73..4d81d93 100644 --- a/Sources/libgir2swift/String+Substring.swift +++ b/Sources/libgir2swift/utilities/String+Substring.swift @@ -29,7 +29,7 @@ public extension String { /// return the unprefixed version of the string /// (e.g. type without namespace) @inlinable var unprefixed: String { - guard let suffix = split(separator: ".").last else { return self } + guard let suffix = components(separatedBy: ".").last else { return self } return suffix } diff --git a/Sources/libgir2swift/utilities/String+Utilities.swift b/Sources/libgir2swift/utilities/String+Utilities.swift new file mode 100644 index 0000000..68a878e --- /dev/null +++ b/Sources/libgir2swift/utilities/String+Utilities.swift @@ -0,0 +1,54 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// + +import Foundation + +public extension String { + /// Remove the name space and return the base name of the receiver + /// representing a fully qualified Swift type + var withoutNameSpace: String { + guard let dot = self.enumerated().filter({ $0.1 == "." }).last else { + return self + } + return String(self[index(startIndex, offsetBy: dot.offset+1).. String { + return String(repeating: " ", count: level * 4) + s +} diff --git a/Sources/libgir2swift/utilities/XML+Utilities.swift b/Sources/libgir2swift/utilities/XML+Utilities.swift new file mode 100644 index 0000000..1d02068 --- /dev/null +++ b/Sources/libgir2swift/utilities/XML+Utilities.swift @@ -0,0 +1,59 @@ +// +// File.swift +// +// +// Created by Mikoláš Stuchlík on 17.11.2020. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif +import SwiftLibXML + +/// Enumerate a subtree of an XML document designated by an XPath expression +/// - Parameters: +/// - xml: the XML document to enumerate +/// - path: XPath representation of the subtry to enumerate +/// - namespaces: namespaces to consider +/// - quiet: suppress warnings if `true` +/// - construct: callback to construct a given type `T` represented by an XML element +/// - prefix: default Namespace prefix to register +/// - check: callback to check whether the current element should be included +func enumerate(_ xml: XMLDocument, path: String, inNS namespaces: AnySequence, quiet: Bool, construct: (XMLElement, Int) -> T?, defaultPrefix prefix: String = "gir", check: (T) -> Bool = { _ in true }) -> [T] where T: GIR.Thing { + if let entries = xml.xpath(path, namespaces: namespaces, defaultPrefix: prefix) { + let things = entries.lazy.enumerated().map { construct($0.1, $0.0) }.filter { + guard let node = $0 else { return false } + guard check(node) else { + if !quiet { + fputs("Warning: duplicate type '\(node.name)' for \(path) ignored!\n", stderr) + } + return false + } + + return true + } + .map { $0! } + + return things + } + return [] +} + +extension XMLElement { + /// + /// return an attribute as a list of sub-attributeds split by a given character + /// and ordered with the longest attribute name first + /// + public func sortedSubAttributesFor(attr: String, splitBy char: Character = ",", orderedBy: (String, String) -> Bool = { $0.count > $1.count || ($0.count == $1.count && $0 < $1)}) -> [String] { + guard let attrs = (attribute(named: attr)?.split(separator: char))?.map({ String($0) }) else { return [] } + return attrs.sorted(by: orderedBy) + } + + /// + /// return the documentation for a given node + /// + public func docs() -> String { + return GIR.docs(children: children.lazy) + } +} diff --git a/Sources/libgir2swift/basename.swift b/Sources/libgir2swift/utilities/basename.swift similarity index 100% rename from Sources/libgir2swift/basename.swift rename to Sources/libgir2swift/utilities/basename.swift diff --git a/Sources/libgir2swift/cstring.swift b/Sources/libgir2swift/utilities/cstring.swift similarity index 100% rename from Sources/libgir2swift/cstring.swift rename to Sources/libgir2swift/utilities/cstring.swift diff --git a/Sources/libgir2swift/getopt.swift b/Sources/libgir2swift/utilities/getopt.swift similarity index 100% rename from Sources/libgir2swift/getopt.swift rename to Sources/libgir2swift/utilities/getopt.swift diff --git a/Sources/libgir2swift/mmap.swift b/Sources/libgir2swift/utilities/mmap.swift similarity index 100% rename from Sources/libgir2swift/mmap.swift rename to Sources/libgir2swift/utilities/mmap.swift From 35220223ff484477f0b37681f3a81226ad6cc829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Thu, 19 Nov 2020 11:45:32 +0100 Subject: [PATCH 05/18] [Signal] Add argument cast for more types --- .../libgir2swift/emmiting/emmit-signals.swift | 139 ++++++++++++------ 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index 1d349e9..a9bda52 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -7,24 +7,25 @@ import Foundation -func signalCheck(_ signal: GIR.Signal) -> Bool { +// TODO: Expand sanity check and add reporting to the code, including ownership and inout +func signalSanityCheck(_ signal: GIR.Signal) -> Bool { signal.args.allSatisfy { $0.typeRef.type.name != "Void" } } func buildSignalExtension(for record: GIR.Record) -> String { - // Check preconditions + // TODO: Add support for generation inside of interface if record.kind == "Interface" { return "// MARK: Signals of \(record.kind) named \(record.name.swift) are dropped" } - if record.signals.isEmpty { + if record.signals.isEmpty { return "// MARK: no \(record.name.swift) signals" } return Code.block(indentation: nil) { Code.block { - Code.loop(over: record.signals.filter({ !signalCheck($0) })) { signal in + Code.loop(over: record.signals.filter({ !signalSanityCheck($0) })) { signal in "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" } } @@ -32,7 +33,7 @@ func buildSignalExtension(for record: GIR.Record) -> String { "// MARK: Signals of \(record.kind) named \(record.name.swift)" "public extension \(record.name.swift) {" Code.block { - Code.loop(over: record.signals.filter(signalCheck(_:))) { signal in + Code.loop(over: record.signals.filter(signalSanityCheck(_:))) { signal in commentCode(signal) "/// - Note: This function represents signal `\(signal.name)`" "/// - Parameter flags: Flags" @@ -47,41 +48,24 @@ func buildSignalExtension(for record: GIR.Record) -> String { let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" } - Code.line { "public func _on\(signal.name.camelSignal.capitalised)(" "flags: ConnectFlags = ConnectFlags(0), " - "handler: @escaping ( _ unownedSelf: \(record.structRef.fullSwiftTypeName)" - Code.loop(over: signal.args) { argument in - ", _ \(argument.prefixedArgumentName): \(argument.typeRef.fullSwiftTypeName)" - } - ") -> " - signal.returns.typeRef.fullSwiftTypeName + "handler: " + handlerType(record: record, signal: signal) " ) -> Int {" } Code.block { - "typealias SwiftHandler = \(signalClosureHolderDecl(type: record.structRef.type.swiftName, args: signal.args.map { $0.typeRef.fullSwiftTypeName }, returnType: signal.returns.typeRef.fullSwiftTypeName))" + "typealias SwiftHandler = \(signalClosureHolderDecl(record: record, signal: signal))" "let swiftHandlerBoxed = Unmanaged.passRetained(SwiftHandler(handler)).toOpaque()" Code.line { - "let cCallback: @convention(c) (" - "gpointer, " - Code.loop(over: signal.args) { argument in - "\(argument.typeRef.fullTypeName), " - } - "gpointer" - ") -> " - signal.returns.typeRef.fullTypeName + "let cCallback: " + cCallbackDecl(record: record, signal: signal) " = { " } Code.block { "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" - Code.line { - "let output = holder.call(\(record.structRef.type.swiftName)(raw: $0)" - Code.loopEnumerated(over: signal.args) { index, argument in - ", \(argument.swiftSignalArgumentConversion(at: index + 1))" - } - ")" - } + "let output = holder.\(generaceCCallbackCall(record: record, signal: signal))" "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" } "}" @@ -116,20 +100,19 @@ func buildSignalExtension(for record: GIR.Record) -> String { } } -extension GIR.Argument { - - func swiftSignalArgumentConversion(at index: Int) -> String { - if let type = self.knownType as? GIR.Record { - return type.structRef.fullSwiftTypeName + "(raw: $\(index))" - } - - return "$\(index)" +@CodeBuilder +private func handlerType(record: GIR.Record, signal: GIR.Signal) -> String { + "@escaping ( _ unownedSelf: \(record.structName)" + Code.loop(over: signal.args) { argument in + ", _ \(argument.prefixedArgumentName): \(argument.swiftIdiomaticType())" } + ") -> " + signal.returns.swiftIdiomaticType() } -func signalClosureHolderDecl(type: String, args: [String], returnType: String = "Void") -> String { +private func signalClosureHolderDecl(record: GIR.Record, signal: GIR.Signal) -> String { let holderType: String - switch args.count { + switch signal.args.count { case 0: holderType = "Tmp__ClosureHolder" case 1: @@ -145,13 +128,83 @@ func signalClosureHolderDecl(type: String, args: [String], returnType: String = case 6: holderType = "Tmp__Closure7Holder" default: - fatalError("Argument count \(args.count) exceeds number of allowed arguments (6)") + fatalError("Argument count \(signal.args.count) exceeds number of allowed arguments (6)") } return holderType - + "<\(type), " - + args.joined(separator: ", ") - + (args.isEmpty ? "" : ", ") - + returnType + + "<" + record.structName + ", " + + signal.args.map { $0.swiftIdiomaticType() }.joined(separator: ", ") + + (signal.args.isEmpty ? "" : ", ") + + signal.returns.swiftIdiomaticType() + ">" } + +@CodeBuilder +private func cCallbackDecl(record: GIR.Record, signal: GIR.Signal) -> String { + "@convention(c) (" + GIR.gpointerType.typeName + ", " // Representing record itself + Code.loop(over: signal.args) { argument in + argument.swiftCCompatibleType() + ", " + } + GIR.gpointerType.typeName // Representing user data + ") -> " + signal.returns.swiftCCompatibleType() +} + +private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> String { + Code.line { + "call(\(record.structRef.type.swiftName)(raw: $0)" + Code.loopEnumerated(over: signal.args) { index, argument in + ", \(argument.swiftSignalArgumentConversion(at: index + 1))" + } + ")" + } +} + +private extension GIR.Argument { + + func swiftIdiomaticType() -> String { + switch knownType { + case let type as GIR.Record: // Also Class, Union, Interface + return type.structName + case let type as GIR.Alias: // use containedTypes + return "" + case is GIR.Bitfield: // use UInt32 + return self.argumentTypeName + case is GIR.Enumeration: // Binary integer (use Int) + return self.argumentTypeName + default: // Treat as fundamental (if not a fundamental, report error) + return self.argumentTypeName + } + } + + func swiftCCompatibleType() -> String { + switch knownType { + case is GIR.Record: // Also Class, Union, Interface + return GIR.gpointerType.typeName + case let type as GIR.Alias: // use containedTypes + return "" + case is GIR.Bitfield: // use UInt32 + return GIR.uint32Type.typeName + case is GIR.Enumeration: // Binary integer (use Int) + return GIR.intType.typeName + default: // Treat as fundamental (if not a fundamental, report error) + return self.typeRef.fullTypeName + } + } + + func swiftSignalArgumentConversion(at index: Int) -> String { + switch knownType { + case let type as GIR.Record: // Also Class, Union, Interface + return type.structRef.fullSwiftTypeName + "(raw: $\(index))" + case let type as GIR.Alias: // use containedTypes + return "" + case is GIR.Bitfield: // use UInt32 + return self.argumentTypeName + "($\(index))" + case is GIR.Enumeration: // Binary integer (use Int) + return self.argumentTypeName + "($\(index))" + default: // Treat as fundamental (if not a fundamental, report error) + return swiftReturnRef.cast(expression: "$\(index)", from: typeRef) + } + } +} From b301f9afd52a919108e805127101f765da930bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= <42004728+mikolasstuchlik@users.noreply.github.com> Date: Fri, 13 Nov 2020 11:28:54 +0100 Subject: [PATCH 06/18] [Build/CI] Update build driver and CI --- .github/workflows/build-gtk.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 5671479..b7639a5 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -40,6 +40,7 @@ jobs: cd SwiftGtk swift package update ../gir2swift/gir2swift-generation-driver.sh "$PWD" "$GIR2SWIFT_PATH" + swift build -Xswiftc -suppress-warnings cd .. - name: Remove unneeded files and archive artifacts @@ -51,10 +52,11 @@ jobs: swift package clean rm -rf .build/repositories cd .. - zip -r build.zip gir2swift swiftGtk - name: 'Upload Artifact' uses: actions/upload-artifact@v2 with: name: build-artifact-20.04-5.3 - path: build.zip + path: | + gir2swift/ + SwiftGtk/ retention-days: 1 From 751a4c8506d269c02063c8d4a3954df1254795da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Thu, 19 Nov 2020 14:08:12 +0100 Subject: [PATCH 07/18] [gir2swift] Restore changes which break project --- Sources/gir2swift/main.swift | 12 ++++----- Sources/libgir2swift/emmiting/gir+swift.swift | 6 ++--- .../libgir2swift/utilities/String+Lines.swift | 25 +++++++++++++++++++ .../utilities/String+Substring.swift | 2 +- 4 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 Sources/libgir2swift/utilities/String+Lines.swift diff --git a/Sources/gir2swift/main.swift b/Sources/gir2swift/main.swift index 6be6080..32d38f2 100644 --- a/Sources/gir2swift/main.swift +++ b/Sources/gir2swift/main.swift @@ -49,7 +49,7 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect let base = file.baseName let node = base.stringByRemoving(suffix: ".gir") ?? base let wlfile = node + ".whitelist" - if let whitelist = String(contentsOfFile: wlfile, quiet: true).flatMap({ Set($0.components(separatedBy: "\n")) }) { + if let whitelist = String(contentsOfFile: wlfile, quiet: true).flatMap({ Set($0.lines) }) { for name in whitelist { GIR.knownDataTypes.removeValue(forKey: name) GIR.knownRecords.removeValue(forKey: name) @@ -57,11 +57,11 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect } } let escfile = node + ".callbackSuffixes" - GIR.callbackSuffixes = String(contentsOfFile: escfile, quiet: true)?.components(separatedBy: "\n") ?? [ + GIR.callbackSuffixes = String(contentsOfFile: escfile, quiet: true)?.lines ?? [ "Notify", "Func", "Marshaller", "Callback" ] let nsfile = node + ".namespaceReplacements" - if let ns = String(contentsOfFile: nsfile, quiet: true).flatMap({Set($0.components(separatedBy: "\n"))}) { + if let ns = String(contentsOfFile: nsfile, quiet: true).flatMap({Set($0.lines)}) { for line in ns { let keyValues: [Substring] let tabbedKeyValues: [Substring] = line.split(separator: "\t") @@ -223,11 +223,11 @@ func processSpecialCases(_ gir: GIR, forFile node: String) { let preamble = node + ".preamble" gir.preamble = preamble.contents ?? "" let blacklist = node + ".blacklist" - GIR.blacklist = blacklist.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] + GIR.blacklist = blacklist.contents.flatMap { Set($0.lines) } ?? [] let verbatimConstants = node + ".verbatim" - GIR.verbatimConstants = verbatimConstants.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] + GIR.verbatimConstants = verbatimConstants.contents.flatMap { Set($0.lines) } ?? [] let overrideFile = node + ".override" - GIR.overrides = overrideFile.contents.flatMap { Set($0.components(separatedBy: "\n")) } ?? [] + GIR.overrides = overrideFile.contents.flatMap { Set($0.lines) } ?? [] } let nTypesPrior = GIR.knownTypes.count diff --git a/Sources/libgir2swift/emmiting/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift index c02cef6..78a1096 100644 --- a/Sources/libgir2swift/emmiting/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -195,7 +195,7 @@ public extension String { @inlinable var withoutDottedPrefix: String { guard !hasPrefix(GIR.dottedPrefix) else { - return components(separatedBy: ".").last ?? self + return split(separator: ".").last ?? self } return self } @@ -583,7 +583,7 @@ public func methodCode(_ indentation: String, initialIndentation: String? = nil, let params = arguments.map(nullableRefParameterCode) let funcParam = params.joined(separator: ", ") let fname: String - if let firstParamName = params.first?.components(separatedBy: " ").first?.components(separatedBy: ":").first?.capitalised { + if let firstParamName = params.first?.split(separator: " ").first?.split(separator: ":").first?.capitalised { fname = name.stringByRemoving(suffix: firstParamName) ?? name } else { fname = name @@ -607,7 +607,7 @@ public func methodCode(_ indentation: String, initialIndentation: String? = nil, let params = arguments.map(templatedParameterCode) let funcParam = params.joined(separator: ", ") let fname: String - if let firstParamName = params.first?.components(separatedBy: " ").first?.components(separatedBy: ":").first?.capitalised { + if let firstParamName = params.first?.split(separator: " ").first?.split(separator: ":").first?.capitalised { fname = name.stringByRemoving(suffix: firstParamName) ?? name } else { fname = name diff --git a/Sources/libgir2swift/utilities/String+Lines.swift b/Sources/libgir2swift/utilities/String+Lines.swift new file mode 100644 index 0000000..d2c3bdd --- /dev/null +++ b/Sources/libgir2swift/utilities/String+Lines.swift @@ -0,0 +1,25 @@ +// +// String+Lines.swift +// gir2swift +// +// Created by Rene Hexel on 13/05/2016. +// Copyright © 2016, 2019 Rene Hexel. All rights reserved. +// +#if os(Linux) + import Glibc +#else + import Darwin +#endif + + +public extension String { + /// Split a string into substrings separated by the given character + func split(separator s: Character = "\n") -> [String] { + let u = String(s).utf8.first! + let components = utf8.split(separator: u).map { String($0)! } + return components + } + + /// return the lines of the given string + var lines: [String] { return split() } +} diff --git a/Sources/libgir2swift/utilities/String+Substring.swift b/Sources/libgir2swift/utilities/String+Substring.swift index 4d81d93..81def73 100644 --- a/Sources/libgir2swift/utilities/String+Substring.swift +++ b/Sources/libgir2swift/utilities/String+Substring.swift @@ -29,7 +29,7 @@ public extension String { /// return the unprefixed version of the string /// (e.g. type without namespace) @inlinable var unprefixed: String { - guard let suffix = components(separatedBy: ".").last else { return self } + guard let suffix = split(separator: ".").last else { return self } return suffix } From 104dae55d6671e339f1e0e924dee054dbc89c532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Thu, 19 Nov 2020 14:44:16 +0100 Subject: [PATCH 08/18] [Signal] Filter unimplemented features --- .../libgir2swift/emmiting/emmit-signals.swift | 97 ++++++++++++++----- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index a9bda52..5afc529 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -7,33 +7,57 @@ import Foundation -// TODO: Expand sanity check and add reporting to the code, including ownership and inout -func signalSanityCheck(_ signal: GIR.Signal) -> Bool { - signal.args.allSatisfy { $0.typeRef.type.name != "Void" } -} +func signalSanityCheck(_ signal: GIR.Signal) -> String? { + if !signal.args.allSatisfy({ $0.typeRef.type.name != "Void" }) { + return "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" + } + + if !signal.args.allSatisfy({ !($0.knownType is GIR.Alias) }) || (signal.returns.knownType is GIR.Alias) { + return "// Warning: signal \(signal.name) is ignored because of Alias argument or return is not yet supported" + } + + if !signal.args.allSatisfy({ $0.ownershipTransfer == .none }) || signal.returns.ownershipTransfer != .none { + return "// Warning: signal \(signal.name) is ignored because of argument or return with owner transfership is not allowed" + } -func buildSignalExtension(for record: GIR.Record) -> String { - // TODO: Add support for generation inside of interface - if record.kind == "Interface" { - return "// MARK: Signals of \(record.kind) named \(record.name.swift) are dropped" + if !signal.args.allSatisfy({ !$0.isOptional }) || signal.returns.isOptional { + return "// Warning: signal \(signal.name) is ignored because of argument or return optional is not allowed" + } + + if !signal.args.allSatisfy({ $0.typeRef.type.name != "utf8" }) || signal.returns.typeRef.type.name == "utf8" { + return "// Warning: signal \(signal.name) is ignored because of argument or return String is not allowed" } + if !signal.args.allSatisfy({ $0.isNullable == false }) || signal.returns.isNullable == true { + return "// Warning: signal \(signal.name) is ignored because of argument or return nullability is not allowed" + } + + if signal.returns.knownType is GIR.Record { + return "// Warning: signal \(signal.name) is ignored because of Record return is not yet supported" + } + + return nil +} + +func buildSignalExtension(for record: GIR.Record) -> String { + if record.signals.isEmpty { return "// MARK: no \(record.name.swift) signals" } return Code.block(indentation: nil) { + "// MARK: Signals of \(record.kind) named \(record.name.swift)" + Code.block { - Code.loop(over: record.signals.filter({ !signalSanityCheck($0) })) { signal in - "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" + Code.loop(over: record.signals.compactMap({ signalSanityCheck($0) })) { error in + "\(error)" } } - "// MARK: Signals of \(record.kind) named \(record.name.swift)" "public extension \(record.name.swift) {" Code.block { - Code.loop(over: record.signals.filter(signalSanityCheck(_:))) { signal in + Code.loop(over: record.signals.filter { signalSanityCheck($0) == nil }) { signal in commentCode(signal) "/// - Note: This function represents signal `\(signal.name)`" "/// - Parameter flags: Flags" @@ -66,11 +90,17 @@ func buildSignalExtension(for record: GIR.Record) -> String { Code.block { "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" "let output = holder.\(generaceCCallbackCall(record: record, signal: signal))" - "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + generateReturnStatement(record: record, signal: signal) } "}" "let __gCallback__ = unsafeBitCast(cCallback, to: GCallback.self)" - "let rv = signalConnectData(" + Code.line { + "let rv = " + if record is GIR.Interface { + "GLibObject.ObjectRef(raw: ptr)." + } + "signalConnectData(" + } Code.block { #"detailedSignal: "\#(signal.name)", "# "cHandler: __gCallback__, " @@ -161,17 +191,32 @@ private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> St } } +private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> String { + switch signal.returns.knownType { + case is GIR.Record: + return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + case let type as GIR.Alias: // use containedTypes + return "" + case is GIR.Bitfield: + return "return output.rawValue" + case is GIR.Enumeration: + return "return output.rawValue" + default: // Treat as fundamental (if not a fundamental, report error) + return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + } +} + private extension GIR.Argument { func swiftIdiomaticType() -> String { switch knownType { - case let type as GIR.Record: // Also Class, Union, Interface - return type.structName + case is GIR.Record: + return typeRef.type.swiftName + "Ref" case let type as GIR.Alias: // use containedTypes return "" - case is GIR.Bitfield: // use UInt32 + case is GIR.Bitfield: return self.argumentTypeName - case is GIR.Enumeration: // Binary integer (use Int) + case is GIR.Enumeration: return self.argumentTypeName default: // Treat as fundamental (if not a fundamental, report error) return self.argumentTypeName @@ -180,14 +225,14 @@ private extension GIR.Argument { func swiftCCompatibleType() -> String { switch knownType { - case is GIR.Record: // Also Class, Union, Interface + case is GIR.Record: return GIR.gpointerType.typeName case let type as GIR.Alias: // use containedTypes return "" - case is GIR.Bitfield: // use UInt32 + case is GIR.Bitfield: + return GIR.uint32Type.typeName + case is GIR.Enumeration: return GIR.uint32Type.typeName - case is GIR.Enumeration: // Binary integer (use Int) - return GIR.intType.typeName default: // Treat as fundamental (if not a fundamental, report error) return self.typeRef.fullTypeName } @@ -195,13 +240,13 @@ private extension GIR.Argument { func swiftSignalArgumentConversion(at index: Int) -> String { switch knownType { - case let type as GIR.Record: // Also Class, Union, Interface - return type.structRef.fullSwiftTypeName + "(raw: $\(index))" + case is GIR.Record: + return typeRef.type.swiftName + "Ref" + "(raw: $\(index))" case let type as GIR.Alias: // use containedTypes return "" - case is GIR.Bitfield: // use UInt32 + case is GIR.Bitfield: return self.argumentTypeName + "($\(index))" - case is GIR.Enumeration: // Binary integer (use Int) + case is GIR.Enumeration: return self.argumentTypeName + "($\(index))" default: // Treat as fundamental (if not a fundamental, report error) return swiftReturnRef.cast(expression: "$\(index)", from: typeRef) From d9ba6af79212bf8e30cc05023f21aa58b559552a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Fri, 20 Nov 2020 15:05:27 +0100 Subject: [PATCH 09/18] [Signal] Allow string argument + return ownership --- .../libgir2swift/emmiting/emmit-signals.swift | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index 5afc529..b374934 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -8,6 +8,15 @@ import Foundation func signalSanityCheck(_ signal: GIR.Signal) -> String? { + + if !signal.args.allSatisfy({ $0.ownershipTransfer == .none }) { + return "// Warning: signal \(signal.name) is ignored because of argument with owner transfership is not allowed" + } + + if !signal.args.allSatisfy({ $0.direction == .in }) { + return "// Warning: signal \(signal.name) is ignored because of argument out or inout direction is not allowed" + } + if !signal.args.allSatisfy({ $0.typeRef.type.name != "Void" }) { return "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" } @@ -15,19 +24,15 @@ func signalSanityCheck(_ signal: GIR.Signal) -> String? { if !signal.args.allSatisfy({ !($0.knownType is GIR.Alias) }) || (signal.returns.knownType is GIR.Alias) { return "// Warning: signal \(signal.name) is ignored because of Alias argument or return is not yet supported" } - - if !signal.args.allSatisfy({ $0.ownershipTransfer == .none }) || signal.returns.ownershipTransfer != .none { - return "// Warning: signal \(signal.name) is ignored because of argument or return with owner transfership is not allowed" - } if !signal.args.allSatisfy({ !$0.isOptional }) || signal.returns.isOptional { return "// Warning: signal \(signal.name) is ignored because of argument or return optional is not allowed" } - if !signal.args.allSatisfy({ $0.typeRef.type.name != "utf8" }) || signal.returns.typeRef.type.name == "utf8" { - return "// Warning: signal \(signal.name) is ignored because of argument or return String is not allowed" + if !signal.args.allSatisfy({ !$0.isArray }) || signal.returns.isArray { + return "// Warning: signal \(signal.name) is ignored because of argument or return array is not allowed" } - + if !signal.args.allSatisfy({ $0.isNullable == false }) || signal.returns.isNullable == true { return "// Warning: signal \(signal.name) is ignored because of argument or return nullability is not allowed" } @@ -58,6 +63,7 @@ func buildSignalExtension(for record: GIR.Record) -> String { "public extension \(record.name.swift) {" Code.block { Code.loop(over: record.signals.filter { signalSanityCheck($0) == nil }) { signal in + commentCode(signal) "/// - Note: This function represents signal `\(signal.name)`" "/// - Parameter flags: Flags" @@ -81,45 +87,28 @@ func buildSignalExtension(for record: GIR.Record) -> String { } Code.block { "typealias SwiftHandler = \(signalClosureHolderDecl(record: record, signal: signal))" - "let swiftHandlerBoxed = Unmanaged.passRetained(SwiftHandler(handler)).toOpaque()" Code.line { "let cCallback: " cCallbackDecl(record: record, signal: signal) " = { " + cCallbackArgumentsDecl(record: record, signal: signal) + " in" } Code.block { - "let holder = Unmanaged.fromOpaque($\(signal.args.count + 1)).takeUnretainedValue()" + "let holder = Unmanaged.fromOpaque(userData).takeUnretainedValue()" "let output = holder.\(generaceCCallbackCall(record: record, signal: signal))" generateReturnStatement(record: record, signal: signal) } "}" - "let __gCallback__ = unsafeBitCast(cCallback, to: GCallback.self)" - Code.line { - "let rv = " - if record is GIR.Interface { - "GLibObject.ObjectRef(raw: ptr)." - } - "signalConnectData(" - } + "return \(record is GIR.Interface ? "GLibObject.ObjectRef(raw: ptr)." : "" )signalConnectData(" Code.block { #"detailedSignal: "\#(signal.name)", "# - "cHandler: __gCallback__, " - "data: swiftHandlerBoxed, " - "destroyData: {" - Code.block { - "if let swift = $0 {" - Code.block { - "let holder = Unmanaged.fromOpaque(swift)" - "holder.release()" - } - "}" - "let _ = $1" - } - "}, " + "cHandler: unsafeBitCast(cCallback, to: GCallback.self), " + "data: Unmanaged.passRetained(SwiftHandler(handler)).toOpaque(), " + "destroyData: { userData, _ in UnsafeRawPointer(userData).flatMap(Unmanaged.fromOpaque(_:))?.release() }," "connectFlags: flags" } ")" - "return rv" } "}" "\n" @@ -181,9 +170,19 @@ private func cCallbackDecl(record: GIR.Record, signal: GIR.Signal) -> String { signal.returns.swiftCCompatibleType() } +private func cCallbackArgumentsDecl(record: GIR.Record, signal: GIR.Signal) -> String { + Code.line { + "unownedSelf" + Code.loopEnumerated(over: signal.args) { index, _ in + ", arg\(index + 1)" + } + ", userData" + } +} + private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> String { Code.line { - "call(\(record.structRef.type.swiftName)(raw: $0)" + "call(\(record.structRef.type.swiftName)(raw: unownedSelf)" Code.loopEnumerated(over: signal.args) { index, argument in ", \(argument.swiftSignalArgumentConversion(at: index + 1))" } @@ -201,6 +200,14 @@ private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> return "return output.rawValue" case is GIR.Enumeration: return "return output.rawValue" + case nil where signal.returns.swiftReturnRef == GIR.stringRef && signal.returns.ownershipTransfer == .full: + return Code.block { + "let length = output.utf8CString.count" + "let buffer = UnsafeMutablePointer.allocate(capacity: length)" + "buffer.initialize(from: output, count: length)" + "return buffer" + } + return "return UnsafeMutablePointer(mutating: output)" default: // Treat as fundamental (if not a fundamental, report error) return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" } @@ -219,7 +226,7 @@ private extension GIR.Argument { case is GIR.Enumeration: return self.argumentTypeName default: // Treat as fundamental (if not a fundamental, report error) - return self.argumentTypeName + return self.swiftReturnRef.fullSwiftTypeName } } @@ -234,22 +241,24 @@ private extension GIR.Argument { case is GIR.Enumeration: return GIR.uint32Type.typeName default: // Treat as fundamental (if not a fundamental, report error) - return self.typeRef.fullTypeName + return self.callbackArgumentTypeName } } func swiftSignalArgumentConversion(at index: Int) -> String { switch knownType { case is GIR.Record: - return typeRef.type.swiftName + "Ref" + "(raw: $\(index))" + return typeRef.type.swiftName + "Ref" + "(raw: arg\(index))" case let type as GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - return self.argumentTypeName + "($\(index))" + return self.argumentTypeName + "(arg\(index))" case is GIR.Enumeration: - return self.argumentTypeName + "($\(index))" + return self.argumentTypeName + "(arg\(index))" + case nil where swiftReturnRef == GIR.stringRef: + return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) + "!" default: // Treat as fundamental (if not a fundamental, report error) - return swiftReturnRef.cast(expression: "$\(index)", from: typeRef) + return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) } } } From b6261c34297b88d347539d0a9453ee5624ef0350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Fri, 20 Nov 2020 18:11:54 +0100 Subject: [PATCH 10/18] [Signal] Implement optional arguments --- .../libgir2swift/emmiting/emmit-signals.swift | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index b374934..17b6776 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -9,39 +9,45 @@ import Foundation func signalSanityCheck(_ signal: GIR.Signal) -> String? { + var errors: String? + if !signal.args.allSatisfy({ $0.ownershipTransfer == .none }) { - return "// Warning: signal \(signal.name) is ignored because of argument with owner transfership is not allowed" + errors = (errors ?? "") + " (1) argument with owner transfership is not allowed;" } if !signal.args.allSatisfy({ $0.direction == .in }) { - return "// Warning: signal \(signal.name) is ignored because of argument out or inout direction is not allowed" + errors = (errors ?? "") + " (2) argument out or inout direction is not allowed;" } if !signal.args.allSatisfy({ $0.typeRef.type.name != "Void" }) { - return "// Warning: signal \(signal.name) is ignored because of Void argument is not yet supported" + errors = (errors ?? "") + " (3) Void argument is not yet supported;" + } + + if !signal.args.allSatisfy({ $0.typeRef.type.name != "gpointer" }) { + errors = (errors ?? "") + " (3.1) gpointer argument is not yet supported;" } if !signal.args.allSatisfy({ !($0.knownType is GIR.Alias) }) || (signal.returns.knownType is GIR.Alias) { - return "// Warning: signal \(signal.name) is ignored because of Alias argument or return is not yet supported" + errors = (errors ?? "") + " (4) Alias argument or return is not yet supported;" } - if !signal.args.allSatisfy({ !$0.isOptional }) || signal.returns.isOptional { - return "// Warning: signal \(signal.name) is ignored because of argument or return optional is not allowed" + if signal.returns.isOptional { + errors = (errors ?? "") + " (5) argument or return optional is not allowed;" } if !signal.args.allSatisfy({ !$0.isArray }) || signal.returns.isArray { - return "// Warning: signal \(signal.name) is ignored because of argument or return array is not allowed" + errors = (errors ?? "") + " (6) argument or return array is not allowed;" } - if !signal.args.allSatisfy({ $0.isNullable == false }) || signal.returns.isNullable == true { - return "// Warning: signal \(signal.name) is ignored because of argument or return nullability is not allowed" + if signal.returns.isNullable == true { + errors = (errors ?? "") + " (7) argument or return nullability is not allowed;" } if signal.returns.knownType is GIR.Record { - return "// Warning: signal \(signal.name) is ignored because of Record return is not yet supported" + errors = (errors ?? "") + " (8) Record return is not yet supported;" } - return nil + return errors.flatMap { "// Warning: signal \(signal.name) is ignored because of" + $0 } } func buildSignalExtension(for record: GIR.Record) -> String { @@ -52,7 +58,7 @@ func buildSignalExtension(for record: GIR.Record) -> String { return Code.block(indentation: nil) { - "// MARK: Signals of \(record.kind) named \(record.name.swift)" + "// MARK: Signals of \(record.name.swift)" Code.block { Code.loop(over: record.signals.compactMap({ signalSanityCheck($0) })) { error in @@ -60,10 +66,10 @@ func buildSignalExtension(for record: GIR.Record) -> String { } } - "public extension \(record.name.swift) {" + "public extension \(record.protocolName) {" Code.block { Code.loop(over: record.signals.filter { signalSanityCheck($0) == nil }) { signal in - + commentCode(signal) "/// - Note: This function represents signal `\(signal.name)`" "/// - Parameter flags: Flags" @@ -194,7 +200,7 @@ private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> switch signal.returns.knownType { case is GIR.Record: return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" - case let type as GIR.Alias: // use containedTypes + case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: return "return output.rawValue" @@ -207,7 +213,6 @@ private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> "buffer.initialize(from: output, count: length)" "return buffer" } - return "return UnsafeMutablePointer(mutating: output)" default: // Treat as fundamental (if not a fundamental, report error) return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" } @@ -218,28 +223,28 @@ private extension GIR.Argument { func swiftIdiomaticType() -> String { switch knownType { case is GIR.Record: - return typeRef.type.swiftName + "Ref" - case let type as GIR.Alias: // use containedTypes + return typeRef.type.swiftName + "Ref" + (isNullable ? "?" : "") + case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - return self.argumentTypeName + return self.argumentTypeName + (isNullable ? "?" : "") case is GIR.Enumeration: - return self.argumentTypeName + return self.argumentTypeName + (isNullable ? "?" : "") default: // Treat as fundamental (if not a fundamental, report error) - return self.swiftReturnRef.fullSwiftTypeName + return self.swiftReturnRef.fullSwiftTypeName + (isNullable ? "?" : "") } } func swiftCCompatibleType() -> String { switch knownType { case is GIR.Record: - return GIR.gpointerType.typeName - case let type as GIR.Alias: // use containedTypes + return GIR.gpointerType.typeName + (isNullable ? "?" : "") + case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - return GIR.uint32Type.typeName + return GIR.uint32Type.typeName + (isNullable ? "?" : "") case is GIR.Enumeration: - return GIR.uint32Type.typeName + return GIR.uint32Type.typeName + (isNullable ? "?" : "") default: // Treat as fundamental (if not a fundamental, report error) return self.callbackArgumentTypeName } @@ -248,15 +253,24 @@ private extension GIR.Argument { func swiftSignalArgumentConversion(at index: Int) -> String { switch knownType { case is GIR.Record: + if isNullable { + return "arg\(index).flatMap(\(typeRef.type.swiftName)Ref.init(raw:))" + } return typeRef.type.swiftName + "Ref" + "(raw: arg\(index))" - case let type as GIR.Alias: // use containedTypes + case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: + if isNullable { + return "arg\(index).flatMap(\(self.argumentTypeName).init(_:))" + } return self.argumentTypeName + "(arg\(index))" case is GIR.Enumeration: + if isNullable { + return "arg\(index).flatMap(\(self.argumentTypeName).init(_:))" + } return self.argumentTypeName + "(arg\(index))" case nil where swiftReturnRef == GIR.stringRef: - return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) + "!" + return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) + (isNullable ? "" : "!") default: // Treat as fundamental (if not a fundamental, report error) return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) } From db63ccb262573163d77602e48c9bab53df3ceefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikola=CC=81s=CC=8C=20Stuchli=CC=81k?= Date: Fri, 20 Nov 2020 19:58:09 +0100 Subject: [PATCH 11/18] [Signal] General generation improvements --- .../libgir2swift/emmiting/CodeBuilder.swift | 6 +- .../libgir2swift/emmiting/emmit-signals.swift | 206 ++++++++++-------- Sources/libgir2swift/emmiting/gir+swift.swift | 106 +-------- .../models/Gir+KnowTypeSets.swift | 6 +- .../libgir2swift/models/Gir+KnownTypes.swift | 1 + .../models/gir elements/GirFunction.swift | 4 - 6 files changed, 122 insertions(+), 207 deletions(-) diff --git a/Sources/libgir2swift/emmiting/CodeBuilder.swift b/Sources/libgir2swift/emmiting/CodeBuilder.swift index 2080294..0f564ca 100644 --- a/Sources/libgir2swift/emmiting/CodeBuilder.swift +++ b/Sources/libgir2swift/emmiting/CodeBuilder.swift @@ -9,7 +9,7 @@ import Foundation @_functionBuilder class CodeBuilder { - private static let ignoringEspace: String = "<%IGNORED%>" + static let ignoringEspace: String = "<%IGNORED%>" static func buildBlock( _ segments: String...) -> String { segments.filter { $0 != CodeBuilder.ignoringEspace } .joined(separator: "\n") @@ -40,10 +40,10 @@ class Code { } static func loop(over items: [T], @CodeBuilder builder: (T)->String) -> String { - items.map(builder).joined(separator: "\n") + !items.isEmpty ? items.map(builder).joined(separator: "\n") : CodeBuilder.ignoringEspace } static func loopEnumerated(over items: [T], @CodeBuilder builder: (Int, T)->String) -> String { - items.enumerated().map(builder).joined(separator: "\n") + !items.isEmpty ? items.enumerated().map(builder).joined(separator: "\n") : CodeBuilder.ignoringEspace } } diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index 17b6776..b0d1566 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -7,122 +7,140 @@ import Foundation -func signalSanityCheck(_ signal: GIR.Signal) -> String? { +func signalSanityCheck(_ signal: GIR.Signal) -> [String] { - var errors: String? + var errors = [String]() if !signal.args.allSatisfy({ $0.ownershipTransfer == .none }) { - errors = (errors ?? "") + " (1) argument with owner transfership is not allowed;" + errors.append("(1) argument with owner transfership is not allowed") } if !signal.args.allSatisfy({ $0.direction == .in }) { - errors = (errors ?? "") + " (2) argument out or inout direction is not allowed;" + errors.append("(2) argument out or inout direction is not allowed") } if !signal.args.allSatisfy({ $0.typeRef.type.name != "Void" }) { - errors = (errors ?? "") + " (3) Void argument is not yet supported;" + errors.append("(3) Void argument is not yet supported") } if !signal.args.allSatisfy({ $0.typeRef.type.name != "gpointer" }) { - errors = (errors ?? "") + " (3.1) gpointer argument is not yet supported;" + errors.append("(4) gpointer argument is not yet supported") } if !signal.args.allSatisfy({ !($0.knownType is GIR.Alias) }) || (signal.returns.knownType is GIR.Alias) { - errors = (errors ?? "") + " (4) Alias argument or return is not yet supported;" + errors.append("(5) Alias argument or return is not yet supported") } if signal.returns.isOptional { - errors = (errors ?? "") + " (5) argument or return optional is not allowed;" + errors.append("(6) argument or return optional is not allowed") } if !signal.args.allSatisfy({ !$0.isArray }) || signal.returns.isArray { - errors = (errors ?? "") + " (6) argument or return array is not allowed;" + errors.append("(7) argument or return array is not allowed") } if signal.returns.isNullable == true { - errors = (errors ?? "") + " (7) argument or return nullability is not allowed;" + errors.append("(8) argument or return nullability is not allowed") } if signal.returns.knownType is GIR.Record { - errors = (errors ?? "") + " (8) Record return is not yet supported;" + errors.append("(9) Record return is not yet supported") } - return errors.flatMap { "// Warning: signal \(signal.name) is ignored because of" + $0 } + return errors } func buildSignalExtension(for record: GIR.Record) -> String { if record.signals.isEmpty { - return "// MARK: no \(record.name.swift) signals" + return "// MARK: \(record.name.swift) has no signals" } return Code.block(indentation: nil) { "// MARK: Signals of \(record.name.swift)" - + "public extension \(record.protocolName) {" Code.block { - Code.loop(over: record.signals.compactMap({ signalSanityCheck($0) })) { error in - "\(error)" + Code.loop(over: record.signals.filter( {!signalSanityCheck($0).isEmpty} )) { signal in + buildUnavailable(signal: signal) + } + Code.loop(over: record.signals.filter( {signalSanityCheck($0).isEmpty } )) { signal in + buildAvailableSignal(record: record, signal: signal) + } + if let notifySignal = GIR.knownRecords["Object"]?.signals.first(where: { $0.name == "notify"}) { + Code.loop(over: record.properties) { property in + buildSignalForProperty(record: record, property: property, notify: notifySignal) + } + } else { + "// Signals of properites were not generated due to unavailability of GObject during generation time" } + } + "}\n\n" + } +} - "public extension \(record.protocolName) {" - Code.block { - Code.loop(over: record.signals.filter { signalSanityCheck($0) == nil }) { signal in - - commentCode(signal) - "/// - Note: This function represents signal `\(signal.name)`" - "/// - Parameter flags: Flags" - - let returnComment = gtkDoc2SwiftDoc(signal.returns.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") - if !returnComment.isEmpty { - "/// - Parameter handler: \(returnComment)" - } +private func buildSignalForProperty(record: GIR.Record, property: GIR.Property, notify: GIR.Signal) -> String { + let propertyNotify = GIR.Signal( + name: notify.name + "::" + property.name, + cname: notify.cname, + returns: notify.returns, + args: notify.args, + comment: notify.comment, + introspectable: notify.introspectable, + deprecated: notify.deprecated, + throwsAnError: notify.throwsError + ) + + return buildAvailableSignal(record: record, signal: propertyNotify) +} - "/// - Parameter unownedSelf: Reference to instance of self" - Code.loop(over: signal.args) { argument in - let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") - "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" - } - Code.line { - "public func _on\(signal.name.camelSignal.capitalised)(" - "flags: ConnectFlags = ConnectFlags(0), " - "handler: " - handlerType(record: record, signal: signal) - " ) -> Int {" - } - Code.block { - "typealias SwiftHandler = \(signalClosureHolderDecl(record: record, signal: signal))" - Code.line { - "let cCallback: " - cCallbackDecl(record: record, signal: signal) - " = { " - cCallbackArgumentsDecl(record: record, signal: signal) - " in" - } - Code.block { - "let holder = Unmanaged.fromOpaque(userData).takeUnretainedValue()" - "let output = holder.\(generaceCCallbackCall(record: record, signal: signal))" - generateReturnStatement(record: record, signal: signal) - } - "}" - "return \(record is GIR.Interface ? "GLibObject.ObjectRef(raw: ptr)." : "" )signalConnectData(" - Code.block { - #"detailedSignal: "\#(signal.name)", "# - "cHandler: unsafeBitCast(cCallback, to: GCallback.self), " - "data: Unmanaged.passRetained(SwiftHandler(handler)).toOpaque(), " - "destroyData: { userData, _ in UnsafeRawPointer(userData).flatMap(Unmanaged.fromOpaque(_:))?.release() }," - "connectFlags: flags" - } - ")" - } - "}" - "\n" - } +@CodeBuilder +private func buildAvailableSignal(record: GIR.Record, signal: GIR.Signal) -> String { + addDocumentation(signal: signal) + + "@discardableResult" + Code.line { + "public func on\(signal.name.replacingOccurrences(of: "::", with: "_").camelSignal.capitalised)(" + "flags: ConnectFlags = ConnectFlags(0), " + "handler: " + handlerType(record: record, signal: signal) + " ) -> Int {" + } + Code.block { + "typealias SwiftHandler = \(signalClosureHolderDecl(record: record, signal: signal))" + Code.line { + "let cCallback: " + cCallbackDecl(record: record, signal: signal) + " = { " + cCallbackArgumentsDecl(record: record, signal: signal) + " in" + } + Code.block { + "let holder = Unmanaged.fromOpaque(userData).takeUnretainedValue()" + "let output\(signal.returns.typeRef.type.name == "Void" ? ": Void" : "") = holder.\(generaceCCallbackCall(record: record, signal: signal))" + generateReturnStatement(record: record, signal: signal) } "}" - "\n\n" + "return \(record is GIR.Interface ? "GLibObject.ObjectRef(raw: ptr)." : "" )signalConnectData(" + Code.block { + #"detailedSignal: "\#(signal.name)", "# + "cHandler: unsafeBitCast(cCallback, to: GCallback.self), " + "data: Unmanaged.passRetained(SwiftHandler(handler)).toOpaque(), " + "destroyData: { userData, _ in UnsafeRawPointer(userData).flatMap(Unmanaged.fromOpaque(_:))?.release() }," + "connectFlags: flags" + } + ")" } + "}\n" +} + +@CodeBuilder +private func buildUnavailable(signal: GIR.Signal) -> String { + addDocumentation(signal: signal) + "/// - Warning: Wrapper of this signal could not be generated because it contains unimplemented features: { \( signalSanityCheck(signal).joined(separator: ", ") ) }" + "/// - Note: Use this string for `signalConnectData` method" + #"public static var on\#(signal.name.camelSignal.capitalised): String { "\#(signal.name)" }"# } @CodeBuilder @@ -136,32 +154,34 @@ private func handlerType(record: GIR.Record, signal: GIR.Signal) -> String { } private func signalClosureHolderDecl(record: GIR.Record, signal: GIR.Signal) -> String { - let holderType: String - switch signal.args.count { - case 0: - holderType = "Tmp__ClosureHolder" - case 1: - holderType = "Tmp__DualClosureHolder" - case 2: - holderType = "Tmp__Closure3Holder" - case 3: - holderType = "Tmp__Closure4Holder" - case 4: - holderType = "Tmp__Closure5Holder" - case 5: - holderType = "Tmp__Closure6Holder" - case 6: - holderType = "Tmp__Closure7Holder" - default: + if signal.args.count > 6 { fatalError("Argument count \(signal.args.count) exceeds number of allowed arguments (6)") } - - return holderType - + "<" + record.structName + ", " - + signal.args.map { $0.swiftIdiomaticType() }.joined(separator: ", ") - + (signal.args.isEmpty ? "" : ", ") - + signal.returns.swiftIdiomaticType() - + ">" + return Code.line { + "GLib.ClosureHolder" + (signal.args.count > 0 ? "\(signal.args.count + 1)" : "") + "<" + record.structName + ", " + signal.args.map { $0.swiftIdiomaticType() }.joined(separator: ", ") + (signal.args.isEmpty ? "" : ", ") + signal.returns.swiftIdiomaticType() + ">" + } +} + +@CodeBuilder +private func addDocumentation(signal: GIR.Signal) -> String { + { str -> String in str.isEmpty ? CodeBuilder.ignoringEspace : str}(commentCode(signal)) + "/// - Note: Representation of signal named `\(signal.name)`" + "/// - Parameter flags: Flags" + let returnComment = gtkDoc2SwiftDoc(signal.returns.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + if !returnComment.isEmpty { + "/// - Parameter handler: \(returnComment)" + } + + "/// - Parameter unownedSelf: Reference to instance of self" + Code.loop(over: signal.args) { argument in + let comment = gtkDoc2SwiftDoc(argument.comment, linePrefix: "").replacingOccurrences(of: "\n", with: " ") + "/// - Parameter \(argument.prefixedArgumentName): \(comment.isEmpty ? "none" : comment)" + } } @CodeBuilder diff --git a/Sources/libgir2swift/emmiting/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift index 78a1096..76313bf 100644 --- a/Sources/libgir2swift/emmiting/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -12,69 +12,6 @@ public extension GIR { var boilerPlate: String { """ -final class Tmp__ClosureHolder { - public let call: (S) -> T - - @inlinable public init(_ closure: @escaping (S) -> T) { - self.call = closure - } -} - -final class Tmp__DualClosureHolder { - - public let call: (S, T) -> U - - public init(_ closure: @escaping (S, T) -> U) { - self.call = closure - } -} - -final class Tmp__Closure3Holder { - - public let call: (S, T, U) -> V - - public init(_ closure: @escaping (S, T, U) -> V) { - self.call = closure - } -} - -final class Tmp__Closure4Holder { - - public let call: (S, T, U, V) -> W - - public init(_ closure: @escaping (S, T, U, V) -> W) { - self.call = closure - } -} - -final class Tmp__Closure5Holder { - - public let call: (S, T, U, V, W) -> X - - public init(_ closure: @escaping (S, T, U, V, W) -> X) { - self.call = closure - } -} - -final class Tmp__Closure6Holder { - - public let call: (S, T, U, V, W, X) -> Y - - public init(_ closure: @escaping (S, T, U, V, W, X) -> Y) { - self.call = closure - } -} - -final class Tmp__Closure7Holder { - - public let call: (S, T, U, V, W, X, Y) -> Z - - public init(_ closure: @escaping (S, T, U, V, W, X, Y) -> Z) { - self.call = closure - } -} - - extension gboolean { private init(_ b: Bool) { self = b ? gboolean(1) : gboolean(0) } } @@ -1348,16 +1285,13 @@ public func recordClassCode(_ e: GIR.Record, parent: String, indentation: String let parentType = e.parentType let hasParent = parentType != nil || !parent.isEmpty let scode = signalNameCode(indentation: indentation) - let ncode = signalNameCode(indentation: indentation, prefixes: ("notify", "notify::")) let ccode = convenienceConstructorCode(typeRef, indentation: indentation, override: "override ", hasParent: hasParent)(e) let fcode = convenienceConstructorCode(typeRef, indentation: indentation, factory: true)(e) let constructors = e.constructors.filter { $0.isConstructorOf(e) && !$0.isBareFactory } let allmethods = e.allMethods let factories = allmethods.filter { $0.isFactoryOf(e) } let properties = e.allProperties - let signals = e.allSignals let noProperties = properties.isEmpty - let noSignals = noProperties && signals.isEmpty let retain: String let retainPtr: String if let ref = e.ref, ref.args.count == 1 { @@ -1592,45 +1526,7 @@ public func recordClassCode(_ e: GIR.Record, parent: String, indentation: String "@inlinable func set(property: \(className)PropertyName, value v: GLibObject.Value) {\n" + doubleIndentation + "g_object_set_property(ptr.assumingMemoryBound(to: GObject.self), property.rawValue, v.value_ptr)\n" + indentation + "}\n}\n\n")) - let legacySignals: String - var legacySignalExt: String = "" - if noSignals { - legacySignals = "// MARK: no \(className) signals\n" - } else { - legacySignals = "public enum \(className)SignalName: String, SignalNameProtocol {\n" + - signals.map(scode).joined(separator: "\n") + "\n" + - properties.map(ncode).joined(separator: "\n") + "\n" - - legacySignalExt = ("}\n\npublic extension \(protocolName) {\n" + indentation + - "/// Connect a `\(className)SignalName` signal to a given signal handler.\n" + indentation + - "/// - Parameter signal: the signal to connect\n" + indentation + - "/// - Parameter flags: signal connection flags\n" + indentation + - "/// - Parameter handler: signal handler to use\n" + indentation + - "/// - Returns: positive handler ID, or a value less than or equal to `0` in case of an error\n" + indentation + - "@inlinable @discardableResult func connect(signal kind: \(className)SignalName, flags f: ConnectFlags = ConnectFlags(0), to handler: @escaping GLibObject.SignalHandler) -> Int {\n" + doubleIndentation + - "func _connect(signal name: UnsafePointer, flags: ConnectFlags, data: GLibObject.SignalHandlerClosureHolder, handler: @convention(c) @escaping (gpointer, gpointer) -> Void) -> Int {\n" + tripleIndentation + - "let holder = UnsafeMutableRawPointer(Unmanaged.passRetained(data).toOpaque())\n" + tripleIndentation + - "let callback = unsafeBitCast(handler, to: GLibObject.Callback.self)\n" + tripleIndentation + - "let rv = GLibObject.ObjectRef(raw: ptr).signalConnectData(detailedSignal: name, cHandler: callback, data: holder, destroyData: {\n" + tripleIndentation + indentation + - "if let swift = UnsafeRawPointer($0) {\n" + tripleIndentation + doubleIndentation + - "let holder = Unmanaged.fromOpaque(swift)\n" + tripleIndentation + doubleIndentation + - "holder.release()\n" + tripleIndentation + indentation + - "}\n" + tripleIndentation + indentation + - "let _ = $1\n" + tripleIndentation + - "}, connectFlags: flags)\n" + tripleIndentation + - "return rv\n" + doubleIndentation + - "}\n" + doubleIndentation + - "let rv = _connect(signal: kind.name, flags: f, data: ClosureHolder(handler)) {\n" + tripleIndentation + - "let ptr = UnsafeRawPointer($1)\n" + tripleIndentation + - "let holder = Unmanaged.fromOpaque(ptr).takeUnretainedValue()\n" + tripleIndentation + - "holder.call(())\n" + doubleIndentation + - "}\n" + doubleIndentation + - "return rv\n" + indentation + - "}\n" + - "}\n\n") - } - let code = code1 + code2 + code3 + legacySignals + legacySignalExt - return code + buildSignalExtension(for: e) + return code1 + code2 + code3 + buildSignalExtension(for: e) } // MARK: - Swift code for Record/Class methods diff --git a/Sources/libgir2swift/models/Gir+KnowTypeSets.swift b/Sources/libgir2swift/models/Gir+KnowTypeSets.swift index b9686bd..a3e390f 100644 --- a/Sources/libgir2swift/models/Gir+KnowTypeSets.swift +++ b/Sources/libgir2swift/models/Gir+KnowTypeSets.swift @@ -14,8 +14,10 @@ public extension GIR { .map { ($0, constCharPtr) } static private let rawStrings = [ utf8Ref, constUTF8Ref, fileRef, constFileRef ] .map { ($0, stringRef) } - static private let ints = [cintType, clongType, cshortType, cuintType, culongType, cushortType, gintType, gintAlias, glongType, glongAlias, gshortType, gshortAlias, guintType, guintAlias, gulongType, gulongAlias, gushortType, gushortAlias, gsizeType] + static private let ints = [cintType, clongType, cshortType, gintType, gintAlias, glongType, glongAlias, gshortType, gshortAlias, gsizeType] .map { (TypeReference(type: $0), intRef) } + static private let uints = [cuintType, culongType, cushortType, guintType, guintAlias, gulongType, gulongAlias, gushortType, gushortAlias] + .map { (TypeReference(type: $0), uintRef) } static private let floats = [floatType, doubleType, gfloatType, gdoubleType] .map { (TypeReference(type: $0), doubleRef) } static private let bools = [gbooleanType, cboolType].map { (TypeReference(type: $0), boolRef) } @@ -31,7 +33,7 @@ public extension GIR { static let swiftFundamentalReplacements = Dictionary(uniqueKeysWithValues: gpointerPointers) /// Idiomatic swift type replacements for return types - static let swiftReturnTypeReplacements = Dictionary(uniqueKeysWithValues: strings + rawStrings + ints + floats + bools + gpointers + gpointerPointers) + static let swiftReturnTypeReplacements = Dictionary(uniqueKeysWithValues: strings + rawStrings + ints + uints + floats + bools + gpointers + gpointerPointers) /// Idiomatic swift type replacements for parameters static let swiftParameterTypeReplacements = Dictionary(uniqueKeysWithValues: ints + floats + bools + rawCharPtrs + gpointers + gpointerPointers) diff --git a/Sources/libgir2swift/models/Gir+KnownTypes.swift b/Sources/libgir2swift/models/Gir+KnownTypes.swift index 3be98c6..d3c9cd6 100644 --- a/Sources/libgir2swift/models/Gir+KnownTypes.swift +++ b/Sources/libgir2swift/models/Gir+KnownTypes.swift @@ -27,6 +27,7 @@ public extension GIR { static let uint64Type = GIRType(name: "UInt64", ctype: "u_int64_t") static let swiftNumericTypes: Set = [floatType, doubleType, float80Type, intType, uintType, int8Type, int16Type, int32Type, int64Type, uint8Type, uint16Type, uint32Type, uint64Type] static let intRef = TypeReference(type: intType) + static let uintRef = TypeReference(type: uintType) static let doubleRef = TypeReference(type: doubleType) static let Bool = "Bool" diff --git a/Sources/libgir2swift/models/gir elements/GirFunction.swift b/Sources/libgir2swift/models/gir elements/GirFunction.swift index 3a4e873..f86f92d 100644 --- a/Sources/libgir2swift/models/gir elements/GirFunction.swift +++ b/Sources/libgir2swift/models/gir elements/GirFunction.swift @@ -15,10 +15,6 @@ extension GIR { /// a function is the same as a method public class Function: Method { public override var kind: String { return "Function" } - - public override init(node: XMLElement, at index: Int) { - super.init(node: node, at: index) - } } } From d300e8a218b12b2d18f44652d3fbdc029357ac51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= <42004728+mikolasstuchlik@users.noreply.github.com> Date: Sat, 21 Nov 2020 10:31:06 +0100 Subject: [PATCH 12/18] [Signal] Add support for unsigned integers --- .../libgir2swift/emmiting/emmit-signals.swift | 36 +++++++++---------- .../emmiting/girtypes+swift.swift | 14 ++++++++ .../models/Gir+KnowTypeSets.swift | 13 +++++-- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index b0d1566..64b059c 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -101,7 +101,7 @@ private func buildAvailableSignal(record: GIR.Record, signal: GIR.Signal) -> Str "@discardableResult" Code.line { - "public func on\(signal.name.replacingOccurrences(of: "::", with: "_").camelSignal.capitalised)(" + "func on\(signal.name.replacingOccurrences(of: "::", with: "_").camelSignal.capitalised)(" "flags: ConnectFlags = ConnectFlags(0), " "handler: " handlerType(record: record, signal: signal) @@ -140,7 +140,7 @@ private func buildUnavailable(signal: GIR.Signal) -> String { addDocumentation(signal: signal) "/// - Warning: Wrapper of this signal could not be generated because it contains unimplemented features: { \( signalSanityCheck(signal).joined(separator: ", ") ) }" "/// - Note: Use this string for `signalConnectData` method" - #"public static var on\#(signal.name.camelSignal.capitalised): String { "\#(signal.name)" }"# + #"static var on\#(signal.name.camelSignal.capitalised): String { "\#(signal.name)" }"# } @CodeBuilder @@ -219,14 +219,14 @@ private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> St private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> String { switch signal.returns.knownType { case is GIR.Record: - return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftSignalRef))" case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: return "return output.rawValue" case is GIR.Enumeration: return "return output.rawValue" - case nil where signal.returns.swiftReturnRef == GIR.stringRef && signal.returns.ownershipTransfer == .full: + case nil where signal.returns.swiftSignalRef == GIR.stringRef && signal.returns.ownershipTransfer == .full: return Code.block { "let length = output.utf8CString.count" "let buffer = UnsafeMutablePointer.allocate(capacity: length)" @@ -234,7 +234,7 @@ private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> "return buffer" } default: // Treat as fundamental (if not a fundamental, report error) - return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftReturnRef))" + return "return \(signal.returns.typeRef.cast(expression: "output", from: signal.returns.swiftSignalRef))" } } @@ -243,28 +243,28 @@ private extension GIR.Argument { func swiftIdiomaticType() -> String { switch knownType { case is GIR.Record: - return typeRef.type.swiftName + "Ref" + (isNullable ? "?" : "") + return typeRef.type.swiftName + "Ref" + ((isNullable || isOptional) ? "?" : "") case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - return self.argumentTypeName + (isNullable ? "?" : "") + return self.argumentTypeName + ((isNullable || isOptional) ? "?" : "") case is GIR.Enumeration: - return self.argumentTypeName + (isNullable ? "?" : "") + return self.argumentTypeName + ((isNullable || isOptional) ? "?" : "") default: // Treat as fundamental (if not a fundamental, report error) - return self.swiftReturnRef.fullSwiftTypeName + (isNullable ? "?" : "") + return self.swiftSignalRef.fullSwiftTypeName + ((isNullable || isOptional) ? "?" : "") } } func swiftCCompatibleType() -> String { switch knownType { case is GIR.Record: - return GIR.gpointerType.typeName + (isNullable ? "?" : "") + return GIR.gpointerType.typeName + ((isNullable || isOptional) ? "?" : "") case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - return GIR.uint32Type.typeName + (isNullable ? "?" : "") + return GIR.uint32Type.typeName + ((isNullable || isOptional) ? "?" : "") case is GIR.Enumeration: - return GIR.uint32Type.typeName + (isNullable ? "?" : "") + return GIR.uint32Type.typeName + ((isNullable || isOptional) ? "?" : "") default: // Treat as fundamental (if not a fundamental, report error) return self.callbackArgumentTypeName } @@ -273,26 +273,26 @@ private extension GIR.Argument { func swiftSignalArgumentConversion(at index: Int) -> String { switch knownType { case is GIR.Record: - if isNullable { + if (isNullable || isOptional) { return "arg\(index).flatMap(\(typeRef.type.swiftName)Ref.init(raw:))" } return typeRef.type.swiftName + "Ref" + "(raw: arg\(index))" case is GIR.Alias: // use containedTypes return "" case is GIR.Bitfield: - if isNullable { + if (isNullable || isOptional) { return "arg\(index).flatMap(\(self.argumentTypeName).init(_:))" } return self.argumentTypeName + "(arg\(index))" case is GIR.Enumeration: - if isNullable { + if (isNullable || isOptional) { return "arg\(index).flatMap(\(self.argumentTypeName).init(_:))" } return self.argumentTypeName + "(arg\(index))" - case nil where swiftReturnRef == GIR.stringRef: - return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) + (isNullable ? "" : "!") + case nil where swiftSignalRef == GIR.stringRef: + return swiftSignalRef.cast(expression: "arg\(index)", from: typeRef) + ((isNullable || isOptional) ? "" : "!") default: // Treat as fundamental (if not a fundamental, report error) - return swiftReturnRef.cast(expression: "arg\(index)", from: typeRef) + return swiftSignalRef.cast(expression: "arg\(index)", from: typeRef) } } } diff --git a/Sources/libgir2swift/emmiting/girtypes+swift.swift b/Sources/libgir2swift/emmiting/girtypes+swift.swift index f4bff7b..5f47d89 100644 --- a/Sources/libgir2swift/emmiting/girtypes+swift.swift +++ b/Sources/libgir2swift/emmiting/girtypes+swift.swift @@ -63,6 +63,20 @@ public extension GIR.CType { return replacement } + /// Type reference to an idiomatic Swift type used for a Swift signals + @inlinable + var swiftSignalRef: TypeReference { + guard var replacement = GIR.swiftSignalTypeReplacements[typeRef] else { + if typeRef.indirectionLevel == 1 && typeRef.type.typeName.hasSuffix("char") && !typeRef.type.typeName.hasSuffix("unichar") { + return GIR.stringRef + } + return typeRef + } + replacement.isConst = typeRef.isConst + replacement.isOptional = typeRef.isOptional + return replacement + } + /// Return a Swift template declaration for a known record, /// or `nil` otherwise @inlinable diff --git a/Sources/libgir2swift/models/Gir+KnowTypeSets.swift b/Sources/libgir2swift/models/Gir+KnowTypeSets.swift index a3e390f..687a009 100644 --- a/Sources/libgir2swift/models/Gir+KnowTypeSets.swift +++ b/Sources/libgir2swift/models/Gir+KnowTypeSets.swift @@ -14,10 +14,16 @@ public extension GIR { .map { ($0, constCharPtr) } static private let rawStrings = [ utf8Ref, constUTF8Ref, fileRef, constFileRef ] .map { ($0, stringRef) } - static private let ints = [cintType, clongType, cshortType, gintType, gintAlias, glongType, glongAlias, gshortType, gshortAlias, gsizeType] + static private let ints = [cintType, clongType, cshortType, cuintType, culongType, cushortType, gintType, gintAlias, glongType, glongAlias, gshortType, gshortAlias, guintType, guintAlias, gulongType, gulongAlias, gushortType, gushortAlias, gsizeType] .map { (TypeReference(type: $0), intRef) } + + // Ints are introduced to fix incorrect casting in signals. static private let uints = [cuintType, culongType, cushortType, guintType, guintAlias, gulongType, gulongAlias, gushortType, gushortAlias] .map { (TypeReference(type: $0), uintRef) } + static private let sints = [cintType, clongType, cshortType, gintType, gintAlias, glongType, glongAlias, gshortType, gshortAlias, gsizeType] + .map { (TypeReference(type: $0), intRef) } + + static private let floats = [floatType, doubleType, gfloatType, gdoubleType] .map { (TypeReference(type: $0), doubleRef) } static private let bools = [gbooleanType, cboolType].map { (TypeReference(type: $0), boolRef) } @@ -33,7 +39,10 @@ public extension GIR { static let swiftFundamentalReplacements = Dictionary(uniqueKeysWithValues: gpointerPointers) /// Idiomatic swift type replacements for return types - static let swiftReturnTypeReplacements = Dictionary(uniqueKeysWithValues: strings + rawStrings + ints + uints + floats + bools + gpointers + gpointerPointers) + static let swiftReturnTypeReplacements = Dictionary(uniqueKeysWithValues: strings + rawStrings + ints + floats + bools + gpointers + gpointerPointers) + + /// Idiomatic swift type replacements for signals + static let swiftSignalTypeReplacements = Dictionary(uniqueKeysWithValues: strings + rawStrings + sints + uints + floats + bools + gpointers + gpointerPointers) /// Idiomatic swift type replacements for parameters static let swiftParameterTypeReplacements = Dictionary(uniqueKeysWithValues: ints + floats + bools + rawCharPtrs + gpointers + gpointerPointers) From 3239627c70ba972d771488da9baf812b5c00fe70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Sun, 22 Nov 2020 19:37:28 +0100 Subject: [PATCH 13/18] [Build] Driver support for pkg-config processing --- gir2swift-generation-driver.sh | 158 +++++++++++++++++++++++++-------- 1 file changed, 123 insertions(+), 35 deletions(-) diff --git a/gir2swift-generation-driver.sh b/gir2swift-generation-driver.sh index 7ba8b02..4957b78 100755 --- a/gir2swift-generation-driver.sh +++ b/gir2swift-generation-driver.sh @@ -8,21 +8,38 @@ function is_processable_arg-path { local PACKAGE=`swift package dump-package` local GENERATED=`jq -r '.dependencies | .[] | select(.name == "gir2swift") | .name' <<< $PACKAGE` + local MANIFEST="gir2swift-manifest.sh" - cd $CALLER - - if [ $GENERATED ] + if [[ $GENERATED && -f "$MANIFEST" ]] then + cd $CALLER return 0 - else + else + cd $CALLER return 1 fi } -function gir_path { - # TODO: Add code to determine path +function gir_path_arg-gir-names { + local GIR_NAMES=$1 + + local GIR_FILES=`for NAME in ${GIR_NAMES}; do echo -n "${NAME}.gir "; done` + + for DIR in "/usr/local/share/gir-1.0" "/usr/share/gir-1.0" ; do + CURRENT=$DIR + for GIR in $GIR_FILES; do + if ! [ -f "${DIR}/${GIR}" ] ; then + unset CURRENT + fi + done + + if ! [ -z ${CURRENT} ] ; then + echo "$CURRENT" + break + fi + done - echo "/usr/share/gir-1.0" + exit 1 } function gir_2_swift_executable_arg-deps { @@ -99,32 +116,103 @@ function package_name_arg-path { # Building process -TOP_LEVEL_PACKAGE_PATH=$1 -OPTIONAL_ALTERNATIVE_G2S_PATH=$2 - -cd $TOP_LEVEL_PACKAGE_PATH -DEPENDENCIES=`swift package show-dependencies --format json` -GIR_PATH=$(gir_path) -PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") -if [ -z "$OPTIONAL_ALTERNATIVE_G2S_PATH" ] -then - G2S_PATH=$(gir_2_swift_executable_arg-deps "$DEPENDENCIES") -else - G2S_PATH=$OPTIONAL_ALTERNATIVE_G2S_PATH - echo "Using custom gir2swift executable at: $G2S_PATH" -fi - -for PACKAGE in $PROCESSABLE -do - PACKAGE_NAME=$(package_name_arg-path "$PACKAGE") - PACKAGE_DEPS=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$PACKAGE_NAME") - GIR_NAMES=$(get_gir_names_arg-packages "$PACKAGE_DEPS") - bash -c "$PACKAGE/gir2swift-manifest.sh generate \"$PACKAGE\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " -done - -if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") -then - GIR_NAMES=$(get_gir_names_arg-packages "$PROCESSABLE") - bash -c "$TOP_LEVEL_PACKAGE_PATH/gir2swift-manifest.sh generate \"$TOP_LEVEL_PACKAGE_PATH\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " -fi +COMMAND=$1 + +case $COMMAND in +generate) + TOP_LEVEL_PACKAGE_PATH=$2 + OPTIONAL_ALTERNATIVE_G2S_PATH=$3 + + cd $TOP_LEVEL_PACKAGE_PATH + DEPENDENCIES=`swift package show-dependencies --format json` + PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") + + ALL_PROCESSABLE="$PROCESSABLE" + if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") + then + ALL_PROCESSABLE="$TOP_LEVEL_PACKAGE_PATH $PROCESSABLE" + fi + + # Search for path that contains all GIR files + ALL_GIR_NAMES=$(get_gir_names_arg-packages "$ALL_PROCESSABLE") + GIR_PATH=$(gir_path_arg-gir-names "$ALL_GIR_NAMES") + echo "Girs located at $GIR_PATH" + + # Determine path to gir2swift executable + if [ -z "$OPTIONAL_ALTERNATIVE_G2S_PATH" ] + then + echo "Building gir2swift" + G2S_PATH=$(gir_2_swift_executable_arg-deps "$DEPENDENCIES") + else + G2S_PATH=$OPTIONAL_ALTERNATIVE_G2S_PATH + echo "Using custom gir2swift executable at: $G2S_PATH" + fi + + echo "Generating" + for PACKAGE in $PROCESSABLE + do + PACKAGE_NAME=$(package_name_arg-path "$PACKAGE") + PACKAGE_DEPS=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$PACKAGE_NAME") + GIR_NAMES=$(get_gir_names_arg-packages "$PACKAGE_DEPS") + bash -c "$PACKAGE/gir2swift-manifest.sh generate \"$PACKAGE\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " + done + + if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") + then + GIR_NAMES=$(get_gir_names_arg-packages "$PROCESSABLE") + bash -c "$TOP_LEVEL_PACKAGE_PATH/gir2swift-manifest.sh generate \"$TOP_LEVEL_PACKAGE_PATH\" \"$G2S_PATH\" \"$GIR_NAMES\" \"$GIR_PATH\" " + fi + + ;; +remove-generated) + TOP_LEVEL_PACKAGE_PATH=$2 + + cd $TOP_LEVEL_PACKAGE_PATH + DEPENDENCIES=`swift package show-dependencies --format json` + PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") + + ALL_PROCESSABLE="$PROCESSABLE" + if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") + then + ALL_PROCESSABLE="$TOP_LEVEL_PACKAGE_PATH $PROCESSABLE" + fi + + for PACKAGE in $ALL_PROCESSABLE + do + cd $PACKAGE + PACK_NAME=$(package_name_arg-path $PACKAGE) + bash -c "rm Sources/$PACK_NAME/*-*.swift" + done + ;; + +c-flags) + TOP_LEVEL_PACKAGE_PATH=$2 + OPTIONAL_ALTERNATIVE_G2S_PATH=$3 + + cd $TOP_LEVEL_PACKAGE_PATH + DEPENDENCIES=`swift package show-dependencies --format json` + PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") + + ALL_PROCESSABLE="$PROCESSABLE" + if $(is_processable_arg-path "$TOP_LEVEL_PACKAGE_PATH") + then + ALL_PROCESSABLE="$TOP_LEVEL_PACKAGE_PATH $PROCESSABLE" + fi + + FLAGS="" + for PACKAGE in $ALL_PROCESSABLE + do + cd $PACKAGE + FLAGS="$FLAGS $(package_pkg_config_arguments)" + done + echo `pkg-config --cflags $FLAGS` + ;; +*) + echo "Gir 2 swift code generation tool" + echo "Commands:" + echo " generate [path to root package] [optional path to gir2swift executable]" + echo " remove-generated [path to root package]" + echo " c-flags [path to root package]" + ;; +esac \ No newline at end of file From d500a7731086a8c39becf908524eace7dacc2d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Wed, 23 Dec 2020 21:32:45 +0100 Subject: [PATCH 14/18] [General] Changes to generated code, read more ... Generates supporting code for GWeak Generate code to automatically consume floating references upon init Adds support for ommiting private types Ommits class generation for metatypes --- .vscode/launch.json | 16 +++++++ .vscode/settings.json | 3 ++ .vscode/tasks.json | 16 +++++++ Sources/gir2swift/main.swift | 25 ++++++++--- .../libgir2swift/emmiting/emmit-class.swift | 24 ++++++++++ .../libgir2swift/emmiting/emmit-signals.swift | 7 --- Sources/libgir2swift/emmiting/gir+swift.swift | 45 +++++++++++++++---- .../models/gir elements/GirRecord.swift | 21 +++++++-- run-gir2swift.sh | 9 ++++ 9 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 Sources/libgir2swift/emmiting/emmit-class.swift create mode 100755 run-gir2swift.sh diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8ccebc4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/.build/debug/${workspaceFolderBasename}", + "cwd": "${workspaceFolder}/../test-SwiftGtk", + "args": ["-o", "Sources/Gtk", "-s", "-m", "Gtk-3.0.module", "-p", "/usr/share/gir-1.0/Atk-1.0.gir", "-p", "/usr/share/gir-1.0/cairo-1.0.gir", "-p", "/usr/share/gir-1.0/Gdk-3.0.gir", "-p", "/usr/share/gir-1.0/GdkPixbuf-2.0.gir", "-p", "/usr/share/gir-1.0/Gio-2.0.gir", "-p", "/usr/share/gir-1.0/GLib-2.0.gir", "-p", "/usr/share/gir-1.0/GModule-2.0.gir", "-p", "/usr/share/gir-1.0/GObject-2.0.gir", "-p", "/usr/share/gir-1.0/Pango-1.0.gir", "-p", "/usr/share/gir-1.0/PangoCairo-1.0.gir", "/usr/share/gir-1.0/Gtk-3.0.gir"] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..04ebd33 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.wordWrap": "on" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..99c9fd2 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,16 @@ +// .vscode/tasks.json + +{ + "version" : "2.0.0", + "tasks": [ + { + "label": "Swift Build", + "type": "shell", + "command": "swift build -Xswiftc -Xfrontend -Xswiftc -serialize-debugging-options", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/Sources/gir2swift/main.swift b/Sources/gir2swift/main.swift index 32d38f2..bc84a8b 100644 --- a/Sources/gir2swift/main.swift +++ b/Sources/gir2swift/main.swift @@ -19,7 +19,7 @@ var verbose = false /// Print command line usage and exit func usage() -> Never { - fputs("Usage: \(CommandLine.arguments[0]) [-v][-s][-m module_boilerplate.swift][-o output_directory]{-p file.gir}[file.gir ...]\n", stderr) + fputs("Usage: \(CommandLine.arguments[0]) [-v][-s][-a][-m module_boilerplate.swift][-o output_directory]{-p file.gir}[file.gir ...]\n", stderr) exit(EXIT_FAILURE) } @@ -45,7 +45,7 @@ func preload_gir(file: String) { /// process a GIR file -func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirectory: String? = nil, split singleFilePerClass: Bool = false) { +func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirectory: String? = nil, split singleFilePerClass: Bool = false, generateAll: Bool = false) { let base = file.baseName let node = base.stringByRemoving(suffix: ".gir") ?? base let wlfile = node + ".whitelist" @@ -76,6 +76,7 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect GIR.namespaceReplacements[key] = value } } + load_gir(file) { gir in processSpecialCases(gir, forFile: node) let blacklist = GIR.blacklist @@ -193,7 +194,16 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect } background.async(group: queues) { let convert = swiftCode(gir.functions) - let types = gir.records.filter {!blacklist.contains($0.name)} + var types = gir.records.filter {!blacklist.contains($0.name)} + if !generateAll { + let classes: [String: GIR.Class] = Dictionary(gir.classes.map { ($0.name, $0) }) { lhs, _ in lhs} + types.removeAll { record in + record.name.hasSuffix("Private") && + record.name.stringByRemoving(suffix: "Private") + .flatMap { classes[$0] } + .flatMap { $0.fields.allSatisfy { field in field.typeRef.type.name != record.name || field.isPrivate } } == true + } + } write(types, using: convert) } background.async(group: queues) { @@ -218,6 +228,7 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect } + /// process blacklist and verbatim constants information func processSpecialCases(_ gir: GIR, forFile node: String) { let preamble = node + ".preamble" @@ -240,7 +251,9 @@ var moduleBoilerPlate: String = "" var outputDirectory: String? /// `true` to create a single file per type var singleFilePerClass = false -while let (opt, param) = get_opt("m:o:p:sv") { +/// `true` gir2swift w +var generateAll = false +while let (opt, param) = get_opt("m:o:p:sv:a") { switch opt { case "m": guard let bpfile = param, @@ -256,13 +269,15 @@ while let (opt, param) = get_opt("m:o:p:sv") { singleFilePerClass = true case "v": verbose = true + case "a": + generateAll = true default: usage() } } for argument in CommandLine.arguments[Int(optind).. String { + return Code.block(indentation: nil) { + "/// Metatype/GType declaration for \(classInstance.name.swift)" + "public extension \(record.structRef.type.swiftName) {" + Code.block { + "" + if let getTypeId = classInstance.typegetter { + "/// This getter returns type identifier in the GLib type system registry" + "public static var metatypeReference: GType { \(getTypeId)() }" + "" + "private static var metatypePointer: UnsafeMutablePointer<\(record.typeRef.type.ctype)>? { g_type_class_peek_static(metatypeReference)?.assumingMemoryBound(to: \(record.typeRef.type.ctype).self) }" + "" + "public static var metatype: \(record.typeRef.type.ctype)? { metatypePointer?.pointee } " + "" + "public static var wrapper: \(record.structRef.type.swiftName)? { \(record.structRef.type.swiftName)(metatypePointer) }" + "" + } else { + "/// Type getter was not found in instance record associated with this class" + } + "" + } + "}" + } +} \ No newline at end of file diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index 64b059c..b6a229b 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -1,10 +1,3 @@ -// -// File.swift -// -// -// Created by Mikoláš Stuchlík on 14.11.2020. -// - import Foundation func signalSanityCheck(_ signal: GIR.Signal) -> [String] { diff --git a/Sources/libgir2swift/emmiting/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift index 76313bf..f00363f 100644 --- a/Sources/libgir2swift/emmiting/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -722,7 +722,7 @@ public func fieldCode(_ indentation: String, record: GIR.Record, avoiding existi /// Swift code for convenience constructors -public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: String, convenience: String = "", override ovr: String = "", publicDesignation: String = "public ", factory: Bool = false, hasParent: Bool = false, convertName: @escaping (String) -> String = { $0.camelCase }) -> (GIR.Record) -> (GIR.Method) -> String { +public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: String, convenience: String = "", override ovr: String = "", publicDesignation: String = "public ", factory: Bool = false, hasParent: Bool = false, shouldSink: Bool = false, convertName: @escaping (String) -> String = { $0.camelCase }) -> (GIR.Record) -> (GIR.Method) -> String { let isConv = !convenience.isEmpty let isExtension = publicDesignation.isEmpty let conv = isConv ? "\(convenience) " : "" @@ -732,6 +732,7 @@ public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: St let call = callCode(doubleIndent, isConstructor: !factory, useStruct: useRef) let returnDeclaration = returnDeclarationCode((typeRef: typeRef, record: record, isConstructor: !factory), useStruct: useRef) let ret = returnCode(indentation, (typeRef: typeRef, record: record, isConstructor: !factory, isConvenience: isConv), hasParent: hasParent) + let isGObject = record.rootType.name == "Object" && record.ref != nil && shouldSink return { (method: GIR.Method) -> String in let rawName = method.name.isEmpty ? method.cname : method.name let rawUTF = rawName.utf8 @@ -782,11 +783,19 @@ public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: St let templateDecl = templateTypes.isEmpty ? "" : ("<" + templateTypes + ">") let p: String? = consPrefix == firstArgName?.swift ? nil : consPrefix let fact = factory ? "static func \(fname.swift + templateDecl)(" : ("\(isOverride ? ovr : conv)init" + templateDecl + "(") + + // https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html + let retainBlock = isGObject == true + ? doubleIndent + "if typeIsA(type: \(factory ? "rv" : "self").type, isAType: InitiallyUnownedClassRef.metatypeReference) { _ = \(factory ? "rv" : "self").refSink() } \n" + : "" + let code = swiftCode(method, indentation + "\(deprecated)@inlinable \(publicDesignation)\(fact)" + constructorParam(method, prefix: p) + ")\(returnDeclaration(method)) {\n" + doubleIndent + call(method) + - indentation + ret(method) + indentation + - "}\n", indentation: indentation) + (factory ? retainBlock : "") + + indentation + ret(method) + + (!factory ? retainBlock : "") + + indentation + "}\n", indentation: indentation) return code } } @@ -1191,11 +1200,13 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: let factories: [GIR.Method] = (e.constructors + allFunctions).filter { $0.isFactoryOf(e) } let subTypeAliases = e.records.map { subTypeAlias(e, $0, publicDesignation: "") }.joined() let documentation = commentCode(e) + let weakReferencable = e.rootType.name == "Object" && e.ref != nil + let weakReferencingProtocol = weakReferencable ? ", GWeakCapturing" : "" let code = "/// The `\(structName)` type acts as a lightweight Swift reference to an underlying `\(ctype)` instance.\n" + "/// It exposes methods that can operate on this data type through `\(protocolName)` conformance.\n" + "/// Use `\(structName)` only as an `unowned` reference to an existing `\(ctype)` instance.\n///\n" + documentation + "\n" + - "public struct \(structName): \(protocolName) {\n" + indentation + + "public struct \(structName): \(protocolName)\(weakReferencingProtocol) {\n" + indentation + subTypeAliases + indentation + "/// Untyped pointer to the underlying `\(ctype)` instance.\n" + indentation + "/// For type-safe access, use the generated, typed pointer `\(ptr)` property instead.\n" + indentation + @@ -1234,6 +1245,14 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: "@inlinable init(_ other: T) {\n" + doubleIndentation + "ptr = other.ptr\n" + indentation + "}\n\n" + indentation + + (weakReferencable + ? + ( + "/// This factory is syntactic sugar for setting weak pointers wrapped in `GWeak`\n" + indentation + + "@inlinable static func ref(_ other: T) -> \(structName) { \(structName)(other) }\n\n" + indentation + ) + : "" + ) + "/// Unsafe typed initialiser.\n" + indentation + "/// **Do not use unless you know the underlying data type the pointer points to conforms to `\(protocolName)`.**\n" + indentation + "@inlinable init(cPointer: UnsafeMutablePointer) {\n" + doubleIndentation + @@ -1262,7 +1281,6 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: constructors.map(ccode).joined(separator: "\n") + factories.map(fcode).joined(separator: "\n") + "}\n\n" - return code } @@ -1285,8 +1303,8 @@ public func recordClassCode(_ e: GIR.Record, parent: String, indentation: String let parentType = e.parentType let hasParent = parentType != nil || !parent.isEmpty let scode = signalNameCode(indentation: indentation) - let ccode = convenienceConstructorCode(typeRef, indentation: indentation, override: "override ", hasParent: hasParent)(e) - let fcode = convenienceConstructorCode(typeRef, indentation: indentation, factory: true)(e) + let ccode = convenienceConstructorCode(typeRef, indentation: indentation, override: "override ", hasParent: hasParent, shouldSink: true)(e) + let fcode = convenienceConstructorCode(typeRef, indentation: indentation, factory: true, shouldSink: true)(e) let constructors = e.constructors.filter { $0.isConstructorOf(e) && !$0.isBareFactory } let allmethods = e.allMethods let factories = allmethods.filter { $0.isFactoryOf(e) } @@ -1545,9 +1563,18 @@ public func swiftCode(_ funcs: [GIR.Function]) -> (String) -> (GIR.Record) -> St }.map { $0.protocolName.withNormalisedPrefix } let p = recordProtocolCode(r, parent: parents.joined(separator: ", "), ptr: ptrName) let s = recordStructCode(r, ptr: ptrName) - let c = recordClassCode(r, parent: cl?.parent.withNormalisedPrefix ?? "", ptr: ptrName) + + // In case we are sure this record represents Class Metatype, return uninstantiable type + var instanceTypeDescriptor = "" + var classDefinition = "" + if let instantiable = r.classInstanceType, instantiable.typegetter != nil { + instanceTypeDescriptor = buildClassTypeDeclaration(for: r, classInstance: instantiable) + "\n\n" + } else { + classDefinition = recordClassCode(r, parent: cl?.parent.withNormalisedPrefix ?? "", ptr: ptrName) + } + let e = recordProtocolExtensionCode(funcs, r, ptr: ptrName) - let code = p + s + c + e + let code = instanceTypeDescriptor + p + s + classDefinition + e return code } } diff --git a/Sources/libgir2swift/models/gir elements/GirRecord.swift b/Sources/libgir2swift/models/gir elements/GirRecord.swift index 0a301c6..811ef67 100644 --- a/Sources/libgir2swift/models/gir elements/GirRecord.swift +++ b/Sources/libgir2swift/models/gir elements/GirRecord.swift @@ -20,7 +20,7 @@ extension GIR { /// C language symbol prefix public let cprefix: String /// C type getter function - public let typegetter: String + public let typegetter: String? /// Methods associated with this record public let methods: [Method] /// Functions associated with this record @@ -35,6 +35,8 @@ extension GIR { public let signals: [Signal] /// Type struct (e.g. class definition), typically nil for records public var typeStruct: String? + /// Name of type which is defined by this record + public var isGTypeStructForType: String? /// Name of the function that returns the GType for this record (`nil` if unspecified) public var parentType: Record? { return nil } /// Root class (`nil` for plain records) @@ -75,9 +77,10 @@ extension GIR { /// - comment: Documentation text for the constant /// - introspectable: Set to `true` if introspectable /// - deprecated: Documentation on deprecation status if non-`nil` - public init(name: String, type: TypeReference, cprefix: String, typegetter: String, methods: [Method] = [], functions: [Function] = [], constructors: [Method] = [], properties: [Property] = [], fields: [Field] = [], signals: [Signal] = [], interfaces: [String] = [], comment: String = "", introspectable: Bool = false, deprecated: String? = nil) { + public init(name: String, type: TypeReference, cprefix: String, typegetter: String? = nil, isGTypeStructForType: String? = nil, methods: [Method] = [], functions: [Function] = [], constructors: [Method] = [], properties: [Property] = [], fields: [Field] = [], signals: [Signal] = [], interfaces: [String] = [], comment: String = "", introspectable: Bool = false, deprecated: String? = nil) { self.cprefix = cprefix self.typegetter = typegetter + self.isGTypeStructForType = isGTypeStructForType self.methods = methods self.functions = functions self.constructors = constructors @@ -94,8 +97,16 @@ extension GIR { /// - index: Index within the siblings of the `node` public init(node: XMLElement, at index: Int) { cprefix = node.attribute(named: "symbol-prefix") ?? "" - typegetter = node.attribute(named: "get-type") ?? "" + + // Regarding "intern": https://gitlab.gnome.org/GNOME/gobject-introspection/-/blob/gnome-3-36/girepository/giregisteredtypeinfo.c + if let typegetter = node.attribute(named: "get-type"), typegetter != "intern" { + self.typegetter = typegetter + } else { + self.typegetter = nil + } + typeStruct = node.attribute(named: "type-struct") + isGTypeStructForType = node.attribute(named: "is-gtype-struct-for") let children = node.children.lazy let funcs = children.filter { $0.name == "function" } functions = funcs.enumerated().map { Function(node: $0.1, at: $0.0) } @@ -237,6 +248,10 @@ extension GIR { let all = Set(signals).union(Set(parent.allSignals)) return all.sorted() } + + var classInstanceType: GIR.Record? { + self.isGTypeStructForType.flatMap { GIR.knownRecords[$0] } + } } } diff --git a/run-gir2swift.sh b/run-gir2swift.sh new file mode 100755 index 0000000..c083883 --- /dev/null +++ b/run-gir2swift.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +swift package update + +case $1 in +flags) .build/checkouts/gir2swift/gir2swift-generation-driver.sh c-flags $PWD ;; +clean) .build/checkouts/gir2swift/gir2swift-generation-driver.sh remove-generated $PWD ;; +*) .build/checkouts/gir2swift/gir2swift-generation-driver.sh generate $PWD ;; +esac From e6c351a67c4ef7daa477efc21fa743745cd0749f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= <42004728+mikolasstuchlik@users.noreply.github.com> Date: Fri, 25 Dec 2020 18:48:40 +0100 Subject: [PATCH 15/18] [CI] Update CI to use latest driver --- .github/workflows/build-gtk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index b7639a5..758285e 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -39,7 +39,7 @@ jobs: run: | cd SwiftGtk swift package update - ../gir2swift/gir2swift-generation-driver.sh "$PWD" "$GIR2SWIFT_PATH" + ../gir2swift/gir2swift-generation-driver.sh generate "$PWD" "$GIR2SWIFT_PATH" swift build -Xswiftc -suppress-warnings cd .. From df970fd5274daadd4e2aa7aae11f2d3d5848011e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Tue, 29 Dec 2020 17:46:49 +0100 Subject: [PATCH 16/18] [General] Rename cast function; Add callback parsing --- Sources/libgir2swift/emmiting/gir+swift.swift | 2 +- Sources/libgir2swift/models/gir elements/GirField.swift | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Sources/libgir2swift/emmiting/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift index f00363f..25d8c84 100644 --- a/Sources/libgir2swift/emmiting/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -1249,7 +1249,7 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: ? ( "/// This factory is syntactic sugar for setting weak pointers wrapped in `GWeak`\n" + indentation + - "@inlinable static func ref(_ other: T) -> \(structName) { \(structName)(other) }\n\n" + indentation + "@inlinable static func unowned(_ other: T) -> \(structName) { \(structName)(other) }\n\n" + indentation ) : "" ) + diff --git a/Sources/libgir2swift/models/gir elements/GirField.swift b/Sources/libgir2swift/models/gir elements/GirField.swift index e5b83f3..1ce11db 100644 --- a/Sources/libgir2swift/models/gir elements/GirField.swift +++ b/Sources/libgir2swift/models/gir elements/GirField.swift @@ -17,7 +17,14 @@ extension GIR { public class Field: Property { public override var kind: String { return "Field" } + /// This is temporary variable introduced to aid signal generation. + public var containedCallback: GIR.Callback? + public init(node: XMLElement, at index: Int) { + if let callback = node.children.filter({ $0.name == "callback" }).first { + containedCallback = Callback.init(node: callback, at: index) + } + super.init(fromChildrenOf: node, at: index) } } From 8b7bed1fe43ef9653c3ffcff0e4bbbe93b421982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Thu, 31 Dec 2020 15:18:44 +0100 Subject: [PATCH 17/18] [General] Add documentation --- Sources/gir2swift/main.swift | 5 +++- .../libgir2swift/emmiting/CodeBuilder.swift | 18 ++++++++----- .../libgir2swift/emmiting/emmit-class.swift | 8 +++--- .../libgir2swift/emmiting/emmit-signals.swift | 27 +++++++++++++++++-- Sources/libgir2swift/emmiting/gir+swift.swift | 5 ++++ .../emmiting/girtypes+swift.swift | 2 +- .../models/gir elements/GirField.swift | 2 +- gir2swift-generation-driver.sh | 24 ++++++++++++++++- run-gir2swift.sh | 9 ++++++- 9 files changed, 83 insertions(+), 17 deletions(-) diff --git a/Sources/gir2swift/main.swift b/Sources/gir2swift/main.swift index bc84a8b..d462d83 100644 --- a/Sources/gir2swift/main.swift +++ b/Sources/gir2swift/main.swift @@ -195,6 +195,9 @@ func process_gir(file: String, boilerPlate modulePrefix: String, to outputDirect background.async(group: queues) { let convert = swiftCode(gir.functions) var types = gir.records.filter {!blacklist.contains($0.name)} + // If `generate all` option was not passed, the driver will not generate records wich are deemed as private. + // Currently only Private records are ommited. Private record is a record, which has suffic Record and, class with it's name without work "Private" exists and contains only private references to this type or none at all. + // Since not all private attributes of classes are marked as private in .gir, only those records with non-private attributed references will be generated. if !generateAll { let classes: [String: GIR.Class] = Dictionary(gir.classes.map { ($0.name, $0) }) { lhs, _ in lhs} types.removeAll { record in @@ -251,7 +254,7 @@ var moduleBoilerPlate: String = "" var outputDirectory: String? /// `true` to create a single file per type var singleFilePerClass = false -/// `true` gir2swift w +/// `true` gir2swift will generate wrappers for all types, despite being private or unreachable var generateAll = false while let (opt, param) = get_opt("m:o:p:sv:a") { switch opt { diff --git a/Sources/libgir2swift/emmiting/CodeBuilder.swift b/Sources/libgir2swift/emmiting/CodeBuilder.swift index 0f564ca..42efcc3 100644 --- a/Sources/libgir2swift/emmiting/CodeBuilder.swift +++ b/Sources/libgir2swift/emmiting/CodeBuilder.swift @@ -1,16 +1,14 @@ -// -// File.swift -// -// -// Created by Mikoláš Stuchlík on 16/10/2020. -// - import Foundation + +/// Code Builder is a ResultBuilder class which provides easier-to-read way of composing string with indentation and new lines. Element of the DSL generated by this class is `String`. As for now, only if-else statements are available. At the time of implementation, for-in statement was not widely available. +/// - Note: ResultBuilder is merged in Swift 5.4 and available since Swift 5.1 @_functionBuilder class CodeBuilder { + /// Ignoring sequence is introduec in order to prevent superfulous line breaks in conditions static let ignoringEspace: String = "<%IGNORED%>" + /// Following static methods are documented in https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md static func buildBlock( _ segments: String...) -> String { segments.filter { $0 != CodeBuilder.ignoringEspace } .joined(separator: "\n") } @@ -22,9 +20,12 @@ class CodeBuilder { static func buildIf(_ segment: String?) -> String { buildOptional(segment) } } +/// Convenience class for using CodeBuilder DSL. This class was introduced to shorten calls. As for now, this class compensates for some missing DSL features like for-in loops. class Code { static var defaultCodeIndentation: String = " " + /// Code in builder block of this function will have additional indentation passed in the first argument. + /// - Parameter indentation: Intendation added on top of existing indentation. Default value is value of static property `defaultCodeIndentation`. Pass `nil` or "" to ommit indentation. static func block(indentation: String? = defaultCodeIndentation, @CodeBuilder builder: ()->String) -> String { let code = builder() @@ -35,14 +36,17 @@ class Code { return builder() } + /// Strings inside of this builder have all occurances of `\n` removed. static func line(@CodeBuilder builder: ()->String) -> String { builder().components(separatedBy: "\n").joined() } + /// Loop provided as a replacement for missing for-in loop. This function will be removed in the future. For Enumerated variant use `loopEnumerated(over:builder:)` static func loop(over items: [T], @CodeBuilder builder: (T)->String) -> String { !items.isEmpty ? items.map(builder).joined(separator: "\n") : CodeBuilder.ignoringEspace } + /// Loop provided as a replacement for missing for-in loop. Array is returned as enumerated. static func loopEnumerated(over items: [T], @CodeBuilder builder: (Int, T)->String) -> String { !items.isEmpty ? items.enumerated().map(builder).joined(separator: "\n") : CodeBuilder.ignoringEspace } diff --git a/Sources/libgir2swift/emmiting/emmit-class.swift b/Sources/libgir2swift/emmiting/emmit-class.swift index ed2366a..b15cf87 100644 --- a/Sources/libgir2swift/emmiting/emmit-class.swift +++ b/Sources/libgir2swift/emmiting/emmit-class.swift @@ -1,3 +1,5 @@ + +/// This function builds declarations for metatypes. This feature was originaly intented to replace all of the metatype code, since dynamic features of GLib/GObject are not supported. This decision was deffered to future. Following declarations wrap only type getter for user convenience. func buildClassTypeDeclaration(for record: GIR.Record, classInstance: GIR.Record) -> String { return Code.block(indentation: nil) { "/// Metatype/GType declaration for \(classInstance.name.swift)" @@ -6,13 +8,13 @@ func buildClassTypeDeclaration(for record: GIR.Record, classInstance: GIR.Record "" if let getTypeId = classInstance.typegetter { "/// This getter returns type identifier in the GLib type system registry" - "public static var metatypeReference: GType { \(getTypeId)() }" + "static var metatypeReference: GType { \(getTypeId)() }" "" "private static var metatypePointer: UnsafeMutablePointer<\(record.typeRef.type.ctype)>? { g_type_class_peek_static(metatypeReference)?.assumingMemoryBound(to: \(record.typeRef.type.ctype).self) }" "" - "public static var metatype: \(record.typeRef.type.ctype)? { metatypePointer?.pointee } " + "static var metatype: \(record.typeRef.type.ctype)? { metatypePointer?.pointee } " "" - "public static var wrapper: \(record.structRef.type.swiftName)? { \(record.structRef.type.swiftName)(metatypePointer) }" + "static var wrapper: \(record.structRef.type.swiftName)? { \(record.structRef.type.swiftName)(metatypePointer) }" "" } else { "/// Type getter was not found in instance record associated with this class" diff --git a/Sources/libgir2swift/emmiting/emmit-signals.swift b/Sources/libgir2swift/emmiting/emmit-signals.swift index b6a229b..75f1b33 100644 --- a/Sources/libgir2swift/emmiting/emmit-signals.swift +++ b/Sources/libgir2swift/emmiting/emmit-signals.swift @@ -1,5 +1,10 @@ import Foundation +/// This file contains support for signal generation. Since signals are described differently than other function types in .gir files, custom behavior for type generation and casting is implemented. +/// Since the custom generation was already needed, focus of this implementation is safety. If a argument lacks a implementation of safe interface generation, whole signal is ommited. All signals are checked before generation and the decision process was summarized into 9 conditions. The future aim is to lift those limitation progressively. Such feature will require better support from the rest of gir2swift. + +/// This method verifies, whether signal is fit to be generated. +/// - Returns: Array of string which contains all the reasons why signal could not be generated. Empty array implies signal is fit to be generated. func signalSanityCheck(_ signal: GIR.Signal) -> [String] { var errors = [String]() @@ -54,12 +59,15 @@ func buildSignalExtension(for record: GIR.Record) -> String { "// MARK: Signals of \(record.name.swift)" "public extension \(record.protocolName) {" Code.block { + // Generation of unavailable signals Code.loop(over: record.signals.filter( {!signalSanityCheck($0).isEmpty} )) { signal in buildUnavailable(signal: signal) } + // Generation of available signals Code.loop(over: record.signals.filter( {signalSanityCheck($0).isEmpty } )) { signal in buildAvailableSignal(record: record, signal: signal) } + // Generation of property obsevers. Property observers have the same delcaration as GObject signal `notify`. This sinal should be available at all times. if let notifySignal = GIR.knownRecords["Object"]?.signals.first(where: { $0.name == "notify"}) { Code.loop(over: record.properties) { property in buildSignalForProperty(record: record, property: property, notify: notifySignal) @@ -73,6 +81,10 @@ func buildSignalExtension(for record: GIR.Record) -> String { } } +/// Modifies provided signal model to notify about property change. +/// - Parameter record: Record of the property +/// - Parameter property: Property which observer will be genrated +/// - Parameter notify: GObject signal "notify" which is basis for the signal generation. private func buildSignalForProperty(record: GIR.Record, property: GIR.Property, notify: GIR.Signal) -> String { let propertyNotify = GIR.Signal( name: notify.name + "::" + property.name, @@ -128,6 +140,7 @@ private func buildAvailableSignal(record: GIR.Record, signal: GIR.Signal) -> Str "}\n" } +/// This function build documentation and name for unavailable signal. @CodeBuilder private func buildUnavailable(signal: GIR.Signal) -> String { addDocumentation(signal: signal) @@ -136,6 +149,7 @@ private func buildUnavailable(signal: GIR.Signal) -> String { #"static var on\#(signal.name.camelSignal.capitalised): String { "\#(signal.name)" }"# } +/// This function build Swift closure handler declaration. @CodeBuilder private func handlerType(record: GIR.Record, signal: GIR.Signal) -> String { "@escaping ( _ unownedSelf: \(record.structName)" @@ -146,6 +160,7 @@ private func handlerType(record: GIR.Record, signal: GIR.Signal) -> String { signal.returns.swiftIdiomaticType() } +/// This function builds declaration for the typealias holding the reference to the Swift closure handler private func signalClosureHolderDecl(record: GIR.Record, signal: GIR.Signal) -> String { if signal.args.count > 6 { fatalError("Argument count \(signal.args.count) exceeds number of allowed arguments (6)") @@ -160,6 +175,7 @@ private func signalClosureHolderDecl(record: GIR.Record, signal: GIR.Signal) -> } } +/// This function adds Parameter documentation to the signal on top of existing documentation generation. @CodeBuilder private func addDocumentation(signal: GIR.Signal) -> String { { str -> String in str.isEmpty ? CodeBuilder.ignoringEspace : str}(commentCode(signal)) @@ -177,6 +193,7 @@ private func addDocumentation(signal: GIR.Signal) -> String { } } +/// Returns declaration for c callback. @CodeBuilder private func cCallbackDecl(record: GIR.Record, signal: GIR.Signal) -> String { "@convention(c) (" @@ -189,6 +206,7 @@ private func cCallbackDecl(record: GIR.Record, signal: GIR.Signal) -> String { signal.returns.swiftCCompatibleType() } +/// list of names of arguments of c callback private func cCallbackArgumentsDecl(record: GIR.Record, signal: GIR.Signal) -> String { Code.line { "unownedSelf" @@ -199,6 +217,7 @@ private func cCallbackArgumentsDecl(record: GIR.Record, signal: GIR.Signal) -> S } } +/// Returns correct call of Swift handler from c callback scope with correct casting. private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> String { Code.line { "call(\(record.structRef.type.swiftName)(raw: unownedSelf)" @@ -209,6 +228,7 @@ private func generaceCCallbackCall(record: GIR.Record, signal: GIR.Signal) -> St } } +/// Generates correct return statement. This method currently contains implementation of ownership-transfer ability for String. private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> String { switch signal.returns.knownType { case is GIR.Record: @@ -233,6 +253,7 @@ private func generateReturnStatement(record: GIR.Record, signal: GIR.Signal) -> private extension GIR.Argument { + /// Returns type names for Swift adjusted for the needs of signal generation func swiftIdiomaticType() -> String { switch knownType { case is GIR.Record: @@ -247,7 +268,8 @@ private extension GIR.Argument { return self.swiftSignalRef.fullSwiftTypeName + ((isNullable || isOptional) ? "?" : "") } } - + + /// Returns names name for C adjusted for the needs of signal generation func swiftCCompatibleType() -> String { switch knownType { case is GIR.Record: @@ -262,7 +284,8 @@ private extension GIR.Argument { return self.callbackArgumentTypeName } } - + + /// Generates correct cast from C type/argument to Swift type. This method currently supports optionals. func swiftSignalArgumentConversion(at index: Int) -> String { switch knownType { case is GIR.Record: diff --git a/Sources/libgir2swift/emmiting/gir+swift.swift b/Sources/libgir2swift/emmiting/gir+swift.swift index 25d8c84..f2542bf 100644 --- a/Sources/libgir2swift/emmiting/gir+swift.swift +++ b/Sources/libgir2swift/emmiting/gir+swift.swift @@ -784,6 +784,7 @@ public func convenienceConstructorCode(_ typeRef: TypeReference, indentation: St let p: String? = consPrefix == firstArgName?.swift ? nil : consPrefix let fact = factory ? "static func \(fname.swift + templateDecl)(" : ("\(isOverride ? ovr : conv)init" + templateDecl + "(") + // This code will consume floating references upon instantiation. This is suggested by the GObject documentation since Floating references are C-specific syntactic sugar. // https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html let retainBlock = isGObject == true ? doubleIndent + "if typeIsA(type: \(factory ? "rv" : "self").type, isAType: InitiallyUnownedClassRef.metatypeReference) { _ = \(factory ? "rv" : "self").refSink() } \n" @@ -1200,8 +1201,11 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: let factories: [GIR.Method] = (e.constructors + allFunctions).filter { $0.isFactoryOf(e) } let subTypeAliases = e.records.map { subTypeAlias(e, $0, publicDesignation: "") }.joined() let documentation = commentCode(e) + + // In case wrapped value supports GObject reference countin, add GWeakCapturing protocol conformance to support GWeak requirements. let weakReferencable = e.rootType.name == "Object" && e.ref != nil let weakReferencingProtocol = weakReferencable ? ", GWeakCapturing" : "" + let code = "/// The `\(structName)` type acts as a lightweight Swift reference to an underlying `\(ctype)` instance.\n" + "/// It exposes methods that can operate on this data type through `\(protocolName)` conformance.\n" + "/// Use `\(structName)` only as an `unowned` reference to an existing `\(ctype)` instance.\n///\n" + @@ -1245,6 +1249,7 @@ public func recordStructCode(_ e: GIR.Record, indentation: String = " ", ptr: "@inlinable init(_ other: T) {\n" + doubleIndentation + "ptr = other.ptr\n" + indentation + "}\n\n" + indentation + + // This factory is syntactic sugar for conversion owning class wrapers to unowning structs. This feature was added to introduce better syntax for working with GWeak class. (weakReferencable ? ( diff --git a/Sources/libgir2swift/emmiting/girtypes+swift.swift b/Sources/libgir2swift/emmiting/girtypes+swift.swift index 5f47d89..15f0ea0 100644 --- a/Sources/libgir2swift/emmiting/girtypes+swift.swift +++ b/Sources/libgir2swift/emmiting/girtypes+swift.swift @@ -63,7 +63,7 @@ public extension GIR.CType { return replacement } - /// Type reference to an idiomatic Swift type used for a Swift signals + /// Type reference to an idiomatic Swift type used for a Swift signals. This property is copy of `swiftReturnRef` with a different domain. The domain for this property was modified to include support for unsigned ints. @inlinable var swiftSignalRef: TypeReference { guard var replacement = GIR.swiftSignalTypeReplacements[typeRef] else { diff --git a/Sources/libgir2swift/models/gir elements/GirField.swift b/Sources/libgir2swift/models/gir elements/GirField.swift index 1ce11db..f3eaf91 100644 --- a/Sources/libgir2swift/models/gir elements/GirField.swift +++ b/Sources/libgir2swift/models/gir elements/GirField.swift @@ -17,7 +17,7 @@ extension GIR { public class Field: Property { public override var kind: String { return "Field" } - /// This is temporary variable introduced to aid signal generation. + /// Since fileds can constain callbacks, this property was introduced to parse it. Such variable should be stored in `CType.containedType` property. This will result in a lot of logic breaking behavior. Thus this property was introduced, since no code is generated using this property and as for now, servers for experimenting. public var containedCallback: GIR.Callback? public init(node: XMLElement, at index: Int) { diff --git a/gir2swift-generation-driver.sh b/gir2swift-generation-driver.sh index 4957b78..c427d9c 100755 --- a/gir2swift-generation-driver.sh +++ b/gir2swift-generation-driver.sh @@ -1,5 +1,8 @@ #!/bin/bash +## Function used to determine, whether provided path requires processing by gir2swift +## ARGUMENT 1: Path to the Swift package in question +## RETURN: `true` if package contains dependency to gir2swift and contains file named "gir2swift-manifest.sh" function is_processable_arg-path { local PACKAGE_PATH=$1 @@ -20,6 +23,9 @@ function is_processable_arg-path { fi } +## Function which searches for folder containing all .gir files provided in argument. Exists if no such folder was found. Searched domain is specified in the foor loop of this function. +## ARGUMENT 1: List of gir names without .gir suffixes. +## RETURN: Path to the folder function gir_path_arg-gir-names { local GIR_NAMES=$1 @@ -42,6 +48,9 @@ function gir_path_arg-gir-names { exit 1 } +## Searches Swift packages provided in the argument for gir2swift package. In case the package is found, the package is built and path to executable is returnes +## ARGUMENT 1: JSON of dependency graph fetched by the root Package. +## RETURN: Path to gir2swift executable function gir_2_swift_executable_arg-deps { local DEPENDENCIES=$1 @@ -58,6 +67,10 @@ function gir_2_swift_executable_arg-deps { echo "${G2S_PACKAGE_PATH}/.build/release/gir2swift" } +## Filters list of dependencies provided in the argument and returns only those which require processing by gir2swift. +## ARGUMENT 1: JSON of dependency graph fetched by the root package. +## ARGUMENT 2: Name of the Swift package which dependencies should be returned. +## RETURN: List of all paths to the Swift packages which should be processed. function get_processable_dependencies_arg-deps_arg-name { local DEPENDENCIES=$1 local PACKAGE_NAME=$2 @@ -75,6 +88,9 @@ function get_processable_dependencies_arg-deps_arg-name { done } +## Returns names of all GIR files of provided packages. +## ARGUMENT 1: List of paths to the packages in question. ONLY PROCESSABLE PACKAGES ARE VALID INPUT. +## RETURN: List of names of gir files. function get_gir_names_arg-packages { local PACKAGES=$1 @@ -84,6 +100,7 @@ function get_gir_names_arg-packages { done } +## Returns name of Swift package. This function depends on working directiory it is run in. This function is exported and required by manifests. function package_name { local PACKAGE=`swift package dump-package` local NAME=`jq -r '.name' <<< $PACKAGE` @@ -92,6 +109,7 @@ function package_name { } export -f package_name +## Returns all pkg-config packages required by targets specified in a Swift package. This feature is intended as a support for macOS. This function depend on working directory. This function is exported and required by manifests. function package_pkg_config_arguments { local PACKAGE=`swift package dump-package` local NAME=`jq -r '.targets[] | select(.pkgConfig != null) | .pkgConfig?' <<< $PACKAGE` @@ -100,6 +118,9 @@ function package_pkg_config_arguments { } export -f package_pkg_config_arguments +## This function name of Swift package on specified path. +## ARGUMENT 1: Path to the package in question. +## RETURN: Name of the package function package_name_arg-path { local PACKAGE_PATH=$1 @@ -115,7 +136,7 @@ function package_name_arg-path { } -# Building process +# Command COMMAND=$1 case $COMMAND in @@ -123,6 +144,7 @@ generate) TOP_LEVEL_PACKAGE_PATH=$2 OPTIONAL_ALTERNATIVE_G2S_PATH=$3 + # Fetch and retain dependency graph, since this operation takes a lot of time. cd $TOP_LEVEL_PACKAGE_PATH DEPENDENCIES=`swift package show-dependencies --format json` PROCESSABLE=$(get_processable_dependencies_arg-deps_arg-name "$DEPENDENCIES" "$(package_name)") diff --git a/run-gir2swift.sh b/run-gir2swift.sh index c083883..6b57e57 100755 --- a/run-gir2swift.sh +++ b/run-gir2swift.sh @@ -1,9 +1,16 @@ #!/bin/bash -swift package update +## Swift package with fetched dependencies is required to run scipt in gir2swift package. Use option -noUpdate to prevent update. +if ! [[ $@ == *'-noUpdate'* ]] +then + swift package update +fi case $1 in +## Returns flags needed for macOS compilation (experimental) flags) .build/checkouts/gir2swift/gir2swift-generation-driver.sh c-flags $PWD ;; +## Removes all generaed files clean) .build/checkouts/gir2swift/gir2swift-generation-driver.sh remove-generated $PWD ;; +## Defaults to generation *) .build/checkouts/gir2swift/gir2swift-generation-driver.sh generate $PWD ;; esac From d7097365fc9ceadb3d2e72526d66e3cfd58bb13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikol=C3=A1=C5=A1=20Stuchl=C3=ADk?= <42004728+mikolasstuchlik@users.noreply.github.com> Date: Mon, 4 Jan 2021 16:43:40 +0100 Subject: [PATCH 18/18] [Build/CI] Fix build until 5.4; add CI for macOS (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Build/CI] Fix build until 5.4; add CI for macOS * Attempt: fix libffi missing in macOS build * Remove libffi brew; fix driver path * Add echo in order to debug * Fix the possible issue * Remove incorrect argument * Attempt to solve the issue by removing all priv memebers Co-authored-by: Mikoláš Stuchlík --- .github/workflows/build-gtk.yml | 58 +++++++++++++++++++ .../models/gir elements/GirCType.swift | 15 ++++- gir2swift-generation-driver.sh | 23 +++++--- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-gtk.yml b/.github/workflows/build-gtk.yml index 758285e..b78a258 100644 --- a/.github/workflows/build-gtk.yml +++ b/.github/workflows/build-gtk.yml @@ -2,12 +2,70 @@ name: Build gir2swift and build SwiftGtk # Dependencies of Glib package env: + MACOS_BREW: ${{ 'glib gobject-introspection pango atk gtk+3 cairo glib-networking gdk-pixbuf' }} UBUNTU_APT: ${{ 'libpango1.0-dev libglib2.0-dev libgdk-pixbuf2.0-dev gobject-introspection libcairo2-dev libatk1.0-dev glib-networking libgtk-3-dev libgirepository1.0-dev' }} + on: pull_request: branches: [ master ] jobs: + # macOS tasks + build-mac-swift-latest: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '12.2' + - name: Print Swift version to confirm + run: swift --version + - name: Fetch dependencies for general repository + run: brew install $MACOS_BREW + + - name: Checkout gir2swift + uses: actions/checkout@v2 + with: + path: gir2swift + - name: Checkout testing repo + uses: actions/checkout@v2 + with: + repository: mikolasstuchlik/SwiftGtk + path: SwiftGtk + + - name: Build current gir2swift + run: | + cd gir2swift + ./build.sh + echo "GIR2SWIFT_PATH=${PWD}/.build/release/gir2swift" >> $GITHUB_ENV + cd .. + + - name: Build gtk + run: | + cd SwiftGtk + swift package update + ../gir2swift/gir2swift-generation-driver.sh generate "$PWD" "$GIR2SWIFT_PATH" + swift build -Xswiftc -suppress-warnings `../gir2swift/gir2swift-generation-driver.sh "c-flags" "${PWD}"` + cd .. + + - name: Remove unneeded files and archive artifacts + run: | + cd gir2swift + swift package clean + rm -rf .build/repositories + cd ../SwiftGtk + swift package clean + rm -rf .build/repositories + cd .. + - name: 'Upload Artifact' + uses: actions/upload-artifact@v2 + with: + name: build-artifact-macos + path: | + gir2swift/ + SwiftGtk/ + retention-days: 1 # Ubuntu 20.04 tasks build-ubuntu-20_04-swift-latest: diff --git a/Sources/libgir2swift/models/gir elements/GirCType.swift b/Sources/libgir2swift/models/gir elements/GirCType.swift index 6973c04..d946c3a 100644 --- a/Sources/libgir2swift/models/gir elements/GirCType.swift +++ b/Sources/libgir2swift/models/gir elements/GirCType.swift @@ -68,7 +68,12 @@ extension GIR { /// - scopeAttr: Key for the attribute to extract the scope string from public init(node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { containedTypes = node.containedTypes - isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + + // Since not all "priv" values are marked as private by gi, all properties names "priv" which type has suffic "Private" are deemed as private. + let isAttributedPrivate = node.attribute(named: privateAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + let isPresumedPrivate = node.attribute(named: nameAttr) == "priv" + + isPrivate = isAttributedPrivate || isPresumedPrivate isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false tupleSize = node.attribute(named: "fixed-size").flatMap(Int.init) @@ -85,7 +90,12 @@ extension GIR { /// - scopeAttr: Key for the attribute to extract the scope string from public init(fromChildrenOf node: XMLElement, at index: Int, nameAttr: String = "name", privateAttr: String = "private", readableAttr: String = "readable", writableAttr: String = "writable", scopeAttr: String = "scope") { let type: TypeReference - isPrivate = node.attribute(named: privateAttr) .flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + + // Since not all "priv" values are marked as private by gi, all properties names "priv" which type has suffic "Private" are deemed as private. + let isAttributedPrivate = node.attribute(named: privateAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false + let isPresumedPrivate = node.attribute(named: nameAttr) == "priv" + + isPrivate = isAttributedPrivate || isPresumedPrivate isReadable = node.attribute(named: readableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? true isWritable = node.attribute(named: writableAttr).flatMap({ Int($0) }).map({ $0 != 0 }) ?? false scope = node.attribute(named: scopeAttr) @@ -98,6 +108,7 @@ extension GIR { tupleSize = nil type = GIR.typeOf(node: node) } + super.init(node: node, at: index, with: type, nameAttr: nameAttr) } diff --git a/gir2swift-generation-driver.sh b/gir2swift-generation-driver.sh index c427d9c..4ae028c 100755 --- a/gir2swift-generation-driver.sh +++ b/gir2swift-generation-driver.sh @@ -209,7 +209,6 @@ remove-generated) c-flags) TOP_LEVEL_PACKAGE_PATH=$2 - OPTIONAL_ALTERNATIVE_G2S_PATH=$3 cd $TOP_LEVEL_PACKAGE_PATH DEPENDENCIES=`swift package show-dependencies --format json` @@ -221,14 +220,24 @@ c-flags) ALL_PROCESSABLE="$TOP_LEVEL_PACKAGE_PATH $PROCESSABLE" fi - FLAGS="" - for PACKAGE in $ALL_PROCESSABLE + PKGS="" + for PACKAGE in $ALL_PROCESSABLE do cd $PACKAGE - FLAGS="$FLAGS $(package_pkg_config_arguments)" - done + PKGS="$PKGS $(package_pkg_config_arguments)" + done + + C=`pkg-config --cflags $PKGS` + LINKER=`pkg-config --libs $PKGS` + + # Decorating flags returned from pkg-config with -Xcc and -Xlinker flags + DECORC=`for FLAG in ${C}; do echo -n "-Xcc ${FLAG} "; done` + DECORL=`for FLAG in ${LINKER}; do echo -n "-Xlinker ${FLAG} "; done` + + # pkg-config on mac generates sequences like "-Wl,-framework,Cocoa" - those sequences are not desirable and refactored into "-framework -Xlinker Cocoa" which with previous decoration results in sequences of "-Xlinker -framework -Xlinker Cocoa" + MAC_LINKER_FIXES=`echo "${DECORL}" | sed -e 's/ *-Wl, */ /g' -e 's/,/ -Xlinker /g'` - echo `pkg-config --cflags $FLAGS` + echo "${DECORC} ${MAC_LINKER_FIXES}" ;; *) echo "Gir 2 swift code generation tool" @@ -237,4 +246,4 @@ c-flags) echo " remove-generated [path to root package]" echo " c-flags [path to root package]" ;; -esac \ No newline at end of file +esac