Skip to content

Commit d210502

Browse files
committed
Generate Clang module maps during the build
This moves generation of module maps for Clang targets into the build process.
1 parent 0cc531b commit d210502

11 files changed

+134
-54
lines changed

Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ let package = Package(
166166
.target(
167167
/** The llbuild manifest model */
168168
name: "LLBuildManifest",
169-
dependencies: ["Basics"],
169+
dependencies: [
170+
"Basics",
171+
"PackageLoading",
172+
],
170173
exclude: ["CMakeLists.txt"]
171174
),
172175

Sources/Build/BuildDescription/ClangTargetBuildDescription.swift

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,17 +155,9 @@ public final class ClangTargetBuildDescription {
155155
if case .custom(let path) = clangTarget.moduleMapType {
156156
self.moduleMap = path
157157
}
158-
// If a generated module map is needed, generate one now in our temporary directory.
159-
else if let generatedModuleMapType = clangTarget.moduleMapType.generatedModuleMapType {
160-
let path = tempsPath.appending(component: moduleMapFilename)
161-
let moduleMapGenerator = ModuleMapGenerator(
162-
targetName: clangTarget.name,
163-
moduleName: clangTarget.c99name,
164-
publicHeadersDir: clangTarget.includeDir,
165-
fileSystem: fileSystem
166-
)
167-
try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: path)
168-
self.moduleMap = path
158+
// If a generated module map is needed, set the path accordingly.
159+
else if clangTarget.moduleMapType.generatedModuleMapType != nil {
160+
self.moduleMap = tempsPath.appending(component: moduleMapFilename)
169161
}
170162
// Otherwise there is no module map, and we leave `moduleMap` unset.
171163
}

Sources/Build/BuildManifest/LLBuildManifestBuilder+Clang.swift

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,30 @@ extension LLBuildManifestBuilder {
3636
inputs.append(resourcesNode)
3737
}
3838

39+
var modulesReadyInputs = [Node]()
40+
41+
// If the given target needs a generated module map, set up the dependency and required task to write out the module map.
42+
if let type = target.clangTarget.moduleMapType.generatedModuleMapType, let moduleMapPath = target.moduleMap {
43+
modulesReadyInputs.append(.file(moduleMapPath))
44+
45+
self.manifest.addWriteClangModuleMapCommand(
46+
targetName: target.clangTarget.name,
47+
moduleName: target.clangTarget.c99name,
48+
publicHeadersDir: target.clangTarget.includeDir,
49+
type: type,
50+
outputPath: moduleMapPath
51+
)
52+
}
53+
54+
let modulesReady = self.addPhonyCommand(
55+
targetName: target.target.getLLBuildModulesReadyCmdName(config: self.buildConfig),
56+
inputs: modulesReadyInputs
57+
)
58+
inputs.append(modulesReady)
59+
3960
func addStaticTargetInputs(_ target: ResolvedTarget) {
61+
inputs.append(.virtual(target.getLLBuildModulesReadyCmdName(config: self.buildConfig)))
62+
4063
if case .swift(let desc)? = self.plan.targetMap[target], target.type == .library {
4164
inputs.append(file: desc.moduleOutputPath)
4265
}
@@ -116,14 +139,9 @@ extension LLBuildManifestBuilder {
116139
try addBuildToolPlugins(.clang(target))
117140

118141
// Create a phony node to represent the entire target.
119-
let targetName = target.target.getLLBuildTargetName(config: self.buildConfig)
120-
let output: Node = .virtual(targetName)
121-
122-
self.manifest.addNode(output, toTarget: targetName)
123-
self.manifest.addPhonyCmd(
124-
name: output.name,
125-
inputs: objectFileNodes,
126-
outputs: [output]
142+
let output = self.addPhonyCommand(
143+
targetName: target.target.getLLBuildTargetName(config: self.buildConfig),
144+
inputs: objectFileNodes
127145
)
128146

129147
if self.plan.graph.isInRootPackages(target.target, satisfying: self.buildEnvironment) {

Sources/Build/BuildManifest/LLBuildManifestBuilder+Product.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,9 @@ extension LLBuildManifestBuilder {
5959
}
6060

6161
// Create a phony node to represent the entire target.
62-
let targetName = try buildProduct.product.getLLBuildTargetName(config: self.buildConfig)
63-
let output: Node = .virtual(targetName)
64-
65-
self.manifest.addNode(output, toTarget: targetName)
66-
try self.manifest.addPhonyCmd(
67-
name: output.name,
68-
inputs: [.file(buildProduct.binaryPath)],
69-
outputs: [output]
62+
let output = try self.addPhonyCommand(
63+
targetName: try buildProduct.product.getLLBuildTargetName(config: self.buildConfig),
64+
inputs: [.file(buildProduct.binaryPath)]
7065
)
7166

7267
if self.plan.graph.reachableProducts.contains(buildProduct.product) {

Sources/Build/BuildManifest/LLBuildManifestBuilder+Resources.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ extension LLBuildManifestBuilder {
4545
outputs.append(output)
4646
}
4747

48-
let cmdName = target.target.getLLBuildResourcesCmdName(config: self.buildConfig)
49-
self.manifest.addPhonyCmd(name: cmdName, inputs: outputs, outputs: [.virtual(cmdName)])
50-
51-
return .virtual(cmdName)
48+
return self.addPhonyCommand(
49+
targetName: target.target.getLLBuildResourcesCmdName(config: self.buildConfig),
50+
inputs: outputs
51+
)
5252
}
5353
}

Sources/Build/BuildManifest/LLBuildManifestBuilder+Swift.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -481,15 +481,11 @@ extension LLBuildManifestBuilder {
481481
/// Adds a top-level phony command that builds the entire target.
482482
private func addTargetCmd(_ target: SwiftTargetBuildDescription, cmdOutputs: [Node]) {
483483
// Create a phony node to represent the entire target.
484-
let targetName = target.target.getLLBuildTargetName(config: self.buildConfig)
485-
let targetOutput: Node = .virtual(targetName)
486-
487-
self.manifest.addNode(targetOutput, toTarget: targetName)
488-
self.manifest.addPhonyCmd(
489-
name: targetOutput.name,
490-
inputs: cmdOutputs,
491-
outputs: [targetOutput]
484+
let targetOutput = self.addPhonyCommand(
485+
targetName: target.target.getLLBuildTargetName(config: self.buildConfig),
486+
inputs: cmdOutputs
492487
)
488+
493489
if self.plan.graph.isInRootPackages(target.target, satisfying: self.buildEnvironment) {
494490
if !target.isTestTarget {
495491
self.addNode(targetOutput, toTarget: .main)

Sources/Build/BuildManifest/LLBuildManifestBuilder.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ extension ResolvedTarget {
285285
public func getLLBuildResourcesCmdName(config: String) -> String {
286286
"\(name)-\(config).module-resources"
287287
}
288+
289+
public func getLLBuildModulesReadyCmdName(config: String) -> String {
290+
"\(name)-\(config).modules-ready"
291+
}
288292
}
289293

290294
extension ResolvedProduct {
@@ -338,6 +342,18 @@ extension LLBuildManifestBuilder {
338342
func destinationPath(forBinaryAt path: AbsolutePath) -> AbsolutePath {
339343
self.plan.buildParameters.buildPath.appending(component: path.basename)
340344
}
345+
346+
/// Adds a phony command and a corresponding virtual node as output.
347+
func addPhonyCommand(targetName: String, inputs: [Node]) -> Node {
348+
let output: Node = .virtual(targetName)
349+
self.manifest.addNode(output, toTarget: targetName)
350+
self.manifest.addPhonyCmd(
351+
name: output.name,
352+
inputs: inputs,
353+
outputs: [output]
354+
)
355+
return output
356+
}
341357
}
342358

343359
extension Sequence where Element: Hashable {

Sources/LLBuildManifest/BuildManifest.swift

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import Basics
1414
import Foundation
15+
import PackageLoading
1516

1617
import class TSCBasic.Process
1718

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

2425
public enum WriteAuxiliary {
2526
public static let fileTypes: [AuxiliaryFileType.Type] = [
27+
ClangModuleMap.self,
2628
EntitlementPlist.self,
2729
LinkFileList.self,
2830
SourcesFileList.self,
@@ -54,6 +56,53 @@ public enum WriteAuxiliary {
5456
}
5557
}
5658

59+
public struct ClangModuleMap: AuxiliaryFileType {
60+
public static let name = "modulemap"
61+
62+
private enum GeneratedModuleMapType: String {
63+
case umbrellaDirectory
64+
case umbrellaHeader
65+
}
66+
67+
public static func computeInputs(targetName: String, moduleName: String, publicHeadersDir: AbsolutePath, type: PackageLoading.GeneratedModuleMapType) -> [Node] {
68+
let typeNodes: [Node]
69+
switch type {
70+
case .umbrellaDirectory(let path):
71+
typeNodes = [.virtual(GeneratedModuleMapType.umbrellaDirectory.rawValue), .directory(path)]
72+
case .umbrellaHeader(let path):
73+
typeNodes = [.virtual(GeneratedModuleMapType.umbrellaHeader.rawValue), .file(path)]
74+
}
75+
return [.virtual(Self.name), .virtual(targetName), .virtual(moduleName), .directory(publicHeadersDir)] + typeNodes
76+
}
77+
78+
public static func getFileContents(inputs: [Node]) throws -> String {
79+
guard inputs.count == 5 else {
80+
throw StringError("invalid module map generation task, inputs: \(inputs)")
81+
}
82+
83+
let generator = ModuleMapGenerator(
84+
targetName: inputs[0].extractedVirtualNodeName,
85+
moduleName: inputs[1].extractedVirtualNodeName,
86+
publicHeadersDir: try AbsolutePath(validating: inputs[2].name),
87+
fileSystem: localFileSystem
88+
)
89+
90+
let declaredType = inputs[3].extractedVirtualNodeName
91+
let path = try AbsolutePath(validating: inputs[4].name)
92+
let type: PackageLoading.GeneratedModuleMapType
93+
switch declaredType {
94+
case GeneratedModuleMapType.umbrellaDirectory.rawValue:
95+
type = .umbrellaDirectory(path)
96+
case GeneratedModuleMapType.umbrellaHeader.rawValue:
97+
type = .umbrellaHeader(path)
98+
default:
99+
throw StringError("invalid module map type in generation task: \(declaredType)")
100+
}
101+
102+
return try generator.generateModuleMap(type: type)
103+
}
104+
}
105+
57106
public struct LinkFileList: AuxiliaryFileType {
58107
public static let name = "link-file-list"
59108

@@ -280,6 +329,24 @@ public struct BuildManifest {
280329
commands[name] = Command(name: name, tool: tool)
281330
}
282331

332+
public mutating func addWriteClangModuleMapCommand(
333+
targetName: String,
334+
moduleName: String,
335+
publicHeadersDir: AbsolutePath,
336+
type: GeneratedModuleMapType,
337+
outputPath: AbsolutePath
338+
) {
339+
let inputs = WriteAuxiliary.ClangModuleMap.computeInputs(
340+
targetName: targetName,
341+
moduleName: moduleName,
342+
publicHeadersDir: publicHeadersDir,
343+
type: type
344+
)
345+
let tool = WriteAuxiliaryFile(inputs: inputs, outputFilePath: outputPath)
346+
let name = outputPath.pathString
347+
commands[name] = Command(name: name, tool: tool)
348+
}
349+
283350
public mutating func addPkgStructureCmd(
284351
name: String,
285352
inputs: [Node],

Sources/LLBuildManifest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_library(LLBuildManifest STATIC
1515
Tools.swift)
1616
target_link_libraries(LLBuildManifest PUBLIC
1717
TSCBasic
18+
PackageLoading
1819
Basics)
1920

2021
# NOTE(compnerd) workaround for CMake not setting up include flags yet

Sources/PackageLoading/ModuleMapGenerator.swift

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,8 @@ public struct ModuleMapGenerator {
173173
return .umbrellaDirectory(publicHeadersDir)
174174
}
175175

176-
/// 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.
177-
public func generateModuleMap(type: GeneratedModuleMapType, at path: AbsolutePath) throws {
176+
/// Generates a module map based of the specified type, throwing an error if anything goes wrong.
177+
public func generateModuleMap(type: GeneratedModuleMapType) throws -> String {
178178
var moduleMap = "module \(moduleName) {\n"
179179
switch type {
180180
case .umbrellaHeader(let hdr):
@@ -189,16 +189,7 @@ public struct ModuleMapGenerator {
189189
190190
"""
191191
)
192-
193-
// FIXME: This doesn't belong here.
194-
try fileSystem.createDirectory(path.parentDirectory, recursive: true)
195-
196-
// If the file exists with the identical contents, we don't need to rewrite it.
197-
// Otherwise, compiler will recompile even if nothing else has changed.
198-
if let contents = try? fileSystem.readFileContents(path).validDescription, contents == moduleMap {
199-
return
200-
}
201-
try fileSystem.writeFileContents(path, string: moduleMap)
192+
return moduleMap
202193
}
203194
}
204195

Tests/PackageLoadingTests/ModuleMapGenerationTests.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,11 @@ func ModuleMapTester(_ targetName: String, includeDir: String = "include", in fi
190190
let generatedModuleMapPath = AbsolutePath.root.appending(components: "module.modulemap")
191191
observability.topScope.trap {
192192
if let generatedModuleMapType = moduleMapType.generatedModuleMapType {
193-
try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType, at: generatedModuleMapPath)
193+
let contents = try moduleMapGenerator.generateModuleMap(type: generatedModuleMapType)
194+
try fileSystem.writeIfChanged(path: generatedModuleMapPath, string: contents)
194195
}
195196
}
196-
197+
197198
// Invoke the closure to check the results.
198199
let result = ModuleMapResult(diagnostics: observability.diagnostics, path: generatedModuleMapPath, fs: fileSystem)
199200
body(result)

0 commit comments

Comments
 (0)