Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-20557] iOS: Allow modules to use third party dynamic libraries

GitHub Issuen/a
TypeNew Feature
PriorityHigh
StatusClosed
ResolutionFixed
Resolution Date2017-08-30T19:15:18.000+0000
Affected Version/sn/a
Fix Version/sRelease 6.2.0
ComponentsiOS
Labelsn/a
ReporterPedro Enrique
AssigneeJan Vennemann
Created2016-03-11T16:51:34.000+0000
Updated2017-09-07T14:07:14.000+0000

Description

Traditionally, any library built for iOS was always a static library. This changed with Swift, and any library build with Swift is now a dynamic library instead. These dynamic libraries are just a binary, but they come in the a *.framework package. We currently do not support dynamic frameworks Titanium. There are a couple of things that need to happen for us to support these types of frameworks:

*Edit your module.xcconfig to look like this*

FRAMEWORK_SEARCH_PATHS="$(SRCROOT)/../../modules/iphone/{mod.id}/{mod.ver}/platform" "/Library/Application\ Support/Titanium/modules/iphone/{mod.id}/{mod.ver}/platform" "~/Library/Application\ Support/Titanium/modules/iphone/{mod.id}/{mod.ver}/platform"
LD_RUNPATH_SEARCH_PATHS= $(inherited) "@executable_path/Frameworks" $(FRAMEWORK_SEARCH_PATHS)
Note: The new LD_RUNPATH_SEARCH_PATHS allows the app to find the framework. And this is the most important step for running the app on simulator.

*Add a temporary plugin and eventually modify the CLI*

I have attached a Titanium plugin to this ticket. To use, place it in the plugins folder at the root of your project. The only change needed is line 46 of the plugins/ti.dynamiclib/hooks/ti.dynamiclib.js Note: This plugin is a workaround for the moment. Eventually, we'll need to copy the framework to the device and _sign_ it. This is something that needs to happen in the CLI.

*Modify your tiapp.xml*

And the very last thing, this is possible only with iOS 8 and greater. So make sure you have this in your tiapp.xml
<ios>
  <min-ios-ver>8.0</min-ios-ver>
  <!-- more stuff -->
</ios>

Attachments

FileDateSize
ti.dynamiclib.zip2016-03-11T16:51:32.000+00002318

