Cocoapods: [RFC] CocoaPods Ranged Swift Version support

Created on 13 Oct 2017  Â·  38Comments  Â·  Source: CocoaPods/CocoaPods

Core PR: https://github.com/CocoaPods/Core/pull/417
CocoaPods PR: https://github.com/CocoaPods/CocoaPods/pull/7253

Background

These issues provide some more context, but I am happy to do a more detailed write up of my goals as a maintainer, if needs be:

Suggested Solution

DSL

Add a swift_version property to a specification, which allows a user to provide
a Requirement a la >= 3.2, <= 4.5 or 4.0.

For example:

Pod::Spec.new do |s|
  s.name = 'BanannaLib'
  s.version = '1.0.0'
  s.swift_version = '>= 3.2, <= 4.0'
  s.source_files = '**/*.swift'
end

Linting

When linting a pod, via pod lib lint or pod spec lint, we will maintain
the current behaviour of allowing a --swift-version or .swift-version file.

A future change for this would be to allow specifying a collection of swift
versions, such as --swift-version=3.2,4.0,4.1 and building and testing the lib
for each of these versions.

Integration

When integrating a Pod, we should always aim to use the most recent version of
Swift that is possible. Because CocoaPods does not have a way to know about
the version of Xcode that is in use, our interpretation of the 'newest' version
should be that of the target you are integrating into.

For example:

- Target A (Swift 5.0)
  - Pod B (>= 3.2) -> Swift 5.0
  - Pod C (>= 3.2, <= 4.0) -> Swift 4.0
    - Pod D (no-range) -> Swift 4.0
    - Pod E (<= 3.2) -> Swift 3.2
      - Pod F (no-range) -> Swift 3.2
      - Pod G (= 4.0) -> Swift 4.0

When we have multiple targets:

- Target A (Swift 5.0)
  - Pod B (>= 3.2) -> Swift 4.0
- Target B (Swift 4.0)
  - Pod B (>= 3.2) -> Swift 4.0

When Compatibility is Broken

The swift_version property represents the library authors intention of the
code to support the version range that they specified, with the information that
they have about past and future versions of Swift.

Due to this being unverifiable on future Swift versions at the time of publishing,
a loose version range, such as >= 3.2 may break in the future.
Authors should be mindful of this, and specify defensive ranges, rather than
being overly broad.

For example, >= 3.2, < 5 may be a reasonable range for a pod authored today.

In the cases where a resolved version of Swift proves to be incompatible, CocoaPods
will not offer a specific language to override this, as doing so for issues in transitive dependencies would require complicating much of the DSL. However, teams may override the setting with a post install hook like below:

post_install do
  # Include example to override swift versions here.
end
moderate enhancement discussion

Most helpful comment

