@@ -331,9 +331,62 @@ public final class ClangTargetBuildDescription {
331
331
332
332
// Try computing modulemap path for a C library. This also creates the file in the file system, if needed.
333
333
if target. type == . library {
334
- // If there's a custom module map, use it as given.
335
- if case . custom( let path) = clangTarget. moduleMapType {
336
- self . moduleMap = path
334
+ if case . custom( let customModuleMapPath) = clangTarget. moduleMapType {
335
+ if isWithinMixedTarget {
336
+ let customModuleMapContents : String = try fileSystem. readFileContents ( customModuleMapPath)
337
+
338
+ // Check that custom module map does not contain a Swift
339
+ // submodule.
340
+ if customModuleMapContents. contains ( " \( clangTarget. c99name) .Swift " ) {
341
+ throw StringError ( " The target's module map may not " +
342
+ " contain a Swift submodule for " +
343
+ " the module \( target. c99name) . " )
344
+ }
345
+
346
+ // Add a submodule to wrap the generated Swift header in
347
+ // the custom module map.
348
+ let modifiedModuleMapContents = """
349
+ \( customModuleMapContents)
350
+
351
+ module \( target. c99name) .Swift {
352
+ header " \( target. c99name) -Swift.h "
353
+ requires objc
354
+ }
355
+ """
356
+
357
+ // Write the modified contents to a new module map in the
358
+ // build directory.
359
+ let writePath = tempsPath. appending ( component: moduleMapFilename)
360
+ try fileSystem. createDirectory ( writePath. parentDirectory, recursive: true )
361
+
362
+ // If the file exists with the identical contents, we don't need to rewrite it.
363
+ // Otherwise, compiler will recompile even if nothing else has changed.
364
+ let contents = try ? fileSystem. readFileContents ( writePath) as String
365
+ if contents != modifiedModuleMapContents {
366
+ try fileSystem. writeFileContents ( writePath, string: modifiedModuleMapContents)
367
+ }
368
+
369
+ self . moduleMap = writePath
370
+
371
+ // The unextended module map purposefully excludes the
372
+ // generated Swift header. This is so, when compiling the
373
+ // mixed target's Swift part, the generated Swift header is
374
+ // not considered an input (the header is an output of
375
+ // compiling the mixed target's Swift part).
376
+ //
377
+ // At this point, the custom module map has been checked
378
+ // that it does not include the Swift submodule. For that
379
+ // reason, it can be copied to the build directory to
380
+ // double as the unextended module map.
381
+ try ? fileSystem. copy (
382
+ from: customModuleMapPath,
383
+ to: tempsPath. appending ( component: unextendedModuleMapFilename)
384
+ )
385
+ } else {
386
+ // When not building within a mixed target, use the custom
387
+ // module map as it does not need to be modified.
388
+ self . moduleMap = customModuleMapPath
389
+ }
337
390
}
338
391
// If a generated module map is needed, generate one now in our temporary directory.
339
392
else if let generatedModuleMapType = clangTarget. moduleMapType. generatedModuleMapType {
@@ -463,7 +516,15 @@ public final class ClangTargetBuildDescription {
463
516
args += [ " -F " , buildParameters. buildPath. pathString]
464
517
}
465
518
466
- args += [ " -I " , clangTarget. includeDir. pathString]
519
+ // For mixed targets, the `include` directory is instead overlayed over
520
+ // the build directory and that directory is passed as a header search
521
+ // path (See `MixedTargetBuildDescription`). This is done to avoid
522
+ // module re-declaration errors for cases when a mixed target contains
523
+ // a custom module map.
524
+ if !isWithinMixedTarget {
525
+ args += [ " -I " , clangTarget. includeDir. pathString]
526
+ }
527
+
467
528
args += additionalFlags
468
529
if enableModules {
469
530
args += try moduleCacheArgs
@@ -1361,6 +1422,10 @@ public final class MixedTargetBuildDescription {
1361
1422
swiftTargetBuildDescription. resourceBundleInfoPlistPath
1362
1423
}
1363
1424
1425
+ /// The path to the VFS overlay file that overlays the public headers of
1426
+ /// the Clang half of the target over the target's build directory.
1427
+ private( set) var allProductHeadersOverlay : AbsolutePath ? = nil
1428
+
1364
1429
/// The modulemap file for this target, if any
1365
1430
var moduleMap : AbsolutePath ? { clangTargetBuildDescription. moduleMap }
1366
1431
@@ -1439,7 +1504,6 @@ public final class MixedTargetBuildDescription {
1439
1504
// with mappings to the target's module map and public headers.
1440
1505
let publicHeadersPath = clangTargetBuildDescription. clangTarget. includeDir
1441
1506
let buildArtifactDirectory = swiftTargetBuildDescription. tempsPath
1442
- let generatedInteropHeaderPath = swiftTargetBuildDescription. objCompatibilityHeaderPath
1443
1507
let allProductHeadersPath = buildArtifactDirectory
1444
1508
. appending ( component: " all-product-headers.yaml " )
1445
1509
@@ -1460,39 +1524,35 @@ public final class MixedTargetBuildDescription {
1460
1524
)
1461
1525
] ) . write ( to: unextendedModuleMapOverlayPath, fileSystem: fileSystem)
1462
1526
1527
+ // TODO(ncooke3): What happens if a custom module map exists with a
1528
+ // name other than `module.modulemap`?
1463
1529
try VFSOverlay ( roots: [
1464
1530
VFSOverlay . Directory (
1465
- name: publicHeadersPath . pathString,
1531
+ name: buildArtifactDirectory . pathString,
1466
1532
contents:
1467
1533
// Public headers
1468
1534
try VFSOverlay . overlayResources (
1469
1535
directoryPath: publicHeadersPath,
1470
- fileSystem: fileSystem
1471
- )
1472
- ) ,
1473
- VFSOverlay . Directory (
1474
- name: buildArtifactDirectory. pathString,
1475
- contents: [
1476
- // Module map
1477
- VFSOverlay . File (
1478
- name: moduleMapFilename,
1479
- externalContents:
1480
- buildArtifactDirectory. appending ( component: moduleMapFilename) . pathString
1481
- )
1482
- ]
1483
- ) ,
1484
- VFSOverlay . Directory (
1485
- name: buildArtifactDirectory. pathString,
1486
- contents: [
1487
- // Generated $(ModuleName)-Swift.h header
1488
- VFSOverlay . File (
1489
- name: generatedInteropHeaderPath. basename,
1490
- externalContents: generatedInteropHeaderPath. pathString
1536
+ fileSystem: fileSystem,
1537
+ shouldInclude: {
1538
+ // Filter out a potential custom module map as
1539
+ // only the generated module map in the build
1540
+ // directory is used.
1541
+ !$0. pathString. hasSuffix ( " module.modulemap " )
1542
+ }
1491
1543
)
1492
- ]
1493
- ) ,
1544
+ )
1494
1545
] ) . write ( to: allProductHeadersPath, fileSystem: fileSystem)
1495
1546
1547
+ // The Objective-C umbrella is virtually placed in the build
1548
+ // directory via a VFS overlay. This is done to preserve
1549
+ // relative paths in custom module maps. But when resources
1550
+ // exist, a warning will appear because the generated resource
1551
+ // header (also in the build directory) is not included in the
1552
+ // umbrella directory. Passing `-Wno-incomplete-umbrella` seems
1553
+ // to prevent it but is not an ideal workaround.
1554
+ // TODO(ncooke3): Investigate a way around this.
1555
+
1496
1556
swiftTargetBuildDescription. additionalFlags. append (
1497
1557
// Builds Objective-C portion of module.
1498
1558
" -import-underlying-module "
@@ -1505,14 +1565,19 @@ public final class MixedTargetBuildDescription {
1505
1565
" -ivfsoverlay " , unextendedModuleMapOverlayPath. pathString
1506
1566
)
1507
1567
1508
- // For mixed targets, the Swift half of the target will generate
1509
- // an Objective-C compatibility header in the build folder.
1510
- // Compiling the Objective-C half of the target may require this
1511
- // generated header if the Objective-C half uses any APIs from
1512
- // the Swift half. For successful compilation, the directory
1513
- // with the generated header is added as a header search path.
1514
- clangTargetBuildDescription. additionalFlags. append ( " -I \( buildArtifactDirectory) " )
1515
- }
1568
+ // Overlay the public headers over the build directory and add
1569
+ // the directory to the header search paths. This will also help
1570
+ // pickup generated headers in the build directory like the
1571
+ // generated interop Swift header.
1572
+ clangTargetBuildDescription. additionalFlags += [
1573
+ " -I " ,
1574
+ buildArtifactDirectory. pathString,
1575
+ " -ivfsoverlay " ,
1576
+ allProductHeadersPath. pathString
1577
+ ]
1578
+
1579
+ self . allProductHeadersOverlay = allProductHeadersPath
1580
+ }
1516
1581
}
1517
1582
}
1518
1583
@@ -2546,8 +2611,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
2546
2611
case let target as MixedTarget where target. type == . library:
2547
2612
// Add the modulemap of the dependency if it has one.
2548
2613
if case let . mixed( dependencyTargetDescription) ? = targetMap [ dependency] {
2549
- if let moduleMap = dependencyTargetDescription. moduleMap {
2550
- clangTarget. additionalFlags += [ " -fmodule-map-file= \( moduleMap. pathString) " ]
2614
+ if let moduleMap = dependencyTargetDescription. moduleMap,
2615
+ let allProductHeadersOverlay = dependencyTargetDescription. allProductHeadersOverlay {
2616
+ clangTarget. additionalFlags += [
2617
+ " -fmodule-map-file= \( moduleMap. pathString) " ,
2618
+ " -ivfsoverlay " ,
2619
+ allProductHeadersOverlay. pathString
2620
+ ]
2551
2621
}
2552
2622
}
2553
2623
case let target as SystemLibraryTarget :
@@ -2608,11 +2678,14 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
2608
2678
// Add the path to modulemap of the dependency. Currently we
2609
2679
// require that all Mixed targets have a modulemap as it is
2610
2680
// required for interoperability.
2611
- guard let moduleMap = target. moduleMap else { break }
2681
+ guard
2682
+ let moduleMap = target. moduleMap,
2683
+ let allProductHeadersOverlay = target. allProductHeadersOverlay
2684
+ else { break }
2612
2685
swiftTarget. appendClangFlags (
2613
2686
" -fmodule-map-file= \( moduleMap. pathString) " ,
2614
- " -I " ,
2615
- target . clangTargetBuildDescription . clangTarget . includeDir . pathString
2687
+ " -ivfsoverlay " ,
2688
+ allProductHeadersOverlay . pathString
2616
2689
)
2617
2690
default :
2618
2691
break
@@ -2622,6 +2695,10 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
2622
2695
2623
2696
/// Plan a Mixed target.
2624
2697
private func plan( mixedTarget: MixedTargetBuildDescription ) throws {
2698
+ // TODO(ncooke3): Add tests for when mixed target depends on
2699
+ // - MixedTarget
2700
+ // - ClangTarget
2701
+ // - SwiftTarget
2625
2702
try plan ( swiftTarget: mixedTarget. swiftTargetBuildDescription)
2626
2703
try plan ( clangTarget: mixedTarget. clangTargetBuildDescription)
2627
2704
}
0 commit comments