简体   繁体   English

自定义 Cordova 插件:将框架添加到“嵌入式二进制文件”

[英]Custom Cordova Plugin: Add framework to “Embedded Binaries”

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?在自定义 Cordova 插件中,如何在 plugin.xml 中配置特定的 .framework 文件,以便将其添加到 Xcode 的“嵌入式二进制文件”部分? If that's not currently possible directly in plugin.xml, I'm open to alternative suggestions.如果目前无法直接在 plugin.xml 中实现,我愿意接受其他建议。

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 的plugin.xml支持它,希望将来,一旦此类条目中的embed属性将具有相同的效果: <framework embed="true" src="..." /> ,目前,此属性没有帮助,因此有以下解决方法。

The following solution worked using Cordova version 5.3.3.以下解决方案使用 Cordova 5.3.3 版。

First, make sure to add the framework entry to plugin.xml:首先,确保将框架条目添加到 plugin.xml:

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

embed="true" doesn't work for now, but add it anyway. embed="true"现在不起作用,但无论如何添加它。

We're gonna create a hook, declare that in your plugin.xml:我们将创建一个钩子,在你的 plugin.xml 中声明:

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

Next, there's a specific node module we're gonna need in our hook's code, that module is node-xcode .接下来,我们的钩子代码中需要一个特定的节点模块,该模块是node-xcode

Install node-xcode (must be 0.8.7 version or above):安装node-xcode(必须是0.8.7以上版本):

npm i xcode

Finally, the code for the hook itself -最后,钩子本身的代码 -

addEmbedded.js file: 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);
};

What this hook actually does:这个钩子实际上做了什么:

  1. Creates a "Build Phase" named after your plugin id, configured to "Copy Files", destination of that copy is "Frameworks".创建一个以您的插件 ID 命名的“构建阶段”,配置为“复制文件”,该副本的目标是“框架”。
  2. Finds and adds your .framework files to the above Build Phase, in turn, embedding it.查找您的 .framework 文件并将其添加到上述构建阶段,进而嵌入它。
  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设置一个名为LD_RUNPATH_SEARCH_PATHS的 Xcode 构建属性,以在"@executable_path/Frameworks"查找嵌入的框架(即嵌入的框架将在“复制文件”->“框架”构建阶段之后被复制到
  4. Configures the ATTRIBUTES key by setting "CodeSignOnCopy" and "RemoveHeadersOnCopy" for your .framework files.通过为 .framework 文件设置“CodeSignOnCopy”和“RemoveHeadersOnCopy”来配置 ATTRIBUTES 键。
  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.从 FrameworksBuildPhase 中删除您的 .framework 文件,并将它们作为新的分离的 PBXBuildFiles(相同的 PBXFileReference)重新添加到 FrameworksBuildPhase 中,如果您打开项目,则必须执行此操作才能使“CodeSignOnCopy”具有任何含义,而不将其删除使用 Xcode,您不会在构建阶段找到一个复选标记,表示它将对其进行签名。

Updated 1: hook code, modifications:更新 1:钩子代码,修改:

  1. The hook automatically finds your .framework files, no need to edit the hook.挂钩会自动查找您的 .framework 文件,无需编辑挂钩。
  2. Added an important modification, setting ATTRIBUTES "CodeSignOnCopy" and "RemoveHeadersOnCopy" for your .framework files.添加了一个重要的修改,为您的 .framework 文件设置属性“CodeSignOnCopy”和“RemoveHeadersOnCopy”。
  3. Improved the hook to allow it to work in such a case where multiple plugins use this hook.改进了钩子以允许它在多个插件使用此钩子的情况下工作。

Update 2更新 2

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

Update 3 (19/09/2016)更新 3 (19/09/2016)

Modified hook script according to Max Whaler's suggestion, as I experienced the same issue over Xcode 8.根据 Max Whaler 的建议修改了钩子脚本,因为我在 Xcode 8 上遇到了同样的问题。

Final Note最后说明

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

For adding libraries to "Embedded Binaries" section in Xcode (Starting from cordova-ios 4.4.0 and cordova 7.0.0), put this in your plugin.xml:要将库添加到 Xcode 中的“嵌入式二进制文件”部分(从cordova-ios 4.4.0 和cordova 7.0.0 开始),请将其放入您的plugin.xml 中:

<framework src="src/ios/XXX.framework"   embed="true" custom="true" />

For adding libraries to "Linked Frameworks and Libraries" section in Xcode, put this in your plugin.xml:要将库添加到 Xcode 中的“链接框架和库”部分,请将其放入 plugin.xml:

<source-file src="src/ios/XXX.framework" target-dir="lib" framework="true" />

Both of them can exist at the same time.两者可以同时存在。 For example:例如:

<!-- iOS Sample -->
<platform name="ios">
    ....
    <source-file src="src/ios/XXX.m"/>
    <source-file src="src/ios/XXX.framework" target-dir="lib" framework="true" />
    <framework src="src/ios/XXX.framework"   embed="true" custom="true" /> 
    ....  
</platform>


<!-- Android Sample for your reference -->
<platform name="android">
    ....
    <source-file src="src/android/XXX.java"/>
    <framework src="src/android/build.gradle" custom="true" type="gradleReference" />
    <resource-file src="src/android/SDK/libs/XXX.aar" target="libs/XXX.aar" />
    ....  
</platform>

To get my plugin to build with a project on XCode 8.0 and cordova-ios 4.2, I had to run the hook in the after_build phase.为了让我的插件在 XCode 8.0 和 cordova-ios 4.2 上构建项目,我必须在after_build阶段运行钩子。 Also, make sure that the node environment is using the latest verison of xcode-node (^0.8.9) or you will get bugs in the copy files phase.另外,请确保节点环境使用的是最新版本的 xcode-node (^0.8.9),否则您将在复制文件阶段遇到错误。

<framework src="lib/myCustom.framework" custom="true" embed="true" /> <hook type="after_build" src="hooks/add_embedded.js" />

The plugin.xml needs custom="true" for Cordova to copy the framework file, which ended up conflicting with the changes made to the .pbxproj when this hook ran in after_platform add or even after_prepare. plugin.xml 需要custom="true"以便 Cordova 复制框架文件,当此钩子在 after_platform add 甚至 after_prepare 中运行时,最终与对 .pbxproj 所做的更改发生冲突。

embed="true" is supported as of cordova-ios 4.4.0 and cordova 7.0.0, which was released today. 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 https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework https://issues.apache.org/jira/browse/CB-11233

@Alon Amir, thanks for sharing, it works beautifully! @Alon Amir,感谢分享,效果很好! Although, my app ran perfectly in Debug but not in Release mode.虽然,我的应用程序在 Debug 下完美运行,但在 Release 模式下运行不正常。 I figured out that the LD_RUNPATH_SEARCH_PATHS was only added to Debug mode as proj.getBuildProperty without a build parameter takes the first result.我发现 LD_RUNPATH_SEARCH_PATHS 仅被添加到调试模式,因为没有构建参数的 proj.getBuildProperty 获取第一个结果。 I modified your code a bit so that it works in Debug as well as in Release mode:我稍微修改了您的代码,以便它可以在 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");

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM