From c243e89c7ecc6c2e4b8012003dbde8c7cb684ae5 Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 23 Sep 2024 06:59:31 -0400 Subject: [PATCH] Support CMake target & SPM library prefixes/suffixes (#318) --- Generator/InteropTests/Package.swift | 4 +- .../WinRTComponent/GenerateProjection.cmake | 4 +- .../CMake/CMakeCommandArgument.swift | 36 +++++++++ .../CodeWriters/CMake/CMakeListsWriter.swift | 61 ++++++++++----- .../SwiftWinRT/BuildSystemOptions.swift | 23 ++++++ .../SwiftWinRT/CommandLineArguments.swift | 14 +++- .../SwiftWinRT/Writing/ABIModule.swift | 14 ++-- .../SwiftWinRT/Writing/SwiftPackageFile.swift | 20 ++--- Generator/Sources/SwiftWinRT/main.swift | 18 +++-- .../SwiftWinRT/writeProjectionFiles.swift | 75 ++++++++++--------- 10 files changed, 190 insertions(+), 79 deletions(-) create mode 100644 Generator/Sources/CodeWriters/CMake/CMakeCommandArgument.swift create mode 100644 Generator/Sources/SwiftWinRT/BuildSystemOptions.swift diff --git a/Generator/InteropTests/Package.swift b/Generator/InteropTests/Package.swift index b863395..5d069af 100644 --- a/Generator/InteropTests/Package.swift +++ b/Generator/InteropTests/Package.swift @@ -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([ diff --git a/Generator/InteropTests/WinRTComponent/GenerateProjection.cmake b/Generator/InteropTests/WinRTComponent/GenerateProjection.cmake index b529f7e..68de598 100644 --- a/Generator/InteropTests/WinRTComponent/GenerateProjection.cmake +++ b/Generator/InteropTests/WinRTComponent/GenerateProjection.cmake @@ -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) diff --git a/Generator/Sources/CodeWriters/CMake/CMakeCommandArgument.swift b/Generator/Sources/CodeWriters/CMake/CMakeCommandArgument.swift new file mode 100644 index 0000000..3738970 --- /dev/null +++ b/Generator/Sources/CodeWriters/CMake/CMakeCommandArgument.swift @@ -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) + } +} \ No newline at end of file diff --git a/Generator/Sources/CodeWriters/CMake/CMakeListsWriter.swift b/Generator/Sources/CodeWriters/CMake/CMakeListsWriter.swift index d79ece9..a999400 100644 --- a/Generator/Sources/CodeWriters/CMake/CMakeListsWriter.swift +++ b/Generator/Sources/CodeWriters/CMake/CMakeListsWriter.swift @@ -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) } } \ No newline at end of file diff --git a/Generator/Sources/SwiftWinRT/BuildSystemOptions.swift b/Generator/Sources/SwiftWinRT/BuildSystemOptions.swift new file mode 100644 index 0000000..6fa2f75 --- /dev/null +++ b/Generator/Sources/SwiftWinRT/BuildSystemOptions.swift @@ -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)" + } +} \ No newline at end of file diff --git a/Generator/Sources/SwiftWinRT/CommandLineArguments.swift b/Generator/Sources/SwiftWinRT/CommandLineArguments.swift index 51f35ef..ff4f120 100644 --- a/Generator/Sources/SwiftWinRT/CommandLineArguments.swift +++ b/Generator/Sources/SwiftWinRT/CommandLineArguments.swift @@ -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 '#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 diff --git a/Generator/Sources/SwiftWinRT/Writing/ABIModule.swift b/Generator/Sources/SwiftWinRT/Writing/ABIModule.swift index 175012c..b0077c7 100644 --- a/Generator/Sources/SwiftWinRT/Writing/ABIModule.swift +++ b/Generator/Sources/SwiftWinRT/Writing/ABIModule.swift @@ -4,7 +4,7 @@ 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" @@ -12,13 +12,15 @@ internal func writeABIModule(_ module: Module, directoryPath: String, generateCM 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) }) } } diff --git a/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift b/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift index a5da516..005ad4d 100644 --- a/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift +++ b/Generator/Sources/SwiftWinRT/Writing/SwiftPackageFile.swift @@ -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 } @@ -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) @@ -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") diff --git a/Generator/Sources/SwiftWinRT/main.swift b/Generator/Sources/SwiftWinRT/main.swift index 71006f9..8844b1f 100644 --- a/Generator/Sources/SwiftWinRT/main.swift +++ b/Generator/Sources/SwiftWinRT/main.swift @@ -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") } diff --git a/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift b/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift index 79cb03d..ab8e3ae 100644 --- a/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift +++ b/Generator/Sources/SwiftWinRT/writeProjectionFiles.swift @@ -6,21 +6,19 @@ import Foundation import ProjectionModel import WindowsMetadata -internal func writeBindingFiles( +internal func writeProjectionFiles( _ projection: Projection, directoryPath: String, - generateCMakeLists: Bool, - dynamicLibraries: Bool) throws { + cmakeOptions: CMakeOptions?) throws { for module in projection.modulesByName.values { guard !module.isEmpty else { continue } print("Generating projection for \(module.name)...") try writeModuleFiles(module, directoryPath: "\(directoryPath)\\\(module.name)", - generateCMakeLists: generateCMakeLists, - dynamicLibrary: dynamicLibraries) + cmakeOptions: cmakeOptions) } - if generateCMakeLists { + if cmakeOptions != nil { let writer = CMakeListsWriter(output: FileTextOutputStream( path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors)) @@ -34,19 +32,16 @@ internal func writeBindingFiles( fileprivate func writeModuleFiles( _ module: Module, directoryPath: String, - generateCMakeLists: Bool, - dynamicLibrary: Bool) throws { - try writeABIModule(module, directoryPath: "\(directoryPath)\\ABI", generateCMakeLists: generateCMakeLists) + cmakeOptions: CMakeOptions?) throws { + try writeABIModule(module, directoryPath: "\(directoryPath)\\ABI", cmakeOptions: cmakeOptions) - try writeSwiftModuleFiles( - module, directoryPath: "\(directoryPath)\\Projection", - generateCMakeLists: generateCMakeLists, dynamicLibrary: dynamicLibrary) + try writeSwiftModuleFiles(module, directoryPath: "\(directoryPath)\\Projection", cmakeOptions: cmakeOptions) if !module.flattenNamespaces { - try writeNamespaceModuleFiles(module, directoryPath: "\(directoryPath)\\Namespaces", generateCMakeLists: generateCMakeLists) + try writeNamespaceModuleFiles(module, directoryPath: "\(directoryPath)\\Namespaces", cmakeOptions: cmakeOptions) } - if generateCMakeLists { + if cmakeOptions != nil { let writer = CMakeListsWriter(output: FileTextOutputStream( path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors)) @@ -58,10 +53,7 @@ fileprivate func writeModuleFiles( } } -fileprivate func writeSwiftModuleFiles( - _ module: Module, directoryPath: String, - generateCMakeLists: Bool, dynamicLibrary: Bool) throws { - var cmakeSources: [String] = [] +fileprivate func writeSwiftModuleFiles(_ module: Module, directoryPath: String, cmakeOptions: CMakeOptions?) throws { for typeDefinition in module.typeDefinitions + Array(module.genericInstantiationsByDefinition.keys) { // All WinRT types should have namespaces guard let namespace = typeDefinition.namespace else { continue } @@ -73,23 +65,19 @@ fileprivate func writeSwiftModuleFiles( // Write the COM interop extensions if typeDefinition is InterfaceDefinition || typeDefinition is DelegateDefinition { let fileName = "SWRT_\(typeName).swift" - if try writeCOMInteropExtensionFile(typeDefinition: typeDefinition, module: module, - toPath: "\(directoryPath)\\\(compactNamespace)\\COMInterop\\\(fileName)") { - cmakeSources.append("\(compactNamespace)/COMInterop/\(fileName)") - } + _ = try writeCOMInteropExtensionFile(typeDefinition: typeDefinition, module: module, + toPath: "\(directoryPath)\\\(compactNamespace)\\COMInterop\\\(fileName)") } guard try hasSwiftDefinition(typeDefinition) else { continue } if module.hasTypeDefinition(typeDefinition) { try writeTypeDefinitionFile(typeDefinition, module: module, toPath: "\(namespaceDirectoryPath)\\\(typeName).swift") - cmakeSources.append("\(compactNamespace)/\(typeName).swift") if let extensionFileBytes = try getExtensionFileBytes(typeDefinition: typeDefinition) { try Data(extensionFileBytes).write(to: URL(fileURLWithPath: "\(namespaceDirectoryPath)\\\(typeName)+extras.swift", isDirectory: false)) - cmakeSources.append("\(compactNamespace)/\(typeName)+extras.swift") } } @@ -100,23 +88,32 @@ fileprivate func writeSwiftModuleFiles( let fileName = "\(typeName)Binding.swift" try writeABIBindingConformanceFile(typeDefinition, module: module, toPath: "\(namespaceDirectoryPath)\\Bindings\\\(fileName)") - cmakeSources.append("\(compactNamespace)/Bindings/\(fileName)") } } - if generateCMakeLists { + if let cmakeOptions { let writer = CMakeListsWriter(output: FileTextOutputStream( path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors)) - cmakeSources.sort() - writer.writeAddLibrary(module.name, dynamicLibrary ? .shared : .static, cmakeSources) - writer.writeTargetLinkLibraries( - module.name, .public, - [ SupportModules.WinRT.moduleName, module.abiModuleName ] + module.references.map { $0.name }) + writer.writeSingleLineCommand("file", "GLOB_RECURSE", "SOURCES", "*.swift") + let targetName = cmakeOptions.getTargetName(moduleName: module.name) + writer.writeSingleLineCommand( + "add_library", + .autoquote(targetName), + .unquoted(cmakeOptions.dynamicLibraries ? "SHARED" : "STATIC"), + .unquoted("${SOURCES}")) + if targetName != module.name { + writer.writeSingleLineCommand( + "set_target_properties", .autoquote(targetName), + "PROPERTIES", "Swift_MODULE_NAME", .autoquote(module.name)) + } + writer.writeTargetLinkLibraries(targetName, .public, + [ cmakeOptions.getTargetName(moduleName: module.abiModuleName), SupportModules.WinRT.moduleName ] + + module.references.map { cmakeOptions.getTargetName(moduleName: $0.name) }) } } -fileprivate func writeNamespaceModuleFiles(_ module: Module, directoryPath: String, generateCMakeLists: Bool) throws { +fileprivate func writeNamespaceModuleFiles(_ module: Module, directoryPath: String, cmakeOptions: CMakeOptions?) throws { let typeDefinitionsByNamespace = Dictionary(grouping: module.typeDefinitions, by: { $0.namespace }) var compactNamespaces: [String] = [] @@ -130,17 +127,23 @@ fileprivate func writeNamespaceModuleFiles(_ module: Module, directoryPath: Stri let namespaceAliasesPath = "\(directoryPath)\\\(compactNamespace)\\Aliases.swift" try writeNamespaceAliasesFile(typeDefinitions: typeDefinitions, module: module, toPath: namespaceAliasesPath) - if generateCMakeLists { + if let cmakeOptions { let writer = CMakeListsWriter(output: FileTextOutputStream( path: "\(directoryPath)\\\(compactNamespace)\\CMakeLists.txt", directoryCreation: .ancestors)) let namespaceModuleName = module.getNamespaceModuleName(namespace: namespace) - writer.writeAddLibrary(namespaceModuleName, .static, ["Aliases.swift"]) - writer.writeTargetLinkLibraries(namespaceModuleName, .public, [module.name]) + let targetName = cmakeOptions.getTargetName(moduleName: namespaceModuleName) + writer.writeAddLibrary(targetName, .static, ["Aliases.swift"]) + if targetName != namespaceModuleName { + writer.writeSingleLineCommand( + "set_target_properties", .unquoted(targetName), + "PROPERTIES", "Swift_MODULE_NAME", .unquoted(namespaceModuleName)) + } + writer.writeTargetLinkLibraries(targetName, .public, [ cmakeOptions.getTargetName(moduleName: module.name) ]) } } - if generateCMakeLists, !compactNamespaces.isEmpty { + if cmakeOptions != nil, !compactNamespaces.isEmpty { let writer = CMakeListsWriter(output: FileTextOutputStream( path: "\(directoryPath)\\CMakeLists.txt", directoryCreation: .ancestors))