Cocoapods: Add Xcode 12 compatible support for debug symbols in XCFrameworks

Created on 1 Oct 2020  路  16Comments  路  Source: CocoaPods/CocoaPods

Report

With Xcode 11, there was no automated mechanism for Xcode to embed debug symbols (dSYMs and BCSymbolMaps) that were embedded in an XCFramework into the built product's archive. As a result, CocoaPods' support for XCFrameworks included automatically extracting debug symbols from each architecture's framework directory, and including them into the built app. The expectation was that the XCFramework would be structured like this:

MySDK.dSYMs/
- MySDK.framework.ios-arm64.dSYM
- MySDK.framework.ios-x86_64-simulator.dSYM
MySDK.xcframework/
- Info.plist
- ios-arm64/
    - MySDK.framework/
        - BCSymbolMaps/
- ios-x86_64-simulator/
    - MySDK.framework/

EDIT (Oct 7): Originally I had described that the dSYMs were placing as siblings of the BCSymbolMaps in the Xcode 11 style, but after some additional testing, I realized that the Xcode 11 style that CocoaPods was expecting was for the dSYMs to be placed in a .dSYMs directory as a top level sibling of the .xcframework.

This was great!

Now, Xcode 12 has added support to xcodebuild -create-xcframework for passing a -debug-symbols argument. When creating an XCFramework this way with Xcode 12, the resulting directory structure does not match what we had presumed when initially building support for Xcode 11:

MySDK.xcframework
- Info.plist
- ios-arm64/
    - BCSymbolMaps/
    - dSYMs/
    - MySDK.framework/
- ios-arm64_x86_64-simulator/
    - dSYMs/
    - MySDK.framework/

The debug symbols are placed as siblings of each architecture's .framework rather than as children of it.

As a result, when integrating through CocoaPods, the debug symbols from this type of XCFramework are not being included in the final product archive.

What did you expect to happen?

XCFrameworks that are compiled with Xcode 12 to include debug symbols should automatically support those debug symbols being included in the built app when integrating the XCFramework through CocoaPods.

What happened instead?

XCFrameworks that are compiled with Xcode 12 to include debug symbols automatically support debug symbol inclusion in the built app when manually added to the parent project, but not when integrated through CocoaPods.

CocoaPods Environment

Stack

   CocoaPods : 1.10.0.rc.1
        Ruby : ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]
    RubyGems : 3.0.3
        Host : Mac OS X 10.15.5 (19F101)
       Xcode : 11.7 (11E801a)
         Git : git version 2.24.3 (Apple Git-128)
Ruby lib dir : /Users/jtm/.rubies/ruby-2.6.5/lib
Repositories : master - git - https://github.com/CocoaPods/Specs.git @ e77184542ea8a2d527a9c094a82fdd5e6fff1642

               trunk - CDN - https://cdn.cocoapods.org/

Project that demonstrates the issue

I do not have an accessible sample project right now, but wanted to go ahead and create the ticket for visibility as it would be fantastic if this support were able to be added as part of the upcoming 1.10.0 release that already includes several other XCFramework improvements.

help wanted xcframeworks

All 16 comments

I did not implement XCFrameworks so I forget...do we manualloy copy those and the issue here is that we are missing some?

Thanks for the great report, yes a sample app is always helpful.

@dnkoutso correct, the symbols are manually copied and it looks like the relevant scripts (described below) are only looking inside of each architecture's .framework.

Background

To add some more background, my understanding from reading up on some of the original work (I think from the PSPDFKit team) and from @steipete's Twitter is that with Xcode 11 Apple had not provided much guidance on how debug symbols should be handled with XCFrameworks. As a result, I _think_ that the current approach of placing them inside the .frameworks may have been community convention more than anything else.

Proposal

Going forward, I assume that the ideal case may be for CocoaPods to support pulling in the debug symbols from both locations (sibling or child of the .framework) to maintain support for the current community convention while also supporting compatibility with the drag-and-drop style now supported with Xcode 12.

Implementation

On the implementation side, from the tracing I've done so far I'm seeing that after running pod install the generated Pods/Target Support Files/MySDK/MySDK-xcframeworks.sh script concludes with the following lines:

install_xcframework "${PODS_ROOT}/MySDK/MySDK.xcframework" "MySDK" "framework" "ios-x86_64-simulator/MySDK.framework" "ios-arm64/MySDK.framework"

The install_xcframework function calls into select_slice to get the relevant slices which are then copied into $PODS_XCFRAMEWORKS_BUILD_DIR. I believe that because the last two input arguments to install_xcframework have the /MySDK.framework suffix, that the result of this script is that only the relevant .frameworks are copied into $PODS_XCFRAMEWORKS_BUILD_DIR, rather than the relevant frameworks and their debug symbol siblings.

Continuing to trace what I think is happening... there's also a generated Pods/Target Support Files/Pods-MyApp/Pods-MyApp-frameworks.sh script which is responsible for taking things from the $PODS_XCFRAMEWORKS_BUILD_DIR and putting them into the final product. That script currently copies dSYMs and BCSymbolMaps from the .framework and then strips them out so a duplicate copy is not leftover inside the final embedded .framework.

