@@ -1336,8 +1336,10 @@ public final class MixedTargetBuildDescription {
1336
1336
1337
1337
/// The path to the VFS overlay file that overlays the public headers of
1338
1338
/// the Clang part of the target over the target's build directory.
1339
- // TODO(ncooke3): Review to see if it should be non-optional.
1340
- let allProductHeadersOverlay : AbsolutePath ?
1339
+ let allProductHeadersOverlay : AbsolutePath
1340
+
1341
+ /// The paths to the targets's public headers.
1342
+ let publicHeaderPaths : [ AbsolutePath ]
1341
1343
1342
1344
/// The modulemap file for this target.
1343
1345
let moduleMap : AbsolutePath
@@ -1411,13 +1413,16 @@ public final class MixedTargetBuildDescription {
1411
1413
1412
1414
let interopHeaderPath = swiftTargetBuildDescription. objCompatibilityHeaderPath
1413
1415
1414
- // A mixed target's build directory uses two subdirectories to
1416
+ // A mixed target's build directory uses three subdirectories to
1415
1417
// distinguish between build artifacts:
1416
1418
// - Intermediates: Stores artifacts used during the target's build.
1417
1419
// - Product: Stores artifacts used by clients of the target.
1420
+ // - InteropSupport: If needed, stores a generated umbrella header
1421
+ // for use during the target's build and by clients of the target.
1418
1422
let tempsPath = buildParameters. buildPath. appending ( component: target. c99name + " .build " )
1419
1423
let intermediatesDirectory = tempsPath. appending ( component: " Intermediates " )
1420
1424
let productDirectory = tempsPath. appending ( component: " Product " )
1425
+ let interopSupportDirectory : AbsolutePath ?
1421
1426
1422
1427
// Filenames for VFS overlay files.
1423
1428
let allProductHeadersFilename = " all-product-headers.yaml "
@@ -1432,33 +1437,35 @@ public final class MixedTargetBuildDescription {
1432
1437
fileSystem: fileSystem
1433
1438
)
1434
1439
1435
- // MARK: Conditionally generate an umbrella header
1436
-
1437
- // When the Swift compiler creates the generated interop header for
1438
- // Objective-C compatible Swift API (via `-emit-objc-header`), any
1439
- // Objective-C symbol that cannot be forward declared (e.g. superclass,
1440
- // protocol, etc.) will attempt to be imported via a bridging header.
1441
- // Unfortunately, a custom bridging header can not be specified because
1442
- // the target is evaluated as a framework target (as opposed to an app
1443
- // target), and framework target do not support bridging headers. So,
1444
- // the compiler defaults to importing the following in the generated
1445
- // interop header ($(ModuleName)-Swift.h):
1440
+ // MARK: Conditionally generate an umbrella header for interoptability
1441
+
1442
+ // When the Swift compiler creates the generated interop header
1443
+ // (`$(ModuleName)-Swift.h`) for Objective-C compatible Swift API
1444
+ // (via `-emit-objc-header`), any Objective-C symbol that cannot be
1445
+ // forward declared (e.g. superclass, protocol, etc.) will attempt to
1446
+ // be imported via a bridging or umbrella header. Since the compiler
1447
+ // evaluates the target as a framework (as opposed to an app), the
1448
+ // compiler assumes* an umbrella header exists in a subdirectory (named
1449
+ // after the module) within the public headers directory:
1446
1450
//
1447
1451
// #import <$(ModuleName)/$(ModuleName).h>
1448
1452
//
1449
- // The compiler assumes that the above path can be resolved. Instead of
1450
- // forcing package authors to structure their packages around that
1451
- // constraint, the package manager generates an umbrella header if
1452
- // needed and will later use a VFS overlay to make the generated header
1453
- // appear in the proper location so the bridging header import in the
1454
- // generated interop header can be resolved during the build.
1455
- let generatedUmbrellaHeaderPath : AbsolutePath ?
1456
- let relativeUmbrellaHeaderPath =
1457
- RelativePath ( " \( mixedTarget. c99name) / \( mixedTarget. c99name) .h " )
1458
- let potentialUmbrellaHeaderPath =
1459
- mixedTarget. clangTarget. includeDir. appending ( relativeUmbrellaHeaderPath)
1460
- if !fileSystem. isFile ( potentialUmbrellaHeaderPath) {
1461
- generatedUmbrellaHeaderPath = tempsPath. appending ( component: " \( mixedTarget. c99name) .h " )
1453
+ // The compiler assumes that the above path can be resolved relative to
1454
+ // the public header directory. Instead of forcing package authors to
1455
+ // structure their packages around that constraint, the package manager
1456
+ // generates an umbrella header if needed and will pass it along as a
1457
+ // header search path when building the target.
1458
+ //
1459
+ // *: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift
1460
+ let umbrellaHeaderPathComponents = [ mixedTarget. c99name, " \( mixedTarget. c99name) .h " ]
1461
+ let potentialUmbrellaHeadersPath = mixedTarget. clangTarget. includeDir
1462
+ . appending ( components: umbrellaHeaderPathComponents)
1463
+ // Check if an umbrella header at
1464
+ // `PUBLIC_HDRS_DIR/$(ModuleName)/$(ModuleName).h` already exists.
1465
+ if !fileSystem. isFile ( potentialUmbrellaHeadersPath) {
1466
+ interopSupportDirectory = tempsPath. appending ( component: " InteropSupport " )
1467
+ let generatedUmbrellaHeaderPath = interopSupportDirectory!
1468
+ . appending ( components: umbrellaHeaderPathComponents)
1462
1469
// Populate a stream that will become the generated umbrella header.
1463
1470
let stream = BufferedOutputByteStream ( )
1464
1471
mixedTarget. clangTarget. headers
@@ -1480,13 +1487,25 @@ public final class MixedTargetBuildDescription {
1480
1487
}
1481
1488
1482
1489
try fileSystem. writeFileContentsIfNeeded (
1483
- generatedUmbrellaHeaderPath! ,
1490
+ generatedUmbrellaHeaderPath,
1484
1491
bytes: stream. bytes
1485
1492
)
1486
1493
} else {
1487
- generatedUmbrellaHeaderPath = nil
1494
+ // An umbrella header in the desired format already exists so the
1495
+ // interop support directory is not needed.
1496
+ interopSupportDirectory = nil
1488
1497
}
1489
1498
1499
+ // TODO(ncooke3): Is there a way to remove the generated umbrella
1500
+ // header when compiling individual clang files? This will reduce the
1501
+ // misuse of importing the generated umbrella header.
1502
+
1503
+ // Clients will later depend on the public header directory, and, if an
1504
+ // umbrella header was created, the header's root directory.
1505
+ self . publicHeaderPaths = interopSupportDirectory != nil ?
1506
+ [ mixedTarget. clangTarget. includeDir, interopSupportDirectory!] :
1507
+ [ mixedTarget. clangTarget. includeDir]
1508
+
1490
1509
// MARK: Generate products to be used by client of the target.
1491
1510
1492
1511
// Path to the module map used by clients to access the mixed target's
@@ -1537,6 +1556,7 @@ public final class MixedTargetBuildDescription {
1537
1556
self . moduleMap = customModuleMapPath
1538
1557
self . allProductHeadersOverlay = productDirectory. appending ( component: allProductHeadersFilename)
1539
1558
1559
+ // TODO(ncooke3): Probably can remove the builder pattern now.
1540
1560
#if swift(>=5.4)
1541
1561
try VFSOverlay ( roots: [
1542
1562
VFSOverlay . Directory ( name: customModuleMapPath. parentDirectory. pathString) {
@@ -1549,23 +1569,8 @@ public final class MixedTargetBuildDescription {
1549
1569
name: interopHeaderPath. basename,
1550
1570
externalContents: interopHeaderPath. pathString
1551
1571
)
1552
-
1553
- // TODO(ncooke3): Unfortunately, this can trigger an
1554
- // umbrella header warning that the umbrella header does
1555
- // not include the header in this directory. So this
1556
- // overlay needs to be pulled out of the public header
1557
- // path and placed elsewhere. This also means there will be
1558
- // two public header search paths.
1559
- if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath {
1560
- VFSOverlay . Directory ( name: mixedTarget. c99name) {
1561
- VFSOverlay . File (
1562
- name: generatedUmbrellaHeaderPath. basename,
1563
- externalContents: generatedUmbrellaHeaderPath. pathString
1564
- )
1565
- }
1566
- }
1567
1572
}
1568
- ] ) . write ( to: self . allProductHeadersOverlay! , fileSystem: fileSystem)
1573
+ ] ) . write ( to: self . allProductHeadersOverlay, fileSystem: fileSystem)
1569
1574
#endif
1570
1575
1571
1576
// When the mixed target does not have a custom module map, one will be
@@ -1593,19 +1598,8 @@ public final class MixedTargetBuildDescription {
1593
1598
name: interopHeaderPath. basename,
1594
1599
externalContents: interopHeaderPath. pathString
1595
1600
)
1596
-
1597
- // If an umbrella header was generated, it needs to be
1598
- // overlayed within the public headers directory.
1599
- if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath {
1600
- VFSOverlay . Directory ( name: mixedTarget. c99name) {
1601
- VFSOverlay . File (
1602
- name: generatedUmbrellaHeaderPath. basename,
1603
- externalContents: generatedUmbrellaHeaderPath. pathString
1604
- )
1605
- }
1606
- }
1607
1601
}
1608
- ] ) . write ( to: self . allProductHeadersOverlay! , fileSystem: fileSystem)
1602
+ ] ) . write ( to: self . allProductHeadersOverlay, fileSystem: fileSystem)
1609
1603
#endif
1610
1604
}
1611
1605
@@ -1675,15 +1669,6 @@ public final class MixedTargetBuildDescription {
1675
1669
name: interopHeaderPath. basename,
1676
1670
externalContents: interopHeaderPath. pathString
1677
1671
)
1678
-
1679
- if let generatedUmbrellaHeaderPath = generatedUmbrellaHeaderPath {
1680
- VFSOverlay . Directory ( name: mixedTarget. c99name) {
1681
- VFSOverlay . File (
1682
- name: generatedUmbrellaHeaderPath. basename,
1683
- externalContents: generatedUmbrellaHeaderPath. pathString
1684
- )
1685
- }
1686
- }
1687
1672
}
1688
1673
] ) . write ( to: allProductHeadersPath, fileSystem: fileSystem)
1689
1674
#endif
@@ -1735,6 +1720,18 @@ public final class MixedTargetBuildDescription {
1735
1720
// generated header can be imported.
1736
1721
" -I " , intermediatesDirectory. pathString
1737
1722
]
1723
+
1724
+ // If a generated umbrella header was created, add it's root directory
1725
+ // as a header search path. This will resolve its import within the
1726
+ // generated interop header.
1727
+ if let interopSupportDirectory = interopSupportDirectory {
1728
+ swiftTargetBuildDescription. appendClangFlags (
1729
+ " -I " , interopSupportDirectory. pathString
1730
+ )
1731
+ clangTargetBuildDescription. additionalFlags += [
1732
+ " -I " , interopSupportDirectory. pathString
1733
+ ]
1734
+ }
1738
1735
}
1739
1736
}
1740
1737
@@ -2766,20 +2763,22 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
2766
2763
}
2767
2764
}
2768
2765
case let target as MixedTarget where target. type == . library:
2769
- // Add the public headers of the dependency.
2770
- clangTarget. additionalFlags += [ " -I " , target. clangTarget. includeDir. pathString]
2771
-
2772
2766
// Add the modulemap of the dependency.
2773
2767
if case let . mixed( dependencyTargetDescription) ? = targetMap [ dependency] {
2768
+ // Add the dependency's modulemap.
2774
2769
clangTarget. additionalFlags. append (
2775
2770
" -fmodule-map-file= \( dependencyTargetDescription. moduleMap. pathString) "
2776
2771
)
2777
2772
2778
- if let allProductHeadersOverlay = dependencyTargetDescription. allProductHeadersOverlay {
2779
- clangTarget. additionalFlags += [
2780
- " -ivfsoverlay " , allProductHeadersOverlay. pathString
2781
- ]
2773
+ // Add the dependency's public headers.
2774
+ dependencyTargetDescription. publicHeaderPaths. forEach {
2775
+ clangTarget. additionalFlags += [ " -I " , $0. pathString ]
2782
2776
}
2777
+
2778
+ // Add the dependency's public VFS overlay.
2779
+ clangTarget. additionalFlags += [
2780
+ " -ivfsoverlay " , dependencyTargetDescription. allProductHeadersOverlay. pathString
2781
+ ]
2783
2782
}
2784
2783
case let target as SystemLibraryTarget :
2785
2784
clangTarget. additionalFlags += [ " -fmodule-map-file= \( target. moduleMapPath. pathString) " ]
@@ -2835,18 +2834,22 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
2835
2834
guard case let . mixed( target) ? = targetMap [ dependency] else {
2836
2835
throw InternalError ( " unexpected mixed target \( underlyingTarget) " )
2837
2836
}
2838
- // Add the dependency's modulemap and public headers.
2837
+
2838
+ // Add the dependency's modulemap.
2839
2839
swiftTarget. appendClangFlags (
2840
- " -fmodule-map-file= \( target. moduleMap. pathString) " ,
2841
- " -I " , target. clangTargetBuildDescription. clangTarget. includeDir. pathString
2840
+ " -fmodule-map-file= \( target. moduleMap. pathString) "
2842
2841
)
2843
2842
2844
- if let allProductHeadersOverlay = target. allProductHeadersOverlay {
2845
- swiftTarget. appendClangFlags (
2846
- " -ivfsoverlay " , allProductHeadersOverlay. pathString
2847
- )
2843
+ // Add the dependency's public headers.
2844
+ target. publicHeaderPaths. forEach {
2845
+ swiftTarget. appendClangFlags ( " -I " , $0. pathString)
2848
2846
}
2849
2847
2848
+ // Add the dependency's public VFS overlay.
2849
+ swiftTarget. appendClangFlags (
2850
+ " -ivfsoverlay " , target. allProductHeadersOverlay. pathString
2851
+ )
2852
+
2850
2853
default :
2851
2854
break
2852
2855
}
0 commit comments