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.
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
All Pods should deduplicate except the Pods defined in the pre-install hook.
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 : 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
Executable Path: /Applications/CocoaPods.app/Contents/Resources/bundle/bin/pod
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
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
@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.
APPLICATION_EXTENSION_API_ONLY=NOAPPLICATION_EXTENSION_API_ONLY=YESThat'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.
Most helpful comment
This doesn't solve the issue. CocoaPods's target deduplication would still set
APPLICATION_EXTENSION_API_ONLYtoYES, only because my project consists of app extension targets. Even though I wouldn't want that for the main target.APPLICATION_EXTENSION_API_ONLY=NOAPPLICATION_EXTENSION_API_ONLY=YESThat's the setup I'd need.
Edit: Just googled some arguments: http://www.atomicbird.com/blog/ios-app-extension-tips
It's really painful to develop app extensions, I don't understand why not more people are having this issue. 馃槄