Skip to content

Commit 73b0b26

Browse files
authored
Add cxx interop support to symbolgraph-extract (#7610)
In order to extract symbol graphs for modules with transitive imports on C++ modules we need parse headers in C++ interop mode using the option: `-cxx-interoperability-mode=default`. This commit updates `swift-symbolgraph-extract` to pass the option when there is a C++ module in the import graph. This option is a new addition to `swift-symbolgraph-extract` added in swiftlang/swift#73963. Prior to this option's introduction, extracting the symbol graph for C++ dependent modules would fail with an error similar to: "unknown type name 'namespace'". With this SwiftPM commit and `swift-symbolgraph-extract` _prior_ to swiftlang/swift#73963, users will instead see an argument parsing error like: "unknown option -cxx-interoperability-mode". With this change and `swift-symbolgraph-extract` change in swiftlang/swift#73963, the symbol graph is extracted properly.
1 parent cd8a89b commit 73b0b26

File tree

7 files changed

+203
-20
lines changed

7 files changed

+203
-20
lines changed

Sources/Build/BuildDescription/ClangTargetBuildDescription.swift

+14
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,20 @@ public final class ClangTargetBuildDescription {
207207
}
208208
}
209209

210+
/// Determines the arguments needed to run `swift-symbolgraph-extract` for
211+
/// this module.
212+
public func symbolGraphExtractArguments() throws -> [String] {
213+
var args = [String]()
214+
215+
if self.clangTarget.isCXX {
216+
args += ["-cxx-interoperability-mode=default"]
217+
}
218+
if let cxxLanguageStandard = self.clangTarget.cxxLanguageStandard {
219+
args += ["-Xcc", "-std=\(cxxLanguageStandard)"]
220+
}
221+
return args
222+
}
223+
210224
/// Builds up basic compilation arguments for a source file in this target; these arguments may be different for C++
211225
/// vs non-C++.
212226
/// NOTE: The parameter to specify whether to get C++ semantics is currently optional, but this is only for revlock

Sources/Build/BuildDescription/SwiftTargetBuildDescription.swift

+63-20
Original file line numberDiff line numberDiff line change
@@ -531,26 +531,8 @@ public final class SwiftTargetBuildDescription {
531531
args += ["-color-diagnostics"]
532532
}
533533

534-
// If this is a generated test discovery target or a test entry point, it might import a test
535-
// target that is built with C++ interop enabled. In that case, the test
536-
// discovery target must enable C++ interop as well
537-
switch testTargetRole {
538-
case .discovery, .entryPoint:
539-
for dependency in try self.target.recursiveTargetDependencies() {
540-
let dependencyScope = self.buildParameters.createScope(for: dependency)
541-
let dependencySwiftFlags = dependencyScope.evaluate(.OTHER_SWIFT_FLAGS)
542-
if let interopModeFlag = dependencySwiftFlags.first(where: { $0.hasPrefix("-cxx-interoperability-mode=") }) {
543-
args += [interopModeFlag]
544-
if interopModeFlag != "-cxx-interoperability-mode=off" {
545-
if let cxxStandard = self.package.manifest.cxxLanguageStandard {
546-
args += ["-Xcc", "-std=\(cxxStandard)"]
547-
}
548-
}
549-
break
550-
}
551-
}
552-
default: break
553-
}
534+
args += try self.cxxInteroperabilityModeArguments(
535+
propagateFromCurrentModuleOtherSwiftFlags: false)
554536

