From cc55f70834f4e7dac10303afaecb1a2d11c1c22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Grillo?= Date: Mon, 20 Sep 2021 18:19:03 +0100 Subject: [PATCH 1/2] Added hooks for Swift Support --- hooks/add_swift_support.js | 222 +++++++++++++++++++++++++++++++++++ hooks/installDependencies.js | 27 +++++ hooks/package.json | 10 ++ hooks/utilities.js | 168 ++++++++++++++++++++++++++ hooks/utils.js | 7 ++ plugin.xml | 11 +- 6 files changed, 444 insertions(+), 1 deletion(-) create mode 100644 hooks/add_swift_support.js create mode 100644 hooks/installDependencies.js create mode 100644 hooks/package.json create mode 100644 hooks/utilities.js create mode 100644 hooks/utils.js diff --git a/hooks/add_swift_support.js b/hooks/add_swift_support.js new file mode 100644 index 0000000..28706b2 --- /dev/null +++ b/hooks/add_swift_support.js @@ -0,0 +1,222 @@ +/* +* 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 5.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 ']; + 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); + } + if (xcodeProject.getBuildProperty('SWIFT_OBJC_INTERFACE_HEADER_NAME', buildConfig.name) !== '"OutSystems-Swift.h"') { + xcodeProject.updateBuildProperty('SWIFT_OBJC_INTERFACE_HEADER_NAME', '"OutSystems-Swift.h"', buildConfig.name); + console.log('Update IOS build setting SWIFT_OBJC_INTERFACE_HEADER_NAME to:', '"OutSystems-Swift.h"', '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') { + console.log(">>> 1"); + if (config.getPreference('UseLegacySwiftLanguageVersion', 'ios')) { + console.log(">>> 2"); + xcodeProject.updateBuildProperty('SWIFT_VERSION', '2.3', buildConfig.name); + console.log('Use legacy Swift language version', buildConfig.name); + } else if (config.getPreference('UseSwiftLanguageVersion', 'ios')) { + console.log(">>> 3"); + const swiftVersion = config.getPreference('UseSwiftLanguageVersion', 'ios'); + xcodeProject.updateBuildProperty('SWIFT_VERSION', swiftVersion, buildConfig.name); + console.log('Use Swift language version', swiftVersion); + } else { + console.log(">>> 4"); + xcodeProject.updateBuildProperty('SWIFT_VERSION', '5.5', buildConfig.name); + console.log('Update SWIFT version to 5.5!', 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); +}; diff --git a/hooks/installDependencies.js b/hooks/installDependencies.js new file mode 100644 index 0000000..d227623 --- /dev/null +++ b/hooks/installDependencies.js @@ -0,0 +1,27 @@ +var utils = require('./utilities'); + +module.exports = function (context) { + var cordovaAbove8 = utils.isCordovaAbove(context, 8); + var child_process; + var deferral; + + if (cordovaAbove8) { + child_process = require('child_process'); + deferral = require('q').defer(); + } else { + child_process = context.requireCordovaModule('child_process'); + deferral = context.requireCordovaModule('q').defer(); + } + + var output = child_process.exec('npm install', {cwd: __dirname}, function (error) { + if (error !== null) { + console.log('exec error: ' + error); + deferral.reject('npm installation failed'); + } + else { + deferral.resolve(); + } + }); + + return deferral.promise; +}; \ No newline at end of file diff --git a/hooks/package.json b/hooks/package.json new file mode 100644 index 0000000..9acef02 --- /dev/null +++ b/hooks/package.json @@ -0,0 +1,10 @@ +{ + "dependencies": { + "adm-zip": "0.4.11", + "child_process": "^1.0.2", + "glob": "^7.1.3", + "q": "^1.5.1", + "semver": "^6.0.0", + "xcode": "^2.0.0" + } +} \ No newline at end of file diff --git a/hooks/utilities.js b/hooks/utilities.js new file mode 100644 index 0000000..e45f4fe --- /dev/null +++ b/hooks/utilities.js @@ -0,0 +1,168 @@ +"use strict" + +var path = require("path"); +var fs = require("fs"); + +var utils = require("./utils"); + +var constants = { + platforms: "platforms", + android: { + platform: "android", + wwwFolder: "assets/www", + firebaseFileExtension: ".json", + soundFileName: "push_sound.wav", + getSoundDestinationFolder: function() { + return "platforms/android/res/raw"; + } + }, + ios: { + platform: "ios", + wwwFolder: "www", + firebaseFileExtension: ".plist", + soundFileName: "push_sound.caf", + getSoundDestinationFolder: function(context) { + return "platforms/ios/" + utils.getAppName(context) + "/Resources"; + } + }, + zipExtension: ".zip", + folderNameSuffix: ".firebase", + folderNamePrefix: "firebase." +}; + +function handleError(errorMessage, defer) { + console.log(errorMessage); + defer.reject(); +} + +function checkIfFolderExists(path) { + return fs.existsSync(path); +} + +function getFilesFromPath(path) { + return fs.readdirSync(path); +} + +function createOrCheckIfFolderExists(path) { + if (!fs.existsSync(path)) { + fs.mkdirSync(path); + } +} + +function getSourceFolderPath(context, wwwPath) { + var sourceFolderPath; + var appId = getAppId(context); + var cordovaAbove7 = isCordovaAbove(context, 7); + + // New way of looking for the configuration files' folder + if (cordovaAbove7) { + sourceFolderPath = path.join(context.opts.projectRoot, "www", appId + constants.folderNameSuffix); + } else { + sourceFolderPath = path.join(wwwPath, appId + constants.folderNameSuffix); + } + + // Fallback to deprecated way of looking for the configuration files' folder + if(!checkIfFolderExists(sourceFolderPath)) { + console.log("Using deprecated way to look for configuration files' folder"); + if (cordovaAbove7) { + sourceFolderPath = path.join(context.opts.projectRoot, "www", constants.folderNamePrefix + appId); + } else { + sourceFolderPath = path.join(wwwPath, constants.folderNamePrefix + appId); + } + } + + return sourceFolderPath; +} + +function getResourcesFolderPath(context, platform, platformConfig) { + var platformPath = path.join(context.opts.projectRoot, constants.platforms, platform); + return path.join(platformPath, platformConfig.wwwFolder); +} + +function getPlatformConfigs(platform) { + if (platform === constants.android.platform) { + return constants.android; + } else if (platform === constants.ios.platform) { + return constants.ios; + } +} + +function getZipFile(folder, zipFileName) { + try { + var files = getFilesFromPath(folder); + for (var i = 0; i < files.length; i++) { + if (files[i].endsWith(constants.zipExtension)) { + var fileName = path.basename(files[i], constants.zipExtension); + if (fileName === zipFileName) { + return path.join(folder, files[i]); + } + } + } + } catch (e) { + console.log(e); + return; + } +} + +function getAppId(context) { + var cordovaAbove8 = isCordovaAbove(context, 8); + var et; + if (cordovaAbove8) { + et = require('elementtree'); + } else { + et = context.requireCordovaModule('elementtree'); + } + + var config_xml = path.join(context.opts.projectRoot, 'config.xml'); + var data = fs.readFileSync(config_xml).toString(); + var etree = et.parse(data); + return etree.getroot().attrib.id; +} + +function isCordovaAbove(context, version) { + var cordovaVersion = context.opts.cordova.version; + console.log(cordovaVersion); + var sp = cordovaVersion.split('.'); + return parseInt(sp[0]) >= version; +} + +function getAndroidTargetSdk() { + var projectPropertiesPath = path.join("platforms", "android", "CordovaLib", "project.properties"); + if (checkIfFolderExists(projectPropertiesPath)) { + var projectProperties = fs.readFileSync(projectPropertiesPath).toString(); + var lookUp = "target=android-"; + var from = projectProperties.indexOf(lookUp) + lookUp.length; + var length = projectProperties.indexOf('\n', from) - from; + var sdk = projectProperties.substr(from, length).trim(); + console.log('getAndroidTargetSdk', sdk); + return parseInt(sdk); + } + + throw new Error('Could not find android target in ' + projectPropertiesPath); +} + +function copyFromSourceToDestPath(defer, sourcePath, destPath) { + fs.createReadStream(sourcePath).pipe(fs.createWriteStream(destPath)) + .on("close", function (err) { + defer.resolve(); + }) + .on("error", function (err) { + console.log(err); + defer.reject(); + }); +} + +module.exports = { + isCordovaAbove, + handleError, + getZipFile, + getResourcesFolderPath, + getPlatformConfigs, + getAppId, + copyFromSourceToDestPath, + getFilesFromPath, + createOrCheckIfFolderExists, + checkIfFolderExists, + getAndroidTargetSdk, + getSourceFolderPath +}; \ No newline at end of file diff --git a/hooks/utils.js b/hooks/utils.js new file mode 100644 index 0000000..8826826 --- /dev/null +++ b/hooks/utils.js @@ -0,0 +1,7 @@ +module.exports = { + getAppName: function (context) { + var ConfigParser = context.requireCordovaModule("cordova-lib").configparser; + var config = new ConfigParser("config.xml"); + return config.name(); + } +}; \ No newline at end of file diff --git a/plugin.xml b/plugin.xml index f5087fa..a892165 100644 --- a/plugin.xml +++ b/plugin.xml @@ -42,8 +42,17 @@ + + + + + + + + + - + $PERFORMANCE_MONITORING_ENABLED From acb059a2b48d4a4e314c9a3c0ed27b6b8dd8ecbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Grillo?= Date: Tue, 21 Sep 2021 09:36:34 +0100 Subject: [PATCH 2/2] Added SwiftVersion preference --- plugin.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin.xml b/plugin.xml index a892165..51d6ae4 100644 --- a/plugin.xml +++ b/plugin.xml @@ -48,7 +48,8 @@ - + +