Cocoapods: Support multiple Swift versions on a per pod basis

Created on 11 Jun 2017  路  64Comments  路  Source: CocoaPods/CocoaPods

Report

What did you do?

Run pod install, then try to compile with Xcode 9 beta 1.

What did you expect to happen?

All pod dependencies are compiled using Swift 3.2. Checking the Pods project, I'd expect to see all the various frameworks have the Swift Language Version set to 3.2

What happened instead?

The various frameworks in the Pods project have the build setting Swift Language Version set to Swift 4.0, and are compiled as such. Therefore, I'm seeing many compilation errors.

Is there a way for me to force Xcode to use a Swift version per pod?

CocoaPods Environment

Stack

   CocoaPods : 1.2.0
        Ruby : ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin16]
    RubyGems : 2.5.1
        Host : Mac OS X 10.12.4 (16E195)
       Xcode : 9.0 (9M136h)
         Git : git version 2.7.0
Ruby lib dir : /usr/local/Cellar/ruby/2.3.0/lib
Repositories : master - https://github.com/CocoaPods/Specs.git @ 5bd03fef224221ba8a5cdf67ba2102be7fe5216b

Installation Source

Executable Path: /usr/local/bin/pod

Plugins

cocoapods-deintegrate : 1.0.1
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.0
cocoapods-stats       : 1.0.0
cocoapods-trunk       : 1.1.2
cocoapods-try         : 1.1.0

Podfile

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'Project' do

    use_frameworks!

    pod 'PureLayout', '~> 3.0.2'
    pod 'Anchorage', :git => 'https://github.com/Raizlabs/Anchorage.git', :tag => '4.0.0'
    pod 'BuddyBuildSDK'
    pod 'SwiftTweaks', :git => 'https://github.com/Khan/SwiftTweaks.git', :tag => 'v2.0-beta.1'
    pod 'RealmSwift'
    pod 'SwiftGen'
    pod 'NVActivityIndicatorView', '~> 3.6.1'

    target 'ProjectTests' do
    inherit! :search_paths
    # Pods for testing
    end

    target 'ProjectUITests' do
    inherit! :search_paths
    # Pods for testing
    end

    # Enable DEBUG flag in Swift for SwiftTweaks
    post_install do |installer|
        installer.pods_project.targets.each do |target|
            if target.name == 'SwiftTweaks'
                target.build_configurations.each do |config|
                    if config.name == 'Debug'
                        config.build_settings['OTHER_SWIFT_FLAGS'] = '-DDEBUG'
                    end
                end
            end
        end
    end
end
moderate enhancement discussion

Most helpful comment

this works:

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if target.name == '<insert target name of your pod here>'
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '3.0'
            end
        end
    end
end

It's not pretty but will do until they update your pods to Swift 4.

All 64 comments

Probably needs to be updated to automatically use Swift 4.0. Xcode 9 is brand new however your post install hook should be a good workaround for now. This will be getting fixed.

