Skip to content

Commit

Permalink
Add build setting for enabling C++ interop (#6276)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
egorzhdan authored Mar 16, 2023
1 parent dbb4ef2 commit fe10910
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 3 deletions.
35 changes: 35 additions & 0 deletions Sources/PackageDescription/BuildSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,41 @@ public struct SwiftSetting {
return SwiftSetting(
name: "enableExperimentalFeature", value: [name], condition: condition)
}

public enum InteroperabilityMode: String {
case C
case Cxx
}

/// Enable Swift interoperability with a given language.
///
/// This is useful for enabling Swift/C++ interoperability for a given
/// target.
///
/// Enabling C++ interoperability mode might alter the way some existing
/// C/Objective-C APIs are imported.
///
/// - Since: First available in PackageDescription 5.9.
///
/// - Parameters:
/// - mode: The language mode, either C or Cxx.
/// - version: If Cxx language mode is used, the version of Swift/C++
/// interoperability, otherwise `nil`.
/// - condition: A condition that restricts the application of the build
/// setting.
@available(_PackageDescription, introduced: 5.9)
public static func interoperabilityMode(
_ mode: InteroperabilityMode,
version: String? = nil,
_ condition: BuildSettingCondition? = nil
) -> SwiftSetting {
var values: [String] = [mode.rawValue]
if let version = version {
values.append(version)
}
return SwiftSetting(
name: "interoperabilityMode", value: values, condition: condition)
}
}

/// A linker build setting.
Expand Down
17 changes: 17 additions & 0 deletions Sources/PackageLoading/ManifestJSONParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,23 @@ extension TargetBuildSettingDescription.Kind {
throw InternalError("invalid (empty) build settings value")
}
return .linkedFramework(value)
case "interoperabilityMode":
guard let rawLang = values.first else {
throw InternalError("invalid (empty) build settings value")
}
guard let lang = TargetBuildSettingDescription.InteroperabilityMode(rawValue: rawLang) else {
throw InternalError("unknown interoperability mode: \(rawLang)")
}
if values.count > 2 {
throw InternalError("invalid build settings value")
}
let version: String?
if values.count == 2 {
version = values[1]
} else {
version = nil
}
return .interoperabilityMode(lang, version)
case "enableUpcomingFeature":
guard let value = values.first else {
throw InternalError("invalid (empty) build settings value")
Expand Down
25 changes: 25 additions & 0 deletions Sources/PackageLoading/PackageBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,31 @@ public final class PackageBuilder {
decl = .LINK_FRAMEWORKS
}

case .interoperabilityMode(let lang, let version):
switch setting.tool {
case .c, .cxx, .linker:
throw InternalError("only Swift supports interoperability")

case .swift:
decl = .OTHER_SWIFT_FLAGS
}

if lang == .Cxx {
// `version` is the compatibility version of Swift/C++ interop,
// which is meant to preserve source compatibility for
// user projects while Swift/C++ interop is evolving.
// At the moment the only supported interop version is
// `swift-5.9` which is aligned with the version of
// Swift itself, but this might not always be the case
// in the future.
guard let version else {
throw InternalError("C++ interoperability requires a version (e.g. 'swift-5.9')")
}
values = ["-cxx-interoperability-mode=\(version)"]
} else {
values = []
}

case .unsafeFlags(let _values):
values = _values

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@ public enum TargetBuildSettingDescription {
case linker
}

public enum InteroperabilityMode: String, Codable, Equatable, Sendable {
case C
case Cxx
}

/// The kind of the build setting, with associate configuration
public enum Kind: Codable, Equatable, Sendable {
case headerSearchPath(String)
case define(String)
case linkedLibrary(String)
case linkedFramework(String)

case interoperabilityMode(InteroperabilityMode, String?)

case enableUpcomingFeature(String)
case enableExperimentalFeature(String)

Expand All @@ -38,7 +45,7 @@ public enum TargetBuildSettingDescription {
case .unsafeFlags(let flags):
// If `.unsafeFlags` is used, but doesn't specify any flags, we treat it the same way as not specifying it.
return !flags.isEmpty
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .enableUpcomingFeature, .enableExperimentalFeature:
case .headerSearchPath, .define, .linkedLibrary, .linkedFramework, .interoperabilityMode, .enableUpcomingFeature, .enableExperimentalFeature:
return false
}
}
Expand Down
8 changes: 8 additions & 0 deletions Sources/PackageModel/ManifestSourceGeneration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,12 @@ fileprivate extension SourceCodeFragment {
params.append(SourceCodeFragment(from: condition))
}
self.init(enum: setting.kind.name, subnodes: params)
case .interoperabilityMode(let lang, let version):
params.append(SourceCodeFragment(enum: lang.rawValue))
if let version = version {
params.append(SourceCodeFragment(key: "version", string: version))
}
self.init(enum: setting.kind.name, subnodes: params)
case .unsafeFlags(let values):
params.append(SourceCodeFragment(strings: values))
if let condition = setting.condition {
Expand Down Expand Up @@ -666,6 +672,8 @@ extension TargetBuildSettingDescription.Kind {
return "linkedFramework"
case .unsafeFlags:
return "unsafeFlags"
case .interoperabilityMode:
return "interoperabilityMode"
case .enableUpcomingFeature:
return "enableUpcomingFeature"
case .enableExperimentalFeature:
Expand Down
7 changes: 5 additions & 2 deletions Tests/BuildTests/BuildPlanTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3242,6 +3242,8 @@ final class BuildPlanTests: XCTestCase {
.init(tool: .swift, kind: .define("RLINUX"), condition: .init(platformNames: ["linux"], config: "release")),
.init(tool: .swift, kind: .define("DMACOS"), condition: .init(platformNames: ["macos"], config: "debug")),
.init(tool: .swift, kind: .unsafeFlags(["-Isfoo", "-L", "sbar"])),
.init(tool: .swift, kind: .interoperabilityMode(.Cxx, "swift-5.9"), condition: .init(platformNames: ["linux"])),
.init(tool: .swift, kind: .interoperabilityMode(.Cxx, "swift-6.0"), condition: .init(platformNames: ["macos"])),
.init(tool: .swift, kind: .enableUpcomingFeature("BestFeature")),
.init(tool: .swift, kind: .enableUpcomingFeature("WorstFeature"), condition: .init(platformNames: ["macos"], config: "debug"))
]
Expand All @@ -3250,6 +3252,7 @@ final class BuildPlanTests: XCTestCase {
name: "exe", dependencies: ["bar"],
settings: [
.init(tool: .swift, kind: .define("FOO")),
.init(tool: .swift, kind: .interoperabilityMode(.C, nil)),
.init(tool: .linker, kind: .linkedLibrary("sqlite3")),
.init(tool: .linker, kind: .linkedFramework("CoreData"), condition: .init(platformNames: ["macos"])),
.init(tool: .linker, kind: .unsafeFlags(["-Ilfoo", "-L", "lbar"])),
Expand Down Expand Up @@ -3308,7 +3311,7 @@ final class BuildPlanTests: XCTestCase {
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])

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

let exe = try result.target(for: "exe").swiftTarget().compileArguments()
XCTAssertMatch(exe, [.anySequence, "-DFOO", .end])
Expand All @@ -3324,7 +3327,7 @@ final class BuildPlanTests: XCTestCase {
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])

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

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

0 comments on commit fe10910

Please sign in to comment.