555537
// Add arguments from declared build settings.
556538
args += try self.buildSettingsFlags()
@@ -639,6 +621,67 @@ public final class SwiftTargetBuildDescription {
639621

640622
return args
641623
}
624+
625+
/// Determines the arguments needed to run `swift-symbolgraph-extract` for
626+
/// this module.
627+
public func symbolGraphExtractArguments() throws -> [String] {
628+
var args = [String]()
629+
args += try self.cxxInteroperabilityModeArguments(
630+
propagateFromCurrentModuleOtherSwiftFlags: true)
631+
return args
632+
}
633+
634+
// FIXME: this function should operation on a strongly typed buildSetting
635+
// Move logic from PackageBuilder here.
636+
/// Determines the arguments needed for cxx interop for this module.
637+
func cxxInteroperabilityModeArguments(
638+
// FIXME: Remove argument
639+
// This argument is added as a stop gap to support generating arguments
640+
// for tools which currently don't leverage "OTHER_SWIFT_FLAGS". In the
641+
// fullness of time this function should operate on a strongly typed
642+
// "interopMode" property of SwiftTargetBuildDescription instead of
643+
// digging through "OTHER_SWIFT_FLAGS" manually.
644+
propagateFromCurrentModuleOtherSwiftFlags: Bool
645+
) throws -> [String] {
646+
func cxxInteroperabilityModeAndStandard(
647+
for module: ResolvedModule
648+
) -> [String]? {
649+
let scope = self.buildParameters.createScope(for: module)
650+
let flags = scope.evaluate(.OTHER_SWIFT_FLAGS)
651+
let mode = flags.first { $0.hasPrefix("-cxx-interoperability-mode=") }
652+
guard let mode else { return nil }
653+
// FIXME: Use a stored self.cxxLanguageStandard property
654+
// It definitely should _never_ reach back into the manifest
655+
if let cxxStandard = self.package.manifest.cxxLanguageStandard {
656+
return [mode, "-Xcc", "-std=\(cxxStandard)"]
657+
} else {
658+
return [mode]
659+
}
660+
}
661+
662+
if propagateFromCurrentModuleOtherSwiftFlags {
663+
// Look for cxx interop mode in the current module, if set exit early,
664+
// the flag is already present.
665+
if let args = cxxInteroperabilityModeAndStandard(for: self.target) {
666+
return args
667+
}
668+
}
669+
670+
// Implicitly propagate cxx interop flags for generated test targets.
671+
// If the current module doesn't have cxx interop mode set, search
672+
// through the module's dependencies looking for the a module that
673+
// enables cxx interop and copy it's flag.
674+
switch self.testTargetRole {
675+
case .discovery, .entryPoint:
676+
for module in try self.target.recursiveTargetDependencies() {
677+
if let args = cxxInteroperabilityModeAndStandard(for: module) {
678+
return args
679+
}
680+
}
681+
default: break
682+
}
683+
return []
684+
}
642685

643686
/// When `scanInvocation` argument is set to `true`, omit the side-effect producing arguments
644687
/// such as emitting a module or supplementary outputs.

Sources/Build/BuildDescription/TargetBuildDescription.swift

+9
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,13 @@ public enum TargetBuildDescription {
115115
return clangTargetBuildDescription.toolsVersion
116116
}
117117
}
118+
119+
/// Determines the arguments needed to run `swift-symbolgraph-extract` for
120+
/// this module.
121+
public func symbolGraphExtractArguments() throws -> [String] {
122+
switch self {
123+
case .swift(let target): try target.symbolGraphExtractArguments()
124+
case .clang(let target): try target.symbolGraphExtractArguments()
125+
}
126+
}
118127
}

Sources/Build/BuildPlan/BuildPlan.swift

+7
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,13 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
647647
try binaryTarget.parseXCFrameworks(for: triple, fileSystem: self.fileSystem)
648648
}
649649
}
650+
651+
public func symbolGraphExtractArguments(for module: ResolvedModule) throws -> [String] {
652+
guard let description = self.targetMap[module.id] else {
653+
throw InternalError("Expected description for module \(module)")
654+
}
655+
return try description.symbolGraphExtractArguments()
656+
}
650657
}
651658

652659
extension Basics.Diagnostic {

Sources/Commands/Utilities/SymbolGraphExtract.swift

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ public struct SymbolGraphExtract {
6565

6666
// Construct arguments for extracting symbols for a single target.
6767
var commandLine = [self.tool.pathString]
68+
commandLine += try buildPlan.symbolGraphExtractArguments(for: module)
69+
70+
// FIXME: everything here should be in symbolGraphExtractArguments
6871
commandLine += ["-module-name", module.c99name]
6972
commandLine += try buildParameters.tripleArgs(for: module)
7073
commandLine += try buildPlan.createAPIToolCommonArgs(includeLibrarySearchPaths: true)

Sources/SPMBuildCore/BuildSystem/BuildSystem.swift

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ public protocol BuildPlan {
9090

9191
func createAPIToolCommonArgs(includeLibrarySearchPaths: Bool) throws -> [String]
9292
func createREPLArguments() throws -> [String]
93+
94+
func symbolGraphExtractArguments(for module: ResolvedModule) throws -> [String]
9395
}
9496

9597
public protocol BuildSystemFactory {

Tests/BuildTests/BuildPlanTests.swift

+105
Original file line numberDiff line numberDiff line change
@@ -1715,13 +1715,36 @@ final class BuildPlanTests: XCTestCase {
17151715
)
17161716
)
17171717

1718+
// Assert compile args for swift modules importing cxx modules
17181719
let swiftInteropLib = try result.target(for: "swiftInteropLib").swiftTarget().compileArguments()
17191720
XCTAssertMatch(
17201721
swiftInteropLib,
17211722
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .anySequence]
17221723
)
17231724
let swiftLib = try result.target(for: "swiftLib").swiftTarget().compileArguments()
17241725
XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence])
1726+
1727+
// Assert symbolgraph-extract args for swift modules importing cxx modules
1728+
do {
1729+
let swiftInteropLib = try result.target(for: "swiftInteropLib").swiftTarget().compileArguments()
1730+
XCTAssertMatch(
1731+
swiftInteropLib,
1732+
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .anySequence]
1733+
)
1734+
let swiftLib = try result.target(for: "swiftLib").swiftTarget().compileArguments()
1735+
XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence])
1736+
}
1737+
1738+
// Assert symbolgraph-extract args for cxx modules
1739+
do {
1740+
let swiftInteropLib = try result.target(for: "swiftInteropLib").swiftTarget().compileArguments()
1741+
XCTAssertMatch(
1742+
swiftInteropLib,
1743+
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++1z", .anySequence]
1744+
)
1745+
let swiftLib = try result.target(for: "swiftLib").swiftTarget().compileArguments()
1746+
XCTAssertNoMatch(swiftLib, [.anySequence, "-Xcc", "-std=c++1z", .anySequence])
1747+
}
17251748
}
17261749

