Cocoapods: Opt-out of deduplication per pod target

Created on 17 May 2016  路  11Comments  路  Source: CocoaPods/CocoaPods

I've already asked for help in an older (and closed) issue about an error I'm experiencing regarding "deduplication per pod target", but maybe that was the wrong place: https://github.com/CocoaPods/CocoaPods/issues/3794#issuecomment-218572129

I always knew that this felt rather like a "hack"/workaround, but it's important to me that I'm able to deactivate deduplication of specific Pods, because I need to set some preprocessor macros on App Extension targets.

What did you do?

Run pod install with a pre-install hook to opt-out of deduplication per pod target, as suggested here: https://github.com/CocoaPods/CocoaPods/issues/3794#issuecomment-129791907

What would you expect to happen?

All Pods should deduplicate except the Pods defined in the pre-install hook.

What happened instead?

NoMethodError - undefined method `project' for nil:NilClass
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/xcodeproj-1.0.0/lib/xcodeproj/project/object/native_target.rb:225:in `add_dependency'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:670:in `block (3 levels) in set_target_dependencies'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:668:in `each'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:668:in `block (2 levels) in set_target_dependencies'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:654:in `each'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:654:in `block in set_target_dependencies'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:649:in `each'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:649:in `set_target_dependencies'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:171:in `block in generate_pods_project'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/user_interface.rb:63:in `section'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:167:in `generate_pods_project'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/installer.rb:119:in `install!'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/command/install.rb:37:in `run'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/claide-1.0.0/lib/claide/command.rb:334:in `run'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/lib/cocoapods/command.rb:50:in `run'
/Applications/CocoaPods.app/Contents/Resources/bundle/lib/ruby/gems/2.2.0/gems/cocoapods-1.0.0/bin/pod:55:in `<top (required)>'
/Applications/CocoaPods.app/Contents/Resources/bundle/bin/pod:23:in `load'
/Applications/CocoaPods.app/Contents/Resources/bundle/bin/pod:23:in `<main>'

CocoaPods Environment

Stack

   CocoaPods : 1.0.0
        Ruby : ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin15]
    RubyGems : 2.5.0
        Host : Mac OS X 10.11.5 (15F34)
       Xcode : 7.3.1 (7D1014)
         Git : git version 2.6.2
Ruby lib dir : /Applications/CocoaPods.app/Contents/Resources/bundle/lib
Repositories : master - https://github.com/CocoaPods/Specs.git @ 9df03b3836f62db2b8142a3a4bca4c5c9c5704d4

Installation Source

Executable Path: /Applications/CocoaPods.app/Contents/Resources/bundle/bin/pod

Plugins

cocoapods-check           : 0.2.1.beta.1
cocoapods-deintegrate     : 1.0.0
cocoapods-plugins         : 1.0.0
cocoapods-plugins-install : 0.0.1
cocoapods-search          : 1.0.0
cocoapods-stats           : 1.0.0
cocoapods-trunk           : 1.0.0
cocoapods-try             : 1.0.0

Podfile

I really would prefer not to post the complete Podfile publicly. The relevant part is the pre-install hook:

pre_install do |installer|
    pod_targets = installer.pod_targets.flat_map do |pod_target|
        pod_target.name == "foo" ? pod_target.scoped : pod_target
    end
    installer.aggregate_targets.each do |aggregate_target|
        aggregate_target.pod_targets = pod_targets.select do |pod_target|
            pod_target.target_definitions.include?(aggregate_target.target_definition)
        end
    end
end
discussion

Most helpful comment

This doesn't solve the issue. CocoaPods's target deduplication would still set APPLICATION_EXTENSION_API_ONLY to YES, only because my project consists of app extension targets. Even though I wouldn't want that for the main target.

  • Main target: APPLICATION_EXTENSION_API_ONLY=NO
  • App extension targets: APPLICATION_EXTENSION_API_ONLY=YES

That's the setup I'd need.

Edit: Just googled some arguments: http://www.atomicbird.com/blog/ios-app-extension-tips

This becomes a little awkward if you're sharing code between an app and an extension. The NS_EXTENSION_UNAVAILABLE directive means you can't write code that does run-time checks, because even referencing the forbidden APIs causes a compile error. One option to deal with this is to refactor shared classes into hierarchies, with a common parent used in both and different subclasses for different build targets. Another is to use the preprocessor via #ifdef checks. There's no built-in target conditional-- nothing like "#if TARGET_IOS_EXTENSION"-- so if you go this route you'll have to create your own.

