Skip to content

Commit c316383

Browse files
committed
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 8004609 commit c316383

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()
@@ -628,6 +610,67 @@ public final class SwiftTargetBuildDescription {
628610

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

632675
/// When `scanInvocation` argument is set to `true`, omit the side-effect producing arguments
633676
/// 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
@@ -1713,13 +1713,36 @@ final class BuildPlanTests: XCTestCase {
17131713
)
17141714
)
17151715

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

17251748
func testSwiftCMixed() throws {
@@ -1892,6 +1915,88 @@ final class BuildPlanTests: XCTestCase {
18921915
])
18931916
}
18941917

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

0 commit comments

Comments
 (0)