自定义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); }; 

这个钩子实际上做了什么:

  1. 创build一个以您的插件ID命名的“Build Phase”,configuration为“Copy Files”,该拷贝的目的地是“Frameworks”。
  2. 查找并添加您的.framework文件到上面的Build阶段,然后embedded它。
  3. 设置一个名为LD_RUNPATH_SEARCH_PATHS的Xcode构build属性,以在"@executable_path/Frameworks"查找embedded式框架(这是embedded式框架将被复制到“Copy Files” – >“Frameworks”构build阶段
  4. 通过为.framework文件设置“CodeSignOnCopy”和“RemoveHeadersOnCopy”来configurationATTRIBUTES键。
  5. 从FrameworksBuildPhase中移除你的.framework文件,并将它们作为新的分离的PBXBuildFiles(Same PBXFileReference)重新添加到FrameworksBuildPhase中,如果你打开项目,必须完成CodeSignOnCopy才能表示任何内容与Xcode,你不会在构build阶段find一个复选标记,说它会签名。

更新1:钩码,修改:

  1. 钩子自动find你的.framework文件,不需要编辑钩子。
  2. 添加了一个重要的修改,为你的.framework文件设置ATTRIBUTES“CodeSignOnCopy”和“RemoveHeadersOnCopy”。
  3. 改进了钩子以允许它在多个插件使用这个钩子的情况下工作。

更新2

  1. 由于我的pull请求已被接受,所以不再需要安装我自己的fork。
  2. 改进的钩子代码。

更新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");