cordova-plugin-add-swift-support
Version:
Add Swift support to your iOS plugins
215 lines (183 loc) • 9.22 kB
JavaScript
/*
* This hook adds all the needed config to implement a Cordova plugin with Swift.
*
* - It adds a Bridging header importing Cordova/CDV.h if it's not already
* the case. Else it concats all the bridging headers in one single file.
*
* /!\ Please be sure not naming your bridging header file 'Bridging-Header.h'
* else it won't be supported.
*
* - It puts the ios deployment target to 7.0 in case your project would have a
* lesser one.
*
* - It updates the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting to YES.
*
* - It updates the SWIFT_VERSION to 4.0.
*/
const fs = require('fs');
const path = require('path');
const xcode = require('xcode');
const childProcess = require('child_process');
const semver = require('semver');
const glob = require('glob');
module.exports = context => {
const projectRoot = context.opts.projectRoot;
// This script has to be executed depending on the command line arguments, not
// on the hook execution cycle.
if ((context.hook === 'after_platform_add' && context.cmdLine.includes('platform add')) ||
(context.hook === 'after_prepare' && context.cmdLine.includes('prepare')) ||
(context.hook === 'after_plugin_add' && context.cmdLine.includes('plugin add'))) {
getPlatformVersionsFromFileSystem(context, projectRoot).then(platformVersions => {
const IOS_MIN_DEPLOYMENT_TARGET = '7.0';
const platformPath = path.join(projectRoot, 'platforms', 'ios');
const config = getConfigParser(context, path.join(projectRoot, 'config.xml'));
let bridgingHeaderPath;
let bridgingHeaderContent;
let projectName;
let projectPath;
let pluginsPath;
let iosPlatformVersion;
let pbxprojPath;
let xcodeProject;
const COMMENT_KEY = /_comment$/;
let buildConfigs;
let buildConfig;
let configName;
platformVersions.forEach((platformVersion) => {
if (platformVersion.platform === 'ios') {
iosPlatformVersion = platformVersion.version;
}
});
if (!iosPlatformVersion) {
return;
}
projectName = config.name();
projectPath = path.join(platformPath, projectName);
pbxprojPath = path.join(platformPath, projectName + '.xcodeproj', 'project.pbxproj');
xcodeProject = xcode.project(pbxprojPath);
pluginsPath = path.join(projectPath, 'Plugins');
xcodeProject.parseSync();
bridgingHeaderPath = getBridgingHeaderPath(projectPath, iosPlatformVersion);
try {
fs.statSync(bridgingHeaderPath);
} catch (err) {
// If the bridging header doesn't exist, we create it with the minimum
// Cordova/CDV.h import.
bridgingHeaderContent = ['//',
'// Use this file to import your target\'s public headers that you would like to expose to Swift.',
'//',
'#import <Cordova/CDV.h>'];
fs.writeFileSync(bridgingHeaderPath, bridgingHeaderContent.join('\n'), { encoding: 'utf-8', flag: 'w' });
xcodeProject.addHeaderFile('Bridging-Header.h');
}
buildConfigs = xcodeProject.pbxXCBuildConfigurationSection();
const bridgingHeaderProperty = '"$(PROJECT_DIR)/$(PROJECT_NAME)' + bridgingHeaderPath.split(projectPath)[1] + '"';
for (configName in buildConfigs) {
if (!COMMENT_KEY.test(configName)) {
buildConfig = buildConfigs[configName];
if (xcodeProject.getBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', buildConfig.name) !== bridgingHeaderProperty) {
xcodeProject.updateBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', bridgingHeaderProperty, buildConfig.name);
console.log('Update IOS build setting SWIFT_OBJC_BRIDGING_HEADER to:', bridgingHeaderProperty, 'for build configuration', buildConfig.name);
}
}
}
// Look for any bridging header defined in the plugin
glob('**/*Bridging-Header*.h', { cwd: pluginsPath }, (error, files) => {
const bridgingHeader = path.basename(bridgingHeaderPath);
const headers = files.map((filePath) => path.basename(filePath));
// if other bridging headers are found, they are imported in the
// one already configured in the project.
let content = fs.readFileSync(bridgingHeaderPath, 'utf-8');
if (error) throw new Error(error);
headers.forEach((header) => {
if (header !== bridgingHeader && !~content.indexOf(header)) {
if (content.charAt(content.length - 1) !== '\n') {
content += '\n';
}
content += '#import "' + header + '"\n';
console.log('Importing', header, 'into', bridgingHeaderPath);
}
});
fs.writeFileSync(bridgingHeaderPath, content, 'utf-8');
for (configName in buildConfigs) {
if (!COMMENT_KEY.test(configName)) {
buildConfig = buildConfigs[configName];
if (parseFloat(xcodeProject.getBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', buildConfig.name)) < parseFloat(IOS_MIN_DEPLOYMENT_TARGET)) {
xcodeProject.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', IOS_MIN_DEPLOYMENT_TARGET, buildConfig.name);
console.log('Update IOS project deployment target to:', IOS_MIN_DEPLOYMENT_TARGET, 'for build configuration', buildConfig.name);
}
if (xcodeProject.getBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', buildConfig.name) !== 'YES') {
xcodeProject.updateBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES', buildConfig.name);
console.log('Update IOS build setting ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES to: YES', 'for build configuration', buildConfig.name);
}
if (xcodeProject.getBuildProperty('LD_RUNPATH_SEARCH_PATHS', buildConfig.name) !== '"@executable_path/Frameworks"') {
xcodeProject.updateBuildProperty('LD_RUNPATH_SEARCH_PATHS', '"@executable_path/Frameworks"', buildConfig.name);
console.log('Update IOS build setting LD_RUNPATH_SEARCH_PATHS to: @executable_path/Frameworks', 'for build configuration', buildConfig.name);
}
if (typeof xcodeProject.getBuildProperty('SWIFT_VERSION', buildConfig.name) === 'undefined') {
if (config.getPreference('UseLegacySwiftLanguageVersion', 'ios')) {
xcodeProject.updateBuildProperty('SWIFT_VERSION', '2.3', buildConfig.name);
console.log('Use legacy Swift language version', buildConfig.name);
} else if (config.getPreference('UseSwiftLanguageVersion', 'ios')) {
const swiftVersion = config.getPreference('UseSwiftLanguageVersion', 'ios');
xcodeProject.updateBuildProperty('SWIFT_VERSION', swiftVersion, buildConfig.name);
console.log('Use Swift language version', swiftVersion);
} else {
xcodeProject.updateBuildProperty('SWIFT_VERSION', '4.0', buildConfig.name);
console.log('Update SWIFT version to 4.0', buildConfig.name);
}
}
if (buildConfig.name === 'Debug') {
if (xcodeProject.getBuildProperty('SWIFT_OPTIMIZATION_LEVEL', buildConfig.name) !== '"-Onone"') {
xcodeProject.updateBuildProperty('SWIFT_OPTIMIZATION_LEVEL', '"-Onone"', buildConfig.name);
console.log('Update IOS build setting SWIFT_OPTIMIZATION_LEVEL to: -Onone', 'for build configuration', buildConfig.name);
}
}
}
}
fs.writeFileSync(pbxprojPath, xcodeProject.writeSync());
});
});
}
};
const getConfigParser = (context, configPath) => {
let ConfigParser;
if (semver.lt(context.opts.cordova.version, '5.4.0')) {
ConfigParser = context.requireCordovaModule('cordova-lib/src/ConfigParser/ConfigParser');
} else {
ConfigParser = context.requireCordovaModule('cordova-common/src/ConfigParser/ConfigParser');
}
return new ConfigParser(configPath);
};
const getBridgingHeaderPath = (projectPath, iosPlatformVersion) => {
let bridgingHeaderPath;
if (semver.lt(iosPlatformVersion, '4.0.0')) {
bridgingHeaderPath = path.posix.join(projectPath, 'Plugins', 'Bridging-Header.h');
} else {
bridgingHeaderPath = path.posix.join(projectPath, 'Bridging-Header.h');
}
return bridgingHeaderPath;
};
const getPlatformVersionsFromFileSystem = (context, projectRoot) => {
const cordovaUtil = context.requireCordovaModule('cordova-lib/src/cordova/util');
const platformsOnFs = cordovaUtil.listPlatforms(projectRoot);
const platformVersions = platformsOnFs.map(platform => {
const script = path.join(projectRoot, 'platforms', platform, 'cordova', 'version');
return new Promise((resolve, reject) => {
childProcess.exec('"' + script + '"', {}, (error, stdout, _) => {
if (error) {
reject(error);
return;
}
resolve(stdout.trim());
});
}).then(result => {
const version = result.replace(/\r?\n|\r/g, '');
return { platform, version };
}, (error) => {
console.log(error);
process.exit(1);
});
});
return Promise.all(platformVersions);
};