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.
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.
The following solution worked using Cordova version 5.3.3.
<framework src="pointToYour/File.framework" embed="true" />
embed="true"
doesn't work for now, but add it anyway.
<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.
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);
};
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 PhaseModified hook script according to Max Whaler's suggestion, as I experienced the same issue over Xcode 8.
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
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:
<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:
<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. 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.
<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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With