在自定义的 Cordova 插件中,如何在 plugin.xml 中配置一个特定的 .framework 文件,以便将其添加到 Xcode 的“嵌入式二进制文件”部分?如果目前无法直接在 plugin.xml 中实现此操作,我愿意接受替代建议。
我已经实现了一种解决方案,直到 Cordova 的 plugin.xml 支持它。希望将来在这样的条目中添加一个 embed
属性将具有相同的效果:<framework embed="true" src="..." />
。但目前,该属性没有帮助,因此需要以下解决方法。
以下方案适用于 Cordova 版本 5.3.3。
<framework src="pointToYour/File.framework" embed="true" />
embed="true"
目前无法使用,但仍需添加。
<hook type="after_platform_add" src="hooks/embedframework/addEmbedded.js" />
接下来,在我们的钩子代码中需要使用一个特定的节点模块,这个模块是node-xcode。
npm i xcode
最后,挂钩本身的代码 -
'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
,也要在 "@executable_path/Frameworks"
中查找嵌入式框架(该嵌入式框架将在“复制文件” -> “Frameworks” Build Phase 后复制到其中)。根据 Max Whaler 的建议修改了钩子脚本,因为我在 Xcode 8 上遇到了相同的问题。
一旦您将应用上传到 AppStore,如果由于不受支持的架构(i386 等)而导致验证失败,请尝试使用以下 Cordova 插件(仅有钩子,没有本机代码):zcordova-plugin-archtrim
npm i xcode
- 可以成功安装xcode模块。 - arjunattam#import
语句与<
和>
(而不是"
)。 - Alon Amir如果要将库添加到Xcode的“嵌入式二进制文件”部分(从cordova-ios 4.4.0和cordova 7.0.0开始),请在你的 plugin.xml 中放置以下代码:
<framework src="src/ios/XXX.framework" embed="true" custom="true" />
如果想要将库添加到Xcode的“链接框架和库”部分,请在您的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>
after_build
阶段运行钩子。此外,请确保node环境使用最新版本的xcode-node (^0.8.9),否则将在拷贝文件阶段中出现错误。custom="true"
,这样Cordova才能复制框架文件,但这会与当钩子在after_platform add或even after_prepare运行时对.pbxproj所做的更改产生冲突。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-11233
@Alon Amir,谢谢分享,它运行得很好!虽然我的应用在Debug模式下运行完美,但在Release模式下却没有。我发现LD_RUNPATH_SEARCH_PATHS仅在Debug模式下添加,因为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");
updateBuildProperty
或者addBuildProperty
没有第三个参数(就像我的钩子一样),它应该已经应用于所有构建设置,这是从node-xcode库中的条件(build
是第三个参数)if ( (build && config.name === build) || (!build) )
。如果你的项目在“Release”中构建失败,请确保嵌入的框架支持“bitcode”,或在你自己的cordova项目中禁用“bitcode”(有插件+钩子可以做到)。 - Alon Amir