自定义 Cordova 插件:向“嵌入式二进制文件"添加框架 [英] Custom Cordova Plugin: Add framework to "Embedded Binaries"

查看:28
本文介绍了自定义 Cordova 插件:向“嵌入式二进制文件"添加框架的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在自定义 Cordova 插件中,如何在 plugin.xml 中配置特定的 .framework 文件,以便将其添加到 Xcode 的嵌入式二进制文件"部分?如果目前无法直接在 plugin.xml 中实现,我愿意接受其他建议.

In a custom Cordova plugin, how can I config a specific .framework file in plugin.xml such that it will be added to the "Embedded Binaries" section in Xcode? If that's not currently possible directly in plugin.xml, I'm open to alternative suggestions.

推荐答案

我已经实施了一个变通方法,直到 Cordova 的 plugin.xml 支持它,希望在将来,一旦 embed 属性在此类条目中将具有相同的效果:<framework embed="true" src="..."/>,目前,此属性没有帮助,因此有以下解决方法.

I've implemented a workaround until it's supported by Cordova's plugin.xml, hopefully, in the future, once an embed property in such entries will have the same effect: <framework embed="true" src="..." />, for now, this property does not help, hence the following workaround.

以下解决方案使用 Cordova 5.3.3 版.

The following solution worked using Cordova version 5.3.3.

<framework src="pointToYour/File.framework" embed="true" />

embed="true" 暂时不起作用,但还是要添加.

embed="true" doesn't work for now, but add it anyway.

<hook type="after_platform_add" src="hooks/embedframework/addEmbedded.js" />

接下来,我们需要在钩子的代码中包含一个特定的节点模块,该模块是 节点-xcode.

Next, there's a specific node module we're gonna need in our hook's code, that module is node-xcode.

npm i xcode

最后是钩子本身的代码-

Finally, the code for the hook itself -

'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. 创建一个以您的插件 ID 命名的构建阶段",配置为复制文件",该副本的目标是框架".
  2. 查找您的 .framework 文件并将其添加到上述构建阶段,然后将其嵌入.
  3. 设置一个名为 LD_RUNPATH_SEARCH_PATHS 的 Xcode 构建属性,以在 "@executable_path/Frameworks" 中查找嵌入式框架(这是嵌入式框架将被复制到复制文件"->框架"构建阶段
  4. 通过为您的 .framework 文件设置CodeSignOnCopy"和RemoveHeadersOnCopy"来配置 ATTRIBUTES 键.
  5. 从 FrameworksBuildPhase 中删除您的 .framework 文件并将它们重新添加到 FrameworksBuildPhase 中作为新的分离的 PBXBuildFiles(相同的 PBXFileReference),必须这样做才能使CodeSignOnCopy"具有任何意义,而不删除它,如果您使用 Xcode 打开项目,您将不会在构建阶段找到表示将对其进行签名的复选标记.
  1. Creates a "Build Phase" named after your plugin id, configured to "Copy Files", destination of that copy is "Frameworks".
  2. Finds and adds your .framework files to the above Build Phase, in turn, embedding it.
  3. Sets an Xcode build property named LD_RUNPATH_SEARCH_PATHS to also look for embedded frameworks in "@executable_path/Frameworks" (That's were the embedded framework is going to be copied to after the "Copy Files"->"Frameworks" Build Phase
  4. Configures the ATTRIBUTES key by setting "CodeSignOnCopy" and "RemoveHeadersOnCopy" for your .framework files.
  5. Removes your .framework files from the FrameworksBuildPhase and re-adds them to the FrameworksBuildPhase as new separated PBXBuildFiles (Same PBXFileReference), it has to be done in order for the "CodeSignOnCopy" to mean anything, without removing it, if you open the project with Xcode, you will not find a checkmark in the build phase that says it will sign it.

更新1:钩子代码,修改:

  1. 挂钩会自动查找您的 .framework 文件,无需编辑挂钩.
  2. 添加了一项重要修改,为您的 .framework 文件设置了属性CodeSignOnCopy"和RemoveHeadersOnCopy".
  3. 改进了钩子,使其能够在多个插件使用此钩子的情况下工作.

更新 2

  1. 自从我的拉取请求被接受后,不再需要安装我自己的 fork.
  2. 改进了钩子代码.
  1. Since my pull request has been accepted, there's no longer a need to install my own fork.
  2. Improved hook code.

更新 3 (19/09/2016)

根据 Max Whaler 的建议修改了钩子脚本,因为我在 Xcode 8 上遇到了同样的问题.

Update 3 (19/09/2016)

Modified hook script according to Max Whaler's suggestion, as I experienced the same issue over Xcode 8.

将应用上传到 AppStore 后,如果由于不支持的架构(i386 等...)导致验证失败,请尝试以下 Cordova 插件(仅挂钩,无本机代码):zcordova-plugin-archtrim

Once you upload your app to the AppStore, if validation fails due to unsupported architectures (i386, etc...), try the following Cordova plugin (only hook, no native code): zcordova-plugin-archtrim

这篇关于自定义 Cordova 插件:向“嵌入式二进制文件"添加框架的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