Search code examples
iosframeworkscocoapods

iOS: How to build a static framework based on CocoaPods dependencies?


I am trying to build a framework that makes use of the geopackage-ios CocoaPods dependency. I would then like to statically embed this framework into one or more apps.

Similar questions have been asked before, but I couldn't find a solution to my problem. Here's what I've been trying so far:

First Approach: Building Manually

In this approach, I would create a static library using Xcode, add the CocoaPods dependencies to it and build it. The problem with this approach, however, is that the resulting framework will not have the CocoaPods dependencies embedded into it.

  1. Create a new static library from Xcode (File > New > Project… > iOS: Static Library) named "MyLibrary" with language Swift.
  2. Create a Podfile in the project root directory with the following content:
target 'MyLibrary' do
    pod 'geopackage-ios', '~> 7.2.1'
end
  1. Run pod install. (This will install the geopackage-ios pod and 10 depending pods.)
  2. Open the newly created MyLibrary.xcworkspace and add a BridgingHeader.h to the "MyLibrary" group having the following content:
#ifndef BridgingHeader_h
#define BridgingHeader_h

#import "geopackage-ios-Bridging-Header.h"

#endif
  1. Reference the bridging header by clicking on the "MyLibrary" project in the project navigator, selecting the "MyLibrary" target and, in the Build Settings, specifying MyLibrary/BridgingHeader.h as the "Objective-C Bridging Header".
  2. Modify MyLibrary.swift to use the GeoPackage dependency:
public class MyLibrary {
    public init() {}
    
    public func test() {
        let manager = GPKGGeoPackageFactory.manager()!
        print("Manager:", manager)
    }
}
  1. Build the library for the iOS simulator by pressing Cmd + B.
  2. Optionally, create a static framework using a shell script like the one in this article (which will compile the library once for the simulator and once for physical devices and then combine the two results to a universal framework using the lipo command).

When I embed either the static library (along with the MyLibrary.swiftmodule) or the universal framework into an app, linking will fail for this app:

Undefined symbols for architecture x86_64:
  "_OBJC_CLASS_$_GPKGGeoPackageManager", referenced from:
      objc-class-ref in libMyLibrary.a(MyLibrary.o)
  "_OBJC_CLASS_$_GPKGGeoPackageFactory", referenced from:
      objc-class-ref in libMyLibrary.a(MyLibrary.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

I could also add the geopackage_ios.framework (found in the build folder of "MyLibrary") to the app (making sure to choose the Embed & Sign embedding option), which will allow the app to be built successfully. Then, however, the app will crash upon launch:

dyld[79209]: Library not loaded: @rpath/ogc_api_features_json_ios.framework/ogc_api_features_json_ios
  Referenced from: /Users/admin1/Library/Developer/CoreSimulator/Devices/C3D61224-CFE8-45CC-951B-7B6AB54BC8B3/data/Containers/Bundle/Application/D41A82D6-4E69-4BB6-AC59-6BC28AE58795/TestApp.app/Frameworks/geopackage_ios.framework/geopackage_ios

One might be able to fix this by adding all eleven pod frameworks to the app, which, however, is what I'm trying to avoid.

Second Approach: Using PodBuilder

A second approach would be to use PodBuilder, which aims at automating this particular kind of build task. This approach, however, fails with a build error, the cause of which I can't quite make out.

  1. Again, I would start out with a new Xcode project. This time, I'd create an iOS Swift app named "MyApp".
  2. I would create a Podfile with almost the same content as above:
use_frameworks!
target 'MyApp' do
    pod 'geopackage-ios', '~> 7.2.1'
end
  1. Then, I'd run pod install.
  2. Next, I would install PodBuilder, create an empty Git repository (which PodBuilder requires for some reason), initialize PodBuilder, and start the build task:
sudo gem install pod-builder
git init
pod_builder init
pod_builder build geopackage-ios
  1. The build will fail, however, with errors like these written to /tmp/pod_builder/pod_builder.err:
Undefined symbols for architecture arm64:
  "_PROJ_WEB_MERCATOR_MAX_LAT_RANGE", referenced from:
      +[GPKGTileBoundingBoxUtils toWebMercatorWithBoundingBox:] in GPKGTileBoundingBoxUtils.o
      +[GPKGTileBoundingBoxUtils boundDegreesBoundingBoxWithWebMercatorLimits:] in GPKGTileBoundingBoxUtils.o
      -[GPKGTileDao isXYZTiles] in GPKGTileDao.o
      -[GPKGTileGenerator adjustXYZBounds] in GPKGTileGenerator.o
  "_PROJ_UNDEFINED_CARTESIAN", referenced from:
      -[GPKGSpatialReferenceSystemDao createIfNeededWithSrs:andOrganization:andCoordsysId:] in GPKGSpatialReferenceSystemDao.o
  "_PROJ_AUTHORITY_NONE", referenced from:
      -[GPKGSpatialReferenceSystemDao createIfNeededWithSrs:andOrganization:andCoordsysId:] in GPKGSpatialReferenceSystemDao.o
  […]
  1. This seems to be a problem with the geopackage-ios pod itself, which can be fixed by cloning our own version of the repo …
cd ..
git clone --branch 7.2.1 https://github.com/ngageoint/geopackage-ios.git
  1. …, installing the pods …
cd geopackage-ios
pod install
  1. … manually copying the proj-ios source files …
cp -r Pods/proj-ios/proj-ios geopackage-ios
  1. …, and opening the geopackage-ios.xcodeproj to reference the proj-ios files in the geopackage-ios group inside that project.

  2. Then telling CocoaPods to use our local version of the pod …

use_frameworks!
target 'MyApp' do
    pod 'geopackage-ios', :path => '../geopackage-ios'
end
  1. …, reinitializing PodBuilder …
cd ../MyApp
pod_builder deintegrate
pod deintegrate
pod install
pod_builder init
  1. … and allowing PodBuilder to use local pods by editing the ./PodBuilder/PodBuilder.json file:
{
  …,
  "allow_building_development_pods": true,
  …
}
  1. Then, one can try to rebuild:
pod_builder build geopackage-ios

This time, the build will fail with a different error:

The following build commands failed:
    CompileC /tmp/pod_builder/build/Pods.build/Release-iphoneos/sf-proj-ios.build/Objects-normal/arm64/SFPGeometryTransform.o /tmp/pod_builder/Pods/sf-proj-ios/sf-proj-ios/SFPGeometryTransform.m normal arm64 objective-c com.apple.compilers.llvm.clang.1_0.compiler (in target 'sf-proj-ios' from project 'Pods')
(1 failure)

The SFPGeometryTransform.m file, however, looks fine.

The full build log is available here.

Third Approach: Using Rome

The third approach would be to use cocoapods-rome.

  1. To use it, one would install Rome …
sudo gem install cocoapods-rome
  1. …, get rid of the PodBuilder stuff …
pod_builder deintegrate
pod deintegrate
  1. …, quickly adjust the Podfile:
use_frameworks!
platform :ios, '13.0'

plugin 'cocoapods-rome', { :pre_compile => Proc.new { |installer|
    installer.pods_project.targets.each do |target|
        target.build_configurations.each do |config|
            config.build_settings['SWIFT_VERSION'] = '5.0'
        end
    end

    installer.pods_project.save
},
    dsym: false,
    configuration: 'Release'
}

target 'MyApp' do
    pod 'geopackage-ios', :path => '../geopackage-ios'
end
  1. … and install the pods:
pod install

This, however, will fail with the exact same error as PodBuilder.

Can anyone give me some advice on any of these approaches? Or does anyone perhaps have a whole different idea on how to build such a framework?

(EDIT: It seems like the PROJProjectionTransform.h file imported to SFPGeometryTransform.m cannot be found. Anyway, I don't think these approaches are expedient. So any help is highly appreciated.)


Solution

  • Solved the problem by simply feeding all the aggregated source files from the pod installation into a new static library, then using either the lipo command to make a fat binary out of it or creating an Xcode framework from it (as described here). The compiled framework could then be used to create another framework built on top of it.