{ "id": "155646", "key": "TIMOB-20557", "fields": { "issuetype": { "id": "2", "description": "A new feature of the product, which has yet to be developed.", "name": "New Feature", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [ { "id": "18414", "description": "", "name": "Release 6.2.0", "archived": false, "released": true, "releaseDate": "2017-09-13" } ], "resolution": { "id": "1", "description": "A fix for this issue is checked into the tree and tested.", "name": "Fixed" }, "resolutiondate": "2017-08-30T19:15:18.000+0000", "created": "2016-03-11T16:51:34.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [], "versions": [], "issuelinks": [ { "id": "55879", "type": { "id": "10122", "name": "Gantt: start-finish", "inward": "is triggered by", "outward": "is triggering" }, "outwardIssue": { "id": "169693", "key": "TIMOB-25227", "fields": { "summary": "iOS: Building app with module that contains a framework fails on 6.2.0", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "High", "id": "2" }, "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false } } } }, { "id": "55893", "type": { "id": "10003", "name": "Relates", "inward": "relates to", "outward": "relates to" }, "outwardIssue": { "id": "169695", "key": "TIDOC-2999", "fields": { "summary": "Document automatic framework integration and dynamic framework support", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "priority": { "name": "High", "id": "2" }, "issuetype": { "id": "4", "description": "An improvement or enhancement to an existing feature or task.", "name": "Improvement", "subtask": false } } } } ], "assignee": { "name": "jvennemann", "key": "jvennemann", "displayName": "Jan Vennemann", "active": true, "timeZone": "Europe/Berlin" }, "updated": "2017-09-07T14:07:14.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [ { "id": "10206", "name": "iOS", "description": "iOS Platform" } ], "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.\r\nWe currently do not support dynamic frameworks Titanium.\r\n\r\nThere are a couple of things that need to happen for us to support these types of frameworks:\r\n\r\n# *Edit your module.xcconfig to look like this*\r\n{code}\r\nFRAMEWORK_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\"\r\nLD_RUNPATH_SEARCH_PATHS= $(inherited) \"@executable_path/Frameworks\" $(FRAMEWORK_SEARCH_PATHS)\r\n{code}\r\nNote: 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.\r\n# *Add a temporary plugin and eventually modify the CLI*\r\nI 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}}\r\nNote: 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.\r\n# *Modify your tiapp.xml*\r\nAnd the very last thing, this is possible only with iOS 8 and greater. So make sure you have this in your {{tiapp.xml}}\r\n{code}\r\n\r\n 8.0\r\n \r\n\r\n{code}", "attachment": [ { "id": "58589", "filename": "ti.dynamiclib.zip", "author": { "name": "penrique", "key": "penrique", "displayName": "Pedro Enrique", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2016-03-11T16:51:32.000+0000", "size": 2318, "mimeType": "application/zip" } ], "flagged": false, "summary": "iOS: Allow modules to use third party dynamic libraries", "creator": { "name": "penrique", "key": "penrique", "displayName": "Pedro Enrique", "active": false, "timeZone": "America/Los_Angeles" }, "subtasks": [], "reporter": { "name": "penrique", "key": "penrique", "displayName": "Pedro Enrique", "active": false, "timeZone": "America/Los_Angeles" }, "environment": null, "comment": { "comments": [ { "id": "381738", "author": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "I can confirm that this solution works!", "updateAuthor": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2016-04-05T12:44:29.000+0000", "updated": "2016-04-05T12:44:29.000+0000" }, { "id": "386051", "author": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "body": "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)", "updateAuthor": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "created": "2016-05-18T14:18:47.000+0000", "updated": "2016-05-18T14:18:47.000+0000" }, { "id": "386080", "author": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "[~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.", "updateAuthor": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2016-05-18T18:39:07.000+0000", "updated": "2016-05-18T18:39:27.000+0000" }, { "id": "386172", "author": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "body": "@jvandijk that is exactly how I solve it at the moment, but am looking for a solution via the CLI or a plugin", "updateAuthor": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "created": "2016-05-19T08:03:16.000+0000", "updated": "2016-05-19T08:04:59.000+0000" }, { "id": "386192", "author": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "[~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\"]`", "updateAuthor": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2016-05-19T11:12:43.000+0000", "updated": "2016-05-19T11:12:43.000+0000" }, { "id": "386320", "author": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "body": "Could you share your code and how you did that here?", "updateAuthor": { "name": "timanrebel", "key": "timanrebel", "displayName": "Timan Rebel", "active": true, "timeZone": "Europe/London" }, "created": "2016-05-20T08:35:56.000+0000", "updated": "2016-05-20T08:35:56.000+0000" }, { "id": "387923", "author": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "body": "[~timanrebel] sorry for the late reply. But here is how we do it.\r\n\r\nIn the addLibrary method:\r\n---\r\n{{\r\nvar module = {\r\n\tversion: 0\r\n};\r\n\r\ncli.tiapp.modules.forEach(function(item) {\r\n\tif (item.id === \"\" && item.platform[0] === \"iphone\") {\r\n\t\tmodule = item;\r\n\t}\r\n});\r\n\r\nvar frameworkPaths = [\r\n\t'../../modules/iphone//' + module.version + '/platform/.framework'\r\n];\r\n\r\nrequire('wrench').rmdirSyncRecursive(\"modules/iphone//\" + module.version + \"/platform/.framework\", true);\r\n\r\nif (cli.argv[\"$platform\"] === \"iphone\" && cli.argv[\"target\"] === \"dist-adhoc\") {\r\n\trequire('wrench').copyDirSyncRecursive(\"modules/iphone//\" + module.version + \"/platform/appstore/.framework\", \"modules/iphone//\" + module.version + \"/platform/.framework\");\r\n} else {\r\n\trequire('wrench').copyDirSyncRecursive(\"modules/iphone//\" + module.version + \"/platform/sim/.framework\", \"modules/iphone//\" + module.version + \"/platform/.framework\");\r\n}\r\n}}\r\n---\r\n\r\nSo in our module we distribute both the architectures, but on build select the right architecture.", "updateAuthor": { "name": "jvandijk", "key": "jvandijk", "displayName": "Jeroen van Dijk", "active": true, "timeZone": "Europe/Amsterdam" }, "created": "2016-06-09T07:35:38.000+0000", "updated": "2016-06-09T07:36:10.000+0000" }, { "id": "389976", "author": { "name": "antonio.romano", "key": "antonio.romano", "displayName": "Antonio Romano", "active": true, "timeZone": "America/Los_Angeles" }, "body": "I tried to use this solution but something went wrong, when I run the project the application crashed for the following error:\r\n\r\n*dyld: Library not loaded: @rpath/libswiftCore.dylib\r\n Referenced from: /Users/antonioromano/Library/Developer/Xcode/DerivedData/MyApplication-bnrttzcdaaaulcbaeovdscbntnfw/Build/Products/Debug-iphonesimulator/MyFramework.framework/MyFramework\r\n Reason: image not found*\r\n\r\nmy module.xconfig is the following:\r\n\r\n*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\"\r\nLD_RUNPATH_SEARCH_PATHS= $(inherited) \"@executable_path/Frameworks\" $(FRAMEWORK_SEARCH_PATHS)\r\nOTHER_LDFLAGS=$(inherited) -F\"$(SRCROOT)/../../modules/iphone/{mod.id}/{mod.ver}/platform/MyFramework.framework\" -framework MyFramework*\r\n\r\n(I also tried without 'OTHER_LDFLAGS')\r\n\r\n\r\nDo I miss something?\r\nI think is a problem of copy, because if I drag and drop the .framework in the xcodeproject then the application doesn't crash anymore.", "updateAuthor": { "name": "antonio.romano", "key": "antonio.romano", "displayName": "Antonio Romano", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2016-07-06T16:03:35.000+0000", "updated": "2016-07-06T16:03:35.000+0000" }, { "id": "390148", "author": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Same problem as Antonio Romano here... application crashes instantly saying \"image not found\". I've added the plugin, updated the appropriate line, etc.", "updateAuthor": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2016-07-08T11:47:57.000+0000", "updated": "2016-07-08T11:47:57.000+0000" }, { "id": "390783", "author": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "body": "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...", "updateAuthor": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2016-07-15T07:45:54.000+0000", "updated": "2016-07-15T07:45:54.000+0000" }, { "id": "393700", "author": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "body": "Ok, eventually and after much trial-and-error I have this working - both on the device and for packaging new apps for distribution.\r\n\r\n1) There is a small omission in the above instructions - in TiApp.xml you also need to ensure that the plugin is called: \r\n \r\n ti.dynamiclib\r\n \r\n\r\n2) Modify ti.dynamiclib.js to add a run script phase to strip the frameworks - after frameworkPaths.forEach(...) add:\r\n\r\n\t// Add frameworks tidyup run script phase (has to be after Frameworks)\r\n\tvar script_uuid = builder.generateXcodeUuid();\r\n\tvar shell_path = '/bin/sh';\r\n\tvar shell_script = 'bash \\\"/strip-frameworks.sh\\\"';\r\n\tcreatePBXRunShellScriptBuildPhase(xobjs, script_uuid, shell_path, shell_script);\r\n\tcreatePBXRunScriptNativeTarget(xobjs, script_uuid);\r\n\r\nThe 2 new functions are:\r\n\r\nfunction createPBXRunShellScriptBuildPhase(xobjs, script_uuid, shell_path, shell_script){\r\n\txobjs.PBXShellScriptBuildPhase = xobjs.PBXShellScriptBuildPhase || {};\r\n\txobjs.PBXShellScriptBuildPhase[script_uuid] = {\r\n\t\tisa: 'PBXShellScriptBuildPhase',\r\n\t\tbuildActionMask: '2147483647',\r\n\t\tfiles: '(\\n)',\r\n\t\tinputPaths: '(\\n)',\r\n\t\toutputPaths: '(\\n)',\r\n\t\trunOnlyForDeploymentPostprocessing: 0,\r\n\t\tshellPath: shell_path,\r\n\t\tshellScript: JSON.stringify(shell_script)\r\n\t};\r\n}\r\n\r\nand\r\n\r\nfunction createPBXRunScriptNativeTarget(xobjs, script_uuid) {\r\n\tfor (var key in xobjs.PBXNativeTarget) {\r\n\t\txobjs.PBXNativeTarget[key].buildPhases.push({\r\n\t\t\tvalue: script_uuid + '',\r\n\t\t\tcomment: 'Run Script Phase'\r\n\t\t});\r\n\t\treturn;\r\n\t}\r\n}\r\n\r\nNot 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.", "updateAuthor": { "name": "john.staunton", "key": "john.staunton", "displayName": "John Staunton", "active": true, "timeZone": "America/Los_Angeles" }, "created": "2016-08-20T18:32:51.000+0000", "updated": "2016-08-20T18:32:51.000+0000" }, { "id": "405177", "author": { "name": "rlustemberg", "key": "rlustemberg", "displayName": "Richard Lustemberg", "active": true, "timeZone": "Europe/Berlin" }, "body": "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.", "updateAuthor": { "name": "rlustemberg", "key": "rlustemberg", "displayName": "Richard Lustemberg", "active": true, "timeZone": "Europe/Berlin" }, "created": "2017-01-24T15:22:03.000+0000", "updated": "2017-01-24T15:23:21.000+0000" }, { "id": "405203", "author": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "body": "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 {{/iphone/hooks/}} and the app-CLI will pick it up during build process. No manual {{}} reference needed.", "updateAuthor": { "name": "hknoechel", "key": "hansknoechel", "displayName": "Hans Knöchel", "active": true, "timeZone": "Europe/Berlin" }, "created": "2017-01-24T18:39:06.000+0000", "updated": "2017-01-24T18:39:06.000+0000" }, { "id": "427130", "author": { "name": "jvennemann", "key": "jvennemann", "displayName": "Jan Vennemann", "active": true, "timeZone": "Europe/Berlin" }, "body": "PR (master): https://github.com/appcelerator/titanium_mobile/pull/9346\r\nPR (6_2_X): https://github.com/appcelerator/titanium_mobile/pull/9351\r\n\r\nTesting 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.", "updateAuthor": { "name": "jvennemann", "key": "jvennemann", "displayName": "Jan Vennemann", "active": true, "timeZone": "Europe/Berlin" }, "created": "2017-08-23T23:31:37.000+0000", "updated": "2017-08-23T23:31:37.000+0000" }, { "id": "427151", "author": { "name": "jvennemann", "key": "jvennemann", "displayName": "Jan Vennemann", "active": true, "timeZone": "Europe/Berlin" }, "body": "This new hook does a lot under the hood, so here are some extended testing steps to check all the use cases.\r\n\r\n*Basic FR steps*\r\n# 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\r\n# Add the module {{com.appc.mapbox}} to your app\r\n# Add the following to {{app/controllers/index.js}:\r\n{code}\r\nvar TestModule = require('com.appc.mapbox');\r\n$.label.text = 'Mapbox: ' + TestModule.mapboxVersion + '\\nFB: ' + TestModule.fbSdkVersion;\r\n{code}\r\n# Run the app. It should print two version strings for the bundled Mapbox and Facebook SDK\r\n\r\n*Additional steps*\r\n# 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: \r\n{code}\r\n[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\r\n[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\r\n[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\r\n{code}\r\n# 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:\r\n{code}\r\n[TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/FBSDKCoreKit.framework deleted, removing metadata\r\n[TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Mapbox.framework deleted, removing metadata\r\n[TRACE] ti:inspectFrameworks: Framework at /Users/jvennemann/Development/appc/dftest/modules/iphone/com.appc.mapbox/1.0.0/platform/Bolts.framework deleted, removing metadata\r\n[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\r\n[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\r\n[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\r\n{code}\r\n# The same should happen if a single framework is added, changed or removed from a module or the project's {{platform/ios}} folder.\r\n# 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:\r\n{code}\r\n[TRACE] Framework with fat binary present, integrating script to strip invalid architectures.\r\n[TRACE] Using bundled script at /Users/jvennemann/Library/Application Support/Titanium/mobilesdk/osx/6.2.0.v20170811022027/iphone/templates/build/strip-frameworks.sh\r\n{code}\r\nTo 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}}\r\nThe log message should change to \r\n{code}\r\n[TRACE] Framework with fat binary present, integrating script to strip invalid architectures.\r\n[TRACE] Using custom user script at /Users/jvennemann/Development/appc/dftest/platform/ios/strip-frameworks.sh\r\n{code}\r\nand you should see the you echo'd text at the very end of the build.", "updateAuthor": { "name": "jvennemann", "key": "jvennemann", "displayName": "Jan Vennemann", "active": true, "timeZone": "Europe/Berlin" }, "created": "2017-08-24T12:46:50.000+0000", "updated": "2017-08-24T12:47:20.000+0000" }, { "id": "427435", "author": { "name": "ewieber", "key": "ewieber", "displayName": "Eric Wieber", "active": false, "timeZone": "America/Los_Angeles" }, "body": "Verified in SDK builds 7.0.0.v20170830123430 & 6.2.0.v20170830125819", "updateAuthor": { "name": "ewieber", "key": "ewieber", "displayName": "Eric Wieber", "active": false, "timeZone": "America/Los_Angeles" }, "created": "2017-08-30T21:02:18.000+0000", "updated": "2017-08-30T21:02:18.000+0000" } ], "maxResults": 16, "total": 16, "startAt": 0 } } }