Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] Pod's vendored libraries directory is passed to the downstream target #9559

Open
theodelrieu opened this issue Feb 25, 2020 · 18 comments
Open

Comments

@theodelrieu
Copy link

@theodelrieu theodelrieu commented Feb 25, 2020

Report

What did you do?

  • Create an empty iOS Single App project with Xcode (later named MyPod)
  • Run pod init
  • Replace Podfile with the following:
source 'https://github.com/CocoaPods/Specs.git'
source 'https://github.com/TankerHQ/PodSpecs.git'

target 'MyPod' do
  use_frameworks!
  pod 'Tanker', '2.2.2'
  pod 'Crashlytics', '3.14.0'
end
  • Run pod install
  • open MyPod.xcworkspace
  • Build the project

What did you expect to happen?

The project builds.

What happened instead?

The linker invocation fails.

Tanker bundles quite a lot of static libraries, which are all in the Libraries folder, and marked in the Tanker.podspec as vendored_libraries.

We do vendor our own version of libc++. Crashlytics adds c++ to the list of required system libraries. It should not cause any issues, since the Tanker vendored libraries should be used during the build/link phase of the Tanker framework.

Unfortunately, if you look at the failing link command line, there is a -L${PODS_ROOT}/Tanker/Libraries part, thus the linker picks up libc++.a from the Tanker pod, instead of the system libc++.tbd.

We found that the Library Search Paths was indeed populated with -L${PODS_ROOT}/Tanker/Libraries in the Xcode project. After removing the following lines in Cocoapods' sources, it stopped populating this variable (but failed to pass integration tests):

# file lib/cocoapods/target/build_settings.rb, line 1079

@return [Array<String>]
define_build_settings_method :library_search_paths, :build_setting => true, :memoized => true, :sorted => true, :uniqued => true, :from_pod_targets_to_link => true, :from_search_paths_aggregate_targets => :vendored_dynamic_library_search_paths do
    []
end

I've played with ruby only once or twice before, and I don't know the CocoaPods codebase at all, so I figured it would cost me a lot of time to fix it in the most proper way...

CocoaPods Environment

Stack

   CocoaPods : 1.8.4
        Ruby : ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]
    RubyGems : 3.0.3
        Host : Mac OS X 10.15.3 (19D76)
       Xcode : 11.3.1 (11C504)
         Git : git version 2.23.0
Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib
Repositories : cocoapods - git - https://github.com/CocoaPods/Specs.git @ faa251c2c8d947a623e12442d3208738a2740f04
               tankerhq - git - https://github.com/TankerHQ/PodSpecs.git @ f7e626946d848b15ce0b6a5da4c92dd4cf143025
               trunk - CDN - https://cdn.cocoapods.org/

Installation Source

Executable Path: /Users/theo/.gem/bin/pod

Plugins

cocoapods-deintegrate : 1.0.4
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.0
cocoapods-stats       : 1.0.0
cocoapods-trunk       : 1.4.1
cocoapods-try         : 1.1.0

EDIT: Same problem with the latest master commit (c59529f)

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Feb 26, 2020

Can you post the linker flags that are in the generated .xcconfig file for MyPod (in Pods/Pods-MyPod.debug.xcconfig) as well as the linker flags for Tanker? (in Tanker/Support Files/Tanker.debug.xcconfig)

@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Feb 26, 2020

@amorde

Here is the Tanker.debug.xcconfig:

CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/Tanker
FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/POSInputStreamLibrary"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = "$(inherited)" "$(PODS_TARGET_SRCROOT)/Headers"
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Tanker/Libraries"
OTHER_LDFLAGS = $(inherited) -exported_symbols_list ${PODS_TARGET_SRCROOT}/export_symbols.list -nostdlib++ -l"boost_atomic" -l"boost_chrono" -l"boost_context" -l"boost_date_time" -l"boost_filesystem" -l"boost_program_options" -l"boost_random" -l"boost_system" -l"boost_thread" -l"c++" -l"c++abi" -l"crypto" -l"ctanker" -l"fmt" -l"sioclient" -l"sodium" -l"sqlcipher" -l"sqlpp11-connector-sqlite3" -l"ssl" -l"tanker_admin-c" -l"tanker_async" -l"tankeradmin" -l"tankercacerts" -l"tankercore" -l"tankercrypto" -l"tankererrors" -l"tankerformat" -l"tankerfunctionalhelpers" -l"tankeridentity" -l"tankerlog" -l"tankernetwork" -l"tankerserialization" -l"tankerstreams" -l"tankertesthelpers" -l"tankertrustchain" -l"tconcurrent" -l"tls" -weak_framework "Photos"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/Tanker
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_HEADERMAP = NO
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

And the Pods-MyPod.debug.xcconfig:

FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/POSInputStreamLibrary" "${PODS_CONFIGURATION_BUILD_DIR}/Tanker" "${PODS_ROOT}/Crashlytics/iOS" "${PODS_ROOT}/Fabric/iOS"
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
HEADER_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/POSInputStreamLibrary/POSInputStreamLibrary.framework/Headers" "${PODS_CONFIGURATION_BUILD_DIR}/Tanker/Tanker.framework/Headers"
LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks'
LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Tanker/Libraries"
OTHER_LDFLAGS = $(inherited) -ObjC -l"c++" -l"z" -framework "AssetsLibrary" -framework "Crashlytics" -framework "Fabric" -framework "Foundation" -framework "ImageIO" -framework "POSInputStreamLibrary" -framework "Security" -framework "SystemConfiguration" -framework "Tanker" -framework "UIKit" -weak_framework "Photos"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_PODFILE_DIR_PATH = ${SRCROOT}/.
PODS_ROOT = ${SRCROOT}/Pods
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES
@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Mar 4, 2020