I _think_ then that what would need to happen to get this working is:

  1. Update the generated MySDK-xcframeworks.sh script to copy everything in the architecture's directory into $PODS_XCFRAMEWORKS_BUILD_DIR rather than only copying <architecture>/MySDK.framework.
  2. Update the generated Pods-MyApp-frameworks.sh script to copy dSYMs and BCSymbolMaps into the built product from the new, sibling location in $PODS_XCFRAMEWORKS_BUILD_DIR, while still keeping the existing support for copying/stripping them from the child location.

Tomorrow, I can put together a couple of small XCFrameworks as local pods that are setup with a sample Podfile and project so we can have one example that covers both use-cases.

This is great. We can even add an example project for this to ensure it fails without any changes and then starts working with your changes.

accidental close, sorry!

Great write up! Your proposed solution sounds great, especially considering we'd like to continue the existing solution for Xcode 11 at least until Xcode 13 / end of 2021. I'd be happy to review PRs towards this, but I likely won't be working on this myself in the near future

@johntmcintosh feel free to try! We can help you land this.

Thanks @dnkoutso -- I have a standalone sample project setup now with two test xcframeworks (one of each style) and a script to compile the wrapper app and evaluate whether the expected symbols are included or not. I think this gets me to a good point now for manually editing the generated scripts until they're doing what I want, and then work backwards into the tool to get CocoaPods to generate the expected scripts on a pod install.

I've run into some trouble getting Rainforest setup and could probably use some pointers if you have any ideas: https://github.com/CocoaPods/Rainforest/issues/88.

@johntmcintosh no need to use Rainforest, just fork this repo normally and cocoapods-integration specs repo and make a PR from that.

@dnkoutso good news -- I've identified a change in the generated scripts (smaller than I originally expected!) that appears to get this working in my example, so now I'm starting to look more seriously into how to test some things out building into the tool locally.

I've gotten setup where I can bundle exec rake spec:integration to run the tests and I've found the existing install_vendored_xcframework test that's probably a good starting point for the new tests that we need.

A couple of questions regarding project conventions and such:

  1. Does it make sense to update the existing test to be something like install_vendored_xcframework_xc11 and add debug symbols to it using the existing Xcode 11 convention, and then setup a new install_vendored_xcframework test (using the original name) that uses the new Xcode 12 convention? (I'm thinking to have the more current name without suffix, and then add the suffix for the previous which is now a special case.)
  2. This test depends on CoconutLib, so we'll want a version of CoconutLib with debug symbols in the Xcode 11 convention and a version with debug symbols in the Xcode 12 convention. Similar question... does it make sense to have a CoconutLib (for Xcode 12 convention) and a CoconutLibXc11 (or something like that) for Xcode 11?
  3. I see that the install_vendored_xcframework test includes a copy of CoconutLib.xcframework, which is used in a few other tests as well, so I'm looking for where to make changes to how that's generated. I've found examples/Vendored XCFramework Example which includes instructions in its Podfile for how to regenerate the xcframework. If I make changes there, are they automatically copied into all places that utilize CoconutLib, or do I copy them manually?

Thanks!

When sitting down this morning to get back into this, it hit me that that the distinction that I was looking to test (differing input library structures) wasn't really a valid use-case for the integration tests, since those are testing the result of a pod install more than they are testing the result of building the resulting project.

I've now updated all of the relevant test cases to pass and reflect the change that I'm proposing. The missing piece still may be adding a new test or example that ensures that the core change of the generated script is actually the right change to be making.

I think I'm at a reasonable point to put up a PR though, and we can sort out any additional test cases like that in the PR.

The examples are built in CI and may be a more appropriate place to test these integrations. We don't currently have separation of Xcode versions in those but we could.

The place that handles building the examples is here: https://github.com/CocoaPods/CocoaPods/blob/a3632cc1ad06245e57ccb46a845a47874f55c4fb/Rakefile#L282-L285

Definitely not a blocker for landing these PRs though, we can make incremental progress here

Thanks @amorde! I actually found those today a little while after getting the PRs up. Most of the current examples there do a standard build on the target and I think that the test passing/failing just depends on the target successfully compiling, is that correct?

For this use-case I _think_ that the evaluation we'd want to do of debug symbols requires an archive build to be done. (At least in my testing I've been doing an archive since the symbols are placed at the root directory of the final archive. It may be that they are available at a different path for regular builds and if so a regular build might be able to be used more simply.)

Assuming that understanding is correct, I can look into creating an aggregate target in the Vendored XCFramework example and then have a run script that does an archive and exits with a failing return code if the expected symbols are not present in the built archive. I'm assuming that we would need to do something like that so that the general loop that runs xcodebuild on all schemes would still support this specific test. Let me know if you have any other/better ideas on how would be best to go about setting that up.

@johntmcintosh should we close this now that the PR landed? I am planning to ship 1.10 very soon.

@dnkoutso I had been leaving it open while the PR that adds the example test was still open, but functionally this issue is resolved now with the original PR merged. Feel free to close it out, or I will if it's still open when the example test case is merged.

@johntmcintosh thanks a lot, will close and merge your examples PR.

Was this page helpful?
0 / 5 - 0 ratings