17271750
func testSwiftCMixed() throws {
@@ -1894,6 +1917,88 @@ final class BuildPlanTests: XCTestCase {
18941917
])
18951918
}
18961919

1920+
func testSwiftSettings_interoperabilityMode_cxx() throws {
1921+
let Pkg: AbsolutePath = "/Pkg"
1922+
1923+
let fs: FileSystem = InMemoryFileSystem(
1924+
emptyFiles:
1925+
Pkg.appending(components: "Sources", "cxxLib", "lib.cpp").pathString,
1926+
Pkg.appending(components: "Sources", "cxxLib", "include", "lib.h").pathString,
1927+
Pkg.appending(components: "Sources", "swiftLib", "lib.swift").pathString,
1928+
Pkg.appending(components: "Sources", "swiftLib2", "lib2.swift").pathString
1929+
)
1930+
1931+
let observability = ObservabilitySystem.makeForTesting()
1932+
let graph = try loadModulesGraph(
1933+
fileSystem: fs,
1934+
manifests: [
1935+
Manifest.createRootManifest(
1936+
displayName: "Pkg",
1937+
path: .init(validating: Pkg.pathString),
1938+
cxxLanguageStandard: "c++20",
1939+
targets: [
1940+
TargetDescription(name: "cxxLib", dependencies: []),
1941+
TargetDescription(
1942+
name: "swiftLib",
1943+
dependencies: ["cxxLib"],
1944+
settings: [.init(tool: .swift, kind: .interoperabilityMode(.Cxx))]
1945+
),
1946+
TargetDescription(name: "swiftLib2", dependencies: ["swiftLib"]),
1947+
]
1948+
),
1949+
],
1950+
observabilityScope: observability.topScope
1951+
)
1952+
XCTAssertNoDiagnostics(observability.diagnostics)
1953+
1954+
let plan = try mockBuildPlan(
1955+
graph: graph,
1956+
fileSystem: fs,
1957+
observabilityScope: observability.topScope
1958+
)
1959+
let result = try BuildPlanResult(plan: plan)
1960+
1961+
// Cxx module
1962+
do {
1963+
try XCTAssertMatch(
1964+
result.target(for: "cxxLib").clangTarget().symbolGraphExtractArguments(),
1965+
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++20", .anySequence]
1966+
)
1967+
}
1968+
1969+
// Swift module directly importing cxx module
1970+
do {
1971+
try XCTAssertMatch(
1972+
result.target(for: "swiftLib").swiftTarget().compileArguments(),
1973+
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++20", .anySequence]
1974+
)
1975+
try XCTAssertMatch(
1976+
result.target(for: "swiftLib").swiftTarget().symbolGraphExtractArguments(),
1977+
[.anySequence, "-cxx-interoperability-mode=default", "-Xcc", "-std=c++20", .anySequence]
1978+
)
1979+
}
1980+
1981+
// Swift module transitively importing cxx module
1982+
do {
1983+
try XCTAssertNoMatch(
1984+
result.target(for: "swiftLib2").swiftTarget().compileArguments(),
1985+
[.anySequence, "-cxx-interoperability-mode=default", .anySequence]
1986+
)
1987+
try XCTAssertNoMatch(
1988+
result.target(for: "swiftLib2").swiftTarget().compileArguments(),
1989+
[.anySequence, "-Xcc", "-std=c++20", .anySequence]
1990+
)
1991+
try XCTAssertNoMatch(
1992+
result.target(for: "swiftLib2").swiftTarget().symbolGraphExtractArguments(),
1993+
[.anySequence, "-cxx-interoperability-mode=default", .anySequence]
1994+
)
1995+
try XCTAssertNoMatch(
1996+
result.target(for: "swiftLib2").swiftTarget().symbolGraphExtractArguments(),
1997+
[.anySequence, "-Xcc", "-std=c++20", .anySequence]
1998+
)
1999+
}
2000+
}
2001+
18972002
func testREPLArguments() throws {
18982003
let Dep = AbsolutePath("/Dep")
18992004
let fs = InMemoryFileSystem(

0 commit comments

Comments
 (0)