Compiling Package.swift in a raw Xcode project
Motivation #
Let me first start with why I even wanted to compile Package.swift
in a raw Xcode project, instead of Apple's default package integration.
Tuist has long had a custom integration of Swift Package Manager dependencies where instead of using the default Xcode integration, which has a myriad of issues, we convert the SPM projects to regular Xcode projects.
For defining the list of dependencies, we've used a Dependencies.swift
manifest but using a custom manifest file broke tools like dependabot. So, for Tuist 4 we're fully moving to Package.swift
.
For users, to be able to edit Package.swift
as any other Tuist manifest, we wanted to include it in the project generated by the tuist edit
command.
The how #
Let's start with the first naive approach – simply including the following Package.swift
in an Xcode project:
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "PackageName",
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", from: "5.8.0"),
]
)
If you do that, you will get the following error: No such module 'PackageDescription'
. The PackageDescription
framework is not included in Xcode projects by default but is inserted by Xcode as part of the default SPM integration.
But where can we find PackageDescription
? We can use the swift package describe --verbose
command which evaluates the Package.swift
– and to do that, it needs to include the PackageDescription
framework.
A part of that output is, indeed, -L /Applications/Xcode-15.1.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/pm/ManifestAPI -lPackageDescription
where -L
is defined as "Add directory to library link search path" and -l
as "Specifies a library which should be linked against". So, the Swift compiler knows it should try to search for PackageDescription
and it also adds the ManifestAPI
path to its search paths. The ManifestAPI
then contains the PackageDescription
itself (more specifically, PackageDescription.swiftmodule
and libPackageDescription.dylib
).
We can now leverage this in our raw Xcode project by adding the ManifestAPI
path to the FRAMEWORK_SEARCH_PATHS
and voila, the project now builds ✨
Swift tools version #
But that's not all. We have successfully added the PackageDescription
framework, however, we currently don't respect the swift-tools-version
defined at the top of the Package.swift
file. Our raw Xcode project treats Package.swift
as any other Swift file, so how could it?
Going back to the output of swift package describe --verbose
, we will also find the following Swift flag: -package-description-version 5.9.0
. For our raw Xcode project, we can simply include this Swift flag in OTHER_SWIFT_FLAGS
and that's it! If we need to get the version number dynamically (as we do in tuist), we can use the swift package tools-version
command.
Afterwards, building the Package.swift
will succeed and use the right Swift tools version. If you are interested in looking at the actual PR we did for tuist edit
, you can find it here.
- Previous: Restarting my blog
- Next: Calm apps