@dnkoutso might finally be time to add something to the spec dsl, to know which version to integrate as :( - fallback to 3.x if not set?

yeap. seems like it.

Thing is, pods might support multiple swift versions, so pods should be able to specify a range of swift versions.

If a pod supports more than one swift version, a user should be able to specify which swift version they want in their Podfile? Or should that selection happen based on the (users) project's swift version?

Swift 3.2 and 4.0 can exist in a single project. In my opinion it makes no sense to compile a pod with Swift 3.2 when it also supports 4.0. A user's 3.2 target could use the 4.0 pod.

Yeah, we've been cautious because each Xcode release (7, 8 & 9 now) has had different rules about how it handles Swift, and the Swift toolchain. If we added rules at 7 and 8, both would be deprecated by the different 9 + Swift 3 +4 rules. So It's worth being super careful with adding DSL attributes in the Podfile for this.

Wouldn't that be a justification for adding a Podfile override on a per Pod basis? In case a future Xcode version has some weird unsupported behaviour, the user can manually specifiy the swift version instead of waiting on a CocoaPods release?

The problem is that what we'd need to do changes with each release, because those constraints change, so it'd be deprecated quickly. The careful consideration needs to be that any additions to the Podfile can happen, but removals or changes to behaviour are a semver breaking change and need be done in a very long-term way

The solution will need careful consideration of course, but the time for one has come. Apple officially supports mixed dependencies with Swift 3.2 and 4. CocoaPods needs to be able to set the Swift version on a per-pod basis, there's no way around that now. I think a versioning scheme can be created that can work independently of Xcode versions with only minor updates needed over time to connect Swift versions with Xcode version. Frankly, I think it has been possible for years now, with the only real difficultly being how Xcode indicates version support. Even there, nothing has changed between Xcode 8 and 9, so I think SWIFT_VERSION is here for good.

The problem is that what we'd need to do changes with each release

That's totally normal for Xcode development. imo you should see it the other way around: whatever you decide now only matters for one year, then it'll need to get changed anyway 馃槣

Sorry to nag but is a solution to this being worked on in a branch somewhere? Or is this being given serious consideration for attention soon?

I'm asking b/c XC9 will most likely be out in 2 months... if the patterns of past years are any indication. I'd like to migrate my app's code to Swift 4, and still use my Swift 3 pods. And did I mention that I really dislike post-install hooks?

Part of the cool thing about XC9 is that Swift3 and Swift4 can co-mingle like this. It would be awesome if Cocoapods could accommodate this. I recently started a brand-new project, and briefly considered using Swift 4, until I hit this problem. I understand that not everyone is starting new projects, or is interested in jumping to the latest Swift right now. But I don't think that I'm such an outlier as one who wants to upgrade to the latest Swift sooner rather than later.

So, I guess this is just my upvote in the hope of getting this addressed soon. Thanks for listening.

This may be the same issue, but in my podspec I have dependencies on pods that need to compile using Swift 3.2. The code of my pod (the one described by the podspec) needs Swift 4.0. If I build with Xcode9 (e.g., beta 4), I get no errors. If I attempt to build with Cocoapods prior to uploading a new pod version (pod lib lint --sources="https://github.com/crspybits/Specs.git,https://github.com/CocoaPods/Specs.git" --allow-warnings), the build fails. E.g.:

- ERROR | [iOS] xcodebuild:  Gloss/Sources/ExtensionArray.swift:76:63: error: initializer 'init()' is internal and cannot be referenced from a default argument value

Let me know, and I can open this as a separate issue if needed.

This feature looks like a good place for someone to try and contribute back, as I've heard of no-one looking at this.

@crspybits CP lints with whatever version of Xcode you had set for your CLI, so if it passed in XC9 then it passes That doesn't mean it would pass on 8, 7, 6 etc. It'd be folly for CP to try support something like that

I didn't realize that about lint. That makes some sense.

This appears not limited to lint though. I get the same issue with pod repo push crspybits-specs SyncServer.podspec --sources="https://github.com/crspybits/Specs.git,https://github.com/CocoaPods/Specs.git" --allow-warnings (unless this also is running lint).

It seems that right now it's not possible to have a Cocoapod where the main code (specified in the .podspec) uses Swift 4 and dependent pods use, say, Swift 3.2. I am using Cocoapods version 1.2.1, so don't know if this changes with the forthcoming version update. Nope. Just installed 1.3.0.rc.1 and the situation is the same.

this works:

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if target.name == '<insert target name of your pod here>'
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '3.0'
            end
        end
    end
end

It's not pretty but will do until they update your pods to Swift 4.

@DanielCreagh That's been a know solution for previous Swift versions, but the issue keeps appearing each year, which is why we're asking for a built in solution. Also, you'll have to look up what swift version is supported by each pod, and that if test can become quite long if you have a project with many pods with mixed swift versions.

The best solution is if a pod can specify supported swift version(s), and CocoaPods does it's own thing automatically.

What's standing in the way of adding a property to the podspec that indicates Swift version compatibility? Apple is explicit about their support for gradually moving to Swift 4 and mixing different Swift versions where needed. The need for Cocoapods to properly support this will only grow, IMO.

I've made PromiseKit support Swift 3.0, 3.1, 3.2 & 4.0. I recommend all pod authors consider it: means whatever CocoaPods does is fine.

Edit: also considering the differences between Swift 3 and 4 are much less vast, and you can do #if swift(>=3.2) since Swift 3 this is actually a pretty straight forward thing to do. It took me an hour to port PromiseKit in a backwards compatible way.

@mxcl - Do you have a guide on how to update a pod for a specific version of swift? We have some private in house pods that I've been asked to update to swift 4 and I can't find much online about it.

@niallwatchorn for PromiseKit I have an .xcodeproj (so it works with Carthage as well), so I opened that with Xcode 9 and set the Swift Version to 4.0. If you don't have an xcodeproj then I'm not sure what to advise.

Then to ensure the support is maintained I have a hefty CI matrix.

Edit: Thinking about it, this doesn鈥檛 guarantee CocoaPods support (there may be minor differences due to the Podspec鈥檚 specifications). Really I should get the CI to run pod lib lint for each configuration too.

Looks like no fix for this yet? We're at Xcode9 GM Seed...

A generalization on @DanielCreagh for those who don't know much Ruby (like me!):

post_install do |installer|
        # Your list of targets here.
    myTargets = ['AlignedCollectionViewFlowLayout', 'JVFloatLabeledTextField']

    installer.pods_project.targets.each do |target|
        if myTargets.include? target.name
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '3.2'
            end
        end
    end
end

for those who don't know much Ruby (like me!):

Right. Me either. And that's my only frustration with finding issues in Cocoapods. (sorry, minor editorial here).

In my career, I've worked with several languages, but never Ruby. I'm 100% Swift now. So, when I see comments like "This feature looks like a good place for someone to try and contribute back"... I don't disagree, but I also don't feel like I'm equipped to do that. I have no experience with the language, no idea what the dev environment would be or how to debug it. It's a bit daunting.

Cocoapods is a colossal engineering achievement, and I absolutely love it and am eternally grateful to all of those who have worked on it so tirelessly. I wish there were a "Never done Ruby, and want to patch Cocoapods -- here's the guide". Does such a thing exist?

Did you check out the CocoaPods Guides website? We have 5 on contributing back to CocoaPods on the homepage. Obviously we can't cover everything, but it's been enough for most contributors.

thanks for the tip. I don't think I've ever scrolled down that far on the Guides page. Nice to know that the resource exists.

For most people this is what you want:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.2'
    end
  end
end

This makes Xcode build all your pods with Swift 3.2.

Why do we need to add a property to the podspec? Isn't "specifying the version of Swift used in this pod" the explicit reason for the .swift-version file?

The current solution for us is to simply use Swift 3.2 until all pods are compatible, to do so all I had to do was update my .swift-version file from 3.0 to 3.2 and then run pod update and things worked flawlessly!

@kdbertel .swift-version is currently used only for linting pods. The Swift version the pod will be build with is derived from the project settings, as far as I can tell.

@mostafaberg That is an untenable solution when we have a dozen pods that we need to one-by-one convert up to Swift 4. We need the ability to control what version of Swift each pod is built with.

@robinkunde It appears to be the settings of the hosting application, which is problematic, because it means you can't have a Swift 3.2 application bring in a Swift 4 pod, or vice-versa.

@kdbertel That's possible, you can do similar in your Podfile. Where swift_32 contains the pods I want to lock to Swift 3.2 and swift4 contains the pods I want to lock to Swift 4. All other pods will use setting set by Xcode/CocoaPods.

swift_32 = ['RxSwift', 'RxCocoa']
swift4 = ['PalaverKit']

post_install do |installer|
  installer.pods_project.targets.each do |target|
    swift_version = nil

    if swift_32.include?(target.name)
      swift_version = '3.2'
    end

    if swift4.include?(target.name)
      swift_version = '4.0'
    end

    if swift_version
      target.build_configurations.each do |config|
        config.build_settings['SWIFT_VERSION'] = swift_version
      end
    end
  end

Of course, this isn't automatic behaviour. But it does allow you to migrate your pods over one-by-one in current versions of CocoaPods.

@kylef That requires the consuming application to happen to magically know what version of Swift all of its component pods are targeting, which is again untenable. A given pod needs to be able to specify what version of Swift it is targeting, so that consuming applications don't need to care.

I took over the UberRides pod recently and migrated it to Swift 4, expecting I could support 3.2/4 codebases without having to make it compile in both Swift versions. In this case, I need to tell my Pod consumers using Swift 3.2 to add post_install tags, which makes no sense.

the problem with the post_install hook solutions is that they are useless when running pod lib lint or pod push

A possible solution could be for cocoapods to not override SWIFT_VERSION at all.

Each pod that supports Swift 4 could explicitly define the following in its podspec file:

spec.xcconfig = { 'SWIFT_VERSION' => '4.0' }

In your project's podfile instead (just for the time being):

  post_install do |installer|
    installer.pods_project.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = '3.2'
    end
  end

In this way, all the pods dependencies will default to SWIFT_VERSION = 3.2, but pods that override that setting in their podspec will build using swift 4.0.

This is working perfectly for me now with a mix of some pods built with swift 4.0 and 3.2

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            if ['Kingfisher', 'RxSwift', 'RxCocoa'].include? target.name
                target.build_configurations.each do |config|
                    config.build_settings['SWIFT_VERSION'] = '3.2'
                end
            end
        end
    end

I agree with @orta and @jshier about being cautious to avoid adding to the Podfile DSL.

I also agree with @edjiang that it doesn't make sense for consumers of a CocoaPod to need to know its compatible Swift versions.

How about a solution like this?

  1. Pod owners are encouraged to specify their highest supported version using:

    spec.xcconfig = { 'SWIFT_VERSION' => '4.0' }
    

    (as @valeriomazzeo mentioned above)

  2. pod spec lint should generate a warning if SWIFT_VERSION is unspecified

  3. pod install should use set SWIFT_VERSION to the oldest Xcode-supported version (currently 3.2 for Xcode 9) if it's unspecified in the Podspec
  4. Pod consumers who want to manually change a pod's SWIFT_VERSION to something lower than the highest supported version for some reason are welcome to do so in the post_install hook.

@getaaron Sorry, but while that's pretty much the current workaround, it's certainly not an acceptable solution long term. CocoaPods users shouldn't have to care about what versions of Swift a dependency is compatible with except when their toolset doesn't support any of them.

Given that the Swift team has already stated that the next version of Swift with be a hybrid 4 / 5 compiler, it seems very likely the situation will remain unchanged from present, so a solution is needed long term. I suggest the following:

  1. An optional Podspec DSL attribute is added, swift_versions (name subject to bikeshedding), which contains the Swift versions a library is compatible with. For instance, Alamofire is currently compatible with 3.1, 3.2, and 4.0, so we'd list it like this: swift_versions = { '3.1', '3.2', '4.0' } (or whatever the appropriate Ruby syntax would be).
  2. On pod install, CocoaPods would continue it's current behavior if there was no swift_versions attribute from the pod and assign the project's SWIFT_VERSION. However, if swift_versions is present, it would attempt to see if the project's SWIFT_VERSION is present in the list. If so, it would use that version. If not, one final check can be performed. CocoaPods can check to see if the project version and any of the swift_versions exist in the hybrid pairs supported by the Swift compiler. So a project that uses Swift 4 can install Swift 3.2 libraries, even if those libraries only support 3.2.
  3. If none of the previous criteria succeed, an error message saying exactly which library and which versions of Swift are incompatible can be produced, rather than build time errors the current workarounds would produce.

Only with fully native support for this sort of version mapping can CocoaPods produce a good user and library developer experience. Barring something like this, the manual experience will be buggy and frustrating.

It needs to be in the podspec: CocoaPods is a dependency manager; each pod specifies its dependencies so users can build them; one of those dependencies is the Swift version. Simple as that.

@valeriomazzeo shouldn't rather pod_target_xcconfig be used instead of xcconfig?

@fabb if you are talking about spec.xcconfig = { 'SWIFT_VERSION' => '4.0' }

it is to go in the podspec not in the podfile, anyway it's just an idea, because currently cocoapods override that value with the project settings.

@valeriomazzeo the xcconfig attribute is deprecated: http://blog.cocoapods.org/CocoaPods-0.38/#split-of-xcconfig

@fabb interesting, so if either pod_target_xcconfig or user_target_xcconfig manage to overrides what cocoapods extract from the project_settings, it could be a workaround for the time being. Did you try?

No I did not try yet

I think I'm missing something obvious but the post install hook solution doesn't work for me, because cocoapods validates that the Swift versions are not the same:

[!] The following pods are integrated into targets that do not have the same Swift version:

The hook is not even executed at this point.

The following pods are integrated into targets that do not have the same Swift version

I think this means that your targets have a different Swift version set to that which you are asking for in your post_install hook.

I have the same issue. I just manually set the swift version of some pods to 3.2 in xcode. Here I found there is something call post_install, that is great.

In addition, I have another problem that pods seem to not compiling when I execute archive. I got many errors say something like that class in pods not found. I use pod 1.3.1. But when I upgrade pod to 1.4.0 Beta.1 and do pod install, the problem seem to solved.

@orta @DanToml and whoever else is currently maintaining CocoaPods:

Not to push too hard as a non-contributor here, but not adding some version of swift_versions to the podspec DSL continues to degrade the CocoaPods user experience and is consistently generating issues here on GitHub. Just a look at the currently open issues shows a couple that wouldn't be an issue if the attribute existed, and I'm sure there have been some closed:

  • #7121, since .swift-version wouldn't even need to exist anymore.
  • #7115, since mixed dependencies would be handled automatically during linting and install.

Plus the assorted issues from other projects citing those issues. These types of issues will keep occurring until a solution is had, especially around major new Swift version releases every year. So, are there plans to address this overarching issue? Is it valuable to discuss how such an attribute would work?

We need to think through what the correct approach is for altering the DSL to support this.

There could be a case where the podspec provides a range of swift versions it supports or even the Podfile to request a specific swift version like:

pod 'Something', :swift_version => '3.2.' etc.

This is issue is not being ignored by the maintainers but everyone is working on their free time on this project so things are not going to be move as fast as expected.

I'll think about it personally further and consider a proposal.

@dnkoutso I recognize everyone does this in their spare time, but this thread hasn't seen much maintainer involvement, so it's hard to judge where the project stands in regards to this issue in general.

While a Podfile attribute might be useful in some capacity, a podspec attribute is the most useful and appropriate solution here. I proposed a solution here. To me it seems to meet all of the needs for users and pod developers and requires only minimal maintenance moving forward (CP would need to update its mapping of Swift versions that can be built by the same compiler). Initial development would likely be harder than simpler solutions but it seems to be a more appropriate solution in the long run.

Thanks @jshier I might be closing all other issues and consolidating into a single issue around multiple Swift version support in CocoaPods. We are in the processing of getting 1.4.0 out of the door first which includes a bunch of new features, I am not sure if this will make it for 1.4.0.

I renamed this issue and thank you all for chiming in. I marked this as a feature enhancement to support multiple swift versions and allow per pod customization for swift version to use instead of applying the same swift version across the entire project.

We've reprioritized this for 1.4.0 instead.

This issue is superseded by #7134

@DanielCreagh You just save me, thank you very very very much!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

If needed to support a bigger list of libraries you can use

    swift4 = ['LibraryA', 'LibraryB']

    post_install do |installer|
        installer.pods_project.targets.each do |target|
            target.build_configurations.each do |config|
                if swift4.include?(target.name)
                    config.build_settings['SWIFT_VERSION'] = '4.0'
                end
            end
        end
    end

To be clear, this has been implemented, and each pod should define a swift_version.

For older pods that haven't been updated, I recommend the following bit of code in your Podfile:

pods_with_specific_swift_versions = {
  'IHKeyboardAvoiding' => '3.0',
  'Fusuma' => '4.0',
}

post_install do |installer|
    installer.pods_project.targets.each do |target|
      if pods_with_specific_swift_versions.key? target.name
        swift_version = pods_with_specific_swift_versions[target.name]
        target.build_configurations.each do |config|
          config.build_settings['SWIFT_VERSION'] = swift_version
        end
      end
    end
end

I use another solution for iOS & tvOS projects (see this link for more details).
Add these lines to end of your Podfile (you can specify other libraries you want):

my_project_pods_swift_versions = Hash[
  "3.0", ["PagingMenuController", "TCPickerView"],
  "4.0", ["PromiseKit"]
]

def setup_all_swift_versions(target, pods_swift_versions)
  pods_swift_versions.each { |swift_version, pods| setup_swift_version(target, pods, swift_version) }
end

def setup_swift_version(target, pods, swift_version)
  if pods.any? { |pod| target.name.include?(pod) }
    target.build_configurations.each do |config|
      config.build_settings['SWIFT_VERSION'] = swift_version
    end
  end
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    setup_all_swift_versions(target, my_project_pods_swift_versions)
  end
end

@RomanPodymov in the end, I decided to follow a different way to do it, using pre_install instead of post_install: see that post to get an idea on how to do it on pre_install.

Sorry, if my question is naive.

My project is still on Swift 4.2 and I've restricted pods to use 4.2 as well, rather than get on the latest version. But, active development of a few pods is happening only on Swift 5.0 & 5.1. As issues are being fixed on those, I'd like to migrate a few pods to Swift 5.0. Can I specify a pod to use the latest version but retain my project on 4.2?

Any advice is of great help. Thanks! :)

@mohan-shyam I think you can mix Swift 4.2 with 5.x, yes.
But don't ask questions on a closed issue: it's unlikely that you'll get more answers (even from me).

Yeah, it's my mistake to post a question here. But, just another one. @Coeur
How can I do that? Any articles that detail it out?

I knew you would ask this, and it's not Stack Overflow here. But exceptionally... please find an answer in this post, where you can simply add more if cases if you want to mix things.

I'm really sorry. But, you are awesome!! @Coeur
Thanks much! 馃檹
No more questions... 馃

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pallaviMN picture pallaviMN  路  3Comments

pronebird picture pronebird  路  3Comments

Mingmingmew picture Mingmingmew  路  3Comments

sonu5 picture sonu5  路  3Comments

iosdev-republicofapps picture iosdev-republicofapps  路  3Comments