diff --git a/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Package.swift b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Package.swift
new file mode 100644
index 00000000000..70af89cbeeb
--- /dev/null
+++ b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Package.swift
@@ -0,0 +1,16 @@
+// swift-tools-version:6.0
+
+import PackageDescription
+
+let package = Package(
+ name: "SwiftFramework",
+ products: [
+ .library(name: "SwiftFramework", type: .dynamic, targets: ["SwiftFramework"]),
+ ],
+ targets: [
+ .target(
+ name: "SwiftFramework",
+ swiftSettings: [.unsafeFlags(["-enable-library-evolution"])]
+ ),
+ ]
+)
diff --git a/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Sources/SwiftFramework/SwiftFramework.swift b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Sources/SwiftFramework/SwiftFramework.swift
new file mode 100644
index 00000000000..5ad86872cd1
--- /dev/null
+++ b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/SwiftFramework/Sources/SwiftFramework/SwiftFramework.swift
@@ -0,0 +1,8 @@
+public enum SwiftFrameworkWithEvolution
+{
+ case v1
+ case v2
+
+ public
+ static var latest:Self { .v2 }
+}
\ No newline at end of file
diff --git a/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Package.swift b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Package.swift
new file mode 100644
index 00000000000..4cbb13f39c2
--- /dev/null
+++ b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Package.swift
@@ -0,0 +1,17 @@
+// swift-tools-version:6.0
+
+import PackageDescription
+
+let package = Package(name: "TestBinary",
+ products: [
+ .executable(name: "TestBinary", targets: ["TestBinary"]),
+ ],
+ targets: [
+ .binaryTarget(name: "SwiftFramework", path: "SwiftFramework.xcframework"),
+ .executableTarget(name: "TestBinary",
+ dependencies: [
+ .target(name: "SwiftFramework"),
+ ]
+ ),
+ ]
+)
\ No newline at end of file
diff --git a/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Sources/TestBinary/Main.swift b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Sources/TestBinary/Main.swift
new file mode 100644
index 00000000000..b088848fa17
--- /dev/null
+++ b/Fixtures/Miscellaneous/LibraryEvolutionLinuxXCF/TestBinary/Sources/TestBinary/Main.swift
@@ -0,0 +1,3 @@
+import SwiftFramework
+
+print("Latest Framework with LibraryEvolution version: \(SwiftFrameworkWithEvolution.latest)")
\ No newline at end of file
diff --git a/Sources/Build/BuildPlan/BuildPlan+Clang.swift b/Sources/Build/BuildPlan/BuildPlan+Clang.swift
index fe9bc8f7619..01306cdcf7d 100644
--- a/Sources/Build/BuildPlan/BuildPlan+Clang.swift
+++ b/Sources/Build/BuildPlan/BuildPlan+Clang.swift
@@ -65,7 +65,11 @@ extension BuildPlan {
clangTarget.libraryBinaryPaths.insert(library.libraryPath)
}
case .xcframework:
- let libraries = try self.parseXCFramework(for: target, triple: clangTarget.buildParameters.triple)
+ let libraries = try self.parseXCFramework(
+ for: target,
+ triple: clangTarget.buildParameters.triple,
+ enableXCFrameworksOnLinux: clangTarget.buildParameters.enableXCFrameworksOnLinux
+ )
for library in libraries {
library.headersPaths.forEach {
clangTarget.additionalFlags += ["-I", $0.pathString]
diff --git a/Sources/Build/BuildPlan/BuildPlan+Product.swift b/Sources/Build/BuildPlan/BuildPlan+Product.swift
index f7b1bb41231..b290f9e3fd3 100644
--- a/Sources/Build/BuildPlan/BuildPlan+Product.swift
+++ b/Sources/Build/BuildPlan/BuildPlan+Product.swift
@@ -298,7 +298,8 @@ extension BuildPlan {
case .xcframework:
let libraries = try self.parseXCFramework(
for: binaryTarget,
- triple: productDescription.buildParameters.triple
+ triple: productDescription.buildParameters.triple,
+ enableXCFrameworksOnLinux: productDescription.buildParameters.enableXCFrameworksOnLinux
)
for library in libraries {
libraryBinaryPaths.insert(library.libraryPath)
diff --git a/Sources/Build/BuildPlan/BuildPlan+Swift.swift b/Sources/Build/BuildPlan/BuildPlan+Swift.swift
index 9e16e02f0e0..b5acf6149d1 100644
--- a/Sources/Build/BuildPlan/BuildPlan+Swift.swift
+++ b/Sources/Build/BuildPlan/BuildPlan+Swift.swift
@@ -61,7 +61,11 @@ extension BuildPlan {
swiftTarget.libraryBinaryPaths.insert(library.libraryPath)
}
case .xcframework:
- let libraries = try self.parseXCFramework(for: target, triple: swiftTarget.buildParameters.triple)
+ let libraries = try self.parseXCFramework(
+ for: target,
+ triple: swiftTarget.buildParameters.triple,
+ enableXCFrameworksOnLinux: swiftTarget.buildParameters.enableXCFrameworksOnLinux
+ )
for library in libraries {
library.headersPaths.forEach {
swiftTarget.additionalFlags += ["-I", $0.pathString, "-Xcc", "-I", "-Xcc", $0.pathString]
diff --git a/Sources/Build/BuildPlan/BuildPlan.swift b/Sources/Build/BuildPlan/BuildPlan.swift
index b54f7b939c6..bef12b95fa8 100644
--- a/Sources/Build/BuildPlan/BuildPlan.swift
+++ b/Sources/Build/BuildPlan/BuildPlan.swift
@@ -653,9 +653,12 @@ public class BuildPlan: SPMBuildCore.BuildPlan {
}
/// Extracts the library information from an XCFramework.
- func parseXCFramework(for binaryTarget: BinaryModule, triple: Basics.Triple) throws -> [LibraryInfo] {
+ func parseXCFramework(for binaryTarget: BinaryModule, triple: Basics.Triple, enableXCFrameworksOnLinux: Bool) throws -> [LibraryInfo] {
try self.externalLibrariesCache.memoize(key: binaryTarget) {
- try binaryTarget.parseXCFrameworks(for: triple, fileSystem: self.fileSystem)
+ if !enableXCFrameworksOnLinux && triple.os == .linux {
+ return []
+ }
+ return try binaryTarget.parseXCFrameworks(for: triple, fileSystem: self.fileSystem)
}
}
diff --git a/Sources/CoreCommands/Options.swift b/Sources/CoreCommands/Options.swift
index ab0db1df264..3b7411d8ce9 100644
--- a/Sources/CoreCommands/Options.swift
+++ b/Sources/CoreCommands/Options.swift
@@ -515,6 +515,13 @@ public struct BuildOptions: ParsableArguments {
@Flag(name: .customLong("experimental-prepare-for-indexing-no-lazy"), help: .hidden)
var prepareForIndexingNoLazy: Bool = false
+ /// Hidden option to allow XCFrameworks on Linux
+ @Flag(
+ name: .customLong("experimental-xcframeworks-on-linux"),
+ help: .hidden
+ )
+ public var enableXCFrameworksOnLinux: Bool = false
+
/// Whether to enable generation of `.swiftinterface`s alongside `.swiftmodule`s.
@Flag(name: .customLong("enable-parseable-module-interfaces"))
public var shouldEnableParseableModuleInterfaces: Bool = false
diff --git a/Sources/CoreCommands/SwiftCommandState.swift b/Sources/CoreCommands/SwiftCommandState.swift
index ceacbf39b79..b4a6158274b 100644
--- a/Sources/CoreCommands/SwiftCommandState.swift
+++ b/Sources/CoreCommands/SwiftCommandState.swift
@@ -941,6 +941,7 @@ public final class SwiftCommandState {
sanitizers: options.build.enabledSanitizers,
indexStoreMode: options.build.indexStoreMode.buildParameter,
prepareForIndexing: prepareForIndexingMode,
+ enableXCFrameworksOnLinux: options.build.enableXCFrameworksOnLinux,
debuggingParameters: .init(
debugInfoFormat: self.options.build.debugInfoFormat.buildParameter,
triple: triple,
diff --git a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift
index 902e51089c0..d5286127a9e 100644
--- a/Sources/SPMBuildCore/BinaryTarget+Extensions.swift
+++ b/Sources/SPMBuildCore/BinaryTarget+Extensions.swift
@@ -45,7 +45,7 @@ extension BinaryModule {
let metadata = try XCFrameworkMetadata.parse(fileSystem: fileSystem, rootPath: self.artifactPath)
// Filter the libraries that are relevant to the triple.
guard let library = metadata.libraries.first(where: {
- $0.platform == triple.os?.asXCFrameworkPlatformString &&
+ $0.platform == triple.asXCFrameworkPlatformString &&
$0.variant == triple.environment?.asXCFrameworkPlatformVariantString &&
$0.architectures.contains(triple.archName)
}) else {
@@ -117,13 +117,11 @@ extension Triple {
return self
}
}
-}
-extension Triple.OS {
/// Returns a representation of the receiver that can be compared with platform strings declared in an XCFramework.
fileprivate var asXCFrameworkPlatformString: String? {
- switch self {
- case .darwin, .linux, .wasi, .win32, .openbsd, .freebsd, .noneOS:
+ switch self.os {
+ case .darwin, .wasi, .win32, .openbsd, .freebsd, .noneOS:
return nil // XCFrameworks do not support any of these platforms today.
case .macosx:
return "macos"
@@ -133,6 +131,11 @@ extension Triple.OS {
return "tvos"
case .watchos:
return "watchos"
+ case .linux:
+ if environment == .android {
+ return nil
+ }
+ return "linux" // Only if --experimental-xcframeworks-on-linux has been passed
default:
return nil // XCFrameworks do not support any of these platforms today.
}
diff --git a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift
index 035f4911c41..e5d9802b79f 100644
--- a/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift
+++ b/Sources/SPMBuildCore/BuildParameters/BuildParameters.swift
@@ -132,6 +132,9 @@ public struct BuildParameters: Encodable {
/// Do minimal build to prepare for indexing
public var prepareForIndexing: PrepareForIndexingMode
+ /// Support Experimental XCF on Linux
+ public var enableXCFrameworksOnLinux: Bool
+
/// Build parameters related to debugging.
public var debuggingParameters: Debugging
@@ -166,6 +169,7 @@ public struct BuildParameters: Encodable {
indexStoreMode: IndexStoreMode = .auto,
shouldSkipBuilding: Bool = false,
prepareForIndexing: PrepareForIndexingMode = .off,
+ enableXCFrameworksOnLinux: Bool = false,
debuggingParameters: Debugging? = nil,
driverParameters: Driver = .init(),
linkingParameters: Linking = .init(),
@@ -229,6 +233,7 @@ public struct BuildParameters: Encodable {
self.indexStoreMode = indexStoreMode
self.shouldSkipBuilding = shouldSkipBuilding
self.prepareForIndexing = prepareForIndexing
+ self.enableXCFrameworksOnLinux = enableXCFrameworksOnLinux
self.driverParameters = driverParameters
self.linkingParameters = linkingParameters
self.outputParameters = outputParameters
diff --git a/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift b/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift
index af0bfac2369..8354b6e19ce 100644
--- a/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift
+++ b/Sources/_InternalBuildTestSupport/MockBuildTestHelper.swift
@@ -66,6 +66,7 @@ public func mockBuildPlan(
commonFlags: PackageModel.BuildFlags = .init(),
indexStoreMode: BuildParameters.IndexStoreMode = .off,
omitFramePointers: Bool? = nil,
+ enableXCFrameworksOnLinux: Bool = false,
driverParameters: BuildParameters.Driver = .init(),
linkingParameters: BuildParameters.Linking = .init(),
targetSanitizers: EnabledSanitizers = .init(),
@@ -105,7 +106,8 @@ public func mockBuildPlan(
toolchain: toolchain,
flags: commonFlags,
triple: inferredTriple,
- indexStoreMode: indexStoreMode
+ indexStoreMode: indexStoreMode,
+ enableXCFrameworksOnLinux: enableXCFrameworksOnLinux
)
destinationParameters.debuggingParameters = commonDebuggingParameters
destinationParameters.driverParameters = driverParameters
@@ -119,7 +121,8 @@ public func mockBuildPlan(
toolchain: toolchain,
flags: commonFlags,
triple: inferredTriple,
- indexStoreMode: indexStoreMode
+ indexStoreMode: indexStoreMode,
+ enableXCFrameworksOnLinux: enableXCFrameworksOnLinux
)
hostParameters.debuggingParameters = commonDebuggingParameters
hostParameters.driverParameters = driverParameters
diff --git a/Sources/_InternalTestSupport/MockBuildTestHelper.swift b/Sources/_InternalTestSupport/MockBuildTestHelper.swift
index 78820551777..024726107c9 100644
--- a/Sources/_InternalTestSupport/MockBuildTestHelper.swift
+++ b/Sources/_InternalTestSupport/MockBuildTestHelper.swift
@@ -91,6 +91,7 @@ public func mockBuildParameters(
linkerDeadStrip: Bool = true,
linkTimeOptimizationMode: BuildParameters.LinkTimeOptimizationMode? = nil,
omitFramePointers: Bool? = nil,
+ enableXCFrameworksOnLinux: Bool = false,
prepareForIndexing: BuildParameters.PrepareForIndexingMode = .off
) -> BuildParameters {
try! BuildParameters(
@@ -105,6 +106,7 @@ public func mockBuildParameters(
workers: 3,
indexStoreMode: indexStoreMode,
prepareForIndexing: prepareForIndexing,
+ enableXCFrameworksOnLinux: enableXCFrameworksOnLinux,
debuggingParameters: .init(
triple: triple,
shouldEnableDebuggingEntitlement: config == .debug,
diff --git a/Tests/BuildTests/BuildPlanTests.swift b/Tests/BuildTests/BuildPlanTests.swift
index c0f61204e73..62abd5aa3db 100644
--- a/Tests/BuildTests/BuildPlanTests.swift
+++ b/Tests/BuildTests/BuildPlanTests.swift
@@ -6807,6 +6807,114 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
XCTAssertMatch(dynamicLibraryPathExtension, "dylib")
}
+ func testXCFrameworkBinaryTargetsLinux(platform: String = "linux", arch: String, targetTriple: Basics.Triple) async throws {
+ let Pkg: AbsolutePath = "/Pkg"
+
+ let fs = InMemoryFileSystem(
+ emptyFiles:
+ Pkg.appending(components: "Sources", "exe", "main.swift").pathString,
+ Pkg.appending(components: "Sources", "Library", "Library.swift").pathString
+ )
+
+ try fs.createDirectory("/Pkg/Framework.xcframework", recursive: true)
+ try fs.writeFileContents(
+ "/Pkg/Framework.xcframework/Info.plist",
+ string: """
+
+
+
+
+ AvailableLibraries
+
+
+ LibraryIdentifier
+ \(platform)-\(arch)
+ LibraryPath
+ Framework.framework
+ SupportedArchitectures
+
+ \(arch)
+
+ SupportedPlatform
+ \(platform)
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
+ """
+ )
+
+ let observability = ObservabilitySystem.makeForTesting()
+
+ let graph = try loadModulesGraph(
+ fileSystem: fs,
+ manifests: [
+ Manifest.createRootManifest(
+ displayName: "Pkg",
+ path: .init(validating: Pkg.pathString),
+ products: [
+ ProductDescription(name: "exe", type: .executable, targets: ["exe"]),
+ ProductDescription(name: "Library", type: .library(.dynamic), targets: ["Library"]),
+ ],
+ targets: [
+ TargetDescription(name: "exe", dependencies: ["Library"]),
+ TargetDescription(name: "Library", dependencies: ["Framework"]),
+ TargetDescription(name: "Framework", path: "Framework.xcframework", type: .binary),
+ ]
+ ),
+ ],
+ binaryArtifacts: [
+ .plain("pkg"): [
+ "Framework": .init(kind: .xcframework, originURL: nil, path: "/Pkg/Framework.xcframework"),
+ ],
+ ],
+ observabilityScope: observability.topScope
+ )
+ XCTAssertNoDiagnostics(observability.diagnostics)
+
+ let result = try await BuildPlanResult(plan: mockBuildPlan(
+ triple: targetTriple,
+ graph: graph,
+ enableXCFrameworksOnLinux: true,
+ fileSystem: fs,
+ observabilityScope: observability.topScope
+ ))
+ XCTAssertNoDiagnostics(observability.diagnostics)
+
+ result.checkProductsCount(2)
+ result.checkTargetsCount(2)
+
+ let buildPath = result.plan.productsBuildPath
+
+ let libraryBasicArguments = try result.moduleBuildDescription(for: "Library").swift().compileArguments()
+ XCTAssertMatch(
+ libraryBasicArguments,
+ [.anySequence, "-I", "\(Pkg.appending(components: "Framework.xcframework", "\(platform)-\(arch)"))", .anySequence]
+ )
+
+ let libraryLinkArguments = try result.buildProduct(for: "Library").linkArguments()
+ XCTAssertMatch(libraryLinkArguments, [.anySequence, "-L", "\(buildPath)", .anySequence])
+
+ let exeCompileArguments = try result.moduleBuildDescription(for: "exe").swift().compileArguments()
+ XCTAssertMatch(
+ exeCompileArguments,
+ [.anySequence, "-I", "\(Pkg.appending(components: "Framework.xcframework", "\(platform)-\(arch)"))", .anySequence]
+ )
+
+ let exeLinkArguments = try result.buildProduct(for: "exe").linkArguments()
+ XCTAssertMatch(exeLinkArguments, [.anySequence, "-L", "\(buildPath)", .anySequence])
+
+ let executablePathExtension = try result.buildProduct(for: "exe").binaryPath.extension ?? ""
+ XCTAssertMatch(executablePathExtension, "")
+
+ let dynamicLibraryPathExtension = try result.buildProduct(for: "Library").binaryPath.extension
+ XCTAssertMatch(dynamicLibraryPathExtension, "so")
+ }
+
func testXCFrameworkBinaryTargets() async throws {
try await self.testXCFrameworkBinaryTargets(platform: "macos", arch: "x86_64", targetTriple: .x86_64MacOS)
@@ -6815,6 +6923,12 @@ class BuildPlanTestCase: BuildSystemProviderTestCase {
let arm64eTriple = try Basics.Triple("arm64e-apple-macosx")
try await self.testXCFrameworkBinaryTargets(platform: "macos", arch: "arm64e", targetTriple: arm64eTriple)
+
+ let x86_64Linux = try Basics.Triple("x86_64-unknown-linux-gnu")
+ try await self.testXCFrameworkBinaryTargetsLinux(arch: "x86_64", targetTriple: x86_64Linux)
+
+ let aarch64Linux = try Basics.Triple("aarch64-unknown-linux-gnu")
+ try await self.testXCFrameworkBinaryTargetsLinux(arch: "aarch64", targetTriple: aarch64Linux)
}
func testArtifactsArchiveBinaryTargets(
diff --git a/Tests/FunctionalTests/LibraryEvolutionXCFLinuxTests.swift b/Tests/FunctionalTests/LibraryEvolutionXCFLinuxTests.swift
new file mode 100644
index 00000000000..b441c1a83f9
--- /dev/null
+++ b/Tests/FunctionalTests/LibraryEvolutionXCFLinuxTests.swift
@@ -0,0 +1,109 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the Swift open source project
+//
+// Copyright (c) 2024 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+//===----------------------------------------------------------------------===//
+
+import _InternalTestSupport
+import Basics
+import Testing
+
+private struct SwiftPMTests {
+ @Test(
+ .requireSwift6_2,
+ .requireHostOS(.linux)
+ )
+ func libraryEvolutionLinuxXCFramework() async throws {
+ try await fixture(name: "Miscellaneous/LibraryEvolutionLinuxXCF") { fixturePath in
+ let swiftFramework = "SwiftFramework"
+ try await withTemporaryDirectory(removeTreeOnDeinit: false) { tmpDir in
+ let scratchPath = tmpDir.appending(component: ".build")
+ try await executeSwiftBuild(
+ fixturePath.appending(component: swiftFramework),
+ configuration: .debug,
+ extraArgs: ["--scratch-path", scratchPath.pathString],
+ buildSystem: .native
+ )
+
+ #if arch(arm64)
+ let arch = "aarch64"
+ #elseif arch(x86_64)
+ let arch = "x86_64"
+ #endif
+
+ let platform = "linux"
+ let libraryExtension = "so"
+
+ let xcframeworkPath = fixturePath.appending(
+ components: "TestBinary",
+ "\(swiftFramework).xcframework"
+ )
+ let libraryName = "lib\(swiftFramework).\(libraryExtension)"
+ let artifactsPath = xcframeworkPath.appending(component: "\(platform)-\(arch)")
+
+ try localFileSystem.createDirectory(artifactsPath, recursive: true)
+
+ try localFileSystem.copy(
+ from: scratchPath.appending(components: "debug", libraryName),
+ to: artifactsPath.appending(component: libraryName)
+ )
+
+ try localFileSystem.copy(
+ from: scratchPath.appending(components: "debug", "Modules", "\(swiftFramework).swiftinterface"),
+ to: artifactsPath.appending(component: "\(swiftFramework).swiftinterface")
+ )
+
+ try localFileSystem.writeFileContents(
+ xcframeworkPath.appending(component: "Info.plist"),
+ string: """
+
+
+
+
+ AvailableLibraries
+
+
+ BinaryPath
+ \(libraryName)
+ LibraryIdentifier
+ \(platform)-\(arch)
+ LibraryPath
+ \(libraryName)
+ SupportedArchitectures
+
+ \(arch)
+
+ SupportedPlatform
+ \(platform)
+
+
+ CFBundlePackageType
+ XFWK
+ XCFrameworkFormatVersion
+ 1.0
+
+
+ """
+ )
+ }
+
+ let packagePath = fixturePath.appending(component: "TestBinary")
+ let scratchPath = packagePath.appending(component: ".build-test")
+ let runOutput = try await executeSwiftRun(
+ packagePath, "TestBinary",
+ extraArgs: [
+ "--scratch-path", scratchPath.pathString, "--experimental-xcframeworks-on-linux",
+ ],
+ buildSystem: .native
+ )
+ #expect(!runOutput.stderr.contains("error:"))
+ #expect(runOutput.stdout.contains("Latest Framework with LibraryEvolution version: v2"))
+ }
+ }
+}