Cocoapods: Allow target-type specific podspec definitions

Created on 19 May 2016  ·  21Comments  ·  Source: CocoaPods/CocoaPods

CocoaPods target deduplication can be undesirable in some cases, when pods are used, which offer an extended API for targets, which are not limited to the extension API. In these cases, it would be desirable to use the full podspec in the app and only the limited features in the extension. This can be achieved in podspec with preprocessor conditionals, which require the presence of macros. A post_install hook would be required to define these macros for the pod targets.

The following could be a better solution to that: We could allow to specify attributes specific to iOS extensions in podspecs. This could look like that:

s.ios.extension.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SV_APP_EXTENSIONS' }

The analyzer could use that relatively easy as indication, that a pod can't be deduplicated across iOS apps and extensions. This could be used as well on tvOS.

This was created based on what I proposed in https://github.com/CocoaPods/CocoaPods/issues/5343#issuecomment-220384801.

/c @segiddins @neonichu @MuscleRumble

awaiting input discussion

Most helpful comment

Hey @ConfusedVorlon, might be a little late, but I recently encountered the issue and had to fix it by using @neonichu 's suggestion. I've written the reasoning and example in my blog (here) if you're interested.

All 21 comments

I think we need a proposal on how this would work in practice to be able to evaluate it (but I'm leaning towards this is a massive undertaking for practically no benefit)

IMO, we already have a solution to this problem with subspecs. A pod which has a subset of functionality that works within an app extension should separate that out into one.

I disagree, @neonichu. I don't think subspecs are suitable for this particular issue. I can already give you two real world examples:

https://github.com/SVProgressHUD/SVProgressHUD
https://github.com/google/gtm-session-fetcher

Two things have to happen in order to use these pods in a project with app extensions:

  1. Target deduplication has to be deactivated on these pods, so that APPLICATION_EXTENSION_API_ONLY is set to NO on the _main target_ and APPLICATION_EXTENSION_API_ONLY to YES on the _app extension targets_.
  2. In order to avoid compile-time errors on the _app extension targets_, I have to set preprocessor macros. In this example, I have to set SV_APP_EXTENSIONS and GTM_BACKGROUND_TASK_FETCHING=0.

You can't just "outsource" functionality to subspecs, because they're part of the main functionality. I don't think that this is a "design flaw" by the creators of these pods. It's just the way to develop more complex applications that are using app extensions.

I think @mrackwitz's solution is clever and it would help tremendously for these kinds of projects.

@MuscleRumble This can also be solved by providing a subspec, though.

If we take the GTMSessionFetcher example, they could add a subspec:

s.subspec 'AppExtension' do |ap|
  ap.source_files = …
  ap.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GTM_BACKGROUND_TASK_FETCHING=0' }
end

and in your Podfile you'd do:

target 'App' do
  pod 'GTMSessionFetcher'
end

target 'Extension' do
  pod 'GTMSessionFetcher/AppExtension'
end

Two distinct Pod targets would be generated and the files which are part of the AppExtension subspec would be compiled with the preprocessor definition set.

Then you would have to duplicate code for ap.source_files = …. In this case, you wouldn't even need to set preprocessors. This is bad for code maintenance.

In this example, you would have to copy GTMSessionFetcher.m explicitly for app extensions, because the preprocessor macro GTM_BACKGROUND_TASK_FETCHING is used inline. You can't just separate the relevant code into another file, without breaking either the main functionality or making the maintenance unnecessarily complicated.

If you wouldn't do that, you'll get compile-time errors. The easiest way is to deactive target deduplication for this pod.

I don't think I understand what you're saying. There's nothing to copy, except for the definition of the list of files in the podspec, if it happens to be exactly the same as for another subspec. If that happens, it can be extracted out, since the podspec is just Ruby code.

No actual source code has to be duplicated and targets will be deduplicated automatically, because they use different sets of subspecs.

What @neonichu proposes here makes sense. It might be not obvious though, but subspecs allow already to apply these sorts of variations to a podspec. They are way more powerful than just adding additional files, which made it already a massive undertaking to support them correctly with target deduplication. Given that we already have a way to express that, I'd be in favor of avoiding further complexity as @segiddins already suggested. Instead a tutorial or guide around that topic would be likely very helpful.

Aye, I'm also on the side of "this is what subspecs are for."

Instead a tutorial or guide around that topic would be likely very helpful.

Would be nice to have a blog post that just explores subspecs, and some interesting uses cases ( e.g. AFNetworking, ARAnalytics, RestKit ) ( we use them for code / dependency organization in Artsy for example )

I think I understand now how subspecs can be utilized for my use case. The thing is: There aren't a lot of frameworks/libraries that deal with app extensions properly. That's why I'm so frustrated and I have to fork/patch everything. I'll give subspecs a try, but I have two questions:

  1. How does this even work? Don't I have to "exclude" the AppExtension subspec for the _main target_? Because I don't want to set the preprocessor macro on the GTMSessionFetcher pod. Aren't all subspecs automatically included, when using pod 'GTMSessionFetcher'?
  2. Let's take OneDriveSDK as an example: https://github.com/OneDrive/onedrive-sdk-ios/blob/master/OneDriveSDK.podspec

There is a subspec OneDriveSDK/Auth that has the dependency ADALiOS. In my OneDriveSDK fork (in which I'm going to create the OneDriveSDK/AppExtension subspec) I would like to distinguish between ADALiOS for my _main target_ and ADALiOS/AppExtension (that doesn't exist yet, I'll have to fork this as well) for my _app extension targets_.

I'm a little overwhelmed by how to structure the podspecs properly. How do I achieve this?

That's true, I don't think many podspec authors have realised that they could support application extensions quite nicely using this approach. If we do a blog post / guide and you have implemented the approach in some Pod(s), we could add that as an example and spread that knowledge this way.

To the questions:

  1. There's a default_subspecs attribute: https://guides.cocoapods.org/syntax/podspec.html#default_subspecs — but by default, all subspecs are included. Podspecs using this approach should use the attribute to make sure the app extension related subspecs aren't included by default.
  2. You can do something like:
s.subspec "AppExtension" do |oda|
    oda.dependency 'ADALiOS/AppExtension', '~> 1.2'
end

The real answer is to use NS_EXTENSION_UNAVAILABLE_IOS (I think that's the macro) to mark class / method declarations that are incompatible with extensions instead of using a custom macro.

@segiddins that only works for marking symbols, though, doesn't it? Many Pods seem to have conditional code depending on their use within an application extension.

Alright, thanks @neonichu! I'll give it a try in the near future. Subspecs seem to be the way to go! 👍

Hi Folks - any chance of that blog post explaining how this would work?
I'm working on a pod that needs to separate out some app extension functionality. I'm not an expert on podspecs, so a 'worked example' blogpost would be great.

Hey @ConfusedVorlon, might be a little late, but I recently encountered the issue and had to fix it by using @neonichu 's suggestion. I've written the reasoning and example in my blog (here) if you're interested.

@neonichu I'm a bit confused about the described solution here, because there's this issue #5643 where it says that it's not possible the use the same pod with different subspecs in app target and it's extension target.

See the linked issue and your comment for more information.

Just wanted to follow-up that I can confirm it's all working out with subspecs. Yes, I waited over half a year to do the change, because I couldn't use 1.0.0.beta.3 anymore. :joy:

It wasn't pretty, I had to fork 7 projects and customize the podspecs, which will probably break my neck in the future, because of poor maintainability. But it wasn't pretty to begin with, so I guess it's just the way it is with App Extensions. ☹️

Nevertheless, I think this issue can be closed, because we found a solution. :smile: Thanks again!

I just can't make the proposed subspec approach work (share a subset of functionality through a subspec for an extension). I always get the famous "target has frameworks with conflicting names" error.

I reproduced the proposed approach with a simple demo.

I have a private framework with two subspecs:

  • App: for the main app target
  • Extension: for the extension target, uses some files "from" the App subspec

Here's the podspec:

Pod::Spec.new do |s|
  s.name        = 'MyFramework'
  s.version       = '0.1.0'
  s.summary       = 'Dummy Framework'
  s.homepage      = 'https://github.com/zierka'
  s.license       = 'MIT'
  s.author        = {'Zier Erik' => '[email protected]'}

  s.source        = {:git => '[email protected]:zierka/subspec-error-example.git', :tag => s.version.to_s}

  s.requires_arc  = true
  s.platform      = :ios
  s.ios.deployment_target = '10.0'

  s.default_subspecs = 'App'

  s.subspec 'App' do |app|
    app.source_files = [
      'App/**/*.{swift}'
    ]
  end

  s.subspec 'Extension' do |ext|
    ext.source_files = [
      'App/CoreClass.swift',
      'Extension/**/*.{swift}'
    ]

    ext.exclude_files = [
      'App/AppClass.swift'
    ]
  end

end

I have a simple xcode project, with 2 targets:

  • the main app target has the MyFramework as dependency in the Podfile
  • the extension target has the MyFramework/Extension as dependency

Here's the Podfile:

platform :ios, '10.0'
use_frameworks!

target 'SubspecProblemExample' do
  pod 'MyFramework', :path => "../MyFramework" . # equal to MyFramework/App as that's declared for default_subspecs
end

target 'todayWidget' do
  pod 'MyFramework/Extension', :path => "../MyFramework"
end

Running pod install (1.2.1) on the project, I get:

Eriks-Machine:SubspecProblemExample erik$ pod install
Analyzing dependencies
Fetching podspec for `MyFramework` from `../MyFramework`
Downloading dependencies
Installing MyFramework (0.1.0)
[!] The 'Pods-SubspecProblemExample' target has frameworks with conflicting names: myframework.

I read through all the issues about this error, and it's still not clear to me what the real problem is, because this is the same setup as previously described, and it's presented as a working solution and further confirmed. Am I missing something here?

I uploaded the full demo project here: https://github.com/zierka/subspec-error-example

Any help is greatly appreciated!

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates.

This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem :+1:

That is what need, thank you @neonichu neonichu

Was this page helpful?
0 / 5 - 0 ratings