diff --git a/Package.swift b/Package.swift index 55ed7a4ecbf..1d6801defdf 100644 --- a/Package.swift +++ b/Package.swift @@ -166,7 +166,10 @@ let package = Package( .target( /** The llbuild manifest model */ name: "LLBuildManifest", - dependencies: ["Basics"], + dependencies: [ + "Basics", + "PackageLoading", + ], exclude: ["CMakeLists.txt"] ), diff --git a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift index c169e48d26c..674f75be040 100644 --- a/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift +++ b/Sources/Build/BuildDescription/ClangTargetBuildDescription.swift @@ -155,17 +155,9 @@ public final class ClangTargetBuildDescription { if case .custom(let path) = clangTarget.moduleMapType { self.moduleMap = path } - // If a generated module map is needed, generate one now in our temporary directory. - else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType { - let path = tempsPath.appending(component: moduleMapFilename) - let moduleMapGenerator = ModuleMapGenerator( - targetName: clangTarget.name, - moduleName: clangTarget.c99name, - publicHeadersDir: clangTarget.includeDir, - fileSystem: fileSystem - ) - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: path) - self.moduleMap = path + // If a generated module map is needed, set the path accordingly. + else if clangTarget.moduleMapType.generatedModuleMapType != nil { + self.moduleMap = tempsPath.appending(component: moduleMapFilename) } // Otherwise there is no module map, and we leave `moduleMap` unset. } diff --git a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift index f5626357d4f..74427be0f41 100644 --- a/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift +++ b/Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift @@ -36,6 +36,19 @@ extension LLBuildManifestBuilder { inputs.append(resourcesNode) } + // If the given target needs a generated module map, set up the dependency and required task to write out the module map. + if let type = target.clangTarget.moduleMapType.generatedModuleMapType, let moduleMapPath = target.moduleMap { + inputs.append(.file(moduleMapPath)) + + self.manifest.addWriteClangModuleMapCommand( + targetName: target.clangTarget.name, + moduleName: target.clangTarget.c99name, + publicHeadersDir: target.clangTarget.includeDir, + type: type, + outputPath: moduleMapPath + ) + } + func addStaticTargetInputs(_ target: ResolvedTarget) { if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library { inputs.append(file: desc.moduleOutputPath) diff --git a/Sources/LLBuildManifest/BuildManifest.swift b/Sources/LLBuildManifest/BuildManifest.swift index 4a84be4c0f9..d264900d58f 100644 --- a/Sources/LLBuildManifest/BuildManifest.swift +++ b/Sources/LLBuildManifest/BuildManifest.swift @@ -12,6 +12,7 @@ import Basics import Foundation +import PackageLoading import class TSCBasic.Process @@ -23,6 +24,7 @@ public protocol AuxiliaryFileType { public enum WriteAuxiliary { public static let fileTypes: [AuxiliaryFileType.Type] = [ + ClangModuleMap.self, EntitlementPlist.self, LinkFileList.self, SourcesFileList.self, @@ -54,6 +56,53 @@ public enum WriteAuxiliary { } } + public struct ClangModuleMap: AuxiliaryFileType { + public static let name = "modulemap" + + private enum GeneratedModuleMapType: String { + case umbrellaDirectory + case umbrellaHeader + } + + public static func computeInputs(targetName: String, moduleName: String, publicHeadersDir: AbsolutePath, type: PackageLoading.GeneratedModuleMapType) -> [Node] { + let typeNodes: [Node] + switch type { + case .umbrellaDirectory(let path): + typeNodes = [.virtual(GeneratedModuleMapType.umbrellaDirectory.rawValue), .directory(path)] + case .umbrellaHeader(let path): + typeNodes = [.virtual(GeneratedModuleMapType.umbrellaHeader.rawValue), .file(path)] + } + return [.virtual(Self.name), .virtual(targetName), .virtual(moduleName), .directory(publicHeadersDir)] + typeNodes + } + + public static func getFileContents(inputs: [Node]) throws -> String { + guard inputs.count == 5 else { + throw StringError("invalid module map generation task, inputs: \(inputs)") + } + + let generator = ModuleMapGenerator( + targetName: inputs[0].extractedVirtualNodeName, + moduleName: inputs[1].extractedVirtualNodeName, + publicHeadersDir: try AbsolutePath(validating: inputs[2].name), + fileSystem: localFileSystem + ) + + let declaredType = inputs[3].extractedVirtualNodeName + let path = try AbsolutePath(validating: inputs[4].name) + let type: PackageLoading.GeneratedModuleMapType + switch declaredType { + case GeneratedModuleMapType.umbrellaDirectory.rawValue: + type = .umbrellaDirectory(path) + case GeneratedModuleMapType.umbrellaHeader.rawValue: + type = .umbrellaHeader(path) + default: + throw StringError("invalid module map type in generation task: \(declaredType)") + } + + return try generator.generateModuleMap(type: type) + } + } + public struct LinkFileList: AuxiliaryFileType { public static let name = "link-file-list" @@ -280,6 +329,24 @@ public struct BuildManifest { commands[name] = Command(name: name, tool: tool) } + public mutating func addWriteClangModuleMapCommand( + targetName: String, + moduleName: String, + publicHeadersDir: AbsolutePath, + type: GeneratedModuleMapType, + outputPath: AbsolutePath + ) { + let inputs = WriteAuxiliary.ClangModuleMap.computeInputs( + targetName: targetName, + moduleName: moduleName, + publicHeadersDir: publicHeadersDir, + type: type + ) + let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath) + let name = outputPath.pathString + commands[name] = Command(name: name, tool: tool) + } + public mutating func addPkgStructureCmd( name: String, inputs: [Node], diff --git a/Sources/PackageLoading/ModuleMapGenerator.swift b/Sources/PackageLoading/ModuleMapGenerator.swift index 4b15d1c3883..24202d3713b 100644 --- a/Sources/PackageLoading/ModuleMapGenerator.swift +++ b/Sources/PackageLoading/ModuleMapGenerator.swift @@ -173,8 +173,8 @@ public struct ModuleMapGenerator { return .umbrellaDirectory(publicHeadersDir) } - /// Generates a module map based of the specified type, throwing an error if anything goes wrong. Any diagnostics are added to the receiver's diagnostics engine. - public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath) throws { + /// Generates a module map based of the specified type, throwing an error if anything goes wrong. + public func generateModuleMap(type: GeneratedModuleMapType) throws -> String { var moduleMap = "module \(moduleName) {\n" switch type { case .umbrellaHeader(let hdr): @@ -189,16 +189,7 @@ public struct ModuleMapGenerator { """ ) - - // FIXME: This doesn't belong here. - try fileSystem.createDirectory(path.parentDirectory, recursive: true) - - // If the file exists with the identical contents, we don't need to rewrite it. - // Otherwise, compiler will recompile even if nothing else has changed. - if let contents = try? fileSystem.readFileContents(path).validDescription, contents == moduleMap { - return - } - try fileSystem.writeFileContents(path, string: moduleMap) + return moduleMap } } diff --git a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift index 80834a55e78..257a9e9c53e 100644 --- a/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift +++ b/Tests/PackageLoadingTests/ModuleMapGenerationTests.swift @@ -190,10 +190,11 @@ func ModuleMapTester(_ targetName: String, includeDir: String = "include", in fi let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap") observability.topScope.trap { if let generatedModuleMapType = moduleMapType.generatedModuleMapType { - try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath) + let contents = try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType) + try fileSystem.writeIfChanged(path: generatedModuleMapPath, string: contents) } } - + // Invoke the closure to check the results. let result = ModuleMapResult(diagnostics: observability.diagnostics, path: generatedModuleMapPath, fs: fileSystem) body(result)