How to import prebuilt frameworks from local project in Swift package?

My org has very large app that currently has appx. internal 40 framework targets, some of which are nested within others, plus another 15 or so Carthage dependencies.

We'd like to transition to using Swift packages instead of frameworks, and using SPM instead of Carthage, but it's not feasible to transition everything at once. We need to migrate in phases.

We'd also like to start implementing any new modules from here on, using Swift packages, so we're not creating tech debt.

Normally in Xcode to link one module against some other module you drag the framework product into the "Frameworks & Libraries" list. Boom, done.

However with Swift packages, it seems the only way to define a dependency is if it's a Swift package AND you have all the source code.

There does not seem to be any way to link a build product (like a framework or library). Since most of our modules depend on prebuilt Firebase modules for analytics, that seems like a dealbreaker, unless I'm missing something.

As well, our CI pipeline currently uses fastlane to cache build artifacts so we don't have to compile everything from source with each run. How can we still take advantage of this if we migrate to using SPM instead of Carthage?

If we have multiple packages, say, that all depend some other package, does Xcode rebulid that dependency over and over when it builds each package?

I believe binary dependencies is coming in Swift 5.3: [Accepted with Modifications] SE-0272: Package Manager Binary Dependencies - #16 by antonino-u

I believe binary dependencies is coming in Swift 5.3: [Accepted with Modifications] SE-0272: Package Manager Binary Dependencies

I wonder when this will be available for use in Xcode iOS projects?

Given the usual cycle (perhaps with changes from the current situation), Xcode 12 will have it enabled in some fashion, with a preview in June and shipping in September. Hopefully adding support for this feature is among of multitude of changes to SPM's integration into Xcode by that time.

It depends:

  • generally speaking, packages behave similar to framework targets in that they get build once and not once for each client
  • contrary to framework targets, packages can build multiple times for different platform in the same build operation if needed (e.g. if the same package product is required by an iOS app and a watch extension). This won't have additional overhead, though, with framework targets you would need to create two distinct targets, one for iOS and one for watchOS.
  • in CI, you would build packages again every time, assuming you start off with a clean build. I am not familiar with how fastlane's caching works, but if it caches build products + intermediates, that would apply to packages as well.

So is it possible to import a prebuilt framework or XCFramework into SPM now? If so how can you do it?

There is some documentation for this here: Apple Developer Documentation

Yeah it seems it only supports XCFrameworks though, not .frameworks. This is a deal-breaker for us, since at least for now Carthage only builds .frameworks.

Guess we'll have to wait.

I think it is unlikely that packages will support plain frameworks since we aim for packages to be multi-platform and frameworks inherently are not.

Maybe you could wrap the frameworks produced by Carthage into XCFrameworks yourself, the xcodebuild -create-xcframework command can do that in one step.

I think it is unlikely that packages will support plain frameworks since we aim for packages to be multi-platform and frameworks inherently are not.

Currently our Carthage dependencies are frameworks, and we support iOS and iOS Simulator platforms with no issues. Why can't we just tell our Swift packages, "here is the framework search path" and it will trust that the right platform's frameworks will be there at compile time and runtime?

Another issue for us is that we have a lot of locally-declared frameworks that are not already built at compile-time. Currently there is no way to import one of these frameworks into a Swift Package, to my knowledge. This is very limiting in terms of how we can use Swift Packages as modules within our application, and I don't understand why it should be a limitation considering the fact that our local frameworks will be built for the same platform the Swift packages are being built for.

We'd be open to building XCFrameworks at build-time if it meant we could import them into the Swift Packages but Xcode does not give you a way to build an XCFrameworks at build-time—you have to archive, then do some command-line stuff.

Both Swift Packages and XCFrameworks both seem designed just to cater to people distributing a library, either prebuilt or not. They don't seem to have many considerations for people using Swift Packages to define modules inside their existing applications. Like, what if you want to import a framework from the same workspace as the package?

You might say, "Well, Swift Packages weren't designed for what you're using them for." But I'd say, they should be! We kind of hate Xcode project files and all the merge conflicts that result from them. Swift Packages seem like a godsend but they have too many limitations and gotchas right now.

Maybe you could wrap the frameworks produced by Carthage into XCFrameworks yourself, the xcodebuild -create-xcframework command can do that in one step.

The problem is that Carthage makes multi-platform frameworks on the assumption that before it bundles a framework into a given bundle, it will strip out the bits that don't go with that platform at build-time. So it's not really feasible to take its "frameworks" and make XCFrameworks using that command, since that command expects that frameworks are already single-platform.

Fortunately, there is a new version of Carthage coming up (it's in PR right now) that creates XCFrameworks instead of frameworks. However so far it only works for things you can build from source—but unfortunately, we have third-party dependencies like Firebase/Crashlytics etc. that are only available as .frameworks.

So it's super annoying not to be able to import .frameworks directly into our local Swift packages, because of course, everything has to depend on the thing that depends on Crashlytics, unless we use some dependency inversion scheme (which is what we're resorting to).

That being said, I guess I don't understand why Swift Packages couldn't be made to import regular .frameworks, given certain conditional build settings, or assuming that the right framework will be at the search path at runtime etc.

Using conditional imports and compiler directives to keep certain code specific to certain platform builds seems like it could have worked as a solution for this, rather than blocking off an entire use case from Swift Packages.

BTW, Crashlytics is distributed both as a Swift package and as an xcframework.