SPM: Using path to binary target (XCFramework) works in Xcode but not from command line?!

While starting to play around with library evolution and binary targets, I've run into a very strange behaviour.

I have a framework (which is built with resiliency with SPM) and a trivial client (to start verifying that things are built as expected, ensuring I can use @unknown default:)

It works when I run the SPM package in Xcode, but not from the command line - then it can't find the .xcframework / module for import.

I've created a minimised reproducer that can be run following these steps:

gh repo clone ordo-one/prototype-library-evolution
gh repo clone ordo-one/prototype-library-evolution-client
cd prototype-library-evolution
swift build
xcodebuild -create-xcframework -library .build/arm64-apple-macosx/debug/libPrototypeLibraryEvolution.dylib -output PrototypeLibraryEvolution.xcframework
cd ../prototype-library-evolution-client
swift run

swift run will fail with:

hassila@max ~/G/prototype-library-evolution-client (main)> swift build

Building for debugging...

**/Users/hassila/GitHub/prototype-library-evolution-client/Sources/PrototypeLibraryEvolutionClient/PrototypeLibraryEvolutionClient.swift:2:8:** **error:** **no such module 'PrototypeLibraryEvolution'**

import PrototypeLibraryEvolution

But, if one then opens Package.swift and runs it in Xcode (14 b5) - things work as expected!

So any ideas what can cause this to not work from the command line?!

This is with Xcode 14b5.

hassila@max ~/G/prototype-library-evolution-client (main) [1]> swift --version
swift-driver version: 1.62.3 Apple Swift version 5.7 (swiftlang-5.7.0.123.8 clang-1400.0.29.50)
Target: arm64-apple-macosx12.0

Not super familiar with building XCFrameworks from loose dylibs, but how does the resulting PrototypeLibraryEvolution.xcframework look like? From the command, I would assume it does not actually contain the .swiftmodule/.swiftinterface

Yep, it does:

hassila@max ~/t/prototype-library-evolution-client (main) [1]> ls ../prototype-library-evolution/PrototypeLibraryEvolution.xcframework/macos-arm64/
PrototypeLibraryEvolution.swiftdoc        PrototypeLibraryEvolution.swiftinterface  libPrototypeLibraryEvolution.dylib*
hassila@max ~/t/prototype-library-evolution-client (main)> more ../prototype-library-evolution/PrototypeLibraryEvolution.xcframework/macos-arm64/PrototypeLibraryEvolution.swiftinterface 
// swift-interface-format-version: 1.0
// swift-compiler-version: Apple Swift version 5.7 (swiftlang-5.7.0.123.8 clang-1400.0.29.50)
// swift-module-flags: -target arm64-apple-macosx10.13 -enable-objc-interop -enable-library-evolution -swift-version 5 -Onone -module-name PrototypeLibraryEvolution
// swift-module-flags-ignorable: -user-module-version 1.0
import Swift
import _Concurrency
import _StringProcessing
public enum PrototypeLibraryEvolutionTest : Swift.Int {
  case a
  case b
  case c
  case d
  public init?(rawValue: Swift.Int)
  public typealias RawValue = Swift.Int
  public var rawValue: Swift.Int {
    get
  }
}
public func getEnum() -> PrototypeLibraryEvolution.PrototypeLibraryEvolutionTest
extension PrototypeLibraryEvolution.PrototypeLibraryEvolutionTest : Swift.Equatable {}
extension PrototypeLibraryEvolution.PrototypeLibraryEvolutionTest : Swift.Hashable {}
extension PrototypeLibraryEvolution.PrototypeLibraryEvolutionTest : Swift.RawRepresentable {}
hassila@max ~/t/prototype-library-evolution-client (main)> 

The weird thing is really that opening the package in Xcode (open Package.swift) and it builds/run fine, quite curious.

(for completeness, I also had it working with a minimal directory containing just the Package.swift + the .xcframework - same thing there, worked in Xcode, but not from CLI)

Ah, I guess -create-xcframework must have some smarts where it automatically finds the module definition.

Could you also share the contents of the Info.plist?

Certainly;

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>AvailableLibraries</key>
	<array>
		<dict>
			<key>LibraryIdentifier</key>
			<string>macos-arm64</string>
			<key>LibraryPath</key>
			<string>libPrototypeLibraryEvolution.dylib</string>
			<key>SupportedArchitectures</key>
			<array>
				<string>arm64</string>
			</array>
			<key>SupportedPlatform</key>
			<string>macos</string>
		</dict>
	</array>
	<key>CFBundlePackageType</key>
	<string>XFWK</string>
	<key>XCFrameworkFormatVersion</key>
	<string>1.0</string>
</dict>
</plist>

Just from reading the code quickly, I wonder if this might be the problem swift-package-manager/BuildPlan.swift at main · apple/swift-package-manager · GitHub, we're only passing -I for any HeadersPath, but the XCFramework does not set one.

So Xcode does use a different implementation? (As it’s found there?)

So does the .xcframework creation tool miss to add that, or is it an issue in SPM in that case?

Anything I can try to nail it down?

Yes, packages get build with Xcode's own build system there, which is entirely different. The Build module is only used by SwiftPM on the commandline.

Since Xcode pretty much defines how an XCFramework looks like, I would say this needs to be fixed in SwiftPM.

1 Like

Ok, thanks! I’ll file an issue with the reproducer then on swiftpm.

For future Googlers, the issues is here:

1 Like