Skip to content

Commit

Permalink
Support CMake target & SPM library prefixes/suffixes (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Sep 23, 2024
1 parent 025ea5d commit c243e89
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 79 deletions.
4 changes: 2 additions & 2 deletions Generator/InteropTests/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ let package = Package(
name: "Tests",
dependencies: [
.product(name: "WindowsRuntime", package: "Support"),
.product(name: "UWP", package: "Projection"),
.product(name: "WinRTComponent", package: "Projection"),
.product(name: "SwiftUWP", package: "Projection"),
.product(name: "SwiftWinRTComponent", package: "Projection"),
],
path: "Tests",
linkerSettings: [ .unsafeFlags([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ function(generate_projection)
--reference "${WINRTCOMPONENT_WINMD_NATIVE}"
--spm
--spm-support-package "${SPM_SUPPORT_PACKAGE_DIR_NATIVE}"
--cmakelists
--spm-library-prefix "Swift"
--cmake
--cmake-target-prefix "Swift"
--out "${PROJECTION_DIR_NATIVE}"
--out-manifest "${PROJECTION_DIR_NATIVE}\\WinRTComponent.manifest"
COMMAND_ERROR_IS_FATAL ANY)
Expand Down
36 changes: 36 additions & 0 deletions Generator/Sources/CodeWriters/CMake/CMakeCommandArgument.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
public struct CMakeCommandArgument: ExpressibleByStringLiteral {
public let value: String
public let quoted: Bool

public init(_ value: String, quoted: Bool) {
self.value = value
self.quoted = quoted
}

public init(autoquote value: String) {
assert(!value.contains("${"))
self.init(value, quoted: value.contains(" ") || value.contains(";"))
}

public init(stringLiteral value: String) {
self.init(autoquote: value)
}

public func write(to stream: inout some TextOutputStream) {
if quoted { stream.write("\"") }
stream.write(value.replacingOccurrences(of: "\\", with: "\\\\"))
if quoted { stream.write("\"") }
}

public static func autoquote(_ value: String) -> CMakeCommandArgument {
Self(autoquote: value)
}

public static func quoted(_ value: String) -> CMakeCommandArgument {
Self(value, quoted: true)
}

public static func unquoted(_ value: String) -> CMakeCommandArgument {
Self(value, quoted: false)
}
}
61 changes: 44 additions & 17 deletions Generator/Sources/CodeWriters/CMake/CMakeListsWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,61 @@ public final class CMakeListsWriter {
self.output = .init(inner: output)
}

public func writeAddLibrary(_ name: String, _ type: CMakeLibraryType? = nil, _ sources: [String] = []) {
let typeSuffix = type.map { " \($0.rawValue)" } ?? ""
output.writeIndentedBlock(grouping: .never, header: "add_library(\(name)\(typeSuffix)", footer: ")") {
for source in sources {
output.writeFullLine(source)
public func writeCommand(_ command: String, headerArguments: [CMakeCommandArgument] = [], multilineArguments: [CMakeCommandArgument] = []) {
var output = output // Safe because IndentedTextOutputStream is a class
output.beginLine(grouping: .withName(command))
output.write(command)
output.write("(")
for (index, argument) in headerArguments.enumerated() {
if index > 0 { output.write(" ") }
argument.write(to: &output)
}
if multilineArguments.isEmpty {
output.write(")", endLine: true)
}
else {
output.writeIndentedBlock {
for argument in multilineArguments {
output.beginLine()
argument.write(to: &output)
}
output.write(")")
}
}
}

public func writeSingleLineCommand(_ command: String, _ arguments: [CMakeCommandArgument]) {
writeCommand(command, headerArguments: arguments)
}

public func writeSingleLineCommand(_ command: String, _ arguments: CMakeCommandArgument...) {
writeCommand(command, headerArguments: arguments)
}

public func writeAddLibrary(_ name: String, _ type: CMakeLibraryType? = nil, _ sources: [String] = []) {
var headerArguments: [CMakeCommandArgument] = [ .autoquote(name) ]
if let type { headerArguments.append(.unquoted(type.rawValue)) }
writeCommand("add_library", headerArguments: headerArguments,
multilineArguments: sources.map { .autoquote($0) })
}

public func writeTargetIncludeDirectories(_ target: String, _ visibility: CMakeVisibility, _ directories: [String]) {
guard !directories.isEmpty else { return }
output.writeIndentedBlock(grouping: .never, header: "target_include_directories(\(target) \(visibility.rawValue)", footer: ")") {
for directory in directories {
output.writeFullLine(directory)
}
}
writeCommand("target_include_directories",
headerArguments: [ .autoquote(target), .unquoted(visibility.rawValue) ],
multilineArguments: directories.map { .autoquote($0) })
}

public func writeTargetLinkLibraries(_ target: String, _ visibility: CMakeVisibility, _ libraries: [String]) {
guard !libraries.isEmpty else { return }
output.writeIndentedBlock(grouping: .never, header: "target_link_libraries(\(target) \(visibility.rawValue)", footer: ")") {
for library in libraries {
output.writeFullLine(library)
}
}
writeCommand("target_link_libraries",
headerArguments: [ .autoquote(target), .unquoted(visibility.rawValue) ],
multilineArguments: libraries.map { .autoquote($0) })
}

public func writeAddSubdirectory(_ sources: String) {
output.writeFullLine(grouping: .withName("add_subdirectory"), "add_subdirectory(\(sources))")
public func writeAddSubdirectory(_ source: String, _ binary: String? = nil) {
var args: [CMakeCommandArgument] = [ .autoquote(source) ]
if let binary { args.append(.autoquote(binary)) }
writeSingleLineCommand("add_subdirectory", args)
}
}
23 changes: 23 additions & 0 deletions Generator/Sources/SwiftWinRT/BuildSystemOptions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
struct SPMOptions {
public let supportPackageReference: String
public let libraryPrefix: String
public let librarySuffix: String
public let dynamicLibraries: Bool
public let excludeCMakeLists: Bool

public func getLibraryName(moduleName: String) -> String {
guard !libraryPrefix.isEmpty || !librarySuffix.isEmpty else { return moduleName }
return "\(libraryPrefix)\(moduleName)\(librarySuffix)"
}
}

struct CMakeOptions {
public let targetPrefix: String
public let targetSuffix: String
public let dynamicLibraries: Bool

public func getTargetName(moduleName: String) -> String {
guard !targetPrefix.isEmpty || !targetSuffix.isEmpty else { return moduleName }
return "\(targetPrefix)\(moduleName)\(targetSuffix)"
}
}
14 changes: 13 additions & 1 deletion Generator/Sources/SwiftWinRT/CommandLineArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,24 @@ struct CommandLineArguments: ParsableCommand {
@Flag(name: .customLong("spm"), help: "Generate a package.swift file for building with SPM.")
var generatePackageDotSwift: Bool = false

@Option(name: .customLong("spm-library-prefix"), help: "A prefix to the module name when naming SPM libraries.")
var spmLibraryPrefix: String = ""

@Option(name: .customLong("spm-library-suffix"), help: "A suffix to the module name when naming SPM libraries.")
var spmLibrarySuffix: String = ""

@Option(name: .customLong("spm-support-package"), help: .init("The directory path or '<url>#branch=<branch>' of the support package to reference.", valueName: "dir-or-url"))
var spmSupportPackageReference: String = "https://github.com/tristanlabelle/swift-winrt.git#branch=main"

@Flag(name: .customLong("cmakelists"), help: "Generate a CMakeLists.txt files for building with CMake.")
@Flag(name: .customLong("cmake"), help: "Generate build definitions for the CMake build system.")
var generateCMakeLists: Bool = false

@Option(name: .customLong("cmake-target-prefix"), help: "A prefix to the module name when naming CMake targets.")
var cmakeTargetPrefix: String = ""

@Option(name: .customLong("cmake-target-suffix"), help: "A suffix to the module name when naming CMake targets.")
var cmakeTargetSuffix: String = ""

@Flag(name: .customLong("dylib"), help: "Makes SPM and CMake build definitions specify to build dynamic libraries.")
var dynamicLibraries: Bool = false

Expand Down
14 changes: 8 additions & 6 deletions Generator/Sources/SwiftWinRT/Writing/ABIModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ import DotNetMetadata
import ProjectionModel
import WindowsMetadata

internal func writeABIModule(_ module: Module, directoryPath: String, generateCMakeLists: Bool) throws {
internal func writeABIModule(_ module: Module, directoryPath: String, cmakeOptions: CMakeOptions?) throws {
let includeDirectoryPath = "\(directoryPath)\\include"
let includeSWRTDirectoryPath = "\(includeDirectoryPath)\\SWRT"

try writeABIFile(module: module, toPath: "\(includeSWRTDirectoryPath)\\\(module.name).h")

try writeModulemapFile(module: module, toPath: "\(includeDirectoryPath)\\module.modulemap")

if generateCMakeLists {
if let cmakeOptions {
let cmakeListsWriter = CMakeListsWriter(output: FileTextOutputStream(
path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors))
cmakeListsWriter.writeAddLibrary(module.abiModuleName, .interface)
cmakeListsWriter.writeTargetIncludeDirectories(module.abiModuleName, .interface, ["include"])
cmakeListsWriter.writeTargetLinkLibraries(module.abiModuleName, .interface,
[ SupportModules.WinRT.abiModuleName ] + module.references.map { $0.abiModuleName })
let targetName = cmakeOptions.getTargetName(moduleName: module.abiModuleName)
cmakeListsWriter.writeAddLibrary(targetName, .interface)
cmakeListsWriter.writeTargetIncludeDirectories(targetName, .interface, ["include"])
cmakeListsWriter.writeTargetLinkLibraries(targetName, .interface,
[ SupportModules.WinRT.abiModuleName ]
+ module.references.map { cmakeOptions.getTargetName(moduleName: $0.abiModuleName) })
}
}

Expand Down
20 changes: 10 additions & 10 deletions Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,9 @@ import DotNetMetadata
import ProjectionModel
import struct Foundation.URL

func writeSwiftPackageFile(
_ projection: Projection,
supportPackageReference: String,
excludeCMakeLists: Bool,
dynamicLibraries: Bool,
toPath path: String) {
func writeSwiftPackageFile(_ projection: Projection, spmOptions: SPMOptions, toPath path: String) {
var package = SwiftPackage(name: "Projection")
package.dependencies.append(getSupportPackageDependency(reference: supportPackageReference))
package.dependencies.append(getSupportPackageDependency(reference: spmOptions.supportPackageReference))

for module in projection.modulesByName.values {
guard !module.isEmpty else { continue }
Expand All @@ -37,7 +32,10 @@ func writeSwiftPackageFile(
package.targets.append(projectionModuleTarget)

// Define a product for the module
var moduleProduct: SwiftPackage.Product = .library(name: module.name, type: dynamicLibraries ? .dynamic : nil, targets: [])
var moduleProduct: SwiftPackage.Product = .library(
name: spmOptions.getLibraryName(moduleName: module.name),
type: spmOptions.dynamicLibraries ? .dynamic : nil,
targets: [])
moduleProduct.targets.append(projectionModuleTarget.name)
moduleProduct.targets.append(abiModuleTarget.name)

Expand All @@ -64,10 +62,12 @@ func writeSwiftPackageFile(

// Create products for the projections and the ABI
package.products.append(moduleProduct)
package.products.append(.library(name: module.abiModuleName, type: .static, targets: [abiModuleTarget.name]))
package.products.append(.library(
name: spmOptions.getLibraryName(moduleName: module.abiModuleName),
type: .static, targets: [abiModuleTarget.name]))
}

if excludeCMakeLists {
if spmOptions.excludeCMakeLists {
// Assume every target has a root CMakeLists.txt file
for targetIndex in package.targets.indices {
package.targets[targetIndex].exclude.append("CMakeLists.txt")
Expand Down
18 changes: 12 additions & 6 deletions Generator/Sources/SwiftWinRT/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ do {
commandLineArguments: commandLineArguments,
projectionConfig: projectionConfig,
winMDLoadContext: context)
try writeBindingFiles(projection,

try writeProjectionFiles(projection,
directoryPath: commandLineArguments.outputDirectoryPath,
generateCMakeLists: commandLineArguments.generateCMakeLists,
dynamicLibraries: commandLineArguments.dynamicLibraries)
cmakeOptions: !commandLineArguments.generateCMakeLists ? nil : CMakeOptions(
targetPrefix: commandLineArguments.cmakeTargetPrefix,
targetSuffix: commandLineArguments.cmakeTargetSuffix,
dynamicLibraries: commandLineArguments.dynamicLibraries))

if commandLineArguments.generatePackageDotSwift {
writeSwiftPackageFile(
projection,
supportPackageReference: commandLineArguments.spmSupportPackageReference,
excludeCMakeLists: commandLineArguments.generateCMakeLists,
dynamicLibraries: commandLineArguments.dynamicLibraries,
spmOptions: SPMOptions(
supportPackageReference: commandLineArguments.spmSupportPackageReference,
libraryPrefix: commandLineArguments.spmLibraryPrefix,
librarySuffix: commandLineArguments.spmLibrarySuffix,
dynamicLibraries: commandLineArguments.dynamicLibraries,
excludeCMakeLists: commandLineArguments.generateCMakeLists),
toPath: "\(commandLineArguments.outputDirectoryPath)\\Package.swift")
}

Expand Down
Loading

0 comments on commit c243e89

Please sign in to comment.