Example of OSS project with SPM, Carthage & Cocoapods support?

Hi all,

I have a project i'd like to publish, that currently only supports cocoapods. Ideally, I'd like to support as many distribution options as possible. Are there any good guides out there on doing this? Or an existing project with a relatively simple setup that I can learn from?

Can‘t provide you much detail, but the basic structure for SPM is described on swift.org, carthage is as simple as enabling the shared toggle for the targets of an Xcode project and publishing a tag on the repository.

You can also find a lot of examples on GitHub, just search for Package.swift.

Each option has different limitations. If you post a link to your project, we might be able to give more specific advice.

Unfortunately I can't share yet, still confirming some licensing issues before publishing. Right now it only works on iOS, but it could probably work anywhere where NSObject exists. It's only a couple of Swift files. I'll see if there are any relatively simple projects on github I can learn from.

You can use Alamofire as an example of a library that supports all 3 dependency managers. Fundamentally it isn't hard, just tedious.

CocoaPods is straightforward, especially if you use pod lib init, but fundamentally you just need to make sure you return the appropriate sources source_files value, which supports wildcard matching. You can also declare the minimum deployment OS version, as well as a variety of other settings in the podspec. CocoaPods is still the most powerful dependency manager for Apple platforms.

Carthage is a bit of a pain because it uses the same project you're using to develop, which means you can't enable a lot of tooling by default, but it really just needs you to have to the appropriate shared schemes, as mentioned. You'll need to declare your own Carthage dependencies if you use any.

SPM is annoying since you either have to match its expected on-disk layout or manually specify your paths, and the different types of build product can be annoying to align, but for a one off dependency isn't too bad. Unfortunately there's no way to declare your minimum deployment target, so users will run into build errors if they try to use your library on an unsupported OS, so you should make a note in your installation instructions. And working with a Swift file as the manifest is quite irritating, but Xcode 11 should help some there.

Yes there is.

1 Like

As I understand it, that doesn't set the deployment target generally, just in any generated Xcode projects. That is, it doesn't trigger any compile time errors when just doing swift build. Perhaps I'm confusing that limitation with some other limitation of the SupportedPlatform type.

I have certainly seen it complain many times during swift resolve, etc. when the package graph has incompatibilities with respect to deployment targets.

If you know of corner cases it fails to catch, it would be worth a bug report.

Not sure what you mean but that API is supposed to pass the specified deployment target when performing builds. SwiftPM also checks that the deployment target of your dependencies is lower than or equal to the deployment target of your package.

I must be thinking of a particular limitation in regards to how deployment targets work, though I can't find the particular example I'm thinking of.

SwiftPM has some support for customizing your sources without specifying them individually. For example, you can do sources: ["path/to/a/custom/directory", "path/to/a/some/source/file.swift"]

It would nice if you can expand on these points. Maybe we can figure out some QoL improvements for package authors.

SPM's fundamental separation between products and targets is confusing, at least for those of us coming from Xcode, where the relationship is different. Having them separate also appears redundant, requiring quite a bit of apparent boilerplate to set up a single target. I'm sure there are reasons, but it makes the learning curve steeper than it needs to be, especially if you have your own dependencies. Alamofire's SPM configuration is about as simple as is possible and there are still quite a few gotchas for it to work properly.

Lack of support for a "subspec" equivalent is also another prime annoyance.

As for the the manifest, well, there are quite a few issues I'm sure you're aware of, but first and foremost for me is tooling. Only in Xcode 11 have we gotten an editor that can do any autocomplete on the manifest, and even then, it's limited to when you open a Package.swift file directly, not any general viewing of the file. Without that we're essentially editing Swift source without benefit of an IDE, making it a strictly inferior experience to plain text or DSL manifests.

2 Likes

A product can have multiple targets in the manifest. I am also interested in the reason why that was designed as it is. I don't think i have seen a package where the relation product to target is not 1 to 1.

Alamofire doesn’t declare tests. If it did, the first reason for a distinction between targets and products would become immediately apparent. :wink:

I think some of the philosophical differences between SwiftPM and the others make the learning curve appear higher to anyone trying to bandage one onto a project designed with the other in mind. Speaking as one who learned SwiftPM first and then looked into the others, the others often seem excessively complicated for no apparent reason. It probably isn’t because they actually are complicated, only that they do things opposite to the way I first learned.

Given the comparative investment evident in your Xcode set‐up vs your package manifest, I suspect much of your frustration is similar to mine, only in reverse.

I believe Cocoapod subspecs roughly correspond to what SwiftPM calls products. Alamofire doesn’t declare any subspecs in its podspec. Is there some other project where you ran into this that you would like help with?

I love the executability of the manifest (much less duplication at times), but you are right that things like better autocomplete would be nice.

SwiftPM itself is one such package.

For one, the distinction allows you to build and test smaller modular components. When there is a large source‐breaking change in a dependency you want to update, it can be a life‐saver to be able to build and test one layer at a time, even when the whole thing should be deployed as a single dynamic library in the end.

2 Likes

You should be able to model most uses of subspecs in terms of products. For example, for the common use case of having platform-/framework-specific extensions, you can instead vend platform-specific products which depends on common "core" targets.

In that situation i would personally prefer multiple small libraries / packages. But with Swift's poor namespacing support it makes sense to put everything in a giant framework.

gRPC Swift supports SPM, CocoaPods and Carthage: GitHub - grpc/grpc-swift: The Swift language implementation of gRPC.

The project is SPM-native and CocoaPods support was easy enough. Carthage support was painful; essentially we build the project using swift package generate-xcodeproj than patch it heavily to make Carthage happy. This is compounded by the fact that our package in turn has other SPM dependencies which need to be part of our Carthage project.

I add GRDB.swift to the list, because it has support for CocoaPods and SPM, but removed Carthage from the desired list of distribution options. Fact is, Carthage is difficult to support for the OSS maintainer as soon as three conditions are met:

  1. the library becomes somewhat complex
  2. the library gains traction
  3. the library has shipped its 1.0.0 version and commits to semantic versioning.

Because of 1, the lib may define shared schemes that Carthage will build regardless of their actual usefulness for the library clients. It will also build schemes that Xcode can handle, but may fail with xcodebuild. When you're the library developer, you don't care. But:

Because of 1 and 2, users will start complaining that Carthage builds fail, or take too long. They may also ask for pre-compiled binaries. You start to care, and to spend a lot of time trying to tame the beast. With little success because it's an ever-changing landscape, and the bucket is never full.

Add 3, and you eventually discover that it is impossible to satisfy everybody anyway. There are skilled Carthage users that understand that they live at the edge and are ready to help themselves. And there are considerably more "poor" Carthage users who can't cope with the high level of difficulty brought by this distribution method. Fact is: you become poorer than them when you try to support them.

What I mean is: Carthage is super cool, but beware you don't spoil your OSS energy on it. For more context, you may check this issue.

PromiseKit by @Max_Howell1 supports SPM, Cocoapods, Carthage and Accio (just learned about that one though)

Pretty sure Accio support is free if you support SPM.