Skip to content

Commit 56ba416

Browse files
authored
Align PIF generation for user-authored modulemaps with native build system (#9295)
Swift Build was relying on hand-authored module maps being discovered in include directories, while the old build system always passed them using -fmodule-map-file as well. This isn't ideal, but packages like Foundation rely on this to avoid clashes with modules that ship in the toolchain, so for now we align the behavior for compatibility with existing code. Closes #9290
1 parent d9bcb45 commit 56ba416

File tree

8 files changed

+86
-28
lines changed

8 files changed

+86
-28
lines changed

Fixtures/CFamilyTargets/ModuleMapGenerationCases/Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ let package = Package(
66
targets: [
77
.target(
88
name: "Baz",
9-
dependencies: ["FlatInclude", "NonModuleDirectoryInclude", "UmbrellaHeader", "UmbrellaDirectoryInclude", "UmbrellaHeaderFlat"]),
9+
dependencies: ["CustomModuleMap", "FlatInclude", "NonModuleDirectoryInclude", "UmbrellaHeader", "UmbrellaDirectoryInclude", "UmbrellaHeaderFlat"]),
10+
.target(
11+
name: "CustomModuleMap",
12+
dependencies: []),
1013
.target(
1114
name: "FlatInclude",
1215
dependencies: []),

Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/Baz/main.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import CustomModuleMap
12
import UmbrellaDirectoryInclude
23
import FlatInclude
34
import UmbrellaHeader
@@ -7,3 +8,4 @@ let _ = foo()
78
let _ = bar()
89
let _ = jaz()
910
let _ = umbrellaHeaderFlat()
11+
let _ = qux()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "CustomModuleMap.h"
2+
3+
int qux() {
4+
int a = 6;
5+
int b = a;
6+
a = b;
7+
return a;
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int qux();
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module CustomModuleMap {
2+
header "CustomModuleMap.h"
3+
4+
export *
5+
}

Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -435,19 +435,6 @@ extension PackageGraph.ResolvedModule {
435435
return self.sourceDirAbsolutePath.appending(includeDirRelativePath)
436436
}
437437

438-
/// Relative path of the module-map file, if any (*only* applies to C-language modules).
439-
func moduleMapFileRelativePath(fileSystem: FileSystem) -> RelativePath? {
440-
guard let clangModule = self.underlying as? ClangModule else { return nil }
441-
let moduleMapFileAbsolutePath = clangModule.moduleMapPath
442-
443-
// Check whether there is actually a modulemap at the specified path.
444-
// FIXME: Feels wrong to do file system access at this level —— instead, libSwiftPM's TargetBuilder should do that?
445-
guard fileSystem.isFile(moduleMapFileAbsolutePath) else { return nil }
446-
447-
let moduleMapFileRelativePath = moduleMapFileAbsolutePath.relative(to: clangModule.sources.root)
448-
return try! RelativePath(validating: moduleMapFileRelativePath.pathString)
449-
}
450-
451438
/// Module map type (*only* applies to C-language modules).
452439
var moduleMapType: ModuleMapType? {
453440
guard let clangModule = self.underlying as? ClangModule else { return nil }

Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ extension PackagePIFProjectBuilder {
350350
// Generate a module map file, if needed.
351351
var moduleMapFileContents = ""
352352
let generatedModuleMapDir = "$(GENERATED_MODULEMAP_DIR)"
353-
let moduleMapFile = try RelativePath(validating:"\(generatedModuleMapDir)/\(sourceModule.name).modulemap").pathString
353+
let generatedModuleMapPath = try RelativePath(validating:"\(generatedModuleMapDir)/\(sourceModule.name).modulemap").pathString
354354

355355
if sourceModule.usesSwift && desiredModuleType != .macro {
356356
// Generate ObjC compatibility header for Swift library targets.
@@ -364,30 +364,40 @@ extension PackagePIFProjectBuilder {
364364
}
365365
"""
366366
// We only need to impart this to C clients.
367-
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"]
368-
} else if sourceModule.moduleMapFileRelativePath(fileSystem: self.pifBuilder.fileSystem) == nil {
367+
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"]
368+
} else {
369369
// Otherwise, this is a C library module and we generate a modulemap if one is already not provided.
370-
if case .umbrellaHeader(let path) = sourceModule.moduleMapType {
370+
switch sourceModule.moduleMapType {
371+
case nil, .some(.none):
372+
// No modulemap, no action required.
373+
break
374+
case .custom(let customModuleMapPath):
375+
// We don't need to generate a modulemap, but we should explicitly impart it on dependents,
376+
// even if it will appear in search paths. See: https://github.com/swiftlang/swift-package-manager/issues/9290
377+
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"]
378+
impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"]
379+
case .umbrellaHeader(let path):
371380
log(.debug, "\(package.name).\(sourceModule.name) generated umbrella header")
372381
moduleMapFileContents = """
373382
module \(sourceModule.c99name) {
374383
umbrella header "\(path.escapedPathString)"
375384
export *
376385
}
377386
"""
378-
} else if case .umbrellaDirectory(let path) = sourceModule.moduleMapType {
387+
// Pass the path of the module map up to all direct and indirect clients.
388+
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"]
389+
impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"]
390+
case .umbrellaDirectory(let path):
379391
log(.debug, "\(package.name).\(sourceModule.name) generated umbrella directory")
380392
moduleMapFileContents = """
381393
module \(sourceModule.c99name) {
382394
umbrella "\(path.escapedPathString)"
383395
export *
384396
}
385397
"""
386-
}
387-
if moduleMapFileContents.hasContent {
388398
// Pass the path of the module map up to all direct and indirect clients.
389-
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"]
390-
impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(moduleMapFile)", "$(inherited)"]
399+
impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"]
400+
impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"]
391401
}
392402
}
393403

@@ -457,7 +467,7 @@ extension PackagePIFProjectBuilder {
457467

458468
settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular"
459469
settings[.MODULEMAP_FILE_CONTENTS] = moduleMapFileContents
460-
settings[.MODULEMAP_PATH] = moduleMapFile
470+
settings[.MODULEMAP_PATH] = generatedModuleMapPath
461471
settings[.DEFINES_MODULE] = "YES"
462472

463473
// Settings for text-based API.

Tests/SwiftBuildSupportTests/PIFBuilderTests.swift

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,19 @@ extension SwiftBuildSupport.PIF.Project {
102102
let matchingTargets = underlying.targets.filter {
103103
$0.common.name == name
104104
}
105-
if matchingTargets.isEmpty {
105+
switch matchingTargets.count {
106+
case 0:
106107
throw StringError("No target named \(name) in PIF project")
107-
} else if matchingTargets.count > 1 {
108-
throw StringError("Multiple target named \(name) in PIF project")
109-
} else {
108+
case 1:
110109
return matchingTargets[0]
110+
case 2:
111+
if let nonDynamicVariant = matchingTargets.filter({ !$0.id.value.hasSuffix("-dynamic") }).only {
112+
return nonDynamicVariant
113+
} else {
114+
fallthrough
115+
}
116+
default:
117+
throw StringError("Multiple targets named \(name) in PIF project")
111118
}
112119
}
113120
}
@@ -197,4 +204,39 @@ struct PIFBuilderTests {
197204
#expect(binaryArtifactMessages.count > 0, "Expected to find binary artifact processing messages")
198205
}
199206
}
207+
208+
@Test func impartedModuleMaps() async throws {
209+
try await withGeneratedPIF(fromFixture: "CFamilyTargets/ModuleMapGenerationCases") { pif, observabilitySystem in
210+
#expect(observabilitySystem.diagnostics.filter {
211+
$0.severity == .error
212+
}.isEmpty)
213+
214+
do {
215+
let releaseConfig = try pif.workspace
216+
.project(named: "ModuleMapGenerationCases")
217+
.target(named: "UmbrellaHeader")
218+
.buildConfig(named: "Release")
219+
220+
#expect(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS] == ["-fmodule-map-file=\(RelativePath("$(GENERATED_MODULEMAP_DIR)").appending(component: "UmbrellaHeader.modulemap").pathString)", "$(inherited)"])
221+
}
222+
223+
do {
224+
let releaseConfig = try pif.workspace
225+
.project(named: "ModuleMapGenerationCases")
226+
.target(named: "UmbrellaDirectoryInclude")
227+
.buildConfig(named: "Release")
228+
229+
#expect(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS] == ["-fmodule-map-file=\(RelativePath("$(GENERATED_MODULEMAP_DIR)").appending(component: "UmbrellaDirectoryInclude.modulemap").pathString)", "$(inherited)"])
230+
}
231+
232+
do {
233+
let releaseConfig = try pif.workspace
234+
.project(named: "ModuleMapGenerationCases")
235+
.target(named: "CustomModuleMap")
236+
.buildConfig(named: "Release")
237+
let arg = try #require(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS]?.first)
238+
#expect(arg.hasPrefix("-fmodule-map-file") && arg.hasSuffix(RelativePath("CustomModuleMap").appending(components: ["include", "module.modulemap"]).pathString))
239+
}
240+
}
241+
}
200242
}

0 commit comments

Comments
 (0)