We might also need to introduce a DSL for the Podfile to allow consumers to select a Swift version for a pod (as long as it's included in the list of supported Swift versions the pod author has declared).

Like:
pod 'SwiftPod', '1.0', :swift_version => '4.0'

Although today there are ways to do this via a post_install hook the DSL will be cleaner.

All 38 comments

@DanToml I'd argue we deprecate or remove --swift-vesion and .swift-version file entirely and let the podspec DSL drive the actual value to use. It seems weird for a podspec to have a Swift version set in its DSL but then to be overridden (and published) using a different version during spec or lib lint (or push).

Thoughts?

@dnkoutso Our issue then, is what do we do when:

  • No version range is provided
  • Only a minimum version range is provided
  • Only a maximum range is provided

Hm good points, should we handle these cases when --swift-version is passed and potentially error out if you are within the range or not?

Pod::Spec.new do |s|
  s.name = 'BanannaLib'
  s.version = '1.0.0'
  s.swift_version = '>= 3.2'
  s.source_files = '**/*.swift'
end

and:

pod lib lint BanannaLib --swift-version=2.3 ---> error?

Btw the RFC LGTM overall!

@dnkoutso I think we’d actually get that for free when it tried to integrate the test project, but yes that is the behaviour that seems most obvious to me

Minor edge case to handle, and not in the core, is a podspec providing a swift_version without having any Swift files in it (uses_swift? returns false)

That should fail linting unless you have a very good reason IMO

Agreed.

IMO we should keep the .swift-version file support if it's easy to keep around - it's a good community standard with SwiftPM also

Just wanted to chime in regarding the test native targets. I believe test native targets generated by test specs of a podspec should inherit the swift version specified. Is that correct or am I missing something @DanToml ?

I would prefer simply:

Pod::Spec.new do |s|
  s.name = 'RubyToSwiftConversionAI'
  s.version = '1.0.0'
  s.swift = '>= 3.2'
  s.source = '**/*.swift'
end

... dropping the unnecessary (and inaccurate) _version and _files.

(Inaccurate because we have a set of ranges of versions, not a version; and because we have a path to directories, files, and symlinks, not just files.)

Other than that I really like this direction. As for @dnkoutso's comment, the answer should be yes they should inherit their parent .podspec's s.swift version, unless y'know, they override it with test_spec.swift = '< 4.0' in which case it would simply add to the parent's s.swift array unless it caused a conflict—dependency management 🗡

@gistya it’s not possible to lint a pod without a concrete version to build and test against, so we need to keep that file around.

Subspecs will not be allowed to override or modify the swift version, and should obey the same range as the root spec.

@DanToml is this case correct that's in the initial proposal?

- Target A (Swift 5.0)
  - Pod C (>= 3.2, <= 4.0) -> Swift 4.0

Should we allow this case since Target A supports Swift 5.0 but Pod C has Swift 4.0?

I guess depends if we treat the targets Swift version as <= 5.0 or not and whether Xcode supports this configuration.

Still need to progress on this. The DSL is good but I haven't had time to implement the integration.

Is the goal of allowing a range instead of a single value so that you can use #if swift(version) inside your code and support multiple versions?

We had a few more discussions around this and decided to scale back down the design a bit. For starters we will only support = version and allow the pod author to explicitly specify the version of swift the pod supports.

This is inline with also the .swift-version file during linting and will be a good stop-gap to ship 1.4.0.

If we need to support ranges in the future we can do it incrementally or via a cocoapods plugin.

Would this still allow a project written in Swift 3.2 to import a Swift 4 pod?

yes as long as the pod author has set s.swift_version = '4.0' in the podspec.

From experimenting with Xcode this is allowed so it will be the same.

I am working on this today. Scaling it down just to use a single version for s.swift_version

Can we get an update to the RFC (or a new comment) that reflects how this feature was actually implemented? Or is there documentation of the expected behavior somewhere else?

Additionally, will there be errors when the user attempts to integrate a project with pods that have incompatible versions? e.g. A 4.0 project trying to use a 3.1 pod, or a 4.1 project trying to use a 4.0 pod.

Finally, as you know, a single allowed Swift version means pods can't support the same versions they can in code, especially across Xcode versions. For instance, with this attribute, Alamofire can't support 3.1, 3.2, and 4.0 as it does in code. So we may not be able to adopt this attribute as it is now, since any version we set would break integration with one of our supported versions of Xcode.

@jshier some valid points raised here. I am going to actually keep this open and mark it for 1.5.0 and specify is for Swift version range support.

@jshier

Additionally, will there be errors when the user attempts to integrate a project with pods that have incompatible versions? e.g. A 4.0 project trying to use a 3.1 pod, or a 4.1 project trying to use a 4.0 pod.

I think with today's implementation this will work as long as Xcode supports the Swift version. Today with Xcode 9 I can easily now have a pod that says it works with Swift 3.2 but my project actually uses Swift 4.0.

Finally, as you know, a single allowed Swift version means pods can't support the same versions they can in code, especially across Xcode versions. For instance, with this attribute, Alamofire can't support 3.1, 3.2, and 4.0 as it does in code. So we may not be able to adopt this attribute as it is now, since any version we set would break integration with one of our supported versions of Xcode.

For this, you are right and this is why I am keeping this issue open. Right now pod authors can only specify a single Swift version using spec.swift_version attribute.

Additionally, will there be errors when the user attempts to integrate a project with pods that have incompatible versions? e.g. A 4.0 project trying to use a 3.1 pod, or a 4.1 project trying to use a 4.0 pod.

I think with today's implementation this will work as long as Xcode supports the Swift version. Today with Xcode 9 I can easily now have a pod that says it works with Swift 3.2 but my project actually uses Swift 4.0.

Right, my question is about what happens when attempting to integrate, say, a Swift 4.0 pod with a 4.1 project (or any incompatible versions in general). Will CP produce an error or will it just fail at build time? My hope is that CP can handle cases like this so that users don't have to wait for confusing build failures to know their configuration isn't going to work.

As for range support, as a library developer I'm okay if, instead of ranges, an explicit list of versions is required. Might be a bit safer anyway, since a >= 3.2, < 5 library published now may fail to build with 4.1, since it may have breaking changes.

Will CP produce an error or will it just fail at build time? My hope is that CP can handle cases like this so that users don't have to wait for confusing build failures to know their configuration isn't going to work.

It will be a build failure by Xcode, not by CocoaPods. We are intentionally staying out of the business to track Xcode versions to Swift versions within CocoaPods. Trying to keep up with Apple will not be a wise strategy as we never know the roadmap and will require us to make releases of CocoaPods as new information is coming in which is not sustainable.

I think CP can reason about Swift version compatibility without having to consider Xcode versions at all. For instance, we know that any attempt to integrate a 3.1 pod with a 4.0 project will fail. We know that any attempt to integrate a 4.0 pod with 4.1 will fail (barring luck that the library is source compatible). Like currently supported Xcode-specific settings (like the last check version, which still hasn't been updated), it could lag behind the current state of the art without breaking anything, but would offer at least some feedback for users. It could just be a warning and still integrate the project if necessary.

I push this issue because these sorts of compatibility issues are a huge source of GitHub issues for Alamofire and other libraries at the moment (or at least around the release of new Swift versions), and if CP could do something to help it would be appreciated, but I understand it would have a maintenance impact.

Thanks. It would be great if the changes in 1.4.0, and proposed changes in 1.5.0 could be clarified.

@dnkoutso After your PR, master now requires the swift_version field and crashes without it, at least for podspecs out of git repos.

@jshier thanks for reporting this. Do you publish podspecs in ruby and not JSON? We used to do the same but I think its preferred to publish them in JSON using --use-json parameter during publishing. I highly recommend doing the same.

Having said that, since this is a minor update (1.4.0) of CocoaPods I think we should guard against this. Will probably have a PR this week after I chat a bit with colleagues on handling this.

Updated title to specify this is support for "Ranged" Swift version Support in 1.5.0 (if we make it).

For 1.4.0 swift_version DSL has been added with a single swift version.

@dnkoutso isn't that still ignored?

To what are you referring to?

Any updates on this?

With things like Swift support for conditional conformances or new protocols in 4.2 (CaseIterable), as an author of a library, we can include the support wrapped in #if swift(>=4.2) guards, but CocoaPods support for a single swift_version it seems to force libraries to either:

  1. Have a branch for every swift version with unique version numbers to segment.
  2. Force all our consumers to adopt the newest Xcode to get the tools support.

The overhead 1 would force has the potential to be non trivial, but 2 doesn't seem right either; especially since 2 would likely mean we'd be back to having to do multiple branches if there was a bug fix we needed to provide for folks that couldn't move to a new toolchain (if they are about to do an release release and cannot change the toolchain right then).

fyi - SwiftPM does support indicating multiple versions, so it doesn't run into this.

Not much progress although I do have my eyes set on this for 1.7.0 version of CocoaPods. Right now I am trying to wrap up the first 1.6.0 beta.

Totally agree we should expand this to support multiple versions, perhaps an array of versions in the DSL similar to that of SwiftPM? I wouldn't mind if its the exact same functionality as SwiftPM in terms of rules of choosing Swift version to use during pod install.

These are the current 1.6.0 PRs open (https://github.com/CocoaPods/CocoaPods/pulls?q=is%3Aopen+is%3Apr+milestone%3A1.6.0) but only one of the two needs to land (the test spec one) in order for 1.6.0 beta to begin as its the last feature enhancement.

If anyone would like to take sometime in the meantime to expand this I'd be more than happy to review and maybe even land it in 1.6.0! I'd start here https://github.com/CocoaPods/Core/blob/master/lib/cocoapods-core/specification/dsl.rb#L132-L143 by expanding the DSL to accept an array of strings perhaps which is all supported Swift versions.

We might also need to introduce a DSL for the Podfile to allow consumers to select a Swift version for a pod (as long as it's included in the list of supported Swift versions the pod author has declared).

Like:
pod 'SwiftPod', '1.0', :swift_version => '4.0'

Although today there are ways to do this via a post_install hook the DSL will be cleaner.

Gonna mark this for 1.7.0

We _might_ also need to introduce a DSL for the Podfile to allow consumers to select a Swift version for a pod (as long as it's included in the list of supported Swift versions the pod author has declared).

Like:
pod 'SwiftPod', '1.0', :swift_version => '4.0'

often have a use case for this!

To everyone following this thread. I am closing it in favor of https://github.com/CocoaPods/CocoaPods/issues/8191

I wrote the entire RFC again and have actually implemented the changes.

Please direct comments to #8191.

Aiming to ship this with 1.7.0.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

intelliot picture intelliot  Â·  3Comments

soleares picture soleares  Â·  3Comments

steffendsommer picture steffendsommer  Â·  3Comments

sonu5 picture sonu5  Â·  3Comments

pronebird picture pronebird  Â·  3Comments