Skip to content

Commit fe10910

Browse files
authored
Add build setting for enabling C++ interop (#6276)
This adds a build setting to the package manifest that enables Swift/C++ Interoperability for a given Swift target: ``` .interoperabilityMode(.Cxx, version: "swift-5.9") ``` This relies on the new Swift driver flag for versioned C++ interop (see swiftlang/swift#64088). rdar://106756067
1 parent dbb4ef2 commit fe10910

File tree

6 files changed

+98
-3
lines changed

6 files changed

+98
-3
lines changed

Sources/PackageDescription/BuildSettings.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,41 @@ public struct SwiftSetting {
352352
return SwiftSetting(
353353
name: "enableExperimentalFeature", value: [name], condition: condition)
354354
}
355+
356+
public enum InteroperabilityMode: String {
357+
case C
358+
case Cxx
359+
}
360+
361+
/// Enable Swift interoperability with a given language.
362+
///
363+
/// This is useful for enabling Swift/C++ interoperability for a given
364+
/// target.
365+
///
366+
/// Enabling C++ interoperability mode might alter the way some existing
367+
/// C/Objective-C APIs are imported.
368+
///
369+
/// - Since: First available in PackageDescription 5.9.
370+
///
371+
/// - Parameters:
372+
/// - mode: The language mode, either C or Cxx.
373+
/// - version: If Cxx language mode is used, the version of Swift/C++
374+
/// interoperability, otherwise `nil`.
375+
/// - condition: A condition that restricts the application of the build
376+
/// setting.
377+
@available(_PackageDescription, introduced: 5.9)
378+
public static func interoperabilityMode(
379+
_ mode: InteroperabilityMode,
380+
version: String? = nil,
381+
_ condition: BuildSettingCondition? = nil
382+
) -> SwiftSetting {
383+
var values: [String] = [mode.rawValue]
384+
if let version = version {
385+
values.append(version)
386+
}
387+
return SwiftSetting(
388+
name: "interoperabilityMode", value: values, condition: condition)
389+
}
355390
}
356391

357392
/// A linker build setting.

Sources/PackageLoading/ManifestJSONParser.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,23 @@ extension TargetBuildSettingDescription.Kind {
675675
throw InternalError("invalid (empty) build settings value")
676676
}
677677
return .linkedFramework(value)
678+
case "interoperabilityMode":
679+
guard let rawLang = values.first else {
680+
throw InternalError("invalid (empty) build settings value")
681+
}
682+
guard let lang = TargetBuildSettingDescription.InteroperabilityMode(rawValue: rawLang) else {
683+
throw InternalError("unknown interoperability mode: \(rawLang)")
684+
}
685+
if values.count > 2 {
686+
throw InternalError("invalid build settings value")
687+
}
688+
let version: String?
689+
if values.count == 2 {
690+
version = values[1]
691+
} else {
692+
version = nil
693+
}
694+
return .interoperabilityMode(lang, version)
678695
case "enableUpcomingFeature":
679696
guard let value = values.first else {
680697
throw InternalError("invalid (empty) build settings value")

Sources/PackageLoading/PackageBuilder.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,31 @@ public final class PackageBuilder {
977977
decl = .LINK_FRAMEWORKS
978978
}
979979

980+
case .interoperabilityMode(let lang, let version):
981+
switch setting.tool {
982+
case .c, .cxx, .linker:
983+
throw InternalError("only Swift supports interoperability")
984+
985+
case .swift:
986+
decl = .OTHER_SWIFT_FLAGS
987+
}
988+
989+
if lang == .Cxx {
990+
// `version` is the compatibility version of Swift/C++ interop,
991+
// which is meant to preserve source compatibility for
992+
// user projects while Swift/C++ interop is evolving.
993+
// At the moment the only supported interop version is
994+
// `swift-5.9` which is aligned with the version of
995+
// Swift itself, but this might not always be the case
996+
// in the future.
997+
guard let version else {
998+
throw InternalError("C++ interoperability requires a version (e.g. 'swift-5.9')")
999+
}
1000+
values = ["-cxx-interoperability-mode=\(version)"]
1001+
} else {
1002+
values = []
1003+
}
1004+
9801005
case .unsafeFlags(let _values):
9811006
values = _values
9821007

Sources/PackageModel/Manifest/TargetBuildSettingDescription.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ public enum TargetBuildSettingDescription {
2121
case linker
2222
}
2323

24+
public enum InteroperabilityMode: String, Codable, Equatable, Sendable {
25+
case C
26+
case Cxx
27+
}
28+
2429
/// The kind of the build setting, with associate configuration
2530
public enum Kind: Codable, Equatable, Sendable {
2631
case headerSearchPath(String)
2732
case define(String)
2833
case linkedLibrary(String)
2934
case linkedFramework(String)
3035

36+
case interoperabilityMode(InteroperabilityMode, String?)
37+
3138
case enableUpcomingFeature(String)
3239
case enableExperimentalFeature(String)
3340

@@ -38,7 +45,7 @@ public enum TargetBuildSettingDescription {
3845
case .unsafeFlags(let flags):
3946
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
4047
return !flags.isEmpty
41-
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .enableUpcomingFeature, .enableExperimentalFeature:
48+
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, .enableUpcomingFeature, .enableExperimentalFeature:
4249
return false
4350
}
4451
}

Sources/PackageModel/ManifestSourceGeneration.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,12 @@ fileprivate extension SourceCodeFragment {
514514
params.append(SourceCodeFragment(from: condition))
515515
}
516516
self.init(enum: setting.kind.name, subnodes: params)
517+
case .interoperabilityMode(let lang, let version):
518+
params.append(SourceCodeFragment(enum: lang.rawValue))
519+
if let version = version {
520+
params.append(SourceCodeFragment(key: "version", string: version))
521+
}
522+
self.init(enum: setting.kind.name, subnodes: params)
517523
case .unsafeFlags(let values):
518524
params.append(SourceCodeFragment(strings: values))
519525
if let condition = setting.condition {
@@ -666,6 +672,8 @@ extension TargetBuildSettingDescription.Kind {
666672
return "linkedFramework"
667673
case .unsafeFlags:
668674
return "unsafeFlags"
675+
case .interoperabilityMode:
676+
return "interoperabilityMode"
669677
case .enableUpcomingFeature:
670678
return "enableUpcomingFeature"
671679
case .enableExperimentalFeature:

Tests/BuildTests/BuildPlanTests.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3242,6 +3242,8 @@ final class BuildPlanTests: XCTestCase {
32423242
.init(tool: .swift, kind: .define("RLINUX"), condition: .init(platformNames: ["linux"], config: "release")),
32433243
.init(tool: .swift, kind: .define("DMACOS"), condition: .init(platformNames: ["macos"], config: "debug")),
32443244
.init(tool: .swift, kind: .unsafeFlags(["-Isfoo", "-L", "sbar"])),
3245+
.init(tool: .swift, kind: .interoperabilityMode(.Cxx, "swift-5.9"), condition: .init(platformNames: ["linux"])),
3246+
.init(tool: .swift, kind: .interoperabilityMode(.Cxx, "swift-6.0"), condition: .init(platformNames: ["macos"])),
32453247
.init(tool: .swift, kind: .enableUpcomingFeature("BestFeature")),
32463248
.init(tool: .swift, kind: .enableUpcomingFeature("WorstFeature"), condition: .init(platformNames: ["macos"], config: "debug"))
32473249
]
@@ -3250,6 +3252,7 @@ final class BuildPlanTests: XCTestCase {
32503252
name: "exe", dependencies: ["bar"],
32513253
settings: [
32523254
.init(tool: .swift, kind: .define("FOO")),
3255+
.init(tool: .swift, kind: .interoperabilityMode(.C, nil)),
32533256
.init(tool: .linker, kind: .linkedLibrary("sqlite3")),
32543257
.init(tool: .linker, kind: .linkedFramework("CoreData"), condition: .init(platformNames: ["macos"])),
32553258
.init(tool: .linker, kind: .unsafeFlags(["-Ilfoo", "-L", "lbar"])),
@@ -3308,7 +3311,7 @@ final class BuildPlanTests: XCTestCase {
33083311
XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", .end])
33093312

33103313
let bar = try result.target(for: "bar").swiftTarget().compileArguments()
3311-
XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-enable-upcoming-feature", "BestFeature", .end])
3314+
XCTAssertMatch(bar, [.anySequence, "-DLINUX", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=swift-5.9", "-enable-upcoming-feature", "BestFeature", .end])
33123315

33133316
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
33143317
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])
@@ -3324,7 +3327,7 @@ final class BuildPlanTests: XCTestCase {
33243327
XCTAssertMatch(cbar, [.anySequence, "-DCCC=2", "-I\(A.appending(components: "Sources", "cbar", "Sources", "headers"))", "-I\(A.appending(components: "Sources", "cbar", "Sources", "cppheaders"))", "-Icfoo", "-L", "cbar", "-Icxxfoo", "-L", "cxxbar", .end])
33253328

33263329
let bar = try result.target(for: "bar").swiftTarget().compileArguments()
3327-
XCTAssertMatch(bar, [.anySequence, "-DDMACOS", "-Isfoo", "-L", "sbar", "-enable-upcoming-feature", "BestFeature", "-enable-upcoming-feature", "WorstFeature", .end])
3330+
XCTAssertMatch(bar, [.anySequence, "-DDMACOS", "-Isfoo", "-L", "sbar", "-cxx-interoperability-mode=swift-6.0", "-enable-upcoming-feature", "BestFeature", "-enable-upcoming-feature", "WorstFeature", .end])
33283331

33293332
let exe = try result.target(for: "exe").swiftTarget().compileArguments()
33303333
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])

0 commit comments

Comments
 (0)