summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/ios/cordova/lib
diff options
context:
space:
mode:
Diffstat (limited to 'StoneIsland/platforms/ios/cordova/lib')
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/Podfile.js230
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/PodsJson.js115
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/build.js304
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/check_reqs.js96
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/clean.js8
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js14
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/list-emulator-images10
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js375
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/prepare.js1003
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/projectFile.js136
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/run.js146
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/spawn.js1
-rwxr-xr-xStoneIsland/platforms/ios/cordova/lib/versions.js20
13 files changed, 2281 insertions, 177 deletions
diff --git a/StoneIsland/platforms/ios/cordova/lib/Podfile.js b/StoneIsland/platforms/ios/cordova/lib/Podfile.js
new file mode 100755
index 00000000..2cf254cc
--- /dev/null
+++ b/StoneIsland/platforms/ios/cordova/lib/Podfile.js
@@ -0,0 +1,230 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs'),
+ path = require('path'),
+ util = require('util'),
+ events = require('cordova-common').events,
+ Q = require('q'),
+ superspawn = require('cordova-common').superspawn,
+ CordovaError = require('cordova-common').CordovaError;
+
+Podfile.FILENAME = 'Podfile';
+
+function Podfile(podFilePath, projectName) {
+ this.podToken = '##INSERT_POD##';
+
+ this.path = podFilePath;
+ this.projectName = projectName;
+ this.contents = null;
+ this.pods = null;
+ this.__dirty = false;
+
+ // check whether it is named Podfile
+ var filename = this.path.split(path.sep).pop();
+ if (filename !== Podfile.FILENAME) {
+ throw new CordovaError(util.format('Podfile: The file at %s is not `%s`.', this.path, Podfile.FILENAME));
+ }
+
+ if (!projectName) {
+ throw new CordovaError('Podfile: The projectName was not specified in the constructor.');
+ }
+
+ if (!fs.existsSync(this.path)) {
+ events.emit('verbose', util.format('Podfile: The file at %s does not exist.', this.path));
+ events.emit('verbose', 'Creating new Podfile in platforms/ios');
+ this.clear();
+ this.write();
+ } else {
+ events.emit('verbose', 'Podfile found in platforms/ios');
+ // parse for pods
+ this.pods = this.__parseForPods(fs.readFileSync(this.path, 'utf8'));
+ }
+}
+
+Podfile.prototype.__parseForPods = function(text) {
+ // split by \n
+ var arr = text.split('\n');
+
+ // aim is to match (space insignificant around the comma, comma optional):
+ // pod 'Foobar', '1.2'
+ // pod 'Foobar', 'abc 123 1.2'
+ // pod 'PonyDebugger', :configurations => ['Debug', 'Beta']
+ var podRE = new RegExp('pod \'(\\w+)\'\\s*,?\\s*(.*)');
+
+ // only grab lines that don't have the pod spec'
+ return arr.filter(function(line) {
+ var m = podRE.exec(line);
+
+ return (m !== null);
+ })
+ .reduce(function(obj, line){
+ var m = podRE.exec(line);
+
+ if (m !== null) {
+ // strip out any single quotes around the value m[2]
+ var podSpec = m[2].replace(/^\'|\'$/g, '');
+ obj[m[1]] = podSpec; // i.e pod 'Foo', '1.2' ==> { 'Foo' : '1.2'}
+ }
+
+ return obj;
+ }, {});
+};
+
+Podfile.prototype.getTemplate = function() {
+ return util.format(
+ '# DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
+ 'platform :ios, \'8.0\'\n' +
+ 'target \'%s\' do\n' +
+ '\tproject \'%s.xcodeproj\'\n' +
+ '%s\n' +
+ 'end\n',
+ this.projectName, this.projectName, this.podToken);
+};
+
+Podfile.prototype.addSpec = function(name, spec) {
+ name = name || '';
+ spec = spec; // optional
+
+ if (!name.length) { // blank names are not allowed
+ throw new CordovaError('Podfile addSpec: name is not specified.');
+ }
+
+ this.pods[name] = spec;
+ this.__dirty = true;
+
+ events.emit('verbose', util.format('Added pod line for `%s`', name));
+};
+
+Podfile.prototype.removeSpec = function(name) {
+ if (this.existsSpec(name)) {
+ delete this.pods[name];
+ this.__dirty = true;
+ }
+
+ events.emit('verbose', util.format('Removed pod line for `%s`', name));
+};
+
+Podfile.prototype.getSpec = function(name) {
+ return this.pods[name];
+};
+
+Podfile.prototype.existsSpec = function(name) {
+ return (name in this.pods);
+};
+
+Podfile.prototype.clear = function() {
+ this.pods = {};
+ this.__dirty = true;
+};
+
+Podfile.prototype.destroy = function() {
+ fs.unlinkSync(this.path);
+ events.emit('verbose', util.format('Deleted `%s`', this.path));
+};
+
+Podfile.prototype.write = function() {
+ var text = this.getTemplate();
+ var self = this;
+
+ var podsString =
+ Object.keys(this.pods).map(function(key) {
+ var name = key;
+ var spec = self.pods[key];
+
+ if (spec.length) {
+ if (spec.indexOf(':') === 0) {
+ // don't quote it, it's a specification (starts with ':')
+ return util.format('\tpod \'%s\', %s', name, spec);
+ } else {
+ // quote it, it's a version
+ return util.format('\tpod \'%s\', \'%s\'', name, spec);
+ }
+ } else {
+ return util.format('\tpod \'%s\'', name);
+ }
+ })
+ .join('\n');
+
+ text = text.replace(this.podToken, podsString);
+ fs.writeFileSync(this.path, text, 'utf8');
+ this.__dirty = false;
+
+ events.emit('verbose', 'Wrote to Podfile.');
+};
+
+Podfile.prototype.isDirty = function() {
+ return this.__dirty;
+};
+
+Podfile.prototype.before_install = function() {
+ // Template tokens in order: project name, project name, debug | release
+ var template =
+ '// DO NOT MODIFY -- auto-generated by Apache Cordova\n' +
+ '#include "Pods/Target Support Files/Pods-%s/Pods-%s.%s.xcconfig"';
+
+ var debugContents = util.format(template, this.projectName, this.projectName, 'debug');
+ var releaseContents = util.format(template, this.projectName, this.projectName, 'release');
+
+ var debugConfigPath = path.join(this.path, '..', 'pods-debug.xcconfig');
+ var releaseConfigPath = path.join(this.path, '..', 'pods-release.xcconfig');
+
+ fs.writeFileSync(debugConfigPath, debugContents, 'utf8');
+ fs.writeFileSync(releaseConfigPath, releaseContents, 'utf8');
+
+ return Q.resolve();
+};
+
+Podfile.prototype.install = function(requirementsCheckerFunction) {
+ var opts = {};
+ opts.cwd = path.join(this.path, '..'); // parent path of this Podfile
+ opts.stdio = 'pipe';
+ var first = true;
+ var self = this;
+
+ if (!requirementsCheckerFunction) {
+ requirementsCheckerFunction = Q();
+ }
+
+ return requirementsCheckerFunction()
+ .then(function() {
+ return self.before_install();
+ })
+ .then(function() {
+ return superspawn.spawn('pod', ['install', '--verbose'], opts)
+ .progress(function (stdio){
+ if (stdio.stderr) { console.error(stdio.stderr); }
+ if (stdio.stdout) {
+ if (first) {
+ events.emit('verbose', '==== pod install start ====\n');
+ first = false;
+ }
+ events.emit('verbose', stdio.stdout);
+ }
+ });
+ })
+ .then(function() { // done
+ events.emit('verbose', '==== pod install end ====\n');
+ })
+ .fail(function(error){
+ throw error;
+ });
+};
+
+module.exports.Podfile = Podfile; \ No newline at end of file
diff --git a/StoneIsland/platforms/ios/cordova/lib/PodsJson.js b/StoneIsland/platforms/ios/cordova/lib/PodsJson.js
new file mode 100755
index 00000000..b13a1afe
--- /dev/null
+++ b/StoneIsland/platforms/ios/cordova/lib/PodsJson.js
@@ -0,0 +1,115 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs'),
+ path = require('path'),
+ util = require('util'),
+ events = require('cordova-common').events,
+ CordovaError = require('cordova-common').CordovaError;
+
+PodsJson.FILENAME = 'pods.json';
+
+function PodsJson(podsJsonPath) {
+ this.path = podsJsonPath;
+ this.contents = null;
+ this.__dirty = false;
+
+ var filename = this.path.split(path.sep).pop();
+ if (filename !== PodsJson.FILENAME) {
+ throw new CordovaError(util.format('PodsJson: The file at %s is not `%s`.', this.path, PodsJson.FILENAME));
+ }
+
+ if (!fs.existsSync(this.path)) {
+ events.emit('verbose', util.format('pods.json: The file at %s does not exist.', this.path));
+ events.emit('verbose', 'Creating new pods.json in platforms/ios');
+ this.clear();
+ this.write();
+ } else {
+ events.emit('verbose', 'pods.json found in platforms/ios');
+ // load contents
+ this.contents = fs.readFileSync(this.path, 'utf8');
+ this.contents = JSON.parse(this.contents);
+ }
+}
+
+PodsJson.prototype.get = function(name) {
+ return this.contents[name];
+};
+
+PodsJson.prototype.remove = function(name) {
+ if (this.contents[name]) {
+ delete this.contents[name];
+ this.__dirty = true;
+ events.emit('verbose', util.format('Remove from pods.json for `%s`', name));
+ }
+};
+
+PodsJson.prototype.clear = function() {
+ this.contents = {};
+ this.__dirty = true;
+};
+
+PodsJson.prototype.destroy = function() {
+ fs.unlinkSync(this.path);
+ events.emit('verbose', util.format('Deleted `%s`', this.path));
+};
+
+PodsJson.prototype.write = function() {
+ if (this.contents) {
+ fs.writeFileSync(this.path, JSON.stringify(this.contents, null, 4));
+ this.__dirty = false;
+ events.emit('verbose', 'Wrote to pods.json.');
+ }
+};
+
+PodsJson.prototype.set = function(name, type, spec, count) {
+ this.setJson(name, { name: name, type: type, spec: spec, count: count });
+};
+
+PodsJson.prototype.increment = function(name) {
+ var val = this.get(name);
+ if (val) {
+ val.count++;
+ this.setJson(val);
+ }
+};
+
+PodsJson.prototype.decrement = function(name) {
+ var val = this.get(name);
+ if (val) {
+ val.count--;
+ if (val.count <= 0) {
+ this.remove(name);
+ } else {
+ this.setJson(val);
+ }
+ }
+};
+
+PodsJson.prototype.setJson = function(name, json) {
+ this.contents[name] = json;
+ this.__dirty = true;
+ events.emit('verbose', util.format('Set pods.json for `%s`', name));
+};
+
+PodsJson.prototype.isDirty = function() {
+ return this.__dirty;
+};
+
+module.exports.PodsJson = PodsJson;
diff --git a/StoneIsland/platforms/ios/cordova/lib/build.js b/StoneIsland/platforms/ios/cordova/lib/build.js
index 2213ef8c..a26f1983 100755
--- a/StoneIsland/platforms/ios/cordova/lib/build.js
+++ b/StoneIsland/platforms/ios/cordova/lib/build.js
@@ -6,9 +6,9 @@
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -20,98 +20,170 @@
/*jshint node: true*/
var Q = require('q'),
- nopt = require('nopt'),
path = require('path'),
shell = require('shelljs'),
spawn = require('./spawn'),
- check_reqs = require('./check_reqs'),
- fs = require('fs');
+ fs = require('fs'),
+ plist = require('plist'),
+ util = require('util');
+
+var check_reqs;
+try {
+ check_reqs = require('./check_reqs');
+} catch (err) {
+ // For unit tests, check_reqs.js is not a sibling to build.js
+ check_reqs = require('../../../../lib/check_reqs');
+}
+
+var events = require('cordova-common').events;
var projectPath = path.join(__dirname, '..', '..');
var projectName = null;
-module.exports.run = function (argv) {
+// These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs
+var buildFlagMatchers = {
+ 'xcconfig' : /^\-xcconfig\s*(.*)$/,
+ 'workspace' : /^\-workspace\s*(.*)/,
+ 'scheme' : /^\-scheme\s*(.*)/,
+ 'configuration' : /^\-configuration\s*(.*)/,
+ 'sdk' : /^\-sdk\s*(.*)/,
+ 'destination' : /^\-destination\s*(.*)/,
+ 'archivePath' : /^\-archivePath\s*(.*)/,
+ 'configuration_build_dir' : /^(CONFIGURATION_BUILD_DIR=.*)/,
+ 'shared_precomps_dir' : /^(SHARED_PRECOMPS_DIR=.*)/
+};
- var args = nopt({
- // "archs": String, // TODO: add support for building different archs
- 'debug': Boolean,
- 'release': Boolean,
- 'device': Boolean,
- 'emulator': Boolean,
- 'codeSignIdentity': String,
- 'codeSignResourceRules': String,
- 'provisioningProfile': String,
- 'buildConfig' : String
- }, {'-r': '--release'}, argv);
+module.exports.run = function (buildOpts) {
- if (args.debug && args.release) {
- return Q.reject('Only one of "debug"/"release" options should be specified');
+ buildOpts = buildOpts || {};
+
+ if (buildOpts.debug && buildOpts.release) {
+ return Q.reject('Cannot specify "debug" and "release" options together.');
}
- if (args.device && args.emulator) {
- return Q.reject('Only one of "device"/"emulator" options should be specified');
+ if (buildOpts.device && buildOpts.emulator) {
+ return Q.reject('Cannot specify "device" and "emulator" options together.');
}
- if(args.buildConfig) {
- if(!fs.existsSync(args.buildConfig)) {
- return Q.reject('Build config file does not exist:' + args.buildConfig);
+ if(buildOpts.buildConfig) {
+ if(!fs.existsSync(buildOpts.buildConfig)) {
+ return Q.reject('Build config file does not exist:' + buildOpts.buildConfig);
}
- console.log('Reading build config file:', path.resolve(args.buildConfig));
- var buildConfig = JSON.parse(fs.readFileSync(args.buildConfig, 'utf-8'));
+ events.emit('log','Reading build config file:', path.resolve(buildOpts.buildConfig));
+ var contents = fs.readFileSync(buildOpts.buildConfig, 'utf-8');
+ var buildConfig = JSON.parse(contents.replace(/^\ufeff/, '')); // Remove BOM
if(buildConfig.ios) {
- var buildType = args.release ? 'release' : 'debug';
+ var buildType = buildOpts.release ? 'release' : 'debug';
var config = buildConfig.ios[buildType];
if(config) {
- ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile'].forEach(
+ ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType'].forEach(
function(key) {
- args[key] = args[key] || config[key];
+ buildOpts[key] = buildOpts[key] || config[key];
});
}
}
}
-
+
return check_reqs.run().then(function () {
return findXCodeProjectIn(projectPath);
}).then(function (name) {
projectName = name;
var extraConfig = '';
- if (args.codeSignIdentity) {
- extraConfig += 'CODE_SIGN_IDENTITY = ' + args.codeSignIdentity + '\n';
- extraConfig += 'CODE_SIGN_IDENTITY[sdk=iphoneos*] = ' + args.codeSignIdentity + '\n';
+ if (buildOpts.codeSignIdentity) {
+ extraConfig += 'CODE_SIGN_IDENTITY = ' + buildOpts.codeSignIdentity + '\n';
+ extraConfig += 'CODE_SIGN_IDENTITY[sdk=iphoneos*] = ' + buildOpts.codeSignIdentity + '\n';
}
- if (args.codeSignResourceRules) {
- extraConfig += 'CODE_SIGN_RESOURCE_RULES_PATH = ' + args.codeSignResourceRules + '\n';
+ if (buildOpts.codeSignResourceRules) {
+ extraConfig += 'CODE_SIGN_RESOURCE_RULES_PATH = ' + buildOpts.codeSignResourceRules + '\n';
}
- if (args.provisioningProfile) {
- extraConfig += 'PROVISIONING_PROFILE = ' + args.provisioningProfile + '\n';
+ if (buildOpts.provisioningProfile) {
+ extraConfig += 'PROVISIONING_PROFILE = ' + buildOpts.provisioningProfile + '\n';
+ }
+ if (buildOpts.developmentTeam) {
+ extraConfig += 'DEVELOPMENT_TEAM = ' + buildOpts.developmentTeam + '\n';
}
return Q.nfcall(fs.writeFile, path.join(__dirname, '..', 'build-extras.xcconfig'), extraConfig, 'utf-8');
}).then(function () {
- var configuration = args.release ? 'Release' : 'Debug';
+ var configuration = buildOpts.release ? 'Release' : 'Debug';
+
+ events.emit('log','Building project: ' + path.join(projectPath, projectName + '.xcworkspace'));
+ events.emit('log','\tConfiguration: ' + configuration);
+ events.emit('log','\tPlatform: ' + (buildOpts.device ? 'device' : 'emulator'));
+
+ var buildOutputDir = path.join(projectPath, 'build', 'device');
- console.log('Building project : ' + path.join(projectPath, projectName + '.xcodeproj'));
- console.log('\tConfiguration : ' + configuration);
- console.log('\tPlatform : ' + (args.device ? 'device' : 'emulator'));
+ // remove the build/device folder before building
+ return spawn('rm', [ '-rf', buildOutputDir ], projectPath)
+ .then(function() {
+ var xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag);
+ return spawn('xcodebuild', xcodebuildArgs, projectPath);
+ });
- var xcodebuildArgs = getXcodeArgs(projectName, projectPath, configuration, args.device);
- return spawn('xcodebuild', xcodebuildArgs, projectPath);
}).then(function () {
- if (!args.device) {
+ if (!buildOpts.device || buildOpts.noSign) {
return;
}
+
+ var exportOptions = {'compileBitcode': false, 'method': 'development'};
+
+ if (buildOpts.packageType) {
+ exportOptions.method = buildOpts.packageType;
+ }
+
+ if (buildOpts.developmentTeam) {
+ exportOptions.teamID = buildOpts.developmentTeam;
+ }
+
+ var exportOptionsPlist = plist.build(exportOptions);
+ var exportOptionsPath = path.join(projectPath, 'exportOptions.plist');
+
var buildOutputDir = path.join(projectPath, 'build', 'device');
- var pathToApp = path.join(buildOutputDir, projectName + '.app');
- var pathToIpa = path.join(buildOutputDir, projectName + '.ipa');
- var xcRunArgs = ['-sdk', 'iphoneos', 'PackageApplication',
- '-v', pathToApp,
- '-o', pathToIpa];
- if (args.codeSignIdentity) {
- xcRunArgs.concat('--sign', args.codeSignIdentity);
+
+
+ function checkSystemRuby() {
+ var ruby_cmd = shell.which('ruby');
+
+ if (ruby_cmd != '/usr/bin/ruby') {
+ events.emit('warn', 'Non-system Ruby in use. This may cause packaging to fail.\n' +
+ 'If you use RVM, please run `rvm use system`.\n' +
+ 'If you use chruby, please run `chruby system`.');
+ }
+ }
+
+ function packageArchive() {
+ var xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath);
+ return spawn('xcodebuild', xcodearchiveArgs, projectPath);
+ }
+
+ function unpackIPA() {
+ var ipafile = path.join(buildOutputDir, projectName + '.ipa');
+
+ // unpack the existing platform/ios/build/device/appname.ipa (zipfile), will create a Payload folder
+ return spawn('unzip', [ '-o', '-qq', ipafile ], buildOutputDir);
}
- if (args.provisioningProfile) {
- xcRunArgs.concat('--embed', args.provisioningProfile);
+
+ function moveApp() {
+ var appFileInflated = path.join(buildOutputDir, 'Payload', projectName + '.app');
+ var appFile = path.join(buildOutputDir, projectName + '.app');
+ var payloadFolder = path.join(buildOutputDir, 'Payload');
+
+ // delete the existing platform/ios/build/device/appname.app
+ return spawn('rm', [ '-rf', appFile ], buildOutputDir)
+ .then(function() {
+ // move the platform/ios/build/device/Payload/appname.app to parent
+ return spawn('mv', [ '-f', appFileInflated, buildOutputDir ], buildOutputDir);
+ })
+ .then(function() {
+ // delete the platform/ios/build/device/Payload folder
+ return spawn('rm', [ '-rf', payloadFolder ], buildOutputDir);
+ });
}
- return spawn('xcrun', xcRunArgs, projectPath);
+
+ return Q.nfcall(fs.writeFile, exportOptionsPath, exportOptionsPlist, 'utf-8')
+ .then(checkSystemRuby)
+ .then(packageArchive)
+ .then(unpackIPA)
+ .then(moveApp);
});
};
@@ -125,12 +197,12 @@ function findXCodeProjectIn(projectPath) {
var xcodeProjFiles = shell.ls(projectPath).filter(function (name) {
return path.extname(name) === '.xcodeproj';
});
-
+
if (xcodeProjFiles.length === 0) {
return Q.reject('No Xcode project found in ' + projectPath);
}
if (xcodeProjFiles.length > 1) {
- console.warn('Found multiple .xcodeproj directories in \n' +
+ events.emit('warn','Found multiple .xcodeproj directories in \n' +
projectPath + '\nUsing first one');
}
@@ -148,38 +220,111 @@ module.exports.findXCodeProjectIn = findXCodeProjectIn;
* @param {Boolean} isDevice Flag that specify target for package (device/emulator)
* @return {Array} Array of arguments that could be passed directly to spawn method
*/
-function getXcodeArgs(projectName, projectPath, configuration, isDevice) {
+function getXcodeBuildArgs(projectName, projectPath, configuration, isDevice, buildFlags) {
var xcodebuildArgs;
+ var options;
+ var buildActions;
+ var settings;
+ var customArgs = {};
+ customArgs.otherFlags = [];
+
+ if (buildFlags) {
+ if (typeof buildFlags === 'string' || buildFlags instanceof String) {
+ parseBuildFlag(buildFlags, customArgs);
+ } else { // buildFlags is an Array of strings
+ buildFlags.forEach( function(flag) {
+ parseBuildFlag(flag, customArgs);
+ });
+ }
+ }
+
if (isDevice) {
- xcodebuildArgs = [
- '-xcconfig', path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'),
- '-project', projectName + '.xcodeproj',
- 'ARCHS=armv7 armv7s arm64',
- '-target', projectName,
- '-configuration', configuration,
- '-sdk', 'iphoneos',
- 'build',
- 'VALID_ARCHS=armv7 armv7s arm64',
- 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'device'),
- 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch')
+ options = [
+ '-xcconfig', customArgs.xcconfig || path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'),
+ '-workspace', customArgs.workspace || projectName + '.xcworkspace',
+ '-scheme', customArgs.scheme || projectName,
+ '-configuration', customArgs.configuration || configuration,
+ '-destination', customArgs.destination || 'generic/platform=iOS',
+ '-archivePath', customArgs.archivePath || projectName + '.xcarchive'
+ ];
+ buildActions = [ 'archive' ];
+ settings = [
+ customArgs.configuration_build_dir || 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'device'),
+ customArgs.shared_precomps_dir || 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch')
];
+ // Add other matched flags to otherFlags to let xcodebuild present an appropriate error.
+ // This is preferable to just ignoring the flags that the user has passed in.
+ if (customArgs.sdk) {
+ customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]);
+ }
} else { // emulator
- xcodebuildArgs = [
- '-xcconfig', path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'),
- '-project', projectName + '.xcodeproj',
- 'ARCHS=i386',
- '-target', projectName ,
- '-configuration', configuration,
- '-sdk', 'iphonesimulator',
- 'build',
- 'VALID_ARCHS=i386',
- 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'emulator'),
- 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch')
+ options = [
+ '-xcconfig', customArgs.xcconfig || path.join(__dirname, '..', 'build-' + configuration.toLowerCase() + '.xcconfig'),
+ '-workspace', customArgs.project || projectName + '.xcworkspace',
+ '-scheme', customArgs.scheme || projectName,
+ '-configuration', customArgs.configuration || configuration,
+ '-sdk', customArgs.sdk || 'iphonesimulator',
+ '-destination', customArgs.destination || 'platform=iOS Simulator,name=iPhone 5s'
+ ];
+ buildActions = [ 'build' ];
+ settings = [
+ customArgs.configuration_build_dir || 'CONFIGURATION_BUILD_DIR=' + path.join(projectPath, 'build', 'emulator'),
+ customArgs.shared_precomps_dir || 'SHARED_PRECOMPS_DIR=' + path.join(projectPath, 'build', 'sharedpch')
];
+ // Add other matched flags to otherFlags to let xcodebuild present an appropriate error.
+ // This is preferable to just ignoring the flags that the user has passed in.
+ if (customArgs.archivePath) {
+ customArgs.otherFlags = customArgs.otherFlags.concat(['-archivePath', customArgs.archivePath]);
+ }
}
+ xcodebuildArgs = options.concat(buildActions).concat(settings).concat(customArgs.otherFlags);
return xcodebuildArgs;
}
+
+/**
+ * Returns array of arguments for xcodebuild
+ * @param {String} projectName Name of xcode project
+ * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild
+ * @param {String} outputPath Output directory to contain the IPA
+ * @param {String} exportOptionsPath Path to the exportOptions.plist file
+ * @return {Array} Array of arguments that could be passed directly to spawn method
+ */
+function getXcodeArchiveArgs(projectName, projectPath, outputPath, exportOptionsPath) {
+ return [
+ '-exportArchive',
+ '-archivePath', projectName + '.xcarchive',
+ '-exportOptionsPlist', exportOptionsPath,
+ '-exportPath', outputPath
+ ];
+}
+
+function parseBuildFlag(buildFlag, args) {
+ var matched;
+ for (var key in buildFlagMatchers) {
+ var found = buildFlag.match(buildFlagMatchers[key]);
+ if (found) {
+ matched = true;
+ // found[0] is the whole match, found[1] is the first match in parentheses.
+ args[key] = found[1];
+ events.emit('warn', util.format('Overriding xcodebuildArg: %s', buildFlag));
+ }
+ }
+
+ if (!matched) {
+ // If the flag starts with a '-' then it is an xcodebuild built-in option or a
+ // user-defined setting. The regex makes sure that we don't split a user-defined
+ // setting that is wrapped in quotes.
+ if (buildFlag[0] === '-' && !buildFlag.match(/^.*=(\".*\")|(\'.*\')$/)) {
+ args.otherFlags = args.otherFlags.concat(buildFlag.split(' '));
+ events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag.split(' ')));
+ } else {
+ args.otherFlags.push(buildFlag);
+ events.emit('warn', util.format('Adding xcodebuildArg: %s', buildFlag));
+ }
+ }
+}
+
// help/usage function
module.exports.help = function help() {
console.log('');
@@ -198,6 +343,7 @@ module.exports.help = function help() {
console.log(' --codeSignIdentity : Type of signing identity used for code signing.');
console.log(' --codeSignResourceRules : Path to ResourceRules.plist.');
console.log(' --provisioningProfile : UUID of the profile.');
+ console.log(' --device --noSign : Builds project without application signing.');
console.log('');
console.log('examples:');
console.log(' build ');
diff --git a/StoneIsland/platforms/ios/cordova/lib/check_reqs.js b/StoneIsland/platforms/ios/cordova/lib/check_reqs.js
index d1f6333c..ae21f989 100755
--- a/StoneIsland/platforms/ios/cordova/lib/check_reqs.js
+++ b/StoneIsland/platforms/ios/cordova/lib/check_reqs.js
@@ -19,22 +19,28 @@
var Q = require('q'),
shell = require('shelljs'),
+ util = require('util'),
versions = require('./versions');
-var XCODEBUILD_MIN_VERSION = '4.6.0';
+var XCODEBUILD_MIN_VERSION = '7.0.0';
var XCODEBUILD_NOT_FOUND_MESSAGE =
'Please install version ' + XCODEBUILD_MIN_VERSION + ' or greater from App Store';
-var IOS_SIM_MIN_VERSION = '3.0.0';
-var IOS_SIM_NOT_FOUND_MESSAGE =
- 'Please download, build and install version ' + IOS_SIM_MIN_VERSION + ' or greater' +
- ' from https://github.com/phonegap/ios-sim into your path, or do \'npm install -g ios-sim\'';
-
-var IOS_DEPLOY_MIN_VERSION = '1.4.0';
+var IOS_DEPLOY_MIN_VERSION = '1.9.0';
var IOS_DEPLOY_NOT_FOUND_MESSAGE =
'Please download, build and install version ' + IOS_DEPLOY_MIN_VERSION + ' or greater' +
' from https://github.com/phonegap/ios-deploy into your path, or do \'npm install -g ios-deploy\'';
+var COCOAPODS_MIN_VERSION = '1.0.1';
+var COCOAPODS_NOT_FOUND_MESSAGE =
+ 'Please install version ' + COCOAPODS_MIN_VERSION + ' or greater from https://cocoapods.org/';
+var COCOAPODS_NOT_SYNCED_MESSAGE =
+ 'The CocoaPods repo has not been synced yet, this will take a long time (approximately 500MB as of Sept 2016). Please run `pod setup` first to sync the repo.';
+var COCOAPODS_SYNCED_MIN_SIZE = 475; // in megabytes
+var COCOAPODS_SYNC_ERROR_MESSAGE =
+ 'The CocoaPods repo has been created, but there appears to be a sync error. The repo size should be at least ' + COCOAPODS_SYNCED_MIN_SIZE + '. Please run `pod setup --verbose` to sync the repo.';
+var COCOAPODS_REPO_NOT_FOUND_MESSAGE = 'The CocoaPods repo at ~/.cocoapods was not found.';
+
/**
* Checks if xcode util is available
* @return {Promise} Returns a promise either resolved with xcode version or rejected
@@ -51,14 +57,6 @@ module.exports.check_ios_deploy = function () {
return checkTool('ios-deploy', IOS_DEPLOY_MIN_VERSION, IOS_DEPLOY_NOT_FOUND_MESSAGE);
};
-/**
- * Checks if ios-sim util is available
- * @return {Promise} Returns a promise either resolved with ios-sim version or rejected
- */
-module.exports.check_ios_sim = function () {
- return checkTool('ios-sim', IOS_SIM_MIN_VERSION, IOS_SIM_NOT_FOUND_MESSAGE);
-};
-
module.exports.check_os = function () {
// Build iOS apps available for OSX platform only, so we reject on others platforms
return process.platform === 'darwin' ?
@@ -66,29 +64,81 @@ module.exports.check_os = function () {
Q.reject('Cordova tooling for iOS requires Apple OS X');
};
-module.exports.help = function () {
- console.log('Usage: check_reqs or node check_reqs');
+function check_cocoapod_tool() {
+ return checkTool('pod', COCOAPODS_MIN_VERSION, COCOAPODS_NOT_FOUND_MESSAGE, 'CocoaPods');
+}
+
+/**
+ * Checks if cocoapods repo size is what is expected
+ * @return {Promise} Returns a promise either resolved or rejected
+ */
+module.exports.check_cocoapods_repo_size = function () {
+ return check_cocoapod_tool()
+ .then(function() {
+ // check size of ~/.cocoapods repo
+ var commandString = util.format('du -sh %s/.cocoapods', process.env.HOME);
+ var command = shell.exec(commandString, { silent:true });
+ if (command.code !== 0) { // error, perhaps not found
+ return Q.reject(util.format('%s (%s)', COCOAPODS_REPO_NOT_FOUND_MESSAGE, command.output));
+ } else { // success, parse output
+ // command.output is e.g "750M path/to/.cocoapods", we just scan the number
+ return Q.resolve(parseFloat(command.output));
+ }
+ })
+ .then(function(repoSize) {
+ if (COCOAPODS_SYNCED_MIN_SIZE > repoSize) {
+ return Q.reject(COCOAPODS_SYNC_ERROR_MESSAGE);
+ } else {
+ return Q.resolve();
+ }
+ });
+};
+
+/**
+ * Checks if cocoapods is available, and whether the repo is synced (because it takes a long time to download)
+ * @return {Promise} Returns a promise either resolved or rejected
+ */
+module.exports.check_cocoapods = function () {
+ return check_cocoapod_tool()
+ // check whether the cocoapods repo has been synced through `pod repo` command
+ // a value of '0 repos' means it hasn't been synced
+ .then(function() {
+ var code = shell.exec('pod repo | grep -e "^0 repos"', { silent:true }).code;
+ return Q.resolve(code !== 0); // non-zero means it is synced (has 1 repo at least)
+ })
+ .then(function(repoIsSynced) {
+ if (repoIsSynced) {
+ // return check_cocoapods_repo_size();
+ // we could check the repo size above, but it takes too long.
+ return Q.resolve();
+ } else {
+ return Q.reject(COCOAPODS_NOT_SYNCED_MESSAGE);
+ }
+ });
};
/**
* Checks if specific tool is available.
- * @param {String} tool Tool name to check. Known tools are 'xcodebuild', 'ios-sim' and 'ios-deploy'
+ * @param {String} tool Tool name to check. Known tools are 'xcodebuild' and 'ios-deploy'
* @param {Number} minVersion Min allowed tool version.
* @param {String} message Message that will be used to reject promise.
+ * @param {String} toolFriendlyName Friendly name of the tool, to report to the user. Optional.
* @return {Promise} Returns a promise either resolved with tool version or rejected
*/
-function checkTool (tool, minVersion, message) {
+function checkTool (tool, minVersion, message, toolFriendlyName) {
+ toolFriendlyName = toolFriendlyName || tool;
+
// Check whether tool command is available at all
var tool_command = shell.which(tool);
if (!tool_command) {
- return Q.reject(tool + ' was not found. ' + (message || ''));
+ return Q.reject(toolFriendlyName + ' was not found. ' + (message || ''));
}
// check if tool version is greater than specified one
return versions.get_tool_version(tool).then(function (version) {
version = version.trim();
return versions.compareVersions(version, minVersion) >= 0 ?
Q.resolve(version) :
- Q.reject('Cordova needs ' + tool + ' version ' + minVersion +
+ Q.reject('Cordova needs ' + toolFriendlyName + ' version ' + minVersion +
' or greater, you have version ' + version + '. ' + (message || ''));
});
}
@@ -120,7 +170,7 @@ module.exports.check_all = function() {
new Requirement('os', 'Apple OS X', true),
new Requirement('xcode', 'Xcode'),
new Requirement('ios-deploy', 'ios-deploy'),
- new Requirement('ios-sim', 'ios-sim')
+ new Requirement('CocoaPods', 'CocoaPods')
];
var result = [];
@@ -130,7 +180,7 @@ module.exports.check_all = function() {
module.exports.check_os,
module.exports.check_xcodebuild,
module.exports.check_ios_deploy,
- module.exports.check_ios_sim
+ module.exports.check_cocoapods
];
// Then execute requirement checks one-by-one
diff --git a/StoneIsland/platforms/ios/cordova/lib/clean.js b/StoneIsland/platforms/ios/cordova/lib/clean.js
index 6d955cf1..7c8cf56e 100755
--- a/StoneIsland/platforms/ios/cordova/lib/clean.js
+++ b/StoneIsland/platforms/ios/cordova/lib/clean.js
@@ -22,8 +22,7 @@
var Q = require('q'),
path = require('path'),
shell = require('shelljs'),
- spawn = require('./spawn'),
- check_reqs = require('./check_reqs');
+ spawn = require('./spawn');
var projectPath = path.join(__dirname, '..', '..');
@@ -36,9 +35,8 @@ module.exports.run = function() {
return Q.reject('No Xcode project found in ' + projectPath);
}
- return check_reqs.run().then(function() {
- return spawn('xcodebuild', ['-project', projectName, '-configuration', 'Debug', '-alltargets', 'clean'], projectPath);
- }).then(function () {
+ return spawn('xcodebuild', ['-project', projectName, '-configuration', 'Debug', '-alltargets', 'clean'], projectPath)
+ .then(function () {
return spawn('xcodebuild', ['-project', projectName, '-configuration', 'Release', '-alltargets', 'clean'], projectPath);
}).then(function () {
return shell.rm('-rf', path.join(projectPath, 'build'));
diff --git a/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js b/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js
index 7a81b93e..7caa200f 100755
--- a/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js
+++ b/StoneIsland/platforms/ios/cordova/lib/copy-www-build-step.js
@@ -32,7 +32,6 @@ var BUILT_PRODUCTS_DIR = process.env.BUILT_PRODUCTS_DIR,
var path = require('path'),
fs = require('fs'),
shell = require('shelljs'),
- glob = require('glob'),
srcDir = 'www',
dstDir = path.join(BUILT_PRODUCTS_DIR, FULL_PRODUCT_NAME),
dstWwwDir = path.join(dstDir, 'www');
@@ -46,7 +45,7 @@ try {
fs.statSync(srcDir);
} catch (e) {
console.error('Path does not exist: ' + srcDir);
- process.exit(1);
+ process.exit(2);
}
// Code signing files must be removed or else there are
@@ -57,11 +56,16 @@ shell.rm('-rf', path.join(dstDir, 'PkgInfo'));
shell.rm('-rf', path.join(dstDir, 'embedded.mobileprovision'));
// Copy www dir recursively
+var code;
if(!!COPY_HIDDEN) {
- shell.mkdir('-p', dstWwwDir);
- shell.cp('-r', glob.sync(srcDir + '/**', { dot: true }), dstWwwDir);
+ code = shell.exec('rsync -Lra "' + srcDir + '" "' + dstDir + '"').code;
} else {
- shell.cp('-r', srcDir, dstDir);
+ code = shell.exec('rsync -Lra --exclude="- .*" "' + srcDir + '" "' + dstDir + '"').code;
+}
+
+if(code !== 0) {
+ console.error('Error occured on copying www. Code: ' + code);
+ process.exit(3);
}
// Copy the config.xml file.
diff --git a/StoneIsland/platforms/ios/cordova/lib/list-emulator-images b/StoneIsland/platforms/ios/cordova/lib/list-emulator-images
index 07dd1a48..87a5ad27 100755
--- a/StoneIsland/platforms/ios/cordova/lib/list-emulator-images
+++ b/StoneIsland/platforms/ios/cordova/lib/list-emulator-images
@@ -22,6 +22,7 @@
/*jshint node: true*/
var Q = require('q'),
+ iossim = require('ios-sim'),
exec = require('child_process').exec,
check_reqs = require('./check_reqs');
@@ -30,15 +31,10 @@ var Q = require('q'),
* @return {Promise} Promise fulfilled with list of devices available for simulation
*/
function listEmulatorImages () {
- return check_reqs.check_ios_sim().then(function () {
- return Q.nfcall(exec, 'ios-sim showdevicetypes 2>&1 | ' +
- 'sed "s/com.apple.CoreSimulator.SimDeviceType.//g"');
- }).then(function (stdio) {
- // Exec promise resolves with array [stout, stderr], and we need stdout only
- return stdio[0].trim().split('\n');
- });
+ return Q.resolve(iossim.getdevicetypes());
}
+
exports.run = listEmulatorImages;
// Check if module is started as separate script.
diff --git a/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js b/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js
new file mode 100755
index 00000000..297e3863
--- /dev/null
+++ b/StoneIsland/platforms/ios/cordova/lib/plugman/pluginHandlers.js
@@ -0,0 +1,375 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var events = require('cordova-common').events;
+var CordovaError = require('cordova-common').CordovaError;
+
+// These frameworks are required by cordova-ios by default. We should never add/remove them.
+var keep_these_frameworks = [
+ 'MobileCoreServices.framework',
+ 'CoreGraphics.framework',
+ 'AssetsLibrary.framework'
+];
+
+var handlers = {
+ 'source-file':{
+ install:function(obj, plugin, project, options) {
+ installHelper('source-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
+ },
+ uninstall:function(obj, plugin, project, options) {
+ uninstallHelper('source-file', obj, project.projectDir, plugin.id, options, project);
+ }
+ },
+ 'header-file':{
+ install:function(obj, plugin, project, options) {
+ installHelper('header-file', obj, plugin.dir, project.projectDir, plugin.id, options, project);
+ },
+ uninstall:function(obj, plugin, project, options) {
+ uninstallHelper('header-file', obj, project.projectDir, plugin.id, options, project);
+ }
+ },
+ 'resource-file':{
+ install:function(obj, plugin, project, options) {
+ var src = obj.src,
+ srcFile = path.resolve(plugin.dir, src),
+ destFile = path.resolve(project.resources_dir, path.basename(src));
+ if (!fs.existsSync(srcFile)) throw new CordovaError('Cannot find resource file "' + srcFile + '" for plugin ' + plugin.id + ' in iOS platform');
+ if (fs.existsSync(destFile)) throw new CordovaError('File already exists at detination "' + destFile + '" for resource file specified by plugin ' + plugin.id + ' in iOS platform');
+ project.xcode.addResourceFile(path.join('Resources', path.basename(src)));
+ var link = !!(options && options.link);
+ copyFile(plugin.dir, src, project.projectDir, destFile, link);
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var src = obj.src,
+ destFile = path.resolve(project.resources_dir, path.basename(src));
+ project.xcode.removeResourceFile(path.join('Resources', path.basename(src)));
+ shell.rm('-rf', destFile);
+ }
+ },
+ 'framework':{ // CB-5238 custom frameworks only
+ install:function(obj, plugin, project, options) {
+ var src = obj.src,
+ custom = obj.custom;
+ if (!custom) {
+ var keepFrameworks = keep_these_frameworks;
+
+ if (keepFrameworks.indexOf(src) < 0) {
+ if (obj.type === 'podspec') {
+ //podspec handled in Api.js
+ } else {
+ project.frameworks[src] = project.frameworks[src] || 0;
+ project.frameworks[src]++;
+ project.xcode.addFramework(src, {weak: obj.weak});
+ }
+ }
+ return;
+ }
+ var srcFile = path.resolve(plugin.dir, src),
+ targetDir = path.resolve(project.plugins_dir, plugin.id, path.basename(src));
+ if (!fs.existsSync(srcFile)) throw new CordovaError('Cannot find framework "' + srcFile + '" for plugin ' + plugin.id + ' in iOS platform');
+ if (fs.existsSync(targetDir)) throw new CordovaError('Framework "' + targetDir + '" for plugin ' + plugin.id + ' already exists in iOS platform');
+ var link = !!(options && options.link);
+ copyFile(plugin.dir, src, project.projectDir, targetDir, link); // frameworks are directories
+ // CB-10773 translate back slashes to forward on win32
+ var project_relative = fixPathSep(path.relative(project.projectDir, targetDir));
+ var pbxFile = project.xcode.addFramework(project_relative, {customFramework: true});
+ if (pbxFile) {
+ project.xcode.addToPbxEmbedFrameworksBuildPhase(pbxFile);
+ }
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var src = obj.src;
+
+ if (!obj.custom) { //CB-9825 cocoapod integration for plugins
+ var keepFrameworks = keep_these_frameworks;
+ if (keepFrameworks.indexOf(src) < 0) {
+ if (obj.type === 'podspec') {
+ var podsJSON = require(path.join(project.projectDir, 'pods.json'));
+ if(podsJSON[src]) {
+ if(podsJSON[src].count > 1) {
+ podsJSON[src].count = podsJSON[src].count - 1;
+ } else {
+ delete podsJSON[src];
+ }
+ }
+ } else {
+ //this should be refactored
+ project.frameworks[src] = project.frameworks[src] || 1;
+ project.frameworks[src]--;
+ if (project.frameworks[src] < 1) {
+ // Only remove non-custom framework from xcode project
+ // if there is no references remains
+ project.xcode.removeFramework(src);
+ delete project.frameworks[src];
+ }
+ }
+ }
+ return;
+ }
+
+ var targetDir = fixPathSep(path.resolve(project.plugins_dir, plugin.id, path.basename(src))),
+ pbxFile = project.xcode.removeFramework(targetDir, {customFramework: true});
+ if (pbxFile) {
+ project.xcode.removeFromPbxEmbedFrameworksBuildPhase(pbxFile);
+ }
+ shell.rm('-rf', targetDir);
+ }
+ },
+ 'lib-file': {
+ install:function(obj, plugin, project, options) {
+ events.emit('verbose', '<lib-file> install is not supported for iOS plugins');
+ },
+ uninstall:function(obj, plugin, project, options) {
+ events.emit('verbose', '<lib-file> uninstall is not supported for iOS plugins');
+ }
+ },
+ 'asset':{
+ install:function(obj, plugin, project, options) {
+ if (!obj.src) {
+ throw new CordovaError(generateAttributeError('src', 'asset', plugin.id));
+ }
+ if (!obj.target) {
+ throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+ }
+
+ copyFile(plugin.dir, obj.src, project.www, obj.target);
+ if (options && options.usePlatformWww) copyFile(plugin.dir, obj.src, project.platformWww, obj.target);
+ },
+ uninstall:function(obj, plugin, project, options) {
+ var target = obj.target;
+
+ if (!target) {
+ throw new CordovaError(generateAttributeError('target', 'asset', plugin.id));
+ }
+
+ removeFile(project.www, target);
+ removeFileF(path.resolve(project.www, 'plugins', plugin.id));
+ if (options && options.usePlatformWww) {
+ removeFile(project.platformWww, target);
+ removeFileF(path.resolve(project.platformWww, 'plugins', plugin.id));
+ }
+ }
+ },
+ 'js-module': {
+ install: function (obj, plugin, project, options) {
+ // Copy the plugin's files into the www directory.
+ var moduleSource = path.resolve(plugin.dir, obj.src);
+ var moduleName = plugin.id + '.' + (obj.name || path.basename(obj.src, path.extname (obj.src)));
+
+ // Read in the file, prepend the cordova.define, and write it back out.
+ var scriptContent = fs.readFileSync(moduleSource, 'utf-8').replace(/^\ufeff/, ''); // Window BOM
+ if (moduleSource.match(/.*\.json$/)) {
+ scriptContent = 'module.exports = ' + scriptContent;
+ }
+ scriptContent = 'cordova.define("' + moduleName + '", function(require, exports, module) {\n' + scriptContent + '\n});\n';
+
+ var moduleDestination = path.resolve(project.www, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(moduleDestination));
+ fs.writeFileSync(moduleDestination, scriptContent, 'utf-8');
+ if (options && options.usePlatformWww) {
+ var platformWwwDestination = path.resolve(project.platformWww, 'plugins', plugin.id, obj.src);
+ shell.mkdir('-p', path.dirname(platformWwwDestination));
+ fs.writeFileSync(platformWwwDestination, scriptContent, 'utf-8');
+ }
+ },
+ uninstall: function (obj, plugin, project, options) {
+ var pluginRelativePath = path.join('plugins', plugin.id, obj.src);
+ removeFileAndParents(project.www, pluginRelativePath);
+ if (options && options.usePlatformWww) removeFileAndParents(project.platformWww, pluginRelativePath);
+ }
+ }
+};
+
+module.exports.getInstaller = function (type) {
+ if (handlers[type] && handlers[type].install) {
+ return handlers[type].install;
+ }
+
+ events.emit('warn', '<' + type + '> is not supported for iOS plugins');
+};
+
+module.exports.getUninstaller = function(type) {
+ if (handlers[type] && handlers[type].uninstall) {
+ return handlers[type].uninstall;
+ }
+
+ events.emit('warn', '<' + type + '> is not supported for iOS plugins');
+};
+
+function installHelper(type, obj, plugin_dir, project_dir, plugin_id, options, project) {
+ var srcFile = path.resolve(plugin_dir, obj.src);
+ var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || '');
+ var destFile = path.join(targetDir, path.basename(obj.src));
+
+ var project_ref;
+ var link = !!(options && options.link);
+ if (link) {
+ var trueSrc = fs.realpathSync(srcFile);
+ // Create a symlink in the expected place, so that uninstall can use it.
+ if (options && options.force) {
+ copyFile(plugin_dir, trueSrc, project_dir, destFile, link);
+ } else {
+ copyNewFile(plugin_dir, trueSrc, project_dir, destFile, link);
+ }
+ // Xcode won't save changes to a file if there is a symlink involved.
+ // Make the Xcode reference the file directly.
+ // Note: Can't use path.join() here since it collapses 'Plugins/..', and xcode
+ // library special-cases Plugins/ prefix.
+ project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc));
+ } else {
+ if (options && options.force) {
+ copyFile(plugin_dir, srcFile, project_dir, destFile, link);
+ } else {
+ copyNewFile(plugin_dir, srcFile, project_dir, destFile, link);
+ }
+ project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile));
+ }
+
+ if (type == 'header-file') {
+ project.xcode.addHeaderFile(project_ref);
+ } else if (obj.framework) {
+ var opt = { weak: obj.weak };
+ var project_relative = path.join(path.basename(project.xcode_path), project_ref);
+ project.xcode.addFramework(project_relative, opt);
+ project.xcode.addToLibrarySearchPaths({path:project_ref});
+ } else {
+ project.xcode.addSourceFile(project_ref, obj.compilerFlags ? {compilerFlags:obj.compilerFlags} : {});
+ }
+}
+
+function uninstallHelper(type, obj, project_dir, plugin_id, options, project) {
+ var targetDir = path.resolve(project.plugins_dir, plugin_id, obj.targetDir || '');
+ var destFile = path.join(targetDir, path.basename(obj.src));
+
+ var project_ref;
+ var link = !!(options && options.link);
+ if (link) {
+ var trueSrc = fs.readlinkSync(destFile);
+ project_ref = 'Plugins/' + fixPathSep(path.relative(fs.realpathSync(project.plugins_dir), trueSrc));
+ } else {
+ project_ref = 'Plugins/' + fixPathSep(path.relative(project.plugins_dir, destFile));
+ }
+
+ shell.rm('-rf', targetDir);
+
+ if (type == 'header-file') {
+ project.xcode.removeHeaderFile(project_ref);
+ } else if (obj.framework) {
+ var project_relative = path.join(path.basename(project.xcode_path), project_ref);
+ project.xcode.removeFramework(project_relative);
+ project.xcode.removeFromLibrarySearchPaths({path:project_ref});
+ } else {
+ project.xcode.removeSourceFile(project_ref);
+ }
+}
+
+var pathSepFix = new RegExp(path.sep.replace(/\\/,'\\\\'),'g');
+function fixPathSep(file) {
+ return file.replace(pathSepFix, '/');
+}
+
+function copyFile (plugin_dir, src, project_dir, dest, link) {
+ src = path.resolve(plugin_dir, src);
+ if (!fs.existsSync(src)) throw new CordovaError('"' + src + '" not found!');
+
+ // check that src path is inside plugin directory
+ var real_path = fs.realpathSync(src);
+ var real_plugin_path = fs.realpathSync(plugin_dir);
+ if (real_path.indexOf(real_plugin_path) !== 0)
+ throw new CordovaError('File "' + src + '" is located outside the plugin directory "' + plugin_dir + '"');
+
+ dest = path.resolve(project_dir, dest);
+
+ // check that dest path is located in project directory
+ if (dest.indexOf(project_dir) !== 0)
+ throw new CordovaError('Destination "' + dest + '" for source file "' + src + '" is located outside the project');
+
+ shell.mkdir('-p', path.dirname(dest));
+
+ if (link) {
+ symlinkFileOrDirTree(src, dest);
+ } else if (fs.statSync(src).isDirectory()) {
+ // XXX shelljs decides to create a directory when -R|-r is used which sucks. http://goo.gl/nbsjq
+ shell.cp('-Rf', path.join(src, '/*'), dest);
+ } else {
+ shell.cp('-f', src, dest);
+ }
+}
+
+// Same as copy file but throws error if target exists
+function copyNewFile (plugin_dir, src, project_dir, dest, link) {
+ var target_path = path.resolve(project_dir, dest);
+ if (fs.existsSync(target_path))
+ throw new CordovaError('"' + target_path + '" already exists!');
+
+ copyFile(plugin_dir, src, project_dir, dest, !!link);
+}
+
+function symlinkFileOrDirTree(src, dest) {
+ if (fs.existsSync(dest)) {
+ shell.rm('-Rf', dest);
+ }
+
+ if (fs.statSync(src).isDirectory()) {
+ shell.mkdir('-p', dest);
+ fs.readdirSync(src).forEach(function(entry) {
+ symlinkFileOrDirTree(path.join(src, entry), path.join(dest, entry));
+ });
+ }
+ else {
+ fs.symlinkSync(path.relative(fs.realpathSync(path.dirname(dest)), src), dest);
+ }
+}
+
+// checks if file exists and then deletes. Error if doesn't exist
+function removeFile (project_dir, src) {
+ var file = path.resolve(project_dir, src);
+ shell.rm('-Rf', file);
+}
+
+// deletes file/directory without checking
+function removeFileF (file) {
+ shell.rm('-Rf', file);
+}
+
+function removeFileAndParents (baseDir, destFile, stopper) {
+ stopper = stopper || '.';
+ var file = path.resolve(baseDir, destFile);
+ if (!fs.existsSync(file)) return;
+
+ removeFileF(file);
+
+ // check if directory is empty
+ var curDir = path.dirname(file);
+
+ while(curDir !== path.resolve(baseDir, stopper)) {
+ if(fs.existsSync(curDir) && fs.readdirSync(curDir).length === 0) {
+ fs.rmdirSync(curDir);
+ curDir = path.resolve(curDir, '..');
+ } else {
+ // directory not empty...do nothing
+ break;
+ }
+ }
+}
+
+function generateAttributeError(attribute, element, id) {
+ return 'Required attribute "' + attribute + '" not specified in <' + element + '> element from plugin: ' + id;
+}
diff --git a/StoneIsland/platforms/ios/cordova/lib/prepare.js b/StoneIsland/platforms/ios/cordova/lib/prepare.js
new file mode 100755
index 00000000..8d1cda94
--- /dev/null
+++ b/StoneIsland/platforms/ios/cordova/lib/prepare.js
@@ -0,0 +1,1003 @@
+/**
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+var Q = require('q');
+var fs = require('fs');
+var path = require('path');
+var shell = require('shelljs');
+var xcode = require('xcode');
+var unorm = require('unorm');
+var plist = require('plist');
+var URL = require('url');
+var events = require('cordova-common').events;
+var xmlHelpers = require('cordova-common').xmlHelpers;
+var ConfigParser = require('cordova-common').ConfigParser;
+var CordovaError = require('cordova-common').CordovaError;
+var PlatformJson = require('cordova-common').PlatformJson;
+var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
+var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
+var FileUpdater = require('cordova-common').FileUpdater;
+
+/*jshint sub:true*/
+
+module.exports.prepare = function (cordovaProject, options) {
+ var self = this;
+
+ var platformJson = PlatformJson.load(this.locations.root, 'ios');
+ var munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider());
+
+ this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations);
+
+ // Update own www dir with project's www assets and plugins' assets and js-files
+ return Q.when(updateWww(cordovaProject, this.locations))
+ .then(function () {
+ // update project according to config.xml changes.
+ return updateProject(self._config, self.locations);
+ })
+ .then(function () {
+ updateIcons(cordovaProject, self.locations);
+ updateSplashScreens(cordovaProject, self.locations);
+ updateLaunchStoryboardImages(cordovaProject, self.locations);
+ })
+ .then(function () {
+ events.emit('verbose', 'Prepared iOS project successfully');
+ });
+};
+
+module.exports.clean = function (options) {
+ // A cordovaProject isn't passed into the clean() function, because it might have
+ // been called from the platform shell script rather than the CLI. Check for the
+ // noPrepare option passed in by the non-CLI clean script. If that's present, or if
+ // there's no config.xml found at the project root, then don't clean prepared files.
+ var projectRoot = path.resolve(this.root, '../..');
+ var projectConfigFile = path.join(projectRoot, 'config.xml');
+ if ((options && options.noPrepare) || !fs.existsSync(projectConfigFile) ||
+ !fs.existsSync(this.locations.configXml)) {
+ return Q();
+ }
+
+ var projectConfig = new ConfigParser(this.locations.configXml);
+
+ var self = this;
+ return Q().then(function () {
+ cleanWww(projectRoot, self.locations);
+ cleanIcons(projectRoot, projectConfig, self.locations);
+ cleanSplashScreens(projectRoot, projectConfig, self.locations);
+ cleanLaunchStoryboardImages(projectRoot, projectConfig, self.locations);
+ });
+};
+
+/**
+ * Updates config files in project based on app's config.xml and config munge,
+ * generated by plugins.
+ *
+ * @param {ConfigParser} sourceConfig A project's configuration that will
+ * be merged into platform's config.xml
+ * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
+ * for this platform.
+ * @param {Object} locations A map of locations for this platform
+ *
+ * @return {ConfigParser} An instance of ConfigParser, that
+ * represents current project's configuration. When returned, the
+ * configuration is already dumped to appropriate config.xml file.
+ */
+function updateConfigFile(sourceConfig, configMunger, locations) {
+ events.emit('verbose', 'Generating platform-specific config.xml from defaults for iOS at ' + locations.configXml);
+
+ // First cleanup current config and merge project's one into own
+ // Overwrite platform config.xml with defaults.xml.
+ shell.cp('-f', locations.defaultConfigXml, locations.configXml);
+
+ // Then apply config changes from global munge to all config files
+ // in project (including project's config)
+ configMunger.reapply_global_munge().save_all();
+
+ events.emit('verbose', 'Merging project\'s config.xml into platform-specific iOS config.xml');
+ // Merge changes from app's config.xml into platform's one
+ var config = new ConfigParser(locations.configXml);
+ xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
+ config.doc.getroot(), 'ios', /*clobber=*/true);
+
+ config.write();
+ return config;
+}
+
+/**
+ * Logs all file operations via the verbose event stream, indented.
+ */
+function logFileOp(message) {
+ events.emit('verbose', ' ' + message);
+}
+
+/**
+ * Updates platform 'www' directory by replacing it with contents of
+ * 'platform_www' and app www. Also copies project's overrides' folder into
+ * the platform 'www' folder
+ *
+ * @param {Object} cordovaProject An object which describes cordova project.
+ * @param {boolean} destinations An object that contains destinations
+ * paths for www files.
+ */
+function updateWww(cordovaProject, destinations) {
+ var sourceDirs = [
+ path.relative(cordovaProject.root, cordovaProject.locations.www),
+ path.relative(cordovaProject.root, destinations.platformWww)
+ ];
+
+ // If project contains 'merges' for our platform, use them as another overrides
+ var merges_path = path.join(cordovaProject.root, 'merges', 'ios');
+ if (fs.existsSync(merges_path)) {
+ events.emit('verbose', 'Found "merges/ios" folder. Copying its contents into the iOS project.');
+ sourceDirs.push(path.join('merges', 'ios'));
+ }
+
+ var targetDir = path.relative(cordovaProject.root, destinations.www);
+ events.emit(
+ 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
+ FileUpdater.mergeAndUpdateDir(
+ sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+/**
+ * Cleans all files from the platform 'www' directory.
+ */
+function cleanWww(projectRoot, locations) {
+ var targetDir = path.relative(projectRoot, locations.www);
+ events.emit('verbose', 'Cleaning ' + targetDir);
+
+ // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
+ FileUpdater.mergeAndUpdateDir(
+ [], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
+}
+
+/**
+ * Updates project structure and AndroidManifest according to project's configuration.
+ *
+ * @param {ConfigParser} platformConfig A project's configuration that will
+ * be used to update project
+ * @param {Object} locations A map of locations for this platform (In/Out)
+ */
+function updateProject(platformConfig, locations) {
+
+ // CB-6992 it is necessary to normalize characters
+ // because node and shell scripts handles unicode symbols differently
+ // We need to normalize the name to NFD form since iOS uses NFD unicode form
+ var name = unorm.nfd(platformConfig.name());
+ var pkg = platformConfig.getAttribute('ios-CFBundleIdentifier') || platformConfig.packageName();
+ var version = platformConfig.version();
+
+ var originalName = path.basename(locations.xcodeCordovaProj);
+
+ // Update package id (bundle id)
+ var plistFile = path.join(locations.xcodeCordovaProj, originalName + '-Info.plist');
+ var infoPlist = plist.parse(fs.readFileSync(plistFile, 'utf8'));
+ infoPlist['CFBundleIdentifier'] = pkg;
+
+ // Update version (bundle version)
+ infoPlist['CFBundleShortVersionString'] = version;
+ var CFBundleVersion = platformConfig.getAttribute('ios-CFBundleVersion') || default_CFBundleVersion(version);
+ infoPlist['CFBundleVersion'] = CFBundleVersion;
+
+ if (platformConfig.getAttribute('defaultlocale')) {
+ infoPlist['CFBundleDevelopmentRegion'] = platformConfig.getAttribute('defaultlocale');
+ }
+
+ // replace Info.plist ATS entries according to <access> and <allow-navigation> config.xml entries
+ var ats = writeATSEntries(platformConfig);
+ if (Object.keys(ats).length > 0) {
+ infoPlist['NSAppTransportSecurity'] = ats;
+ } else {
+ delete infoPlist['NSAppTransportSecurity'];
+ }
+
+ handleOrientationSettings(platformConfig, infoPlist);
+ updateProjectPlistForLaunchStoryboard(platformConfig, infoPlist);
+
+ var info_contents = plist.build(infoPlist);
+ info_contents = info_contents.replace(/<string>[\s\r\n]*<\/string>/g,'<string></string>');
+ fs.writeFileSync(plistFile, info_contents, 'utf-8');
+ events.emit('verbose', 'Wrote out iOS Bundle Identifier "' + pkg + '" and iOS Bundle Version "' + version + '" to ' + plistFile);
+
+ return handleBuildSettings(platformConfig, locations).then(function() {
+ if (name == originalName) {
+ events.emit('verbose', 'iOS Product Name has not changed (still "' + originalName + '")');
+ return Q();
+ } else { // CB-11712 <name> was changed, we don't support it'
+ var errorString =
+ 'The product name change (<name> tag) in config.xml is not supported dynamically.\n' +
+ 'To change your product name, you have to remove, then add your ios platform again.\n' +
+ 'Make sure you save your plugins beforehand using `cordova plugin save`.\n' +
+ '\tcordova plugin save\n' +
+ '\tcordova platform rm ios\n' +
+ '\tcordova platform add ios\n'
+ ;
+
+ return Q.reject(new CordovaError(errorString));
+ }
+ });
+}
+
+function handleOrientationSettings(platformConfig, infoPlist) {
+
+ switch (getOrientationValue(platformConfig)) {
+ case 'portrait':
+ infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
+ infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
+ infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
+ break;
+ case 'landscape':
+ infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationLandscapeLeft' ];
+ infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ break;
+ case 'all':
+ infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
+ infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ break;
+ case 'default':
+ infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
+ delete infoPlist['UIInterfaceOrientation'];
+ }
+}
+
+function handleBuildSettings(platformConfig, locations) {
+ var targetDevice = parseTargetDevicePreference(platformConfig.getPreference('target-device', 'ios'));
+ var deploymentTarget = platformConfig.getPreference('deployment-target', 'ios');
+
+ // no build settings provided, we don't need to parse and update .pbxproj file
+ if (!targetDevice && !deploymentTarget) {
+ return Q();
+ }
+
+ var proj = new xcode.project(locations.pbxproj);
+
+ try {
+ proj.parseSync();
+ } catch (err) {
+ return Q.reject(new CordovaError('Could not parse project.pbxproj: ' + err));
+ }
+
+ if (targetDevice) {
+ events.emit('verbose', 'Set TARGETED_DEVICE_FAMILY to ' + targetDevice + '.');
+ proj.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice);
+ }
+
+ if (deploymentTarget) {
+ events.emit('verbose', 'Set IPHONEOS_DEPLOYMENT_TARGET to "' + deploymentTarget + '".');
+ proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget);
+ }
+
+ fs.writeFileSync(locations.pbxproj, proj.writeSync(), 'utf-8');
+
+ return Q();
+}
+
+function mapIconResources(icons, iconsDir) {
+ // See https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html
+ // for launch images sizes reference.
+ var platformIcons = [
+ {dest: 'icon-60@2x.png', width: 120, height: 120},
+ {dest: 'icon-60@3x.png', width: 180, height: 180},
+ {dest: 'icon-76.png', width: 76, height: 76},
+ {dest: 'icon-76@2x.png', width: 152, height: 152},
+ {dest: 'icon-small.png', width: 29, height: 29},
+ {dest: 'icon-small@2x.png', width: 58, height: 58},
+ {dest: 'icon-40.png', width: 40, height: 40},
+ {dest: 'icon-40@2x.png', width: 80, height: 80},
+ {dest: 'icon-small@3x.png', width: 87, height: 87},
+ {dest: 'icon.png', width: 57, height: 57},
+ {dest: 'icon@2x.png', width: 114, height: 114},
+ {dest: 'icon-72.png', width: 72, height: 72},
+ {dest: 'icon-72@2x.png', width: 144, height: 144},
+ {dest: 'icon-50.png', width: 50, height: 50},
+ {dest: 'icon-50@2x.png', width: 100, height: 100},
+ {dest: 'icon-83.5@2x.png', width: 167, height: 167}
+ ];
+
+ var pathMap = {};
+ platformIcons.forEach(function (item) {
+ var icon = icons.getBySize(item.width, item.height) || icons.getDefault();
+ if (icon) {
+ var target = path.join(iconsDir, item.dest);
+ pathMap[target] = icon.src;
+ }
+ });
+ return pathMap;
+}
+
+function getIconsDir(projectRoot, platformProjDir) {
+ var iconsDir;
+ var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
+
+ if (xcassetsExists) {
+ iconsDir = path.join(platformProjDir, 'Images.xcassets/AppIcon.appiconset/');
+ } else {
+ iconsDir = path.join(platformProjDir, 'Resources/icons/');
+ }
+
+ return iconsDir;
+}
+
+function updateIcons(cordovaProject, locations) {
+ var icons = cordovaProject.projectConfig.getIcons('ios');
+
+ if (icons.length === 0) {
+ events.emit('verbose', 'This app does not have icons defined');
+ return;
+ }
+
+ var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
+ var iconsDir = getIconsDir(cordovaProject.root, platformProjDir);
+ var resourceMap = mapIconResources(icons, iconsDir);
+ events.emit('verbose', 'Updating icons at ' + iconsDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanIcons(projectRoot, projectConfig, locations) {
+ var icons = projectConfig.getIcons('ios');
+ if (icons.length > 0) {
+ var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
+ var iconsDir = getIconsDir(projectRoot, platformProjDir);
+ var resourceMap = mapIconResources(icons, iconsDir);
+ Object.keys(resourceMap).forEach(function (targetIconPath) {
+ resourceMap[targetIconPath] = null;
+ });
+ events.emit('verbose', 'Cleaning icons at ' + iconsDir);
+
+ // Source paths are removed from the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+function mapSplashScreenResources(splashScreens, splashScreensDir) {
+ var platformSplashScreens = [
+ {dest: 'Default~iphone.png', width: 320, height: 480},
+ {dest: 'Default@2x~iphone.png', width: 640, height: 960},
+ {dest: 'Default-Portrait~ipad.png', width: 768, height: 1024},
+ {dest: 'Default-Portrait@2x~ipad.png', width: 1536, height: 2048},
+ {dest: 'Default-Landscape~ipad.png', width: 1024, height: 768},
+ {dest: 'Default-Landscape@2x~ipad.png', width: 2048, height: 1536},
+ {dest: 'Default-568h@2x~iphone.png', width: 640, height: 1136},
+ {dest: 'Default-667h.png', width: 750, height: 1334},
+ {dest: 'Default-736h.png', width: 1242, height: 2208},
+ {dest: 'Default-Landscape-736h.png', width: 2208, height: 1242}
+ ];
+
+ var pathMap = {};
+ platformSplashScreens.forEach(function (item) {
+ var splash = splashScreens.getBySize(item.width, item.height);
+ if (splash) {
+ var target = path.join(splashScreensDir, item.dest);
+ pathMap[target] = splash.src;
+ }
+ });
+ return pathMap;
+}
+
+function getSplashScreensDir(projectRoot, platformProjDir) {
+ var splashScreensDir;
+ var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
+
+ if (xcassetsExists) {
+ splashScreensDir = path.join(platformProjDir, 'Images.xcassets/LaunchImage.launchimage/');
+ } else {
+ splashScreensDir = path.join(platformProjDir, 'Resources/splash/');
+ }
+
+ return splashScreensDir;
+}
+
+function updateSplashScreens(cordovaProject, locations) {
+ var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
+
+ if (splashScreens.length === 0) {
+ events.emit('verbose', 'This app does not have splash screens defined');
+ return;
+ }
+
+ var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
+ var splashScreensDir = getSplashScreensDir(cordovaProject.root, platformProjDir);
+ var resourceMap = mapSplashScreenResources(splashScreens, splashScreensDir);
+ events.emit('verbose', 'Updating splash screens at ' + splashScreensDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+}
+
+function cleanSplashScreens(projectRoot, projectConfig, locations) {
+ var splashScreens = projectConfig.getSplashScreens('ios');
+ if (splashScreens.length > 0) {
+ var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
+ var splashScreensDir = getSplashScreensDir(projectRoot, platformProjDir);
+ var resourceMap = mapIconResources(splashScreens, splashScreensDir);
+ Object.keys(resourceMap).forEach(function (targetSplashPath) {
+ resourceMap[targetSplashPath] = null;
+ });
+ events.emit('verbose', 'Cleaning splash screens at ' + splashScreensDir);
+
+ // Source paths are removed from the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+ }
+}
+
+/**
+ * Returns an array of images for each possible idiom, scale, and size class. The images themselves are
+ * located in the platform's splash images by their pattern (@scale~idiom~sizesize). All possible
+ * combinations are returned, but not all will have a `filename` property. If the latter isn't present,
+ * the device won't attempt to load an image matching the same traits. If the filename is present,
+ * the device will try to load the image if it corresponds to the traits.
+ *
+ * The resulting return looks like this:
+ *
+ * [
+ * {
+ * idiom: 'universal|ipad|iphone',
+ * scale: '1x|2x|3x',
+ * width: 'any|com',
+ * height: 'any|com',
+ * filename: undefined|'Default@scale~idiom~widthheight.png',
+ * src: undefined|'path/to/original/matched/image/from/splash/screens.png',
+ * target: undefined|'path/to/asset/library/Default@scale~idiom~widthheight.png'
+ * }, ...
+ * ]
+ *
+ * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
+ * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
+ * @return {Array<Object>}
+ */
+function mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir) {
+ var platformLaunchStoryboardImages = [];
+ var idioms = ['universal', 'ipad', 'iphone'];
+ var scalesForIdiom = {
+ universal: ['1x', '2x', '3x'],
+ ipad: ['1x', '2x'],
+ iphone: ['1x', '2x', '3x']
+ };
+ var sizes = ['com', 'any'];
+
+ idioms.forEach(function (idiom) {
+ scalesForIdiom[idiom].forEach(function (scale) {
+ sizes.forEach(function(width) {
+ sizes.forEach(function(height) {
+ var item = {
+ idiom: idiom,
+ scale: scale,
+ width: width,
+ height: height
+ };
+
+ /* examples of the search pattern:
+ * scale ~ idiom ~ width height
+ * @2x ~ universal ~ any any
+ * @3x ~ iphone ~ com any
+ * @2x ~ ipad ~ com any
+ */
+ var searchPattern = '@' + scale + '~' + idiom + '~' + width + height;
+
+ /* because old node versions don't have Array.find, the below is
+ * functionally equivalent to this:
+ * var launchStoryboardImage = splashScreens.find(function(item) {
+ * return item.src.indexOf(searchPattern) >= 0;
+ * });
+ */
+ var launchStoryboardImage = splashScreens.reduce(function (p, c) {
+ return (c.src.indexOf(searchPattern) >= 0) ? c : p;
+ }, undefined);
+
+ if (launchStoryboardImage) {
+ item.filename = 'Default' + searchPattern + '.png';
+ item.src = launchStoryboardImage.src;
+ item.target = path.join(launchStoryboardImagesDir, item.filename);
+ }
+
+ platformLaunchStoryboardImages.push(item);
+ });
+ });
+ });
+ });
+ return platformLaunchStoryboardImages;
+}
+
+/**
+ * Returns a dictionary representing the source and destination paths for the launch storyboard images
+ * that need to be copied.
+ *
+ * The resulting return looks like this:
+ *
+ * {
+ * 'target-path': 'source-path',
+ * ...
+ * }
+ *
+ * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
+ * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
+ * @return {Object}
+ */
+function mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir) {
+ var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
+ var pathMap = {};
+ platformLaunchStoryboardImages.forEach(function (item) {
+ if (item.target) {
+ pathMap[item.target] = item.src;
+ }
+ });
+ return pathMap;
+}
+
+/**
+ * Builds the object that represents the contents.json file for the LaunchStoryboard image set.
+ *
+ * The resulting return looks like this:
+ *
+ * {
+ * images: [
+ * {
+ * idiom: 'universal|ipad|iphone',
+ * scale: '1x|2x|3x',
+ * width-class: undefined|'compact',
+ * height-class: undefined|'compact'
+ * }, ...
+ * ],
+ * info: {
+ * author: 'Xcode',
+ * version: 1
+ * }
+ * }
+ *
+ * A bit of minor logic is used to map from the array of images returned from mapLaunchStoryboardContents
+ * to the format requried by Xcode.
+ *
+ * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
+ * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
+ * @return {Object}
+ */
+function getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir) {
+ var IMAGESET_COMPACT_SIZE_CLASS = 'compact';
+ var CDV_ANY_SIZE_CLASS = 'any';
+
+ var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
+ var contentsJSON = {
+ images: [],
+ info: {
+ author: 'Xcode',
+ version: 1
+ }
+ };
+ contentsJSON.images = platformLaunchStoryboardImages.map(function(item) {
+ var newItem = {
+ idiom: item.idiom,
+ scale: item.scale
+ };
+
+ // Xcode doesn't want any size class property if the class is "any"
+ // If our size class is "com", Xcode wants "compact".
+ if (item.width !== CDV_ANY_SIZE_CLASS) {
+ newItem['width-class'] = IMAGESET_COMPACT_SIZE_CLASS;
+ }
+ if (item.height !== CDV_ANY_SIZE_CLASS) {
+ newItem['height-class'] = IMAGESET_COMPACT_SIZE_CLASS;
+ }
+
+ // Xcode doesn't want a filename property if there's no image for these traits
+ if (item.filename) {
+ newItem.filename = item.filename;
+ }
+ return newItem;
+ });
+ return contentsJSON;
+}
+
+/**
+ * Updates the project's plist based upon our launch storyboard images. If there are no images, then we should
+ * fall back to the regular launch images that might be supplied (that is, our app will be scaled on an iPad Pro),
+ * and if there are some images, we need to alter the UILaunchStoryboardName property to point to
+ * CDVLaunchScreen.
+ *
+ * There's some logic here to avoid overwriting changes the user might have made to their plist if they are using
+ * their own launch storyboard.
+ */
+function updateProjectPlistForLaunchStoryboard(platformConfig, infoPlist) {
+ var UI_LAUNCH_STORYBOARD_NAME = 'UILaunchStoryboardName';
+ var CDV_LAUNCH_STORYBOARD_NAME = 'CDVLaunchScreen';
+
+ var splashScreens = platformConfig.getSplashScreens('ios');
+ var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, ''); // note: we don't need a file path here; we're just counting
+ var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
+
+ events.emit('verbose', 'Current launch storyboard ' + currentLaunchStoryboard);
+
+
+ /* do we have any launch images do we have for our launch storyboard?
+ * Again, for old Node versions, the below code is equivalent to this:
+ * var hasLaunchStoryboardImages = !!contentsJSON.images.find(function (item) {
+ * return item.filename !== undefined;
+ * });
+ */
+ var hasLaunchStoryboardImages = !!contentsJSON.images.reduce(function (p, c) {
+ return (c.filename !== undefined) ? c : p;
+ }, undefined);
+
+ if (hasLaunchStoryboardImages && !currentLaunchStoryboard) {
+ // only change the launch storyboard if we have images to use AND the current value is blank
+ // if it's not blank, we've either done this before, or the user has their own launch storyboard
+ events.emit('verbose', 'Changing project to use our launch storyboard');
+ infoPlist[UI_LAUNCH_STORYBOARD_NAME] = CDV_LAUNCH_STORYBOARD_NAME;
+ return;
+ }
+
+ if (!hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME) {
+ // only revert to using the launch images if we have don't have any images for the launch storyboard
+ // but only clear it if current launch storyboard is our storyboard; the user might be using their
+ // own storyboard instead.
+ events.emit('verbose', 'Changing project to use launch images');
+ infoPlist[UI_LAUNCH_STORYBOARD_NAME] = undefined;
+ return;
+ }
+ events.emit('verbose', 'Not changing launch storyboard setting.');
+}
+
+/**
+ * Returns the directory for the Launch Storyboard image set, if image sets are being used. If they aren't
+ * being used, returns null.
+ *
+ * @param {string} projectRoot The project's root directory
+ * @param {string} platformProjDir The platform's project directory
+ */
+function getLaunchStoryboardImagesDir(projectRoot, platformProjDir) {
+ var launchStoryboardImagesDir;
+ var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
+
+ if (xcassetsExists) {
+ launchStoryboardImagesDir = path.join(platformProjDir, 'Images.xcassets/LaunchStoryboard.imageset/');
+ } else {
+ // if we don't have a asset library for images, we can't do the storyboard.
+ launchStoryboardImagesDir = null;
+ }
+
+ return launchStoryboardImagesDir;
+}
+
+/**
+ * Update the images for the Launch Storyboard and updates the image set's contents.json file appropriately.
+ *
+ * @param {Object} cordovaProject The cordova project
+ * @param {Object} locations A dictionary containing useful location paths
+ */
+function updateLaunchStoryboardImages(cordovaProject, locations) {
+ var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
+ var platformProjDir = locations.xcodeCordovaProj;
+ var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(cordovaProject.root, platformProjDir);
+
+ if (launchStoryboardImagesDir) {
+ var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
+ var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
+
+ events.emit('verbose', 'Updating launch storyboard images at ' + launchStoryboardImagesDir);
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: cordovaProject.root }, logFileOp);
+
+ events.emit('verbose', 'Updating Storyboard image set contents.json');
+ fs.writeFileSync(path.join(launchStoryboardImagesDir, 'contents.json'),
+ JSON.stringify(contentsJSON, null, 2));
+ }
+}
+
+/**
+ * Removes the images from the launch storyboard's image set and updates the image set's contents.json
+ * file appropriately.
+ *
+ * @param {string} projectRoot Path to the project root
+ * @param {Object} projectConfig The project's config.xml
+ * @param {Object} locations A dictionary containing useful location paths
+ */
+function cleanLaunchStoryboardImages(projectRoot, projectConfig, locations) {
+ var splashScreens = projectConfig.getSplashScreens('ios');
+ var platformProjDir = locations.xcodeCordovaProj;
+ var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(projectRoot, platformProjDir);
+ if (launchStoryboardImagesDir) {
+ var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
+ var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
+
+ Object.keys(resourceMap).forEach(function (targetPath) {
+ resourceMap[targetPath] = null;
+ });
+ events.emit('verbose', 'Cleaning storyboard image set at ' + launchStoryboardImagesDir);
+
+ // Source paths are removed from the map, so updatePaths() will delete the target files.
+ FileUpdater.updatePaths(
+ resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
+
+ // delete filename from contents.json
+ contentsJSON.images.forEach(function(image) {
+ image.filename = undefined;
+ });
+
+ events.emit('verbose', 'Updating Storyboard image set contents.json');
+ fs.writeFileSync(path.join(launchStoryboardImagesDir, 'contents.json'),
+ JSON.stringify(contentsJSON, null, 2));
+ }
+}
+
+/**
+ * Queries ConfigParser object for the orientation <preference> value. Warns if
+ * global preference value is not supported by platform.
+ *
+ * @param {Object} platformConfig ConfigParser object
+ *
+ * @return {String} Global/platform-specific orientation in lower-case
+ * (or empty string if both are undefined).
+ */
+function getOrientationValue(platformConfig) {
+
+ var ORIENTATION_DEFAULT = 'default';
+
+ var orientation = platformConfig.getPreference('orientation');
+ if (!orientation) {
+ return '';
+ }
+
+ orientation = orientation.toLowerCase();
+
+ // Check if the given global orientation is supported
+ if (['default', 'portrait','landscape', 'all'].indexOf(orientation) >= 0) {
+ return orientation;
+ }
+
+ events.emit('warn', 'Unrecognized value for Orientation preference: ' + orientation +
+ '. Defaulting to value: ' + ORIENTATION_DEFAULT + '.');
+
+ return ORIENTATION_DEFAULT;
+}
+
+/*
+ Parses all <access> and <allow-navigation> entries and consolidates duplicates (for ATS).
+ Returns an object with a Hostname as the key, and the value an object with properties:
+ {
+ Hostname, // String
+ NSExceptionAllowsInsecureHTTPLoads, // boolean
+ NSIncludesSubdomains, // boolean
+ NSExceptionMinimumTLSVersion, // String
+ NSExceptionRequiresForwardSecrecy, // boolean
+ NSRequiresCertificateTransparency, // boolean
+
+ // the three below _only_ show when the Hostname is '*'
+ // if any of the three are set, it disables setting NSAllowsArbitraryLoads
+ // (Apple already enforces this in ATS)
+ NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
+ NSAllowsLocalNetworking, // boolean (default: false)
+ NSAllowsArbitraryLoadsInMedia, // boolean (default:false)
+ }
+*/
+function processAccessAndAllowNavigationEntries(config) {
+ var accesses = config.getAccesses();
+ var allow_navigations = config.getAllowNavigations();
+
+ return allow_navigations
+ // we concat allow_navigations and accesses, after processing accesses
+ .concat(accesses.map(function(obj) {
+ // map accesses to a common key interface using 'href', not origin
+ obj.href = obj.origin;
+ delete obj.origin;
+ return obj;
+ }))
+ // we reduce the array to an object with all the entries processed (key is Hostname)
+ .reduce(function(previousReturn, currentElement) {
+ var options = {
+ minimum_tls_version : currentElement.minimum_tls_version,
+ requires_forward_secrecy : currentElement.requires_forward_secrecy,
+ requires_certificate_transparency : currentElement.requires_certificate_transparency,
+ allows_arbitrary_loads_in_media : currentElement.allows_arbitrary_loads_in_media,
+ allows_arbitrary_loads_in_web_content : currentElement.allows_arbitrary_loads_in_web_content,
+ allows_local_networking : currentElement.allows_local_networking
+ };
+ var obj = parseWhitelistUrlForATS(currentElement.href, options);
+
+ if (obj) {
+ // we 'union' duplicate entries
+ var item = previousReturn[obj.Hostname];
+ if (!item) {
+ item = {};
+ }
+ for(var o in obj) {
+ if (obj.hasOwnProperty(o)) {
+ item[o] = obj[o];
+ }
+ }
+ previousReturn[obj.Hostname] = item;
+ }
+ return previousReturn;
+ }, {});
+}
+
+/*
+ Parses a URL and returns an object with these keys:
+ {
+ Hostname, // String
+ NSExceptionAllowsInsecureHTTPLoads, // boolean (default: false)
+ NSIncludesSubdomains, // boolean (default: false)
+ NSExceptionMinimumTLSVersion, // String (default: 'TLSv1.2')
+ NSExceptionRequiresForwardSecrecy, // boolean (default: true)
+ NSRequiresCertificateTransparency, // boolean (default: false)
+
+ // the three below _only_ apply when the Hostname is '*'
+ // if any of the three are set, it disables setting NSAllowsArbitraryLoads
+ // (Apple already enforces this in ATS)
+ NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
+ NSAllowsLocalNetworking, // boolean (default: false)
+ NSAllowsArbitraryLoadsInMedia, // boolean (default:false)
+ }
+
+ null is returned if the URL cannot be parsed, or is to be skipped for ATS.
+*/
+function parseWhitelistUrlForATS(url, options) {
+ var href = URL.parse(url);
+ var retObj = {};
+ retObj.Hostname = href.hostname;
+
+ // Guiding principle: we only set values in retObj if they are NOT the default
+
+ if (url === '*') {
+ retObj.Hostname = '*';
+ var val;
+
+ val = (options.allows_arbitrary_loads_in_web_content === 'true');
+ if (options.allows_arbitrary_loads_in_web_content && val) { // default is false
+ retObj.NSAllowsArbitraryLoadsInWebContent = true;
+ }
+
+ val = (options.allows_arbitrary_loads_in_media === 'true');
+ if (options.allows_arbitrary_loads_in_media && val) { // default is false
+ retObj.NSAllowsArbitraryLoadsInMedia = true;
+ }
+
+ val = (options.allows_local_networking === 'true');
+ if (options.allows_local_networking && val) { // default is false
+ retObj.NSAllowsLocalNetworking = true;
+ }
+
+ return retObj;
+ }
+
+ if (!retObj.Hostname) {
+ // check origin, if it allows subdomains (wildcard in hostname), we set NSIncludesSubdomains to YES. Default is NO
+ var subdomain1 = '/*.'; // wildcard in hostname
+ var subdomain2 = '*://*.'; // wildcard in hostname and protocol
+ var subdomain3 = '*://'; // wildcard in protocol only
+ if (href.pathname.indexOf(subdomain1) === 0) {
+ retObj.NSIncludesSubdomains = true;
+ retObj.Hostname = href.pathname.substring(subdomain1.length);
+ } else if (href.pathname.indexOf(subdomain2) === 0) {
+ retObj.NSIncludesSubdomains = true;
+ retObj.Hostname = href.pathname.substring(subdomain2.length);
+ } else if (href.pathname.indexOf(subdomain3) === 0) {
+ retObj.Hostname = href.pathname.substring(subdomain3.length);
+ } else {
+ // Handling "scheme:*" case to avoid creating of a blank key in NSExceptionDomains.
+ return null;
+ }
+ }
+
+ if (options.minimum_tls_version && options.minimum_tls_version !== 'TLSv1.2') { // default is TLSv1.2
+ retObj.NSExceptionMinimumTLSVersion = options.minimum_tls_version;
+ }
+
+ var rfs = (options.requires_forward_secrecy === 'true');
+ if (options.requires_forward_secrecy && !rfs) { // default is true
+ retObj.NSExceptionRequiresForwardSecrecy = false;
+ }
+
+ var rct = (options.requires_certificate_transparency === 'true');
+ if (options.requires_certificate_transparency && rct) { // default is false
+ retObj.NSRequiresCertificateTransparency = true;
+ }
+
+ // if the scheme is HTTP, we set NSExceptionAllowsInsecureHTTPLoads to YES. Default is NO
+ if (href.protocol === 'http:') {
+ retObj.NSExceptionAllowsInsecureHTTPLoads = true;
+ }
+ else if (!href.protocol && href.pathname.indexOf('*:/') === 0) { // wilcard in protocol
+ retObj.NSExceptionAllowsInsecureHTTPLoads = true;
+ }
+
+ return retObj;
+}
+
+
+/*
+ App Transport Security (ATS) writer from <access> and <allow-navigation> tags
+ in config.xml
+*/
+function writeATSEntries(config) {
+ var pObj = processAccessAndAllowNavigationEntries(config);
+
+ var ats = {};
+
+ for(var hostname in pObj) {
+ if (pObj.hasOwnProperty(hostname)) {
+ var entry = pObj[hostname];
+
+ // Guiding principle: we only set values if they are available
+
+ if (hostname === '*') {
+ // always write this, for iOS 9, since in iOS 10 it will be overriden if
+ // any of the other three keys are written
+ ats['NSAllowsArbitraryLoads'] = true;
+
+ // at least one of the overriding keys is present
+ if (entry.NSAllowsArbitraryLoadsInWebContent) {
+ ats['NSAllowsArbitraryLoadsInWebContent'] = true;
+ }
+ if (entry.NSAllowsArbitraryLoadsInMedia) {
+ ats['NSAllowsArbitraryLoadsInMedia'] = true;
+ }
+ if (entry.NSAllowsLocalNetworking) {
+ ats['NSAllowsLocalNetworking'] = true;
+ }
+
+ continue;
+ }
+
+ var exceptionDomain = {};
+
+ for(var key in entry) {
+ if (entry.hasOwnProperty(key) && key !== 'Hostname') {
+ exceptionDomain[key] = entry[key];
+ }
+ }
+
+ if (!ats['NSExceptionDomains']) {
+ ats['NSExceptionDomains'] = {};
+ }
+
+ ats['NSExceptionDomains'][hostname] = exceptionDomain;
+ }
+ }
+
+ return ats;
+}
+
+function folderExists(folderPath) {
+ try {
+ var stat = fs.statSync(folderPath);
+ return stat && stat.isDirectory();
+ } catch (e) {
+ return false;
+ }
+}
+
+// Construct a default value for CFBundleVersion as the version with any
+// -rclabel stripped=.
+function default_CFBundleVersion(version) {
+ return version.split('-')[0];
+}
+
+// Converts cordova specific representation of target device to XCode value
+function parseTargetDevicePreference(value) {
+ if (!value) return null;
+ var map = { 'universal': '"1,2"', 'handset': '"1"', 'tablet': '"2"'};
+ if (map[value.toLowerCase()]) {
+ return map[value.toLowerCase()];
+ }
+ events.emit('warn', 'Unrecognized value for target-device preference: ' + value + '.');
+ return null;
+}
diff --git a/StoneIsland/platforms/ios/cordova/lib/projectFile.js b/StoneIsland/platforms/ios/cordova/lib/projectFile.js
new file mode 100755
index 00000000..aab38639
--- /dev/null
+++ b/StoneIsland/platforms/ios/cordova/lib/projectFile.js
@@ -0,0 +1,136 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+*/
+
+/*jshint node: true*/
+
+var xcode = require('xcode');
+var plist = require('plist');
+var _ = require('underscore');
+var path = require('path');
+var fs = require('fs');
+var shell = require('shelljs');
+
+var pluginHandlers = require('./plugman/pluginHandlers');
+var CordovaError = require('cordova-common').CordovaError;
+
+var cachedProjectFiles = {};
+
+function parseProjectFile(locations) {
+ var project_dir = locations.root;
+ var pbxPath = locations.pbxproj;
+
+ if (cachedProjectFiles[project_dir]) {
+ return cachedProjectFiles[project_dir];
+ }
+
+ var xcodeproj = xcode.project(pbxPath);
+ xcodeproj.parseSync();
+
+ var xcBuildConfiguration = xcodeproj.pbxXCBuildConfigurationSection();
+ var plist_file_entry = _.find(xcBuildConfiguration, function (entry) { return entry.buildSettings && entry.buildSettings.INFOPLIST_FILE; });
+ var plist_file = path.join(project_dir, plist_file_entry.buildSettings.INFOPLIST_FILE.replace(/^"(.*)"$/g, '$1').replace(/\\&/g, '&'));
+ var config_file = path.join(path.dirname(plist_file), 'config.xml');
+
+ if (!fs.existsSync(plist_file) || !fs.existsSync(config_file)) {
+ throw new CordovaError('Could not find *-Info.plist file, or config.xml file.');
+ }
+
+ var frameworks_file = path.join(project_dir, 'frameworks.json');
+ var frameworks = {};
+ try {
+ frameworks = require(frameworks_file);
+ } catch (e) { }
+
+ var xcode_dir = path.dirname(plist_file);
+ var pluginsDir = path.resolve(xcode_dir, 'Plugins');
+ var resourcesDir = path.resolve(xcode_dir, 'Resources');
+
+ cachedProjectFiles[project_dir] = {
+ plugins_dir:pluginsDir,
+ resources_dir:resourcesDir,
+ xcode:xcodeproj,
+ xcode_path:xcode_dir,
+ pbx: pbxPath,
+ projectDir: project_dir,
+ platformWww: path.join(project_dir, 'platform_www'),
+ www: path.join(project_dir, 'www'),
+ write: function () {
+ fs.writeFileSync(pbxPath, xcodeproj.writeSync());
+ if (Object.keys(this.frameworks).length === 0){
+ // If there is no framework references remain in the project, just remove this file
+ shell.rm('-rf', frameworks_file);
+ return;
+ }
+ fs.writeFileSync(frameworks_file, JSON.stringify(this.frameworks, null, 4));
+ },
+ getPackageName: function() {
+ return plist.parse(fs.readFileSync(plist_file, 'utf8')).CFBundleIdentifier;
+ },
+ getInstaller: function (name) {
+ return pluginHandlers.getInstaller(name);
+ },
+ getUninstaller: function (name) {
+ return pluginHandlers.getUninstaller(name);
+ },
+ frameworks: frameworks
+ };
+ return cachedProjectFiles[project_dir];
+}
+
+function purgeProjectFileCache(project_dir) {
+ delete cachedProjectFiles[project_dir];
+}
+
+module.exports = {
+ parse: parseProjectFile,
+ purgeProjectFileCache: purgeProjectFileCache
+};
+
+xcode.project.prototype.pbxEmbedFrameworksBuildPhaseObj = function (target) {
+ return this.buildPhaseObject('PBXCopyFilesBuildPhase', 'Embed Frameworks', target);
+};
+
+xcode.project.prototype.addToPbxEmbedFrameworksBuildPhase = function (file) {
+ var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
+ if (sources) {
+ sources.files.push(pbxBuildPhaseObj(file));
+ }
+};
+xcode.project.prototype.removeFromPbxEmbedFrameworksBuildPhase = function (file) {
+ var sources = this.pbxEmbedFrameworksBuildPhaseObj(file.target);
+ if (sources) {
+ sources.files = _.reject(sources.files, function(file){
+ return file.comment === longComment(file);
+ });
+ }
+};
+
+// special handlers to add frameworks to the 'Embed Frameworks' build phase, needed for custom frameworks
+// see CB-9517. should probably be moved to node-xcode.
+var util = require('util');
+function pbxBuildPhaseObj(file) {
+ var obj = Object.create(null);
+ obj.value = file.uuid;
+ obj.comment = longComment(file);
+ return obj;
+}
+
+function longComment(file) {
+ return util.format('%s in %s', file.basename, file.group);
+}
diff --git a/StoneIsland/platforms/ios/cordova/lib/run.js b/StoneIsland/platforms/ios/cordova/lib/run.js
index fcd39015..68c315a5 100755
--- a/StoneIsland/platforms/ios/cordova/lib/run.js
+++ b/StoneIsland/platforms/ios/cordova/lib/run.js
@@ -20,84 +20,103 @@
/*jshint node: true*/
var Q = require('q'),
- nopt = require('nopt'),
- path = require('path'),
- build = require('./build'),
- spawn = require('./spawn'),
+ path = require('path'),
+ iossim = require('ios-sim'),
+ build = require('./build'),
+ spawn = require('./spawn'),
check_reqs = require('./check_reqs');
+var events = require('cordova-common').events;
+
var cordovaPath = path.join(__dirname, '..');
var projectPath = path.join(__dirname, '..', '..');
-module.exports.run = function (argv) {
-
- // parse args here
- // --debug and --release args not parsed here
- // but still valid since they can be passed down to build command
- var args = nopt({
- // "archs": String, // TODO: add support for building different archs
- 'list': Boolean,
- 'nobuild': Boolean,
- 'device': Boolean, 'emulator': Boolean, 'target': String
- }, {}, argv);
+module.exports.run = function (runOptions) {
// Validate args
- if (args.device && args.emulator) {
+ if (runOptions.device && runOptions.emulator) {
return Q.reject('Only one of "device"/"emulator" options should be specified');
}
- // validate target device for ios-sim
- // Valid values for "--target" (case sensitive):
- var validTargets = ['iPhone-4s', 'iPhone-5', 'iPhone-5s', 'iPhone-6-Plus', 'iPhone-6',
- 'iPad-2', 'iPad-Retina', 'iPad-Air', 'Resizable-iPhone', 'Resizable-iPad'];
- if (!(args.device) && args.target && validTargets.indexOf(args.target.split(',')[0]) < 0 ) {
- return Q.reject(args.target + ' is not a valid target for emulator');
- }
-
// support for CB-8168 `cordova/run --list`
- if (args.list) {
- if (args.device) return listDevices();
- if (args.emulator) return listEmulators();
+ if (runOptions.list) {
+ if (runOptions.device) return listDevices();
+ if (runOptions.emulator) return listEmulators();
// if no --device or --emulator flag is specified, list both devices and emulators
return listDevices().then(function () {
return listEmulators();
});
}
- // check for either ios-sim or ios-deploy is available
- // depending on arguments provided
- var checkTools = args.device ? check_reqs.check_ios_deploy() : check_reqs.check_ios_sim();
+ var useDevice = !!runOptions.device;
- return checkTools.then(function () {
- // if --nobuild isn't specified then build app first
- if (!args.nobuild) {
- return build.run(argv);
+ return require('./list-devices').run()
+ .then(function (devices) {
+ if (devices.length > 0 && !(runOptions.emulator)) {
+ useDevice = true;
+ // we also explicitly set device flag in options as we pass
+ // those parameters to other api (build as an example)
+ runOptions.device = true;
+ return check_reqs.check_ios_deploy();
+ }
+ }).then(function () {
+ if (!runOptions.nobuild) {
+ return build.run(runOptions);
+ } else {
+ return Q.resolve();
}
}).then(function () {
return build.findXCodeProjectIn(projectPath);
}).then(function (projectName) {
- var appPath = path.join(projectPath, 'build', (args.device ? 'device' : 'emulator'), projectName + '.app');
+ var appPath = path.join(projectPath, 'build', 'emulator', projectName + '.app');
// select command to run and arguments depending whether
// we're running on device/emulator
- if (args.device) {
+ if (useDevice) {
return checkDeviceConnected().then(function () {
- return deployToDevice(appPath);
+ appPath = path.join(projectPath, 'build', 'device', projectName + '.app');
+ var extraArgs = [];
+ if (runOptions.argv) {
+ // argv.slice(2) removes node and run.js, filterSupportedArgs removes the run.js args
+ extraArgs = filterSupportedArgs(runOptions.argv.slice(2));
+ }
+ return deployToDevice(appPath, runOptions.target, extraArgs);
}, function () {
// if device connection check failed use emulator then
- return deployToSim(appPath, args.target);
+ return deployToSim(appPath, runOptions.target);
});
} else {
- return deployToSim(appPath, args.target);
+ return deployToSim(appPath, runOptions.target);
}
});
};
/**
+ * Filters the args array and removes supported args for the 'run' command.
+ *
+ * @return {Array} array with unsupported args for the 'run' command
+ */
+function filterSupportedArgs(args) {
+ var filtered = [];
+ var sargs = ['--device', '--emulator', '--nobuild', '--list', '--target', '--debug', '--release'];
+ var re = new RegExp(sargs.join('|'));
+
+ args.forEach(function(element) {
+ // supported args not found, we add
+ // we do a regex search because --target can be "--target=XXX"
+ if (element.search(re) == -1) {
+ filtered.push(element);
+ }
+ }, this);
+
+ return filtered;
+}
+
+/**
* Checks if any iOS device is connected
* @return {Promise} Fullfilled when any device is connected, rejected otherwise
*/
function checkDeviceConnected() {
- return spawn('ios-deploy', ['-c']);
+ return spawn('ios-deploy', ['-c', '-t', '1']);
}
/**
@@ -106,9 +125,13 @@ function checkDeviceConnected() {
* @param {String} appPath Path to application package
* @return {Promise} Resolves when deploy succeeds otherwise rejects
*/
-function deployToDevice(appPath) {
+function deployToDevice(appPath, target, extraArgs) {
// Deploying to device...
- return spawn('ios-deploy', ['-d', '-b', appPath]);
+ if (target) {
+ return spawn('ios-deploy', ['--justlaunch', '-d', '-b', appPath, '-i', target].concat(extraArgs));
+ } else {
+ return spawn('ios-deploy', ['--justlaunch', '--no-wifi', '-d', '-b', appPath].concat(extraArgs));
+ }
}
/**
@@ -118,27 +141,38 @@ function deployToDevice(appPath) {
* @return {Promise} Resolves when deploy succeeds otherwise rejects
*/
function deployToSim(appPath, target) {
- // Select target device for emulator. Default is 'iPhone-6'
+ // Select target device for emulator. Default is 'iPhone-6'
if (!target) {
- target = 'iPhone-6';
- console.log('No target specified for emulator. Deploying to ' + target + ' simulator');
+ return require('./list-emulator-images').run()
+ .then(function (emulators) {
+ if (emulators.length > 0) {
+ target = emulators[0];
+ }
+ emulators.forEach(function (emulator) {
+ if (emulator.indexOf('iPhone') === 0) {
+ target = emulator;
+ }
+ });
+ events.emit('log','No target specified for emulator. Deploying to ' + target + ' simulator');
+ return startSim(appPath, target);
+ });
+ } else {
+ return startSim(appPath, target);
}
+}
+
+function startSim(appPath, target) {
var logPath = path.join(cordovaPath, 'console.log');
- var simArgs = ['launch', appPath,
- '--devicetypeid', 'com.apple.CoreSimulator.SimDeviceType.' + target,
- // We need to redirect simulator output here to use cordova/log command
- // TODO: Is there any other way to get emulator's output to use in log command?
- '--stderr', logPath, '--stdout', logPath,
- '--exit'];
- return spawn('ios-sim', simArgs);
+
+ return iossim.launch(appPath, 'com.apple.CoreSimulator.SimDeviceType.' + target, logPath, '--exit');
}
function listDevices() {
return require('./list-devices').run()
.then(function (devices) {
- console.log('Available iOS Devices:');
+ events.emit('log','Available iOS Devices:');
devices.forEach(function (device) {
- console.log('\t' + device);
+ events.emit('log','\t' + device);
});
});
}
@@ -146,9 +180,9 @@ function listDevices() {
function listEmulators() {
return require('./list-emulator-images').run()
.then(function (emulators) {
- console.log('Available iOS Virtual Devices:');
+ events.emit('log','Available iOS Simulators:');
emulators.forEach(function (emulator) {
- console.log('\t' + emulator);
+ events.emit('log','\t' + emulator);
});
});
}
diff --git a/StoneIsland/platforms/ios/cordova/lib/spawn.js b/StoneIsland/platforms/ios/cordova/lib/spawn.js
index 1cb31615..2162b9c7 100755
--- a/StoneIsland/platforms/ios/cordova/lib/spawn.js
+++ b/StoneIsland/platforms/ios/cordova/lib/spawn.js
@@ -43,7 +43,6 @@ module.exports = function(cmd, args, opt_cwd) {
}
});
} catch(e) {
- console.error('error caught: ' + e);
d.reject(e);
}
return d.promise;
diff --git a/StoneIsland/platforms/ios/cordova/lib/versions.js b/StoneIsland/platforms/ios/cordova/lib/versions.js
index e22e499a..da31d4fa 100755
--- a/StoneIsland/platforms/ios/cordova/lib/versions.js
+++ b/StoneIsland/platforms/ios/cordova/lib/versions.js
@@ -111,6 +111,23 @@ exports.get_ios_deploy_version = function() {
};
/**
+ * Gets pod (CocoaPods) util version
+ * @return {Promise} Promise that either resolved with pod version
+ * or rejected in case of error
+ */
+exports.get_cocoapods_version = function() {
+ var d = Q.defer();
+ child_process.exec('pod --version', function(error, stdout, stderr) {
+ if (error) {
+ d.reject(stderr);
+ } else {
+ d.resolve(stdout);
+ }
+ });
+ return d.promise;
+};
+
+/**
* Gets ios-sim util version
* @return {Promise} Promise that either resolved with ios-sim version
* or rejected in case of error
@@ -138,7 +155,8 @@ exports.get_tool_version = function (toolName) {
case 'xcodebuild': return exports.get_apple_xcode_version();
case 'ios-sim': return exports.get_ios_sim_version();
case 'ios-deploy': return exports.get_ios_deploy_version();
- default: return Q.reject(toolName + ' is not valid tool name. Valid names are: \'xcodebuild\', \'ios-sim\' and \'ios-deploy\'');
+ case 'pod': return exports.get_cocoapods_version();
+ default: return Q.reject(toolName + ' is not valid tool name. Valid names are: \'xcodebuild\', \'ios-sim\', \'ios-deploy\', and \'pod\'');
}
};