Skip to content

Commit

Permalink
Generate Clang module maps during the build
Browse files Browse the repository at this point in the history
This moves generation of module maps for Clang targets into the build process.
  • Loading branch information
neonichu committed Oct 9, 2023
1 parent 46e0ae8 commit d063c9a
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 26 deletions.
5 changes: 4 additions & 1 deletion Package.swift
Expand Up @@ -166,7 +166,10 @@ let package = Package(
.target(
/** The llbuild manifest model */
name: "LLBuildManifest",
dependencies: ["Basics"],
dependencies: [
"Basics",
"PackageLoading",
],
exclude: ["CMakeLists.txt"]
),

Expand Down
14 changes: 3 additions & 11 deletions Sources/Build/BuildDescription/ClangTargetBuildDescription.swift
Expand Up @@ -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.
}
Expand Down
13 changes: 13 additions & 0 deletions Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift
Expand Up @@ -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)
Expand Down
67 changes: 67 additions & 0 deletions Sources/LLBuildManifest/BuildManifest.swift
Expand Up @@ -12,6 +12,7 @@

import Basics
import Foundation
import PackageLoading

import class TSCBasic.Process

Expand All @@ -23,6 +24,7 @@ public protocol AuxiliaryFileType {

public enum WriteAuxiliary {
public static let fileTypes: [AuxiliaryFileType.Type] = [
ClangModuleMap.self,
EntitlementPlist.self,
LinkFileList.self,
SourcesFileList.self,
Expand Down Expand Up @@ -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"

Expand Down Expand Up @@ -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],
Expand Down
15 changes: 3 additions & 12 deletions Sources/PackageLoading/ModuleMapGenerator.swift
Expand Up @@ -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):
Expand All @@ -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
}
}

Expand Down
5 changes: 3 additions & 2 deletions Tests/PackageLoadingTests/ModuleMapGenerationTests.swift
Expand Up @@ -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)
Expand Down

0 comments on commit d063c9a

Please sign in to comment.