When a test spec is specified in a Podfile, it will take on the test spec's dependencies and put them into the target corresponding to the pod.
pod 'LocalPod', :path => './LocalPod', :testspecs => ['Tests']
This means that, even if the dependency is never used outside of the test specs, they are still bundled into the target, and the symbols in the unused dependency end up being shipped in production builds.
The important parts of the pod spec for "LocalPod" mentioned above is as below:
s.subspec 'Core' do |subspec|
subspec.source_files = 'Source'
end
s.default_subspecs = 'Core'
s.subspec 'TestMocks' do |subspec|
subspec.source_files = 'Tests/Mocks/*.swift'
end
s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'Tests/*.swift'
test_spec.dependency 'LocalPod/TestMocks'
end
pod installunzip app.ipa)/usr/bin/nm -Uj "/Path_to_output_ipa/Payload/TransitiveTestspecDependency.app/Frameworks/LocalPod.framework/LocalPod"There shouldn't be any symbols containing MockClassForSomething. If I removed the test spec specification in the Podfile so it looks like pod 'LocalPod', :path => './LocalPod', the output is
_LocalPodVersionString
_OBJC_CLASS_$_PodsDummy_LocalPod
_OBJC_METACLASS_$_PodsDummy_LocalPod
__T08LocalPod013UsefulClassInaB0VMa
__T08LocalPod013UsefulClassInaB0VMn
__T08LocalPod013UsefulClassInaB0VN
Here's an output locally on my machine:
_LocalPodVersionString
_OBJC_CLASS_$_PodsDummy_LocalPod
_OBJC_METACLASS_$_PodsDummy_LocalPod
__T08LocalPod013UsefulClassInaB0VMa
__T08LocalPod013UsefulClassInaB0VMn
__T08LocalPod013UsefulClassInaB0VN
__T08LocalPod21MockClassForSomethingCACycfC
__T08LocalPod21MockClassForSomethingCACycfc
__T08LocalPod21MockClassForSomethingCMa
__T08LocalPod21MockClassForSomethingCMm
__T08LocalPod21MockClassForSomethingCMn
__T08LocalPod21MockClassForSomethingCN
__T08LocalPod21MockClassForSomethingCfD
__T08LocalPod21MockClassForSomethingCfd
CocoaPods : 1.5.3
Ruby : ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]
RubyGems : 2.5.1
Host : Mac OS X 10.13.5 (17F77)
Xcode : 9.4.1 (9F2000)
Git : git version 2.15.2 (Apple Git-101.1)
Repositories : master - https://github.com/CocoaPods/Specs.git @ dce8b25a6829d611e5c06440af48874d8d589812
target 'TransitiveTestspecDependency' do
use_frameworks!
pod 'LocalPod', :path => './LocalPod', :testspecs => ['Tests']
end
@allen-zeng thanks for the report, this is most likely fixed with 1.6.0 but I have not yet verified.
Thanks @dnkoutso - is there an alpha/beta version I could try?
I added a Gemfile like so
source 'https://rubygems.org'
gem 'cocoapods', :git => 'https://github.com/CocoaPods/CocoaPods.git'
gem 'cocoapods-core', :git => 'https://github.com/CocoaPods/Core.git'
And then performed these steps:
bundle installrm -rf Podsbundle exec pod install/usr/bin/nm -Uj "/Path_to_output_ipa/Payload/TransitiveTestspecDependency.app/Frameworks/LocalPod.framework/LocalPod"Unfortunately the output is still the same. The mock symbols are still in the output framework.
I think then this might be because it's using subspecs, and it's all getting merged in to the one built product :/ I'd probably have to recommend not using subspecs in this case
I must concede that the use of a subspec was not my top choice. I didn't see an alternative though. I tried putting the mock inside of a testspec but it turns out that it's not possible to consume a testspec like a subspec.
I'm guessing the only option right now is to have a standalone pod purely for the mocks?
That’s what we do at the moment. It’s not ideal, but subspecs dont (yet?) work for this purpose. We’ve thought about introducing some idea of a “testing_spec”, but only very briefly.
A "testing_spec" would make the intention very clear, and it would prevent accidentally including them.
That said, I'm not entirely sure how big a problem accidental inclusions of these pods is. So maybe a much simpler solution is to use a pod spec with a proper name "SomethingSomethingMocks" so it's clear that they shouldn't be used.
Yes this can be marked as an enhancement to basically not merge all subspecs into a single target.
Yes this can be marked as an enhancement to basically not merge all subspecs into a single target.
@dnkoutso I think the fix for this could have a smaller scope than that.
I think the issue is that test_specs aren't treated the same as user-created targets in terms of scoping for dependency resolution and subspec unionization.
If @allen-zeng converted the test_spec Tests to a target in the main project and specified pod 'LocalPod/TestMocks', :path => './LocalPod' for the Tests target, then I believe CocoaPods will generate 2 targets for LocalPod, one for use in the original TransitiveTestspecDependency target, and another for use in the new Test target. Consider:
# LocalPod/LocalPod.podspec
s.subspec 'Core' do |subspec|
subspec.source_files = 'Source'
end
s.default_subspecs = 'Core'
s.subspec 'TestMocks' do |subspec|
subspec.source_files = 'Tests/Mocks/*.swift'
end
# Podfile
use_frameworks!
target 'TransitiveTestspecDependency' do
pod 'LocalPod', :path => './LocalPod'
end
target 'Tests' do
pod 'LocalPod/TestMocks', :path => './LocalPod'
end
This is further illustrated by keeping the test_spec but moving the dependency :testspecs => ['Tests'] to another target in the Podfile:
# LocalPod/LocalPod.podspec
s.subspec 'Core' do |subspec|
subspec.source_files = 'Source'
end
s.default_subspecs = 'Core'
s.subspec 'TestMocks' do |subspec|
subspec.source_files = 'Tests/Mocks/*.swift'
end
s.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'Tests/*.swift'
test_spec.dependency 'LocalPod/TestMocks'
end
# Podfile
use_frameworks!
target 'TransitiveTestspecDependency' do
pod 'LocalPod', :path => './LocalPod'
end
target 'AnotherTarget' do
pod 'LocalPod', :path => './LocalPod', :testspecs => ['Tests']
end
Here the 2 user targets create different dependency scopes, so you have TransitiveTestspecDependency relying on a version of LocalPod that includes only the Core subspec, while AnotherTarget links to the same LocalPod target that the Tests test_spec links to, which includes both Core and TestMocks subspecs.
This implies that subspecs are unioned within a scope, where scope is being defined as a user target (among other things I'm sure), while a test_spec target doesn't count as a user target for the purpose of scoping for this unionization.
I believe that if test_specs created a separate scope in the same way as user targets that @allen-zeng's issue would be solved.
This also implies a workaround, which is to not include :testspecs => ['Tests'] in the same target that you plan on distributing, but to add it to a different user target within the Podfile.
Most helpful comment
Yes this can be marked as an enhancement to basically not merge all subspecs into a single target.