@amorde Hi Eric, have you had time to look at this issue?

One of our clients just had the issue, they went with patching the .xcodeproj and removing LIBRARY_SEARCH_PATHS entirely, but it's still a hack...

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 4, 2020

Only briefly - I think this is a symptom of a more general issue with static linking. CocoaPods doesn't link vendored libraries to both the pod and the app, otherwise there would always be duplicate symbols in the binary.

The problem here is that you have a vendored dependency "Crashlytics" which depends on libc++ but another pod "Tanker" which depends on the same library but a completely different version of it. Crashlytics may not work with your version of the c++ library, and if we link both then you'll end up with duplicate symbols, so CocoaPods is a bit stuck here. I do not think we can apply the change you suggest because it would break other projects.

That said, I'm not an expert here so there may be other bits of information I'm missing.

@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Mar 4, 2020

@amorde

In the build flags of our Podspec, we use a linker script to only make visible the objective-c symbols, all of the others are hidden, to avoid any collision (including the libc++ ones of course).

The problem here is that the Tanker library directory appears in the LIBRARY_SEARCH_PATHS, which makes little to no sense since all of Tanker static libraries were linked to the dynamic Tanker.framework beforehand.

Only -F/path/to/parent/dir/of/Tanker.framework and -framework Tanker should appear

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 5, 2020

Does Tanker not have vendored_libraries = ['path/to/c++'] or something similar? Can you share the podspec?

@theodelrieu

This comment has been minimized.

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 5, 2020

This line:

https://github.com/TankerHQ/sdk-ios/blob/6d1cabbcef33075186a42b9d41e3d3818ba902db/Tanker/Tanker.podspec#L34

will cause CocoaPods to put that directory into LIBRARY_SEARCH_PATHS so that the app can link it. As I mentioned above the lib gets linked into the app, that way if two dependencies have the same static dependency it only gets linked once.

Since you're already manually exporting symbols, you might be able to manually link your library to your pod with something like this:

s.preserve_paths = 'Libraries/*.a'
# In addition to the settings you already have in `pod_target_xcconfig`
s.pod_target_xcconfig = {
  'LIBRARY_SEARCH_PATHS' = '$(PODS_ROOT)/Tanker/Libraries'
  'OTHER_LDFLAGS' = '-lc++' # (or other lib name here)
}
@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Mar 9, 2020

@amorde Hi Eric, thanks to you we managed to fix the problem by generating the Podspec with the correct linker flags.

It would be great to change the documentation of vendored_libraries to specify what this attribute does w.r.t. the final app.

Right now, it merely states that vendored_libraries are the Pod's vendored libraries, which is misleading.

Also, I don't see a "real" use-case for that attribute, excepting maybe having multiple internal Pods and a final App that links static libs from those pods.

CaptainAchab pushed a commit to TankerHQ/sdk-ios that referenced this issue Mar 9, 2020
This fixes using Tanker alongside Crashlytics in the same project for
instance.

See CocoaPods/CocoaPods#9559 for details
@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 9, 2020

vendored_libraries was designed for pods that "wrap" pre-compiled libraries. It's used quite a lot, so I wouldn't say there isn't a real use case for it. I think your use case might be less common, as you are bundling a pre-compiled SDK that is an implementation detail of your pod and does not expose any symbols from that library.

That said, the documentation could definitely be improved 👍

@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Mar 10, 2020

@amorde Right, but if two different pods wrap the same precompiled library, but a different version, there will be a symbol collision in the final app.

That's why we chose to hide symbols in the first place. But I might miss something huge...

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 10, 2020

Yes you're right, but usually in these cases they're depending on a pod instead of the library itself. For example:

s.dependency 'OpenSSL', '~> 1.0'

and CocoaPods will do the dependency resolution to ensure only 1 version of OpenSSL is included, and will complain about conflicts. But you are correct, if two pods bundle their own versions of libraries with vendored_libraries then they will conflict, but there's nothing we can do about that.

@theodelrieu

This comment has been minimized.

Copy link
Author

@theodelrieu theodelrieu commented Mar 10, 2020

@amorde Oh right I'm silly, those pods really wrap a library 🤦‍♂, nevermind my previous comment

@jcesarmobile

This comment has been minimized.

Copy link

@jcesarmobile jcesarmobile commented Mar 25, 2020

So, to be clear, vendored_libraries is for providing libraries to the app, not for libraries the pod needs for building?
Is it the same for vendored_frameworks?

I've been using both in a wrong way then.

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 26, 2020

vendored_libraries will expose the library to both your pod, and those who depend on your pod (whether that be other pods or an app).

If you have a library which is effectively a private implementation detail and you do not expose any symbols from that library, then vendored_libraries is not meant for that. If you don't mind the symbols being exposed then you can still use it, but if you specifically do not want to expose the symbols then you should not use vendored_libraries / vendored_frameworks

@amorde

This comment has been minimized.

Copy link
Member

@amorde amorde commented Mar 26, 2020

Actually this is less true for vendored_frameworks because it needs to be there to get included in the Embed Frameworks Script

@jcesarmobile

This comment has been minimized.

Copy link

@jcesarmobile jcesarmobile commented Mar 26, 2020

Thanks for the answer. In the end my problem was that the library wasn’t called libsomething.a, just something.a, and they were not found because of that.

@dnkoutso

This comment has been minimized.

Copy link
Contributor

@dnkoutso dnkoutso commented Apr 3, 2020

Is there anything to be done here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
4 participants
You can’t perform that action at this time.