Comments

  1. Jeroen van Dijk 2016-04-05

    I can confirm that this solution works!
  2. Timan Rebel 2016-05-18

    The script works! But to upload the app to iTunes Connect we'd need to strip the unwanted i386 and x64 archs from the dynamic library. We could do that by building the app via the cli, open Xcode, add the needed Build Pahse shell script and build from Xcode, but I'm curious if we could do it via the plugin or Appcelerator CLI. I have tried a couple of things but could not get it to work (yet)
  3. Jeroen van Dijk 2016-05-18

    [~timanrebel]: have a look here https://developer.pathsense.com/node/6, this framework suffers from the same issue and might help you resolve the issue.
  4. Timan Rebel 2016-05-19

    @jvandijk that is exactly how I solve it at the moment, but am looking for a solution via the CLI or a plugin
  5. Jeroen van Dijk 2016-05-19

    [~timanrebel] We've just packaged both frameworks for the simulator & the one for the app store in the external dependency. After this we updated this dynamic lib to switch based on the active cli.argv["target"]
  6. Timan Rebel 2016-05-20

    Could you share your code and how you did that here?
  7. Jeroen van Dijk 2016-06-09

    [~timanrebel] sorry for the late reply. But here is how we do it. In the addLibrary method: --- {{ var module = { version: 0 }; cli.tiapp.modules.forEach(function(item) { if (item.id === "" && item.platform[0] === "iphone") { module = item; } }); var frameworkPaths = [ '../../modules/iphone//' + module.version + '/platform/.framework' ]; require('wrench').rmdirSyncRecursive("modules/iphone//" + module.version + "/platform/.framework", true); if (cli.argv["$platform"] === "iphone" && cli.argv["target"] === "dist-adhoc") { require('wrench').copyDirSyncRecursive("modules/iphone//" + module.version + "/platform/appstore/.framework", "modules/iphone//" + module.version + "/platform/.framework"); } else { require('wrench').copyDirSyncRecursive("modules/iphone//" + module.version + "/platform/sim/.framework", "modules/iphone//" + module.version + "/platform/.framework"); } }} --- So in our module we distribute both the architectures, but on build select the right architecture.
  8. Antonio Romano 2016-07-06

    I tried to use this solution but something went wrong, when I run the project the application crashed for the following error: *dyld: Library not loaded: @rpath/libswiftCore.dylib Referenced from: /Users/antonioromano/Library/Developer/Xcode/DerivedData/MyApplication-bnrttzcdaaaulcbaeovdscbntnfw/Build/Products/Debug-iphonesimulator/MyFramework.framework/MyFramework Reason: image not found* my module.xconfig is the following: *FRAMEWORK_SEARCH_PATHS="$(SRCROOT)/../../modules/iphone/{mod.id}/{mod.ver}/platform" "/Library/Application\ Support/Titanium/modules/iphone/{mod.id}/{mod.ver}/platform" "~/Library/Application\ Support/Titanium/modules/iphone/{mod.id}/{mod.ver}/platform" LD_RUNPATH_SEARCH_PATHS= $(inherited) "@executable_path/Frameworks" $(FRAMEWORK_SEARCH_PATHS) OTHER_LDFLAGS=$(inherited) -F"$(SRCROOT)/../../modules/iphone/{mod.id}/{mod.ver}/platform/MyFramework.framework" -framework MyFramework* (I also tried without 'OTHER_LDFLAGS') Do I miss something? I think is a problem of copy, because if I drag and drop the .framework in the xcodeproject then the application doesn't crash anymore.
  9. John Staunton 2016-07-08

    Same problem as Antonio Romano here... application crashes instantly saying "image not found". I've added the plugin, updated the appropriate line, etc.
  10. John Staunton 2016-07-15

    Anyone with any ideas? It works 'fine' in the simulator (doesn't load our icons properly, but that's a different story) but crashes immediately on a real device...
  11. John Staunton 2016-08-20

    Ok, eventually and after much trial-and-error I have this working - both on the device and for packaging new apps for distribution. 1) There is a small omission in the above instructions - in TiApp.xml you also need to ensure that the plugin is called: ti.dynamiclib 2) Modify ti.dynamiclib.js to add a run script phase to strip the frameworks - after frameworkPaths.forEach(...) add: // Add frameworks tidyup run script phase (has to be after Frameworks) var script_uuid = builder.generateXcodeUuid(); var shell_path = '/bin/sh'; var shell_script = 'bash \"/strip-frameworks.sh\"'; createPBXRunShellScriptBuildPhase(xobjs, script_uuid, shell_path, shell_script); createPBXRunScriptNativeTarget(xobjs, script_uuid); The 2 new functions are: function createPBXRunShellScriptBuildPhase(xobjs, script_uuid, shell_path, shell_script){ xobjs.PBXShellScriptBuildPhase = xobjs.PBXShellScriptBuildPhase || {}; xobjs.PBXShellScriptBuildPhase[script_uuid] = { isa: 'PBXShellScriptBuildPhase', buildActionMask: '2147483647', files: '(\n)', inputPaths: '(\n)', outputPaths: '(\n)', runOnlyForDeploymentPostprocessing: 0, shellPath: shell_path, shellScript: JSON.stringify(shell_script) }; } and function createPBXRunScriptNativeTarget(xobjs, script_uuid) { for (var key in xobjs.PBXNativeTarget) { xobjs.PBXNativeTarget[key].buildPhases.push({ value: script_uuid + '', comment: 'Run Script Phase' }); return; } } Not the best code ever written, but gets the job done. Doing the above, I am able to build apps with embedded frameworks again and can directly publish apps for the app store from Studio.
  12. Richard Lustemberg 2017-01-24

    I am using John Staunton's approach with success. The dynamic libs are on a native module. I am including the CLI hook plugin as a module hook, not a global one.
  13. Hans Knöchel 2017-01-24

    Hey guys, we recently updated the CLI-hook with the script-phase, Hyperloop usage and more docs. It can be found [here](https://gist.github.com/hansemannn/5046fcc9a14cc3d09d0874f964b443aa). To make things easier for the user of your module, you can simply place the file in <module-root>/iphone/hooks/ and the app-CLI will pick it up during build process. No manual <plugin/> reference needed.
  14. Jan Vennemann 2017-08-23

    PR (master): https://github.com/appcelerator/titanium_mobile/pull/9346 PR (6_2_X): https://github.com/appcelerator/titanium_mobile/pull/9351 Testing instructions for QE will follow tomorrow. Just wanted to put this in review now so [~hknoechel] and [~cbarber] can already do a CR since it's quite some change to the iOS build.
  15. Jan Vennemann 2017-08-24

    This new hook does a lot under the hood, so here are some extended testing steps to check all the use cases. *Basic FR steps*

    Download the test module at https://drive.google.com/open?id=0BzhMoExz43YBX2F6ODVCb2Ntd1k. This is a simple test module with one static (FBSDK) and dynamic (Mapbox) framework

    Add the module com.appc.mapbox to your app

    Add the following to {{app/controllers/index.js}:

        var TestModule = require('com.appc.mapbox');
        $.label.text = 'Mapbox: ' + TestModule.mapboxVersion + '\nFB: ' + TestModule.fbSdkVersion;
        

    Run the app. It should print two version strings for the bundled Mapbox and Facebook SDK

    *Additional steps*

    The hook includes a caching system for the framework metadata it collects to properly integrate the frameworks. This metadata will only be (re)generated if a framework changes. In the first run after dropping in the test module you should see log messages that indicate the metadata generation like this:

        [DEBUG] ti:inspectFrameworks: Found framework FBSDKCoreKit (type: static, archs: i386, armv7, x86_64, arm64) at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/FBSDKCoreKit.framework
        [DEBUG] ti:inspectFrameworks: Found framework Mapbox (type: dynamic, archs: i386, x86_64, armv7, arm64) at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Mapbox.framework
        [DEBUG] ti:inspectFrameworks: Found framework Bolts (type: static, archs: i386, armv7, x86_64, arm64) at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Bolts.framework
        

    Moving the module to a new location, e.g. from the app to the global modules folder, the metabase should be invalidated and regenerated, indicated by the following log messages:

        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/FBSDKCoreKit.framework deleted, removing metadata
        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Mapbox.framework deleted, removing metadata
        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Bolts.framework deleted, removing metadata
        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Library/Application Support/Titanium/modules/iphone/com.appc.mapbox/1.0.0/platform/Bolts.framework changed, regenerating metadata
        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Library/Application Support/Titanium/modules/iphone/com.appc.mapbox/1.0.0/platform/FBSDKCoreKit.framework changed, regenerating metadata
        [TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Library/Application Support/Titanium/modules/iphone/com.appc.mapbox/1.0.0/platform/Mapbox.framework changed, regenerating metadata
        

    The same should happen if a single framework is added, changed or removed from a module or the project's platform/ios folder.

    When a dynamic framework is used, a script will be provided by default that strips the frameworks of unused architectures. This is indicated by this log message:

        [TRACE] Framework with fat binary present, integrating script to strip invalid architectures.
        [TRACE] Using bundled script at /Users/jvennemann/Library/Application Support/Titanium/mobilesdk/osx/6.2.0.v20170811022027/iphone/templates/build/strip-frameworks.sh
        
    To allow the user to use their own script it is possible to override this script by dropping in their own script in platform/ios and name it strip-frameworks.sh. To test that create a simple shell script which only contains an echo The log message should change to
        [TRACE] Framework with fat binary present, integrating script to strip invalid architectures.
        [TRACE] Using custom user script at /Users/jvennemann/Development/appc/dftest/platform/ios/strip-frameworks.sh
        
    and you should see the you echo'd text at the very end of the build.
  16. Eric Wieber 2017-08-30

    Verified in SDK builds 7.0.0.v20170830123430 & 6.2.0.v20170830125819

JSON Source