web-dev-qa-db-ja.com

カスタムCordovaプラグイン:「Embedded Binaries」にフレームワークを追加

カスタムCordovaプラグインで、plugin.xmlの特定の.frameworkファイルをXcodeの「Embedded Binaries」セクションに追加されるように構成するにはどうすればよいですか?現在、plugin.xmlでそれが直接不可能である場合は、別の提案を受け付けています。

14
Alon Amir

Cordovaのplugin.xmlでサポートされるまで、回避策を実装しました。うまくいけば、将来的には、このようなembedプロパティがエントリには同じ効果があります:<framework embed="true" src="..." />、今のところ、このプロパティは役に立たないため、次の回避策があります。

次のソリューションは、Cordovaバージョン5.3.3を使用して機能しました。

まず、必ずplugin.xmlにフレームワークエントリを追加します。

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

embed="true"は今のところ機能しませんが、とにかく追加してください。

フックを作成し、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. プラグインIDにちなんだ名前の「ビルドフェーズ」を作成し、「ファイルのコピー」に設定します。そのコピーの宛先は「フレームワーク」です。
  2. .frameworkファイルを検索して上記のビルドフェーズに追加し、次にそれを埋め込みます。
  3. LD_RUNPATH_SEARCH_PATHSという名前のXcodeビルドプロパティを設定して、"@executable_path/Frameworks"内の埋め込みフレームワークも検索します(埋め込みフレームワークは、「ファイルのコピー」->「フレームワーク」ビルドフェーズの後にコピーされます)
  4. .frameworkファイルに "CodeSignOnCopy"と "RemoveHeadersOnCopy"を設定して、ATTRIBUTESキーを構成します。
  5. FrameworksBuildPhaseから.frameworkファイルを削除し、それらを新しい分離されたPBXBuildFiles(同じPBXFileReference)としてFrameworksBuildPhaseに再度追加します。プロジェクトを開いた場合、 "CodeSignOnCopy"が削除されずに何かを意味するために実行する必要があります。 Xcodeでは、ビルドフェーズで署名することを示すチェックマークはありません。

更新1:フックコード、変更:

  1. フックは自動的に.frameworkファイルを見つけます。フックを編集する必要はありません。
  2. .frameworkファイルにATTRIBUTES "CodeSignOnCopy"および "RemoveHeadersOnCopy"を設定する重要な変更を追加しました。
  3. 複数のプラグインがこのフックを使用するような場合に機能するようにフックを改善しました。

アップデート2

  1. pull request が受け入れられたので、自分のフォークをインストールする必要はありません。
  2. フックコードの改善。

アップデート3(2016年9月19日)

Max Whalerの提案に従ってフックスクリプトを変更しました。Xcode8で同じ問題が発生したためです。

最終メモ

アプリをAppStoreにアップロードしたら、サポートされていないアーキテクチャ(i386など)が原因で検証が失敗した場合、次のCordovaプラグイン(フックのみ、ネイティブコードなし)を試してください。 zcordova-plugin-archtrim =

32
Alon Amir

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" />

Cordovaがフレームワークファイルをコピーするには、plugin.xmlにcustom="true"が必要です。このフックは、このフックがafter_platform addまたはafter_prepareで実行されたときに.pbxprojに加えられた変更と競合します。

7
Ryan R Sundberg

Xcodeの「Embedded Binaries」セクションにライブラリを追加するには(cordova-ios 4.4.0およびcordova 7.0.0から開始)、これをplugin.xmlに入れます。

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

Xcodeの「Linked Frameworks and Libraries」セクションにライブラリーを追加するには、これをplugin.xmlに入れます。

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

それらの両方が同時に存在できます。例えば:

<!-- 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>
6
Joanne

embed="true"は、本日リリースされたcordova-ios 4.4.0およびcordova 7.0.0以降でサポートされています。 https://cordova.Apache.org/docs/en/latest/plugin_ref/spec.html#frameworkhttps://issues.Apache.org/jira/browse/CB- 112

4
grantpatterson

@Alon Amir、共有してくれてありがとう、それは美しく機能します!しかし、私のアプリはデバッグモードでは完全に動作しましたが、リリースモードでは動作しませんでした。 LD_RUNPATH_SEARCH_PATHSがデバッグモードに追加されたのは、ビルドパラメータのないproj.getBuildPropertyが最初の結果を取得するためです。デバッグとリリースモードで機能するようにコードを少し変更しました。

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");
1
Max Wahler