自定义Cordova插件:将框架添加到“embedded式二进制文件”
在一个自定义的Cordova插件中,如何在plugin.xml中configuration特定的.framework文件,以便将其添加到Xcode中的“embedded式二进制文件”部分? 如果目前不能直接在plugin.xml中,我打开替代的build议。
我已经实现了一个解决方法,直到它被Cordova的plugin.xml支持,希望将来一旦这些条目中的embed
属性具有相同的效果: <framework embed="true" src="..." />
,现在,这个属性没有帮助,因此下面的解决方法。
以下解决scheme使用Cordova版本5.3.3。
首先,确保将框架条目添加到plugin.xml中:
<framework src="pointToYour/File.framework" embed="true" />
embed="true"
现在不起作用,但无论如何都要添加它。
我们要创build一个钩子,在你的plugin.xml中声明:
<hook type="after_platform_add" src="hooks/embedframework/addEmbedded.js" />
接下来,我们需要在钩子代码中使用特定的节点模块,该模块是node-xcode 。
安装node-xcode(必须是0.8.7或更高版本):
npm i xcode
最后,钩子本身的代码 –
addEmbedded.js文件:
'use strict'; const xcode = require('xcode'), fs = require('fs'), path = require('path'); module.exports = function(context) { if(process.length >=5 && process.argv[1].indexOf('cordova') == -1) { if(process.argv[4] != 'ios') { return; // plugin only meant to work for ios platform. } } function fromDir(startPath,filter, rec, multiple){ if (!fs.existsSync(startPath)){ console.log("no dir ", startPath); return; } const files=fs.readdirSync(startPath); var resultFiles = [] for(var i=0;i<files.length;i++){ var filename=path.join(startPath,files[i]); var stat = fs.lstatSync(filename); if (stat.isDirectory() && rec){ fromDir(filename,filter); //recurse } if (filename.indexOf(filter)>=0) { if (multiple) { resultFiles.push(filename); } else { return filename; } } } if(multiple) { return resultFiles; } } function getFileIdAndRemoveFromFrameworks(myProj, fileBasename) { var fileId = ''; const pbxFrameworksBuildPhaseObjFiles = myProj.pbxFrameworksBuildPhaseObj(myProj.getFirstTarget().uuid).files; for(var i=0; i<pbxFrameworksBuildPhaseObjFiles.length;i++) { var frameworkBuildPhaseFile = pbxFrameworksBuildPhaseObjFiles[i]; if(frameworkBuildPhaseFile.comment && frameworkBuildPhaseFile.comment.indexOf(fileBasename) != -1) { fileId = frameworkBuildPhaseFile.value; pbxFrameworksBuildPhaseObjFiles.splice(i,1); // MUST remove from frameworks build phase or else CodeSignOnCopy won't do anything. break; } } return fileId; } function getFileRefFromName(myProj, fName) { const fileReferences = myProj.hash.project.objects['PBXFileReference']; var fileRef = ''; for(var ref in fileReferences) { if(ref.indexOf('_comment') == -1) { var tmpFileRef = fileReferences[ref]; if(tmpFileRef.name && tmpFileRef.name.indexOf(fName) != -1) { fileRef = ref; break; } } } return fileRef; } const xcodeProjPath = fromDir('platforms/ios','.xcodeproj', false); const projectPath = xcodeProjPath + '/project.pbxproj'; const myProj = xcode.project(projectPath); function addRunpathSearchBuildProperty(proj, build) { const LD_RUNPATH_SEARCH_PATHS = proj.getBuildProperty("LD_RUNPATH_SEARCH_PATHS", build); if(!LD_RUNPATH_SEARCH_PATHS) { proj.addBuildProperty("LD_RUNPATH_SEARCH_PATHS", "\"$(inherited) @executable_path/Frameworks\"", build); } else if(LD_RUNPATH_SEARCH_PATHS.indexOf("@executable_path/Frameworks") == -1) { var newValue = LD_RUNPATH_SEARCH_PATHS.substr(0,LD_RUNPATH_SEARCH_PATHS.length-1); newValue += ' @executable_path/Frameworks\"'; proj.updateBuildProperty("LD_RUNPATH_SEARCH_PATHS", newValue, build); } } myProj.parseSync(); addRunpathSearchBuildProperty(myProj, "Debug"); addRunpathSearchBuildProperty(myProj, "Release"); // unquote (remove trailing ") var projectName = myProj.getFirstTarget().firstTarget.name.substr(1); projectName = projectName.substr(0, projectName.length-1); //Removing the char " at beginning and the end. const groupName = 'Embed Frameworks ' + context.opts.plugin.id; const pluginPathInPlatformIosDir = projectName + '/Plugins/' + context.opts.plugin.id; process.chdir('./platforms/ios'); const frameworkFilesToEmbed = fromDir(pluginPathInPlatformIosDir ,'.framework', false, true); process.chdir('../../'); if(!frameworkFilesToEmbed.length) return; myProj.addBuildPhase(frameworkFilesToEmbed, 'PBXCopyFilesBuildPhase', groupName, myProj.getFirstTarget().uuid, 'frameworks'); for(var frmFileFullPath of frameworkFilesToEmbed) { var justFrameworkFile = path.basename(frmFileFullPath); var fileRef = getFileRefFromName(myProj, justFrameworkFile); var fileId = getFileIdAndRemoveFromFrameworks(myProj, justFrameworkFile); // Adding PBXBuildFile for embedded frameworks var file = { uuid: fileId, basename: justFrameworkFile, settings: { ATTRIBUTES: ["CodeSignOnCopy", "RemoveHeadersOnCopy"] }, fileRef:fileRef, group:groupName }; myProj.addToPbxBuildFileSection(file); // Adding to Frameworks as well (separate PBXBuildFile) var newFrameworkFileEntry = { uuid: myProj.generateUuid(), basename: justFrameworkFile, fileRef:fileRef, group: "Frameworks" }; myProj.addToPbxBuildFileSection(newFrameworkFileEntry); myProj.addToPbxFrameworksBuildPhase(newFrameworkFileEntry); } fs.writeFileSync(projectPath, myProj.writeSync()); console.log('Embedded Frameworks In ' + context.opts.plugin.id); };
这个钩子实际上做了什么:
- 创build一个以您的插件ID命名的“Build Phase”,configuration为“Copy Files”,该拷贝的目的地是“Frameworks”。
- 查找并添加您的.framework文件到上面的Build阶段,然后embedded它。
- 设置一个名为
LD_RUNPATH_SEARCH_PATHS
的Xcode构build属性,以在"@executable_path/Frameworks"
查找embedded式框架(这是embedded式框架将被复制到“Copy Files” – >“Frameworks”构build阶段 - 通过为.framework文件设置“CodeSignOnCopy”和“RemoveHeadersOnCopy”来configurationATTRIBUTES键。
- 从FrameworksBuildPhase中移除你的.framework文件,并将它们作为新的分离的PBXBuildFiles(Same PBXFileReference)重新添加到FrameworksBuildPhase中,如果你打开项目,必须完成CodeSignOnCopy才能表示任何内容与Xcode,你不会在构build阶段find一个复选标记,说它会签名。
更新1:钩码,修改:
- 钩子自动find你的.framework文件,不需要编辑钩子。
- 添加了一个重要的修改,为你的.framework文件设置ATTRIBUTES“CodeSignOnCopy”和“RemoveHeadersOnCopy”。
- 改进了钩子以允许它在多个插件使用这个钩子的情况下工作。
更新2
- 由于我的pull请求已被接受,所以不再需要安装我自己的fork。
- 改进的钩子代码。
更新3(19/09/2016)
根据Max Whaler的build议修改了钩子脚本,因为我在Xcode 8上遇到过同样的问题。
最后的注意
一旦你上传你的应用程序到AppStore,如果由于不支持的体系结构(i386等)而导致validation失败,请尝试下面的Cordova插件(仅钩子,没有本机代码): zcordova-plugin-archtrim
为了让我的插件在XCode 8.0和cordova-ios 4.2上创build项目,我必须在after_build
阶段运行钩子。 另外,请确保节点环境正在使用xcode-node(^ 0.8.9)的最新版本,否则在复制文件阶段会出现错误。
<framework src="lib/myCustom.framework" custom="true" embed="true" /> <hook type="after_build" src="hooks/add_embedded.js" />
对于Cordova来说,plugin.xml需要custom="true"
才能复制框架文件,最终与在after_platform add或after_prepare中运行此挂接时对.pbxproj所做的更改相冲突。
embed="true"
今天发布的cordova-ios 4.4.0和cordova 7.0.0开始支持embed="true"
。 https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework https://issues.apache.org/jira/browse/CB-11233
@Alon Amir,感谢分享,它的作品非常漂亮! 虽然,我的应用程序运行完美,但不是在发布模式。 我发现LD_RUNPATH_SEARCH_PATHS只是作为proj.getBuildProperty添加到debugging模式,而没有构build参数取得第一个结果。 我修改了一下你的代码,以便在Debug和Release模式下工作:
function addRunpathSearchBuildProperty(proj, build) { const LD_RUNPATH_SEARCH_PATHS = proj.getBuildProperty("LD_RUNPATH_SEARCH_PATHS", build); if(!LD_RUNPATH_SEARCH_PATHS) { proj.addBuildProperty("LD_RUNPATH_SEARCH_PATHS", "\"$(inherited) @executable_path/Frameworks\"", build); } else if(LD_RUNPATH_SEARCH_PATHS.indexOf("@executable_path/Frameworks") == -1) { var newValue = LD_RUNPATH_SEARCH_PATHS.substr(0,LD_RUNPATH_SEARCH_PATHS.length-1); newValue += ' @executable_path/Frameworks\"'; proj.updateBuildProperty("LD_RUNPATH_SEARCH_PATHS", newValue, build); } } myProj.parseSync(); addRunpathSearchBuildProperty(myProj, "Debug"); addRunpathSearchBuildProperty(myProj, "Release");
- 在highcharts图表上滚动
- cordova/一般:为不同的屏幕布局开发最简单的方法?
- 有没有办法使用worklight 6.2或phonegap混合应用程序从Android和iOS的另一个应用程序启动一个应用程序?
- 在phonegap 3.3.0本地通知
- 插件与UIViewController
- 如何find返回YES的响应者canPerformAction:withSender:
- 如何在cordova中dynamic加载CSS
- IOS设备可以使用html5video和phonegap / cordova从本地文件系统stream式传输m3u8分段video吗?
- 手机应用程序的jquery移动与phonegap不正确的风格