diff --git a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Package.swift b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Package.swift index 04523ae3a07..bc7e8c77aa4 100644 --- a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Package.swift +++ b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Package.swift @@ -6,7 +6,10 @@ let package = Package( targets: [ .target( name: "Baz", - dependencies: ["FlatInclude", "NonModuleDirectoryInclude", "UmbrellaHeader", "UmbrellaDirectoryInclude", "UmbrellaHeaderFlat"]), + dependencies: ["CustomModuleMap", "FlatInclude", "NonModuleDirectoryInclude", "UmbrellaHeader", "UmbrellaDirectoryInclude", "UmbrellaHeaderFlat"]), + .target( + name: "CustomModuleMap", + dependencies: []), .target( name: "FlatInclude", dependencies: []), diff --git a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/Baz/main.swift b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/Baz/main.swift index 13f5c6f9fc8..f7a1f7f6500 100644 --- a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/Baz/main.swift +++ b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/Baz/main.swift @@ -1,3 +1,4 @@ +import CustomModuleMap import UmbrellaDirectoryInclude import FlatInclude import UmbrellaHeader @@ -7,3 +8,4 @@ let _ = foo() let _ = bar() let _ = jaz() let _ = umbrellaHeaderFlat() +let _ = qux() diff --git a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/CustomModuleMap.c b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/CustomModuleMap.c new file mode 100644 index 00000000000..472781ba8b7 --- /dev/null +++ b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/CustomModuleMap.c @@ -0,0 +1,8 @@ +#include "CustomModuleMap.h" + +int qux() { + int a = 6; + int b = a; + a = b; + return a; +} diff --git a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/CustomModuleMap.h b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/CustomModuleMap.h new file mode 100644 index 00000000000..9e9c8c63533 --- /dev/null +++ b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/CustomModuleMap.h @@ -0,0 +1 @@ +int qux(); diff --git a/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/module.modulemap b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/module.modulemap new file mode 100644 index 00000000000..b4890569949 --- /dev/null +++ b/Fixtures/CFamilyTargets/ModuleMapGenerationCases/Sources/CustomModuleMap/include/module.modulemap @@ -0,0 +1,5 @@ +module CustomModuleMap { + header "CustomModuleMap.h" + + export * +} diff --git a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift index e69f7bac192..60a2a177f73 100644 --- a/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift +++ b/Sources/SwiftBuildSupport/PackagePIFBuilder+Helpers.swift @@ -435,19 +435,6 @@ extension PackageGraph.ResolvedModule { return self.sourceDirAbsolutePath.appending(includeDirRelativePath) } - /// Relative path of the module-map file, if any (*only* applies to C-language modules). - func moduleMapFileRelativePath(fileSystem: FileSystem) -> RelativePath? { - guard let clangModule = self.underlying as? ClangModule else { return nil } - let moduleMapFileAbsolutePath = clangModule.moduleMapPath - - // Check whether there is actually a modulemap at the specified path. - // FIXME: Feels wrong to do file system access at this level —— instead, libSwiftPM's TargetBuilder should do that? - guard fileSystem.isFile(moduleMapFileAbsolutePath) else { return nil } - - let moduleMapFileRelativePath = moduleMapFileAbsolutePath.relative(to: clangModule.sources.root) - return try! RelativePath(validating: moduleMapFileRelativePath.pathString) - } - /// Module map type (*only* applies to C-language modules). var moduleMapType: ModuleMapType? { guard let clangModule = self.underlying as? ClangModule else { return nil } diff --git a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift index b0fc05cbe03..0bd80d5252c 100644 --- a/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift +++ b/Sources/SwiftBuildSupport/PackagePIFProjectBuilder+Modules.swift @@ -350,7 +350,7 @@ extension PackagePIFProjectBuilder { // Generate a module map file, if needed. var moduleMapFileContents = "" let generatedModuleMapDir = "$(GENERATED_MODULEMAP_DIR)" - let moduleMapFile = try RelativePath(validating:"\(generatedModuleMapDir)/\(sourceModule.name).modulemap").pathString + let generatedModuleMapPath = try RelativePath(validating:"\(generatedModuleMapDir)/\(sourceModule.name).modulemap").pathString if sourceModule.usesSwift && desiredModuleType != .macro { // Generate ObjC compatibility header for Swift library targets. @@ -364,10 +364,19 @@ extension PackagePIFProjectBuilder { } """ // We only need to impart this to C clients. - impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] - } else if sourceModule.moduleMapFileRelativePath(fileSystem: self.pifBuilder.fileSystem) == nil { + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + } else { // Otherwise, this is a C library module and we generate a modulemap if one is already not provided. - if case .umbrellaHeader(let path) = sourceModule.moduleMapType { + switch sourceModule.moduleMapType { + case nil, .some(.none): + // No modulemap, no action required. + break + case .custom(let customModuleMapPath): + // We don't need to generate a modulemap, but we should explicitly impart it on dependents, + // even if it will appear in search paths. See: https://github.com/swiftlang/swift-package-manager/issues/9290 + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(customModuleMapPath)", "$(inherited)"] + case .umbrellaHeader(let path): log(.debug, "\(package.name).\(sourceModule.name) generated umbrella header") moduleMapFileContents = """ module \(sourceModule.c99name) { @@ -375,7 +384,10 @@ extension PackagePIFProjectBuilder { export * } """ - } else if case .umbrellaDirectory(let path) = sourceModule.moduleMapType { + // Pass the path of the module map up to all direct and indirect clients. + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + case .umbrellaDirectory(let path): log(.debug, "\(package.name).\(sourceModule.name) generated umbrella directory") moduleMapFileContents = """ module \(sourceModule.c99name) { @@ -383,11 +395,9 @@ extension PackagePIFProjectBuilder { export * } """ - } - if moduleMapFileContents.hasContent { // Pass the path of the module map up to all direct and indirect clients. - impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] - impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(moduleMapFile)", "$(inherited)"] + impartedSettings[.OTHER_CFLAGS] = ["-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] + impartedSettings[.OTHER_SWIFT_FLAGS] = ["-Xcc", "-fmodule-map-file=\(generatedModuleMapPath)", "$(inherited)"] } } @@ -457,7 +467,7 @@ extension PackagePIFProjectBuilder { settings[.PACKAGE_RESOURCE_TARGET_KIND] = "regular" settings[.MODULEMAP_FILE_CONTENTS] = moduleMapFileContents - settings[.MODULEMAP_PATH] = moduleMapFile + settings[.MODULEMAP_PATH] = generatedModuleMapPath settings[.DEFINES_MODULE] = "YES" // Settings for text-based API. diff --git a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift index 23bbd411c6b..f4ba17551a0 100644 --- a/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift +++ b/Tests/SwiftBuildSupportTests/PIFBuilderTests.swift @@ -102,12 +102,19 @@ extension SwiftBuildSupport.PIF.Project { let matchingTargets = underlying.targets.filter { $0.common.name == name } - if matchingTargets.isEmpty { + switch matchingTargets.count { + case 0: throw StringError("No target named \(name) in PIF project") - } else if matchingTargets.count > 1 { - throw StringError("Multiple target named \(name) in PIF project") - } else { + case 1: return matchingTargets[0] + case 2: + if let nonDynamicVariant = matchingTargets.filter({ !$0.id.value.hasSuffix("-dynamic") }).only { + return nonDynamicVariant + } else { + fallthrough + } + default: + throw StringError("Multiple targets named \(name) in PIF project") } } } @@ -197,4 +204,39 @@ struct PIFBuilderTests { #expect(binaryArtifactMessages.count > 0, "Expected to find binary artifact processing messages") } } + + @Test func impartedModuleMaps() async throws { + try await withGeneratedPIF(fromFixture: "CFamilyTargets/ModuleMapGenerationCases") { pif, observabilitySystem in + #expect(observabilitySystem.diagnostics.filter { + $0.severity == .error + }.isEmpty) + + do { + let releaseConfig = try pif.workspace + .project(named: "ModuleMapGenerationCases") + .target(named: "UmbrellaHeader") + .buildConfig(named: "Release") + + #expect(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS] == ["-fmodule-map-file=\(RelativePath("$(GENERATED_MODULEMAP_DIR)").appending(component: "UmbrellaHeader.modulemap").pathString)", "$(inherited)"]) + } + + do { + let releaseConfig = try pif.workspace + .project(named: "ModuleMapGenerationCases") + .target(named: "UmbrellaDirectoryInclude") + .buildConfig(named: "Release") + + #expect(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS] == ["-fmodule-map-file=\(RelativePath("$(GENERATED_MODULEMAP_DIR)").appending(component: "UmbrellaDirectoryInclude.modulemap").pathString)", "$(inherited)"]) + } + + do { + let releaseConfig = try pif.workspace + .project(named: "ModuleMapGenerationCases") + .target(named: "CustomModuleMap") + .buildConfig(named: "Release") + let arg = try #require(releaseConfig.impartedBuildProperties.settings[.OTHER_CFLAGS]?.first) + #expect(arg.hasPrefix("-fmodule-map-file") && arg.hasSuffix(RelativePath("CustomModuleMap").appending(components: ["include", "module.modulemap"]).pathString)) + } + } + } }