It's really painful to develop app extensions, I don't understand why not more people are having this issue. 馃槄

All 11 comments

@mrackwitz do you think this is something worth supporting?

@MuscleRumble: Are these preprocessor macros are necessary so that the targets can fulfill the APPLICATION_EXTENSION_API_ONLY? It would help to understand your motivation a bit better.

Yes, that's the case. Otherwise, I would get compile-time errors on the App Extension targets. Quick example of my post-install hook:

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if target.name == "Pods-Action-SVProgressHUD" || target.name == "Pods-DocumentPicker-SVProgressHUD" || target.name == "Pods-FileProvider-SVProgressHUD"
            target.build_configurations.each do |config|
                config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)', 'SV_APP_EXTENSIONS']
            end
        end
    end
end

Probably not the best syntax, but yeah that's how I'm adding the preprocessor macros to the App Extension targets.

Maybe I haven't explained my motivation enough: My project consists of 4 targets (main app and 3 app extensions). I have 20 pods and I'd like to use them all in the app extension targets as well.

The issue is that 6 pods aren't compile-clean, because they are using APIs not available in app extensions. That's why I'm using preprocessor macros only on the app extension targets to exclude code in compile-time that would normally lead to an error due to APPLICATION_EXTENSION_API_ONLY.

I can't just use APPLICATION_EXTENSION_API_ONLY on the main target, because I'd like to use the pods as they were intended for the main target. It's okay to have a "workaround" for the app extensions targets. Classic example is: https://github.com/SVProgressHUD/SVProgressHUD#app-extensions

@mrackwitz Your pre-install hook worked very well until recently. Not sure if there is a way to rewrite the pre-install hook to achieve the same goal. Did the target deduplication algorithm of CocoaPods change that much, that it's not possible anymore?

If that's the case: Can I deactivate target deduplication in the Podfile? Last time you told me that I'd have to configure this in a system-wide setting: https://github.com/CocoaPods/CocoaPods/issues/3794#issuecomment-127190259

If this hasn't changed, I'm not sure what to do here. I can't tell my team to deactivate target deduplication system-wide. I can't even do this on my own machine, I have other projects, where I would like to keep this setting.

Is it possible to re-install 1.0.0.beta.3 somehow? Edit: Fortunately, I could restore the CocoaPods Mac App in version 1.0.0.beta.3 with Time Machine. At least I'm able to run pod install again.

The pods should be using NS_EXTENSION_UNAVAILABLE to limit extension-unsafe API visibility rather than their own custom macros

This doesn't solve the issue. CocoaPods's target deduplication would still set APPLICATION_EXTENSION_API_ONLY to YES, only because my project consists of app extension targets. Even though I wouldn't want that for the main target.

  • Main target: APPLICATION_EXTENSION_API_ONLY=NO
  • App extension targets: APPLICATION_EXTENSION_API_ONLY=YES

That's the setup I'd need.

Edit: Just googled some arguments: http://www.atomicbird.com/blog/ios-app-extension-tips

This becomes a little awkward if you're sharing code between an app and an extension. The NS_EXTENSION_UNAVAILABLE directive means you can't write code that does run-time checks, because even referencing the forbidden APIs causes a compile error. One option to deal with this is to refactor shared classes into hierarchies, with a common parent used in both and different subclasses for different build targets. Another is to use the preprocessor via #ifdef checks. There's no built-in target conditional-- nothing like "#if TARGET_IOS_EXTENSION"-- so if you go this route you'll have to create your own.

It's really painful to develop app extensions, I don't understand why not more people are having this issue. 馃槄

Just came up with a whole different idea, how we could address that. Maybe 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.

@mrackwitz that and https://github.com/CocoaPods/CocoaPods/issues/5071 seem very similar

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

Yes, that would be perfect! Defining this in the podspec would be very elegant.

@mrackwitz that and https://github.com/CocoaPods/CocoaPods/issues/5071 seem very similar

Similar but not the same. What I proposed here can be resolved at the time of the target generation. #5071 is resolved when targets are built, because the platform is determined on runtime.

I created https://github.com/CocoaPods/CocoaPods/issues/5373 on base of what I proposed here as the solution is different from what was originally requested here and might still make sense independently.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pallaviMN picture pallaviMN  路  3Comments

gerchicov-bp picture gerchicov-bp  路  3Comments

steffendsommer picture steffendsommer  路  3Comments

Mingmingmew picture Mingmingmew  路  3Comments

Curtis-Halbrook picture Curtis-Halbrook  路  3Comments