Skip to content

Commit 18da011

Browse files
committed
[SR-13235] libSwiftPM should vend a ClangTarget's module map generation info
This is separate from the module map generation for now, but will be reconciled so that the generation will use the same module map information as what's vended to the client. Vending the target's module map layout information provides more semantic information to the client than just vending the raw contents of the generated module map. This semantic information can then (for example) be shown in the user interface of an IDE, etc. rdar://65678318
1 parent 5b204a3 commit 18da011

File tree

5 files changed

+115
-2
lines changed

5 files changed

+115
-2
lines changed

Sources/PackageLoading/ModuleMapGenerator.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,49 @@ public struct ModuleMapGenerator {
7878
self.diagnostics = diagnostics
7979
}
8080

81+
/// Determine the type of module map that applies to the specifed public-headers directory.
82+
public static func determineModuleMapType(includeDir: AbsolutePath, moduleName: String, fileSystem: FileSystem) -> ModuleMapType {
83+
84+
// First check for a custom module map.
85+
let customModuleMap = includeDir.appending(component: moduleMapFilename)
86+
if fileSystem.isFile(customModuleMap) {
87+
return .custom(customModuleMap)
88+
}
89+
90+
// Next try to get the entries in the public-headers directory; if we cannot, we have no module map.
91+
guard let entries = try? fileSystem.getDirectoryContents(includeDir).map({ includeDir.appending(component: $0) }) else {
92+
return .none
93+
}
94+
let headers = entries.filter({ fileSystem.isFile($0) && $0.suffix == ".h" })
95+
let directories = entries.filter({ fileSystem.isDirectory($0) })
96+
97+
// Look for `includeDir/moduleName.h`.
98+
let flatUmbrellaHeader = includeDir.appending(component: moduleName + ".h")
99+
if fileSystem.isFile(flatUmbrellaHeader) {
100+
// In this case we expect no directories next to the header.
101+
if directories.count != 0 {
102+
return .none
103+
}
104+
return .umbrellaHeader(flatUmbrellaHeader)
105+
}
106+
107+
// Look for `includeDir/moduleName/moduleName.h`.
108+
let nestedUmbrellaHeader = includeDir.appending(components: moduleName, moduleName + ".h")
109+
if fileSystem.isFile(nestedUmbrellaHeader) {
110+
// In this case we expect no headers and no other directories next to the directory containing the header.
111+
if directories.count != 1 {
112+
return .none
113+
}
114+
if headers.count != 0 {
115+
return .none
116+
}
117+
return .umbrellaHeader(nestedUmbrellaHeader)
118+
}
119+
120+
// Otherwise, treat the public-headers directory as an umbrella directory anyway, for backward compatibility.
121+
return .umbrellaDirectory(includeDir)
122+
}
123+
81124
/// Generates a module map based on the layout of the target's public headers, or does nothing if an error occurs or no module map is appropriate. Any diagnostics are added to the receiver's diagnostics engine.
82125
public mutating func generateModuleMap(inDir wd: AbsolutePath) throws {
83126
assert(target.type == .library)

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,8 @@ public final class PackageBuilder {
742742
buildSettings: buildSettings
743743
)
744744
} else {
745+
let moduleMapType = ModuleMapGenerator.determineModuleMapType(includeDir: publicHeadersPath, moduleName: potentialModule.name.spm_mangledToC99ExtendedIdentifier(), fileSystem: fileSystem)
746+
745747
return ClangTarget(
746748
name: potentialModule.name,
747749
bundleName: bundleName,
@@ -750,6 +752,7 @@ public final class PackageBuilder {
750752
cLanguageStandard: manifest.cLanguageStandard,
751753
cxxLanguageStandard: manifest.cxxLanguageStandard,
752754
includeDir: publicHeadersPath,
755+
moduleMapType: moduleMapType,
753756
headers: headers,
754757
isTest: potentialModule.isTest,
755758
sources: sources,

Sources/PackageModel/Target.swift

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ public class ClangTarget: Target {
356356

357357
/// The path to include directory.
358358
public let includeDir: AbsolutePath
359+
360+
/// The target's module map type; determined by the file system layout, and controls whether the build system will use a custom module map, generate a module map, etc.
361+
public let moduleMapType: ModuleMapType
359362

360363
/// The headers present in the target.
361364
///
@@ -379,6 +382,7 @@ public class ClangTarget: Target {
379382
cLanguageStandard: String?,
380383
cxxLanguageStandard: String?,
381384
includeDir: AbsolutePath,
385+
moduleMapType: ModuleMapType,
382386
headers: [AbsolutePath] = [],
383387
isTest: Bool = false,
384388
sources: Sources,
@@ -392,6 +396,7 @@ public class ClangTarget: Target {
392396
self.cLanguageStandard = cLanguageStandard
393397
self.cxxLanguageStandard = cxxLanguageStandard
394398
self.includeDir = includeDir
399+
self.moduleMapType = moduleMapType
395400
self.headers = headers
396401
super.init(
397402
name: name,
@@ -407,12 +412,13 @@ public class ClangTarget: Target {
407412
}
408413

409414
private enum CodingKeys: String, CodingKey {
410-
case includeDir, headers, isCXX, cLanguageStandard, cxxLanguageStandard
415+
case includeDir, moduleMapType, headers, isCXX, cLanguageStandard, cxxLanguageStandard
411416
}
412417

413418
public override func encode(to encoder: Encoder) throws {
414419
var container = encoder.container(keyedBy: CodingKeys.self)
415420
try container.encode(includeDir, forKey: .includeDir)
421+
try container.encode(moduleMapType, forKey: .moduleMapType)
416422
try container.encode(headers, forKey: .headers)
417423
try container.encode(isCXX, forKey: .isCXX)
418424
try container.encode(cLanguageStandard, forKey: .cLanguageStandard)
@@ -423,6 +429,7 @@ public class ClangTarget: Target {
423429
required public init(from decoder: Decoder) throws {
424430
let container = try decoder.container(keyedBy: CodingKeys.self)
425431
self.includeDir = try container.decode(AbsolutePath.self, forKey: .includeDir)
432+
self.moduleMapType = try container.decode(ModuleMapType.self, forKey: .moduleMapType)
426433
self.headers = try container.decode([AbsolutePath].self, forKey: .headers)
427434
self.isCXX = try container.decode(Bool.self, forKey: .isCXX)
428435
self.cLanguageStandard = try container.decodeIfPresent(String.self, forKey: .cLanguageStandard)
@@ -486,6 +493,52 @@ public class BinaryTarget: Target {
486493
}
487494
}
488495

496+
/// A type of module map layout. Contains all the information needed to generate or use a module map for a target that can have C-style headers.
497+
public enum ModuleMapType: Equatable, Codable {
498+
/// No supported module map layout.
499+
case none
500+
/// A custom module map file.
501+
case custom(AbsolutePath)
502+
/// An umbrella header to be included in the module map.
503+
case umbrellaHeader(AbsolutePath)
504+
/// An umbrella directory to be included in the module map.
505+
case umbrellaDirectory(AbsolutePath)
506+
507+
private enum CodingKeys: String, CodingKey {
508+
case none, custom, umbrellaHeader, umbrellaDirectory
509+
}
510+
511+
public init(from decoder: Decoder) throws {
512+
let container = try decoder.container(keyedBy: CodingKeys.self)
513+
if let path = try container.decodeIfPresent(AbsolutePath.self, forKey: .custom) {
514+
self = .custom(path)
515+
}
516+
else if let path = try container.decodeIfPresent(AbsolutePath.self, forKey: .umbrellaHeader) {
517+
self = .umbrellaHeader(path)
518+
}
519+
else if let path = try container.decodeIfPresent(AbsolutePath.self, forKey: .umbrellaDirectory) {
520+
self = .umbrellaDirectory(path)
521+
}
522+
else {
523+
self = .none
524+
}
525+
}
526+
527+
public func encode(to encoder: Encoder) throws {
528+
var container = encoder.container(keyedBy: CodingKeys.self)
529+
switch self {
530+
case .none:
531+
break
532+
case .custom(let path):
533+
try container.encode(path, forKey: .custom)
534+
case .umbrellaHeader(let path):
535+
try container.encode(path, forKey: .umbrellaHeader)
536+
case .umbrellaDirectory(let path):
537+
try container.encode(path, forKey: .umbrellaDirectory)
538+
}
539+
}
540+
}
541+
489542
extension Target: CustomStringConvertible {
490543
public var description: String {
491544
return "<\(Swift.type(of: self)): \(name)>"

Tests/PackageLoadingTests/ModuleMapGenerationTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ class ModuleMapGeneration: XCTestCase {
146146

147147
func ModuleMapTester(_ name: String, includeDir: String = "include", in fileSystem: FileSystem, _ body: (ModuleMapResult) -> Void) {
148148
let includeDir = AbsolutePath.root.appending(component: includeDir)
149-
let target = ClangTarget(name: name, cLanguageStandard: nil, cxxLanguageStandard: nil, includeDir: includeDir, isTest: false, sources: Sources(paths: [], root: .root))
149+
let moduleMapType = ModuleMapGenerator.determineModuleMapType(includeDir: includeDir, moduleName: name.spm_mangledToC99ExtendedIdentifier(), fileSystem: fileSystem)
150+
let target = ClangTarget(name: name, cLanguageStandard: nil, cxxLanguageStandard: nil, includeDir: includeDir, moduleMapType: moduleMapType, isTest: false, sources: Sources(paths: [], root: .root))
150151
let diagnostics = DiagnosticsEngine()
151152
var generator = ModuleMapGenerator(for: target, fileSystem: fileSystem, diagnostics: diagnostics)
152153
do {

Tests/PackageLoadingTests/PackageBuilderTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ class PackageBuilderTests: XCTestCase {
211211
package.checkModule("clib") { module in
212212
module.check(c99name: "clib", type: .library)
213213
module.checkSources(root: "/Sources/clib", paths: "clib.c")
214+
module.check(moduleMapType: .custom(AbsolutePath("/Sources/clib/include/module.modulemap")))
214215
}
215216
}
216217
}
@@ -242,6 +243,7 @@ class PackageBuilderTests: XCTestCase {
242243
package.checkModule("clib") { module in
243244
module.check(c99name: "clib", type: .library)
244245
module.checkSources(root: "/Sources", paths: "clib/clib.c", "clib/clib2.c", "clib/nested/nested.c")
246+
module.check(moduleMapType: .umbrellaHeader(AbsolutePath("/Sources/clib/clib.h")))
245247
}
246248
}
247249
}
@@ -614,12 +616,14 @@ class PackageBuilderTests: XCTestCase {
614616
module.check(c99name: "Foo", type: .library)
615617
module.checkSources(root: "/Sources/Foo", paths: "Foo.c")
616618
module.check(includeDir: "/Sources/Foo/inc")
619+
module.check(moduleMapType: .custom(AbsolutePath("/Sources/Foo/inc/module.modulemap")))
617620
}
618621

619622
package.checkModule("Bar") { module in
620623
module.check(c99name: "Bar", type: .library)
621624
module.checkSources(root: "/Sources/Bar", paths: "Bar.c")
622625
module.check(includeDir: "/Sources/Bar/include")
626+
module.check(moduleMapType: .custom(AbsolutePath("/Sources/Bar/include/module.modulemap")))
623627
}
624628
}
625629
}
@@ -1712,6 +1716,7 @@ class PackageBuilderTests: XCTestCase {
17121716
package.checkModule("lib") { module in
17131717
module.checkSources(root: "/Sources/lib", paths: "lib.c")
17141718
module.check(includeDir: "/Sources/lib/include")
1719+
module.check(moduleMapType: .umbrellaHeader(AbsolutePath("/Sources/lib/include/lib.h")))
17151720
}
17161721
}
17171722
}
@@ -1736,6 +1741,7 @@ class PackageBuilderTests: XCTestCase {
17361741
package.checkModule("lib") { module in
17371742
module.checkSources(root: "/Sources/lib", paths: "movie.mkv", "lib.c")
17381743
module.check(includeDir: "/Sources/lib/include")
1744+
module.check(moduleMapType: .umbrellaHeader(AbsolutePath("/Sources/lib/include/lib.h")))
17391745
}
17401746
}
17411747
}
@@ -2181,6 +2187,13 @@ final class PackageBuilderTester {
21812187
XCTAssertEqual(target.includeDir.pathString, includeDir, file: file, line: line)
21822188
}
21832189

2190+
func check(moduleMapType: ModuleMapType, file: StaticString = #file, line: UInt = #line) {
2191+
guard case let target as ClangTarget = target else {
2192+
return XCTFail("Module map type is being checked on a non-Clang target", file: file, line: line)
2193+
}
2194+
XCTAssertEqual(target.moduleMapType, moduleMapType, file: file, line: line)
2195+
}
2196+
21842197
func check(c99name: String? = nil, type: PackageModel.Target.Kind? = nil, file: StaticString = #file, line: UInt = #line) {
21852198
if let c99name = c99name {
21862199
XCTAssertEqual(target.c99name, c99name, file: file, line: line)

0 commit comments

Comments
 (0)