Plugin Cordova personnalisé: ajout d'une structure aux "binarys incorporés"

Dans un plugin Cordova personnalisé, comment puis-je configurer un file .framework spécifique dans plugin.xml afin qu'il soit ajouté à la section "Embedded Binaries" de Xcode? Si ce n'est pas possible directement dans plugin.xml, je suis ouvert à d'autres suggestions.

    J'ai implémenté une solution de contournement jusqu'à ce qu'elle soit prise en charge par le plugin.xml de Cordova dans le futur, une fois qu'une propriété embed dans ces inputs aura le même effet: <framework embed="true" src="..." /> , pour l'instant, cette propriété n'aide pas, d'où la solution suivante.

    La solution suivante a fonctionné en utilisant la version 5.3.3 de Cordova.

    Tout d'abord, assurez-vous d'append l'input framework à plugin.xml:

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

    embed="true" ne fonctionne pas pour l'instant, mais l'ajoute quand même.

    Nous allons créer un hook, déclarons cela dans votre file plugin.xml:

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

    Ensuite, il y a un module de noeud spécifique dont nous aurons besoin dans le code de notre hook, ce module est node-xcode .

    Installez node-xcode (doit être de version 0.8.7 ou supérieure):

     npm i xcode 

    Enfin, le code pour le crochet lui-même –

    Fichier addEmbedded.js:

     'use ssortingct'; 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); }; 

    Qu'est-ce que ce crochet fait réellement:

    1. Crée une "phase de construction" nommée après votre ID de plugin, configurée pour "Copier des files", la destination de cette copy est "Frameworks".
    2. Trouve et ajoute vos files .framework à la phase de construction ci-dessus, à son tour, l'intégrant.
    3. Définit une propriété de construction Xcode nommée LD_RUNPATH_SEARCH_PATHS pour searchr également les frameworks incorporés dans "@executable_path/Frameworks" (C'est à dire que le framework incorporé va être copié après la phase de construction "Copier les files" -> "Frameworks"
    4. Configure la key ATTRIBUTES en définissant "CodeSignOnCopy" et "RemoveHeadersOnCopy" pour vos files .framework.
    5. Supprime vos files .framework de FrameworksBuildPhase et les ajoute de nouveau à FrameworksBuildPhase en tant que nouveaux PBXBuildFiles séparés (Same PBXFileReference), il doit être fait pour que "CodeSignOnCopy" puisse signifier quelque chose, sans le supprimer, si vous ouvrez le projet avec Xcode, vous ne findez pas de coche dans la phase de construction qui dit qu'il va le signer.

    Mise à jour 1: code du crochet, modifications:

    1. Le crochet trouve automatiquement vos files .framework, pas besoin de modifier le crochet.
    2. Ajout d'une modification importante, définissant ATTRIBUTES "CodeSignOnCopy" et "RemoveHeadersOnCopy" pour vos files .framework.
    3. Amélioration du hook pour lui permettre de fonctionner dans un tel cas où plusieurs plugins utilisent ce hook.

    Mise à jour 2

    1. Depuis que ma request de tirage a été acceptée, il n'y a plus besoin d'installer ma propre fourche.
    2. Code de crochet amélioré.

    Mise à jour 3 (19/09/2016)

    Script de crochet modifié selon la suggestion de Max Whaler, car j'ai vécu le même problème sur Xcode 8.

    Note finale

    Une fois votre application téléchargée sur l'AppStore, si la validation échoue en raison d'architectures non supscopes (i386, etc …), essayez le plugin Cordova suivant (uniquement le hook, pas de code natif): zcordova-plugin-archsortingm

    Pour que mon plugin soit construit avec un projet sur XCode 8.0 et cordova-ios 4.2, j'ai dû exécuter le hook dans la phase after_build . Assurez-vous également que l'environnement de noeud utilise la dernière version de xcode-node (^ 0.8.9) ou vous obtiendrez des bogues dans la phase de copy de files.

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

    Le plugin.xml a besoin de custom="true" pour que Cordova copy le file framework, ce qui finit par être en conflit avec les changements apportés au file .pbxproj lorsque ce hook est exécuté dans after_platform add ou after_prepare.

    embed="true" est pris en charge à partir de Cordova-ios 4.4.0 et Cordova 7.0.0, qui a été publié aujourd'hui. https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html#framework https://issues.apache.org/jira/browse/CB-11233

    @Alon Amir, merci pour le partage, ça fonctionne magnifiquement! Bien que, mon application a fonctionné parfaitement dans Debug mais pas en mode Release. J'ai compris que le LD_RUNPATH_SEARCH_PATHS était seulement ajouté au mode de debugging comme proj.getBuildProperty sans un paramètre de construction prend le premier résultat. J'ai légèrement modifié votre code pour qu'il fonctionne en mode Debug ainsi qu'en mode 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");