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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36650522/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 08:44:30  来源:igfitidea点击:

Custom Cordova Plugin: Add framework to "Embedded Binaries"

iosxcodecordovacordova-plugins

提问by Alon Amir

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 中配置特定的 .framework 文件,以便将其添加到 Xcode 的“嵌入式二进制文件”部分?如果目前无法直接在 plugin.xml 中实现,我愿意接受其他建议。

回答by Alon Amir

I've implemented a workaround until it's supported by Cordova's plugin.xml, hopefully, in the future, once an embedproperty 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:

添加Embedded.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".
  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_PATHSto 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. 创建一个以您的插件 ID 命名的“构建阶段”,配置为“复制文件”,该副本的目标是“框架”。
  2. 查找您的 .framework 文件并将其添加到上述构建阶段,进而嵌入它。
  3. 设置一个名为的 Xcode 构建属性LD_RUNPATH_SEARCH_PATHS,也可以在其中查找嵌入式框架"@executable_path/Frameworks"(在“复制文件”->“框架”构建阶段之后,嵌入式框架将被复制到
  4. 通过为 .framework 文件设置“CodeSignOnCopy”和“RemoveHeadersOnCopy”来配置 ATTRIBUTES 键。
  5. 从 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.
  2. Added an important modification, setting ATTRIBUTES "CodeSignOnCopy" and "RemoveHeadersOnCopy" for your .framework files.
  3. Improved the hook to allow it to work in such a case where multiple plugins use this hook.
  1. 挂钩会自动找到您的 .framework 文件,无需编辑挂钩。
  2. 添加了一个重要的修改,为您的 .framework 文件设置属性“CodeSignOnCopy”和“RemoveHeadersOnCopy”。
  3. 改进了钩子以允许它在多个插件使用此钩子的情况下工作。

Update 2

更新 2

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

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

回答by Ryan R Sundberg

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_buildphase. 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 8.0 和 cordova-ios 4.2 上构建一个项目,我必须在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" />

<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 所做的更改发生冲突。

回答by Joanne

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>

回答by grantpatterson

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

embed="true"从今天发布的cordova-ios 4.4.0和cordova 7.0.0开始支持。 https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework https://issues.apache.org/jira/browse/CB-11233

回答by Max Wahler

@Alon Amir, thanks for sharing, it works beautifully! Although, my app ran perfectly in Debug but not in Release mode. 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. I modified your code a bit so that it works in Debug as well as in Release mode:

@Alon Amir,感谢分享,效果很好!虽然,我的应用程序在 Debug 下完美运行,但在 Release 模式下运行不正常。我发现 LD_RUNPATH_SEARCH_PATHS 仅被添加到调试模式,因为没有构建参数的 proj.getBuildProperty 获取第一个结果。我稍微修改了您的代码,以便它可以在 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");