Skip to content

Enable swift module interfaces if the package author enables library evolution via unsafe flags #3528

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Fixtures/Miscellaneous/LibraryEvolution/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// swift-tools-version:5.1
import PackageDescription

let package = Package(
name: "LibraryEvolution",
products: [
],
targets: [
.target(name: "A", dependencies: [], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
.target(name: "B", dependencies: ["A"], swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]),
])
Empty file.
Empty file.
10 changes: 5 additions & 5 deletions Sources/Build/BuildPlan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -778,14 +778,14 @@ public final class SwiftTargetBuildDescription {
args += ["-color-diagnostics"]
}

// Add the output for the `.swiftinterface`, if requested.
if buildParameters.enableParseableModuleInterfaces {
args += ["-emit-parseable-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
}

// Add agruments from declared build settings.
args += self.buildSettingsFlags()

// Add the output for the `.swiftinterface`, if requested or if library evolution has been enabled some other way.
if buildParameters.enableParseableModuleInterfaces || args.contains("-enable-library-evolution") {
args += ["-emit-module-interface-path", parseableModuleInterfaceOutputPath.pathString]
}

// User arguments (from -Xswiftc) should follow generated arguments to allow user overrides
args += buildParameters.swiftCompilerFlags
return args
Expand Down
1 change: 1 addition & 0 deletions Sources/XCBuildSupport/PIF.swift
Original file line number Diff line number Diff line change
Expand Up @@ -936,6 +936,7 @@ public enum PIF {
case WATCHOS_DEPLOYMENT_TARGET
case MARKETING_VERSION
case CURRENT_PROJECT_VERSION
case SWIFT_EMIT_MODULE_INTERFACE
}

public enum MultipleValueSetting: String, Codable {
Expand Down
19 changes: 19 additions & 0 deletions Sources/XCBuildSupport/PIFBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,21 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {

return bundleName
}

// Add inferred build settings for a particular value for a manifest setting and value.
private func addInferredBuildSettings(
for setting: PIF.BuildSettings.MultipleValueSetting,
value: [String],
platform: PIF.BuildSettings.Platform? = nil,
configuration: BuildConfiguration,
settings: inout PIF.BuildSettings
) {
// Automatically set SWIFT_EMIT_MODULE_INTERFACE if the package author uses unsafe flags to enable
// library evolution (this is needed until there is a way to specify this in the package manifest).
if setting == .OTHER_SWIFT_FLAGS && value.contains("-enable-library-evolution") {
settings[.SWIFT_EMIT_MODULE_INTERFACE] = "YES"
}
}

// Apply target-specific build settings defined in the manifest.
private func addManifestBuildSettings(
Expand All @@ -833,8 +848,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
switch configuration {
case .debug:
debugSettings[setting, for: platform, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .debug, settings: &debugSettings)
case .release:
releaseSettings[setting, for: platform, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, platform: platform, configuration: .release, settings: &releaseSettings)
}
}

Expand All @@ -847,8 +864,10 @@ final class PackagePIFProjectBuilder: PIFProjectBuilder {
switch configuration {
case .debug:
debugSettings[setting, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, configuration: .debug, settings: &debugSettings)
case .release:
releaseSettings[setting, default: ["$(inherited)"]] += value
addInferredBuildSettings(for: setting, value: value, configuration: .release, settings: &releaseSettings)
}
}

Expand Down
12 changes: 12 additions & 0 deletions Tests/CommandsTests/BuildToolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,18 @@ final class BuildToolTests: XCTestCase {
}
}

func testAutomaticParseableInterfacesWithLibraryEvolution() {
fixture(name: "Miscellaneous/LibraryEvolution") { path in
do {
let result = try build([], packagePath: path)
XCTAssert(result.binContents.contains("A.swiftinterface"))
XCTAssert(result.binContents.contains("B.swiftinterface"))
} catch SwiftPMProductError.executionFailure(_, _, let stderr) {
XCTFail(stderr)
}
}
}

func testBuildCompleteMessage() {
fixture(name: "DependencyResolution/Internal/Simple") { path in
do {
Expand Down
62 changes: 62 additions & 0 deletions Tests/XCBuildSupportTests/PIFBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,68 @@ class PIFBuilderTests: XCTestCase {
}
}
}

/// Tests that the inference of XCBuild build settings based on the package manifest's declared unsafe settings
/// works as expected.
func testUnsafeFlagsBuildSettingInference() throws {
let fs = InMemoryFileSystem(emptyFiles:
"/MyLib/Sources/MyLib/Foo.swift"
)

let diagnostics = DiagnosticsEngine()
let graph = try loadPackageGraph(
fs: fs,
diagnostics: diagnostics,
manifests: [
Manifest.createManifest(
name: "MyLib",
path: "/MyLib",
packageKind: .root,
packageLocation: "/MyLib",
v: .v5,
products: [
.init(name: "MyLib", type: .library(.automatic), targets: ["MyLib"]),
],
targets: [
.init(name: "MyLib", settings: [
.init(
tool: .swift,
name: .unsafeFlags,
value: ["-enable-library-evolution"],
condition: .init(config: "release")),
]),
]),
],
shouldCreateMultipleTestProducts: true
)

let builder = PIFBuilder(graph: graph, parameters: .mock(), diagnostics: diagnostics)
let pif = try builder.construct()

XCTAssertNoDiagnostics(diagnostics)

PIFTester(pif) { workspace in
workspace.checkProject("PACKAGE:/MyLib") { project in
project.checkTarget("PACKAGE-TARGET:MyLib") { target in
target.checkBuildConfiguration("Debug") { configuration in
configuration.checkBuildSettings { settings in
// Check that the `-enable-library-evolution` setting for Release didn't affect Debug.
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], nil)
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], nil)
}
}
target.checkBuildConfiguration("Release") { configuration in
configuration.checkBuildSettings { settings in
// Check that the `-enable-library-evolution` setting for Release also set SWIFT_EMIT_MODULE_INTERFACE.
XCTAssertEqual(settings[.SWIFT_EMIT_MODULE_INTERFACE], "YES")
XCTAssertEqual(settings[.OTHER_SWIFT_FLAGS], ["$(inherited)", "-enable-library-evolution"])
}
}
}
}
}
}

#endif
}

Expand Down