Skip to content

Commit 6bb68a8

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 eb22bcb commit 6bb68a8

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
@@ -71,6 +71,49 @@ public struct ModuleMapGenerator {
7171
self.diagnostics = diagnostics
7272
}
7373

74+
/// Determine the type of module map that applies to the specifed public-headers directory.
75+
public static func determineModuleMapType(includeDir: AbsolutePath, moduleName: String, fileSystem: FileSystem) -> ModuleMapType {
76+
77+
// First check for a custom module map.
78+
let customModuleMap = includeDir.appending(component: moduleMapFilename)
79+
if fileSystem.isFile(customModuleMap) {
80+
return .custom(customModuleMap)
81+
}
82+
83+
// Next try to get the entries in the public-headers directory; if we cannot, we have no module map.
84+
guard let entries = try? fileSystem.getDirectoryContents(includeDir).map({ includeDir.appending(component: $0) }) else {
85+
return .none
86+
}
87+
let headers = entries.filter({ fileSystem.isFile($0) && $0.suffix == ".h" })
88+
let directories = entries.filter({ fileSystem.isDirectory($0) })
89+
90+
// Look for `includeDir/moduleName.h`.
91+
let flatUmbrellaHeader = includeDir.appending(component: moduleName + ".h")
92+
if fileSystem.isFile(flatUmbrellaHeader) {
93+
// In this case we expect no directories next to the header.
94+
if directories.count != 0 {
95+
return .none
96+
}
97+
return .umbrellaHeader(flatUmbrellaHeader)
98+
}
99+
100+
// Look for `includeDir/moduleName/moduleName.h`.
101+
let nestedUmbrellaHeader = includeDir.appending(components: moduleName, moduleName + ".h")
102+
if fileSystem.isFile(nestedUmbrellaHeader) {
103+
// In this case we expect no headers and no other directories next to the directory containing the header.
104+
if directories.count != 1 {
105+
return .none
106+
}
107+
if headers.count != 0 {
108+
return .none
109+
}
110+
return .umbrellaHeader(nestedUmbrellaHeader)
111+
}
112+
113+
// Otherwise, treat the public-headers directory as an umbrella directory anyway, for backward compatibility.
114+
return .umbrellaDirectory(includeDir)
115+
}
116+
74117
/// 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.
75118
public mutating func generateModuleMap(inDir wd: AbsolutePath) throws {
76119
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 = try 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 = (try? ModuleMapGenerator.determineModuleMapType(includeDir: includeDir, moduleName: name.spm_mangledToC99ExtendedIdentifier(), fileSystem: fileSystem)) ?? ModuleMapType.none
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/lib/include/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/inc/module.modulemap")))
623627
}
624628
}
625629
}
@@ -1710,6 +1714,7 @@ class PackageBuilderTests: XCTestCase {
17101714
package.checkModule("lib") { module in
17111715
module.checkSources(root: "/Sources/lib", paths: "lib.c")
17121716
module.check(includeDir: "/Sources/lib/include")
1717+
module.check(moduleMapType: .umbrellaHeader(AbsolutePath("/Sources/lib/include/lib.h")))
17131718
}
17141719
}
17151720
}
@@ -1734,6 +1739,7 @@ class PackageBuilderTests: XCTestCase {
17341739
package.checkModule("lib") { module in
17351740
module.checkSources(root: "/Sources/lib", paths: "movie.mkv", "lib.c")
17361741
module.check(includeDir: "/Sources/lib/include")
1742+
module.check(moduleMapType: .umbrellaHeader(AbsolutePath("/Sources/lib/include/lib.h")))
17371743
}
17381744
}
17391745
}
@@ -2179,6 +2185,13 @@ final class PackageBuilderTester {
21792185
XCTAssertEqual(target.includeDir.pathString, includeDir, file: file, line: line)
21802186
}
21812187

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

0 commit comments

Comments
 (0)