From b335c4cbf82a9b9983bfc9eeffaeb3e9a781b149 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Thu, 5 Sep 2024 06:56:46 -0400 Subject: [PATCH] Support generating projections that build as dynamic libraries (#268) --- .../Swift/SwiftPackage+write.swift | 6 ++++ .../CodeWriters/Swift/SwiftPackage.swift | 22 +++++++------- .../SwiftWinRT/CommandLineArguments.swift | 5 +++- .../SwiftWinRT/Writing/SwiftPackageFile.swift | 29 ++++++++++++------- Generator/Sources/SwiftWinRT/main.swift | 4 ++- .../SwiftWinRT/writeProjectionFiles.swift | 27 +++++++++++++---- InteropTests/Package.swift | 3 +- 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/Generator/Sources/CodeWriters/Swift/SwiftPackage+write.swift b/Generator/Sources/CodeWriters/Swift/SwiftPackage+write.swift index 95bfecf5..b7637ca6 100644 --- a/Generator/Sources/CodeWriters/Swift/SwiftPackage+write.swift +++ b/Generator/Sources/CodeWriters/Swift/SwiftPackage+write.swift @@ -48,6 +48,12 @@ extension SwiftPackage.Product { fileprivate func write(to writer: IndentedTextOutputStream) { writer.writeIndentedBlock(header: ".library(", footer: ")", endFooterLine: false) { writer.write("name: \"\(escapeStringLiteral(name))\"") + + if let type { + writer.write(",", endLine: true) + writer.write("type: .\(type.rawValue)") + } + if !targets.isEmpty { writer.write(",", endLine: true) writer.writeIndentedBlock(header: "targets: [", footer: "]", endFooterLine: false) { diff --git a/Generator/Sources/CodeWriters/Swift/SwiftPackage.swift b/Generator/Sources/CodeWriters/Swift/SwiftPackage.swift index e8ed9ae6..6bcc958e 100644 --- a/Generator/Sources/CodeWriters/Swift/SwiftPackage.swift +++ b/Generator/Sources/CodeWriters/Swift/SwiftPackage.swift @@ -29,10 +29,16 @@ public struct SwiftPackage { public struct Product { public var name: String + public var type: ProductType? public var targets: [String] - public static func library(name: String, targets: [String]) -> Product { - .init(name: name, targets: targets) + public static func library(name: String, type: ProductType? = nil, targets: [String]) -> Product { + .init(name: name, type: type, targets: targets) + } + + public enum ProductType: String { + case `static` + case dynamic } } @@ -51,19 +57,11 @@ public struct SwiftPackage { public struct Target { public var name: String - public var dependencies: [Dependency] = [] + public var dependencies: [Dependency] public var path: String? - public var exclude: [String] = [] + public var exclude: [String] public var cUnsafeFlags: [String] - public init(name: String, dependencies: [Dependency] = [], path: String? = nil, exclude: [String] = [], cUnsafeFlags: [String] = []) { - self.name = name - self.dependencies = dependencies - self.path = path - self.exclude = exclude - self.cUnsafeFlags = cUnsafeFlags - } - public static func target(name: String, dependencies: [Dependency] = [], path: String? = nil, exclude: [String] = [], cUnsafeFlags: [String] = []) -> Target { .init(name: name, dependencies: dependencies, path: path, exclude: exclude, cUnsafeFlags: cUnsafeFlags) } diff --git a/Generator/Sources/SwiftWinRT/CommandLineArguments.swift b/Generator/Sources/SwiftWinRT/CommandLineArguments.swift index a3d4b435..7500f247 100644 --- a/Generator/Sources/SwiftWinRT/CommandLineArguments.swift +++ b/Generator/Sources/SwiftWinRT/CommandLineArguments.swift @@ -30,10 +30,13 @@ struct CommandLineArguments: ParsableCommand { @Option(name: .customLong("support"), help: .init("The directory path or url:branch or url@revision of the support package to use.", valueName: "dir-or-url")) var supportPackageLocation: String = "https://github.com/tristanlabelle/swift-winrt.git:main" - + @Flag(name: .customLong("cmakelists"), help: "Generate a CMakeLists.txt files for building with CMake.") var generateCMakeLists: Bool = false + @Flag(name: .customLong("dylib"), help: "Makes SPM and CMake build definitions specify to build dynamic libraries.") + var dynamicLibraries: Bool = false + @Option(name: .customLong("out-manifest"), help: .init("Path to generate an embeddable exe manifest file to.", valueName: "file")) var exeManifestPath: String? = nil } diff --git a/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift b/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift index 927b0be7..a0f3f523 100644 --- a/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift +++ b/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift @@ -4,22 +4,25 @@ import DotNetMetadata import ProjectionModel import struct Foundation.URL -func writeSwiftPackageFile(_ projection: SwiftProjection, supportPackageLocation: String, excludeCMakeLists: Bool, toPath path: String) { +func writeSwiftPackageFile( + _ projection: SwiftProjection, + supportPackageLocation: String, + excludeCMakeLists: Bool, + dynamicLibraries: Bool, + toPath path: String) { var package = SwiftPackage(name: "Projection") package.dependencies.append(getSupportPackageDependency(location: supportPackageLocation)) - var productTargets = [String]() - for module in projection.modulesByName.values { guard !module.isEmpty else { continue } // ABI module - var abiModuleTarget = SwiftPackage.Target(name: module.abiModuleName, path: "\(module.name)/ABI") + var abiModuleTarget: SwiftPackage.Target = .target(name: module.abiModuleName, path: "\(module.name)/ABI") abiModuleTarget.dependencies.append(.product(name: "WindowsRuntime_ABI", package: "swift-winrt")) package.targets.append(abiModuleTarget) // Assembly module - var assemblyModuleTarget = SwiftPackage.Target(name: module.name) + var assemblyModuleTarget: SwiftPackage.Target = .target(name: module.name) assemblyModuleTarget.path = "\(module.name)/Assembly" assemblyModuleTarget.dependencies.append(.product(name: "WindowsRuntime", package: "swift-winrt")) @@ -31,7 +34,11 @@ func writeSwiftPackageFile(_ projection: SwiftProjection, supportPackageLocation assemblyModuleTarget.dependencies.append(.target(name: module.abiModuleName)) package.targets.append(assemblyModuleTarget) - productTargets.append(assemblyModuleTarget.name) + + // Define a product for the module + var moduleProduct: SwiftPackage.Product = .library(name: module.name, type: dynamicLibraries ? .dynamic : nil, targets: []) + moduleProduct.targets.append(assemblyModuleTarget.name) + moduleProduct.targets.append(abiModuleTarget.name) // Namespace modules if !module.flattenNamespaces { @@ -44,15 +51,19 @@ func writeSwiftPackageFile(_ projection: SwiftProjection, supportPackageLocation namespaces.sort() for namespace in namespaces { - var namespaceModuleTarget = SwiftPackage.Target( + var namespaceModuleTarget: SwiftPackage.Target = .target( name: module.getNamespaceModuleName(namespace: namespace)) let compactNamespace = SwiftProjection.toCompactNamespace(namespace) namespaceModuleTarget.path = "\(module.name)/Namespaces/\(compactNamespace)" namespaceModuleTarget.dependencies.append(.target(name: module.name)) package.targets.append(namespaceModuleTarget) - productTargets.append(namespaceModuleTarget.name) + moduleProduct.targets.append(namespaceModuleTarget.name) } } + + // Create products for the projections and the ABI + package.products.append(moduleProduct) + package.products.append(.library(name: module.abiModuleName, type: .static, targets: [abiModuleTarget.name])) } if excludeCMakeLists { @@ -62,8 +73,6 @@ func writeSwiftPackageFile(_ projection: SwiftProjection, supportPackageLocation } } - package.products.append(.library(name: "Projection", targets: productTargets)) - package.write(version: "5.10", to: FileTextOutputStream(path: path, directoryCreation: .ancestors)) } diff --git a/Generator/Sources/SwiftWinRT/main.swift b/Generator/Sources/SwiftWinRT/main.swift index 1f169bc3..de38fe21 100644 --- a/Generator/Sources/SwiftWinRT/main.swift +++ b/Generator/Sources/SwiftWinRT/main.swift @@ -30,13 +30,15 @@ do { assemblyLoadContext: context) try writeProjectionFiles(projection, directoryPath: commandLineArguments.outputDirectoryPath, - generateCMakeLists: commandLineArguments.generateCMakeLists) + generateCMakeLists: commandLineArguments.generateCMakeLists, + dynamicLibraries: commandLineArguments.dynamicLibraries) if commandLineArguments.generatePackageDotSwift { writeSwiftPackageFile( projection, supportPackageLocation: commandLineArguments.supportPackageLocation, excludeCMakeLists: commandLineArguments.generateCMakeLists, + dynamicLibraries: commandLineArguments.dynamicLibraries, toPath: "\(commandLineArguments.outputDirectoryPath)\\Package.swift") } diff --git a/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift b/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift index e9e89ffd..f4b8b6e8 100644 --- a/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift +++ b/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift @@ -6,12 +6,17 @@ import Foundation import ProjectionModel import WindowsMetadata -internal func writeProjectionFiles(_ projection: SwiftProjection, directoryPath: String, generateCMakeLists: Bool) throws { +internal func writeProjectionFiles( + _ projection: SwiftProjection, + directoryPath: String, + generateCMakeLists: Bool, + dynamicLibraries: Bool) throws { for module in projection.modulesByName.values { guard !module.isEmpty else { continue } try writeModuleFiles(module, directoryPath: "\(directoryPath)\\\(module.name)", - generateCMakeLists: generateCMakeLists) + generateCMakeLists: generateCMakeLists, + dynamicLibrary: dynamicLibraries) } if generateCMakeLists { @@ -25,9 +30,17 @@ internal func writeProjectionFiles(_ projection: SwiftProjection, directoryPath: } } -fileprivate func writeModuleFiles(_ module: SwiftProjection.Module, directoryPath: String, generateCMakeLists: Bool) throws { +fileprivate func writeModuleFiles( + _ module: SwiftProjection.Module, + directoryPath: String, + generateCMakeLists: Bool, + dynamicLibrary: Bool) throws { try writeABIModule(module, directoryPath: "\(directoryPath)\\ABI", generateCMakeLists: generateCMakeLists) - try writeAssemblyModuleFiles(module, directoryPath: "\(directoryPath)\\Assembly", generateCMakeLists: generateCMakeLists) + + try writeAssemblyModuleFiles( + module, directoryPath: "\(directoryPath)\\Assembly", + generateCMakeLists: generateCMakeLists, dynamicLibrary: dynamicLibrary) + if !module.flattenNamespaces { try writeNamespaceModuleFiles(module, directoryPath: "\(directoryPath)\\Namespaces", generateCMakeLists: generateCMakeLists) } @@ -44,7 +57,9 @@ fileprivate func writeModuleFiles(_ module: SwiftProjection.Module, directoryPat } } -fileprivate func writeAssemblyModuleFiles(_ module: SwiftProjection.Module, directoryPath: String, generateCMakeLists: Bool) throws { +fileprivate func writeAssemblyModuleFiles( + _ module: SwiftProjection.Module, directoryPath: String, + generateCMakeLists: Bool, dynamicLibrary: Bool) throws { var cmakeSources: [String] = [] for typeDefinition in module.typeDefinitions + Array(module.genericInstantiationsByDefinition.keys) { guard try hasSwiftDefinition(typeDefinition) else { continue } @@ -88,7 +103,7 @@ fileprivate func writeAssemblyModuleFiles(_ module: SwiftProjection.Module, dire path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors)) cmakeSources.sort() - writer.writeAddLibrary(module.name, .static, cmakeSources) + writer.writeAddLibrary(module.name, dynamicLibrary ? .shared : .static, cmakeSources) writer.writeTargetLinkLibraries( module.name, .public, [ SupportModules.WinRT.moduleName, module.abiModuleName ] + module.references.map { $0.name }) diff --git a/InteropTests/Package.swift b/InteropTests/Package.swift index f3a4a320..08782d23 100644 --- a/InteropTests/Package.swift +++ b/InteropTests/Package.swift @@ -12,7 +12,8 @@ let package = Package( name: "Tests", dependencies: [ .product(name: "WindowsRuntime", package: "swift-winrt"), - .product(name: "Projection", package: "Generated"), + .product(name: "UWP", package: "Generated"), + .product(name: "WinRTComponent", package: "Generated"), ], path: "Tests", // Workaround for SPM library support limitations causing "LNK4217: locally defined symbol imported" spew