diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2017-09-26 01:35:13 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2017-09-26 01:35:13 +0200 |
| commit | 597fa051833ca3df6eb185c0143ff82e02dacba1 (patch) | |
| tree | cb25347477c57f82e955b054b70f4bb5359fb0d2 /StoneIsland/platforms/android/cordova/lib | |
| parent | 6a9186aea6b85beef28e3eb765fbf2322a1c7890 (diff) | |
push plugin ugh
Diffstat (limited to 'StoneIsland/platforms/android/cordova/lib')
9 files changed, 444 insertions, 172 deletions
diff --git a/StoneIsland/platforms/android/cordova/lib/android_sdk.js b/StoneIsland/platforms/android/cordova/lib/android_sdk.js new file mode 100755 index 00000000..a1a806a6 --- /dev/null +++ b/StoneIsland/platforms/android/cordova/lib/android_sdk.js @@ -0,0 +1,106 @@ +/* + 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'), + superspawn = require('cordova-common').superspawn; + +var suffix_number_regex = /(\d+)$/; +// Used for sorting Android targets, example strings to sort: +// android-19 +// android-L +// Google Inc.:Google APIs:20 +// Google Inc.:Glass Development Kit Preview:20 +// The idea is to sort based on largest "suffix" number - meaning the bigger +// the number at the end, the more recent the target, the closer to the +// start of the array. +function sort_by_largest_numerical_suffix(a, b) { + var suffix_a = a.match(suffix_number_regex); + var suffix_b = b.match(suffix_number_regex); + if (suffix_a && suffix_b) { + // If the two targets being compared have suffixes, return less than + // zero, or greater than zero, based on which suffix is larger. + return (parseInt(suffix_a[1]) > parseInt(suffix_b[1]) ? -1 : 1); + } else { + // If no suffix numbers were detected, leave the order as-is between + // elements a and b. + return 0; + } +} + +module.exports.print_newest_available_sdk_target = function() { + return module.exports.list_targets() + .then(function(targets) { + targets.sort(sort_by_largest_numerical_suffix); + console.log(targets[0]); + }); +}; + +module.exports.version_string_to_api_level = { + '4.0': 14, + '4.0.3': 15, + '4.1': 16, + '4.2': 17, + '4.3': 18, + '4.4': 19, + '4.4W': 20, + '5.0': 21, + '5.1': 22, + '6.0': 23, + '7.0': 24, + '7.1.1': 25 +}; + +function parse_targets(output) { + var target_out = output.split('\n'); + var targets = []; + for (var i = target_out.length - 1; i >= 0; i--) { + if(target_out[i].match(/id:/)) { // if "id:" is in the line... + targets.push(target_out[i].match(/"(.+)"/)[1]); //.. match whatever is in quotes. + } + } + return targets; +} + +module.exports.list_targets_with_android = function() { + return superspawn.spawn('android', ['list', 'target']) + .then(parse_targets); +}; + +module.exports.list_targets_with_avdmanager = function() { + return superspawn.spawn('avdmanager', ['list', 'target']) + .then(parse_targets); +}; + +module.exports.list_targets = function() { + return module.exports.list_targets_with_avdmanager() + .catch(function(err) { + // If there's an error, like avdmanager could not be found, we can try + // as a last resort, to run `android`, in case this is a super old + // SDK installation. + if (err && (err.code == 'ENOENT' || (err.stderr && err.stderr.match(/not recognized/)))) { + return module.exports.list_targets_with_android(); + } else throw err; + }) + .then(function(targets) { + if (targets.length === 0) { + return Q.reject(new Error('No android targets (SDKs) installed!')); + } + return targets; + }); +}; diff --git a/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js b/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js deleted file mode 100755 index 79af2727..00000000 --- a/StoneIsland/platforms/android/cordova/lib/android_sdk_version.js +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env node - -/* - 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 child_process = require('child_process'), - Q = require('q'); - -var get_highest_sdk = function(results){ - var reg = /\d+/; - var apiLevels = []; - for(var i=0;i<results.length;i++){ - apiLevels[i] = parseInt(results[i].match(reg)[0]); - } - apiLevels.sort(function(a,b){return b-a;}); - console.log(apiLevels[0]); -}; - -var get_sdks = function() { - var d = Q.defer(); - child_process.exec('android list targets', function(err, stdout, stderr) { - if (err) d.reject(stderr); - else d.resolve(stdout); - }); - - return d.promise.then(function(output) { - var reg = /android-\d+/gi; - var results = output.match(reg); - if(results.length===0){ - return Q.reject(new Error('No android sdks installed.')); - }else{ - get_highest_sdk(results); - } - - return Q(); - }, function(stderr) { - if (stderr.match(/command\snot\sfound/) || stderr.match(/'android' is not recognized/)) { - return Q.reject(new Error('The command \"android\" failed. Make sure you have the latest Android SDK installed, and the \"android\" command (inside the tools/ folder) is added to your path.')); - } else { - return Q.reject(new Error('An error occurred while listing Android targets')); - } - }); -}; - -module.exports.run = function() { - return Q.all([get_sdks()]); -}; - diff --git a/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js index f415646e..5b5ce13d 100644 --- a/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js +++ b/StoneIsland/platforms/android/cordova/lib/builders/GradleBuilder.js @@ -65,6 +65,21 @@ GradleBuilder.prototype.getArgs = function(cmd, opts) { return args; }; +/* + * This returns a promise + */ + +GradleBuilder.prototype.runGradleWrapper = function(gradle_cmd) { + var gradlePath = path.join(this.root, 'gradlew'); + var wrapperGradle = path.join(this.root, 'wrapper.gradle'); + if(fs.existsSync(gradlePath)) { + //Literally do nothing, for some reason this works, while !fs.existsSync didn't on Windows + } else { + return spawn(gradle_cmd, ['-p', this.root, 'wrapper', '-b', wrapperGradle], {stdio: 'inherit'}); + } +}; + + // Makes the project buildable, minus the gradle wrapper. GradleBuilder.prototype.prepBuildFiles = function() { // Update the version of build.gradle in each dependent library. @@ -159,17 +174,15 @@ GradleBuilder.prototype.prepBuildFiles = function() { GradleBuilder.prototype.prepEnv = function(opts) { var self = this; return check_reqs.check_gradle() - .then(function() { - return self.prepBuildFiles(); - }).then(function() { - // Copy the gradle wrapper on each build so that: - // A) we don't require the Android SDK at project creation time, and - // B) we always use the SDK's latest version of it. - // check_reqs ensures that this is set. - /*jshint -W069 */ - var sdkDir = process.env['ANDROID_HOME']; - /*jshint +W069 */ - var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); + .then(function(gradlePath) { + return self.runGradleWrapper(gradlePath); + }).then(function() { + return self.prepBuildFiles(); + }).then(function() { + // We now copy the gradle out of the framework + // This is a dirty patch to get the build working + /* + var wrapperDir = path.join(self.root, 'CordovaLib'); if (process.platform == 'win32') { shell.rm('-f', path.join(self.root, 'gradlew.bat')); shell.cp(path.join(wrapperDir, 'gradlew.bat'), self.root); @@ -180,13 +193,13 @@ GradleBuilder.prototype.prepEnv = function(opts) { shell.rm('-rf', path.join(self.root, 'gradle', 'wrapper')); shell.mkdir('-p', path.join(self.root, 'gradle')); shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(self.root, 'gradle')); - +*/ // If the gradle distribution URL is set, make sure it points to version we want. // If it's not set, do nothing, assuming that we're using a future version of gradle that we don't want to mess with. // For some reason, using ^ and $ don't work. This does the job, though. var distributionUrlRegex = /distributionUrl.*zip/; /*jshint -W069 */ - var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-2.14.1-all.zip'; + var distributionUrl = process.env['CORDOVA_ANDROID_GRADLE_DISTRIBUTION_URL'] || 'https\\://services.gradle.org/distributions/gradle-3.3-all.zip'; /*jshint +W069 */ var gradleWrapperPropertiesPath = path.join(self.root, 'gradle', 'wrapper', 'gradle-wrapper.properties'); shell.chmod('u+w', gradleWrapperPropertiesPath); diff --git a/StoneIsland/platforms/android/cordova/lib/check_reqs.js b/StoneIsland/platforms/android/cordova/lib/check_reqs.js index ac6fa4c1..1fd397ad 100644 --- a/StoneIsland/platforms/android/cordova/lib/check_reqs.js +++ b/StoneIsland/platforms/android/cordova/lib/check_reqs.js @@ -26,10 +26,12 @@ var shelljs = require('shelljs'), Q = require('q'), path = require('path'), fs = require('fs'), - ROOT = path.join(__dirname, '..', '..'); + os = require('os'), + REPO_ROOT = path.join(__dirname, '..', '..', '..', '..'), + PROJECT_ROOT = path.join(__dirname, '..', '..'); var CordovaError = require('cordova-common').CordovaError; - -var isWindows = process.platform == 'win32'; +var superspawn = require('cordova-common').superspawn; +var android_sdk = require('./android_sdk'); function forgivingWhichSync(cmd) { try { @@ -50,7 +52,16 @@ function tryCommand(cmd, errMsg, catchStderr) { return d.promise; } -// Get valid target from framework/project.properties +module.exports.isWindows = function() { + return (os.platform() == 'win32'); +}; + +module.exports.isDarwin = function() { + return (os.platform() == 'darwin'); +}; + +// Get valid target from framework/project.properties if run from this repo +// Otherwise get target from project.properties file within a generated cordova-android project module.exports.get_target = function() { function extractFromFile(filePath) { var target = shelljs.grep(/\btarget=/, filePath); @@ -59,38 +70,83 @@ module.exports.get_target = function() { } return target.split('=')[1].trim(); } - if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) { - return extractFromFile(path.join(ROOT, 'framework', 'project.properties')); + var repo_file = path.join(REPO_ROOT, 'framework', 'project.properties'); + if (fs.existsSync(repo_file)) { + return extractFromFile(repo_file); } - if (fs.existsSync(path.join(ROOT, 'project.properties'))) { - // if no target found, we're probably in a project and project.properties is in ROOT. - return extractFromFile(path.join(ROOT, 'project.properties')); + var project_file = path.join(PROJECT_ROOT, 'project.properties'); + if (fs.existsSync(project_file)) { + // if no target found, we're probably in a project and project.properties is in PROJECT_ROOT. + return extractFromFile(project_file); } - throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties')); + throw new Error('Could not find android target in either ' + repo_file + ' nor ' + project_file); }; // Returns a promise. Called only by build and clean commands. module.exports.check_ant = function() { - return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.') - .then(function (output) { + return superspawn.spawn('ant', ['-version']) + .then(function(output) { // Parse Ant version from command output return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; + }).catch(function(err) { + throw new CordovaError('Failed to run `ant -version`. Make sure you have `ant` on your $PATH.'); }); }; +module.exports.get_gradle_wrapper = function() { + var androidStudioPath; + var i = 0; + var foundStudio = false; + var program_dir; + if (module.exports.isDarwin()) { + program_dir = fs.readdirSync('/Applications'); + while (i < program_dir.length && !foundStudio) { + if (program_dir[i].startsWith('Android Studio')) { + //TODO: Check for a specific Android Studio version, make sure it's not Canary + androidStudioPath = path.join('/Applications', program_dir[i], 'Contents', 'gradle'); + foundStudio = true; + } else { ++i; } + } + } else if (module.exports.isWindows()) { + var androidPath = path.join(process.env['ProgramFiles'], 'Android') + '/'; + if (fs.existsSync(androidPath)) { + program_dir = fs.readdirSync(androidPath); + while (i < program_dir.length && !foundStudio) { + if (program_dir[i].startsWith('Android Studio')) { + foundStudio = true; + androidStudioPath = path.join(process.env['ProgramFiles'], 'Android', program_dir[i], 'gradle'); + } else { ++i; } + } + } + } + + if (androidStudioPath !== null && fs.existsSync(androidStudioPath)) { + var dirs = fs.readdirSync(androidStudioPath); + if(dirs[0].split('-')[0] == 'gradle') { + return path.join(androidStudioPath, dirs[0], 'bin', 'gradle'); + } + } else { + //OK, let's try to check for Gradle! + return forgivingWhichSync('gradle'); + } +}; + // Returns a promise. Called only by build and clean commands. module.exports.check_gradle = function() { var sdkDir = process.env['ANDROID_HOME']; + var d = Q.defer(); if (!sdkDir) return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' + 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.')); - var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); - if (!fs.existsSync(wrapperDir)) { - return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' + - 'Looked here: ' + wrapperDir)); - } - return Q.when(); + var gradlePath = module.exports.get_gradle_wrapper(); + if (gradlePath.length !== 0) + d.resolve(gradlePath); + else + d.reject(new CordovaError('Could not find an installed version of Gradle either in Android Studio,\n' + + 'or on your system to install the gradle wrapper. Please include gradle \n' + + 'in your path, or install Android Studio')); + return d.promise; }; // Returns a promise. @@ -105,12 +161,15 @@ module.exports.check_java = function() { } } else { if (javacPath) { - var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.'; // OS X has a command for finding JAVA_HOME. - if (fs.existsSync('/usr/libexec/java_home')) { - return tryCommand('/usr/libexec/java_home', msg) + var find_java = '/usr/libexec/java_home'; + var default_java_error_msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.'; + if (fs.existsSync(find_java)) { + return superspawn.spawn(find_java) .then(function(stdout) { process.env['JAVA_HOME'] = stdout.trim(); + }).catch(function(err) { + throw new CordovaError(default_java_error_msg); }); } else { // See if we can derive it from javac's location. @@ -119,10 +178,10 @@ module.exports.check_java = function() { if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) { process.env['JAVA_HOME'] = maybeJavaHome; } else { - throw new CordovaError(msg); + throw new CordovaError(default_java_error_msg); } } - } else if (isWindows) { + } else if (module.exports.isWindows()) { // Try to auto-detect java in the default install paths. var oldSilent = shelljs.config.silent; shelljs.config.silent = true; @@ -142,28 +201,29 @@ module.exports.check_java = function() { } } }).then(function() { - var msg = - 'Failed to run "javac -version", make sure that you have a JDK installed.\n' + - 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n'; - if (process.env['JAVA_HOME']) { - msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n'; - } - // We use tryCommand with catchStderr = true, because - // javac writes version info to stderr instead of stdout - return tryCommand('javac -version', msg, true) - .then(function (output) { - //Let's check for at least Java 8, and keep it future proof so we can support Java 10 - var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output); - return match && match[1]; - }); + var msg = + 'Failed to run "javac -version", make sure that you have a JDK installed.\n' + + 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n'; + if (process.env['JAVA_HOME']) { + msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n'; + } + // We use tryCommand with catchStderr = true, because + // javac writes version info to stderr instead of stdout + return tryCommand('javac -version', msg, true) + .then(function (output) { + //Let's check for at least Java 8, and keep it future proof so we can support Java 10 + var match = /javac ((?:1\.)(?:[8-9]\.)(?:\d+))|((?:1\.)(?:[1-9]\d+\.)(?:\d+))/i.exec(output); + return match && match[1]; }); + }); }; // Returns a promise. module.exports.check_android = function() { return Q().then(function() { var androidCmdPath = forgivingWhichSync('android'); - var adbInPath = !!forgivingWhichSync('adb'); + var adbInPath = forgivingWhichSync('adb'); + var avdmanagerInPath = forgivingWhichSync('avdmanager'); var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); function maybeSetAndroidHome(value) { if (!hasAndroidHome && fs.existsSync(value)) { @@ -171,8 +231,10 @@ module.exports.check_android = function() { process.env['ANDROID_HOME'] = value; } } - if (!hasAndroidHome && !androidCmdPath) { - if (isWindows) { + // First ensure ANDROID_HOME is set + // If we have no hints (nothing in PATH), try a few default locations + if (!hasAndroidHome && !androidCmdPath && !adbInPath && !avdmanagerInPath) { + if (module.exports.isWindows()) { // Android Studio 1.0 installer maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk')); maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk')); @@ -182,7 +244,7 @@ module.exports.check_android = function() { // Stand-alone installer maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk')); maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk')); - } else if (process.platform == 'darwin') { + } else if (module.exports.isDarwin()) { // Android Studio 1.0 installer maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk')); // Android Studio pre-1.0 installer @@ -197,26 +259,42 @@ module.exports.check_android = function() { maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk')); } } - if (hasAndroidHome && !androidCmdPath) { - process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); - } - if (androidCmdPath && !hasAndroidHome) { - var parentDir = path.dirname(androidCmdPath); - var grandParentDir = path.dirname(parentDir); - if (path.basename(parentDir) == 'tools') { - process.env['ANDROID_HOME'] = path.dirname(parentDir); - hasAndroidHome = true; - } else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { - process.env['ANDROID_HOME'] = grandParentDir; - hasAndroidHome = true; - } else { - throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + - 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' + - 'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.'); + if (!hasAndroidHome) { + // If we dont have ANDROID_HOME, but we do have some tools on the PATH, try to infer from the tooling PATH. + var parentDir, grandParentDir; + if (androidCmdPath) { + parentDir = path.dirname(androidCmdPath); + grandParentDir = path.dirname(parentDir); + if (path.basename(parentDir) == 'tools' || fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) { + maybeSetAndroidHome(grandParentDir); + } else { + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' + + 'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools directory.'); + } + } + if (adbInPath) { + parentDir = path.dirname(adbInPath); + grandParentDir = path.dirname(parentDir); + if (path.basename(parentDir) == 'platform-tools') { + maybeSetAndroidHome(grandParentDir); + } else { + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Detected \'adb\' command at ' + parentDir + ' but no \'platform-tools\' directory found near.\n' + + 'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'platform-tools directory.'); + } + } + if (avdmanagerInPath) { + parentDir = path.dirname(avdmanagerInPath); + grandParentDir = path.dirname(parentDir); + if (path.basename(parentDir) == 'bin' && path.basename(grandParentDir) == 'tools') { + maybeSetAndroidHome(path.dirname(grandParentDir)); + } else { + throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Detected \'avdmanager\' command at ' + parentDir + ' but no \'tools' + path.sep + 'bin\' directory found near.\n' + + 'Try reinstall Android SDK or update your PATH to include valid path to SDK' + path.sep + 'tools' + path.sep + 'bin directory.'); + } } - } - if (hasAndroidHome && !adbInPath) { - process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); } if (!process.env['ANDROID_HOME']) { throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + @@ -226,13 +304,27 @@ module.exports.check_android = function() { throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] + '\nTry update it manually to point to valid SDK directory.'); } + // Next let's make sure relevant parts of the SDK tooling is in our PATH + if (hasAndroidHome && !androidCmdPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); + } + if (hasAndroidHome && !adbInPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); + } + if (hasAndroidHome && !avdmanagerInPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools', 'bin'); + } return hasAndroidHome; }); }; +// TODO: is this actually needed? module.exports.getAbsoluteAndroidCmd = function () { var cmd = forgivingWhichSync('android'); - if (process.platform === 'win32') { + if (cmd.length === 0) { + cmd = forgivingWhichSync('sdkmanager'); + } + if (module.exports.isWindows()) { return '"' + cmd + '"'; } return cmd.replace(/(\s)/g, '\\$1'); @@ -244,20 +336,17 @@ module.exports.check_android_target = function(originalError) { // android-L // Google Inc.:Google APIs:20 // Google Inc.:Glass Development Kit Preview:20 - var valid_target = module.exports.get_target(); - var msg = 'Android SDK not found. Make sure that it is installed. If it is not at the default location, set the ANDROID_HOME environment variable.'; - return tryCommand('android list targets --compact', msg) - .then(function(output) { - var targets = output.split('\n'); - if (targets.indexOf(valid_target) >= 0) { + var desired_api_level = module.exports.get_target(); + return android_sdk.list_targets() + .then(function(targets) { + if (targets.indexOf(desired_api_level) >= 0) { return targets; } - var androidCmd = module.exports.getAbsoluteAndroidCmd(); - var msg = 'Please install Android target: "' + valid_target + '".\n\n' + + var msg = 'Please install Android target / API level: "' + desired_api_level + '".\n\n' + 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' + 'You will require:\n' + - '1. "SDK Platform" for ' + valid_target + '\n' + + '1. "SDK Platform" for API level ' + desired_api_level + '\n' + '2. "Android SDK Platform-tools (latest)\n' + '3. "Android SDK Build-tools" (latest)'; if (originalError) { @@ -278,7 +367,6 @@ module.exports.run = function() { throw new CordovaError('Requirements check failed for JDK 1.8 or greater'); } - if (!values[1]) { throw new CordovaError('Requirements check failed for Android SDK'); } diff --git a/StoneIsland/platforms/android/cordova/lib/emulator.js b/StoneIsland/platforms/android/cordova/lib/emulator.js index ff1e261c..22209aa0 100644 --- a/StoneIsland/platforms/android/cordova/lib/emulator.js +++ b/StoneIsland/platforms/android/cordova/lib/emulator.js @@ -27,11 +27,15 @@ var path = require('path'); var Adb = require('./Adb'); var AndroidManifest = require('./AndroidManifest'); var events = require('cordova-common').events; -var spawn = require('cordova-common').superspawn.spawn; +var superspawn = require('cordova-common').superspawn; var CordovaError = require('cordova-common').CordovaError; +var shelljs = require('shelljs'); +var android_sdk = require('./android_sdk'); +var check_reqs = require('./check_reqs'); var Q = require('q'); var os = require('os'); +var fs = require('fs'); var child_process = require('child_process'); // constants @@ -42,18 +46,77 @@ var NUM_INSTALL_RETRIES = 3; var CHECK_BOOTED_INTERVAL = 3 * ONE_SECOND; // in milliseconds var EXEC_KILL_SIGNAL = 'SIGKILL'; -/** - * Returns a Promise for a list of emulator images in the form of objects - * { - name : <emulator_name>, - path : <path_to_emulator_image>, - target : <api_target>, - abi : <cpu>, - skin : <skin> - } - */ -module.exports.list_images = function() { - return spawn('android', ['list', 'avds']) +function forgivingWhichSync(cmd) { + try { + return fs.realpathSync(shelljs.which(cmd)); + } catch (e) { + return ''; + } +} + +module.exports.list_images_using_avdmanager = function () { + return superspawn.spawn('avdmanager', ['list', 'avd']) + .then(function(output) { + var response = output.split('\n'); + var emulator_list = []; + for (var i = 1; i < response.length; i++) { + // To return more detailed information use img_obj + var img_obj = {}; + if (response[i].match(/Name:\s/)) { + img_obj['name'] = response[i].split('Name: ')[1].replace('\r', ''); + if (response[i + 1].match(/Device:\s/)) { + i++; + img_obj['device'] = response[i].split('Device: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/Path:\s/)) { + i++; + img_obj['path'] = response[i].split('Path: ')[1].replace('\r', ''); + } + if (response[i + 1].match(/Target:\s/)) { + i++; + if (response[i + 1].match(/ABI:\s/)) { + img_obj['abi'] = response[i + 1].split('ABI: ')[1].replace('\r', ''); + } + // This next conditional just aims to match the old output of `android list avd` + // We do so so that we don't have to change the logic when parsing for the + // best emulator target to spawn (see below in `best_image`) + // This allows us to transitionally support both `android` and `avdmanager` binaries, + // depending on what SDK version the user has + if (response[i + 1].match(/Based\son:\s/)) { + img_obj['target'] = response[i + 1].split('Based on:')[1]; + if (img_obj['target'].match(/Tag\/ABI:\s/)) { + img_obj['target'] = img_obj['target'].split('Tag/ABI:')[0].replace('\r', '').trim(); + if (img_obj['target'].indexOf('(') > -1) { + img_obj['target'] = img_obj['target'].substr(0, img_obj['target'].indexOf('(') - 1).trim(); + } + } + var version_string = img_obj['target'].replace(/Android\s+/, ''); + + var api_level = android_sdk.version_string_to_api_level[version_string]; + if (api_level) { + img_obj['target'] += ' (API level ' + api_level + ')'; + } + } + } + if (response[i + 1].match(/Skin:\s/)) { + i++; + img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', ''); + } + + emulator_list.push(img_obj); + } + /* To just return a list of names use this + if (response[i].match(/Name:\s/)) { + emulator_list.push(response[i].split('Name: ')[1].replace('\r', ''); + }*/ + + } + return emulator_list; + }); +}; + +module.exports.list_images_using_android = function() { + return superspawn.spawn('android', ['list', 'avd']) .then(function(output) { var response = output.split('\n'); var emulator_list = []; @@ -97,6 +160,29 @@ module.exports.list_images = function() { }; /** + * Returns a Promise for a list of emulator images in the form of objects + * { + name : <emulator_name>, + device : <device>, + path : <path_to_emulator_image>, + target : <api_target>, + abi : <cpu>, + skin : <skin> + } + */ +module.exports.list_images = function() { + if (forgivingWhichSync('avdmanager')) { + return module.exports.list_images_using_avdmanager(); + } else if (forgivingWhichSync('android')) { + return module.exports.list_images_using_android(); + } else { + return Q().then(function() { + throw new CordovaError('Could not find either `android` or `avdmanager` on your $PATH! Are you sure the Android SDK is installed and available?'); + }); + } +}; + +/** * Will return the closest avd to the projects target * or undefined if no avds exist. * Returns a promise. @@ -109,8 +195,7 @@ module.exports.best_image = function() { var closest = 9999; var best = images[0]; - // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue - var project_target = require('./check_reqs').get_target().replace('android-', ''); + var project_target = check_reqs.get_target().replace('android-', ''); for (var i in images) { var target = images[i].target; if(target) { @@ -133,8 +218,9 @@ module.exports.list_started = function() { }; // Returns a promise. +// TODO: we should remove this, there's a more robust method under android_sdk.js module.exports.list_targets = function() { - return spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}) + return superspawn.spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}) .then(function(output) { var target_out = output.split('\n'); var targets = []; @@ -189,8 +275,7 @@ module.exports.start = function(emulator_ID, boot_timeout) { return best.name; } - // Loading check_reqs at run-time to avoid test-time vs run-time directory structure difference issue - var androidCmd = require('./check_reqs').getAbsoluteAndroidCmd(); + var androidCmd = check_reqs.getAbsoluteAndroidCmd(); return Q.reject(new CordovaError('No emulator images (avds) found.\n' + '1. Download desired System Image by running: ' + androidCmd + ' sdk\n' + '2. Create an AVD by running: ' + androidCmd + ' avd\n' + @@ -199,10 +284,13 @@ module.exports.start = function(emulator_ID, boot_timeout) { }).then(function(emulatorId) { return self.get_available_port() .then(function (port) { + // Figure out the directory the emulator binary runs in, and set the cwd to that directory. + // Workaround for https://code.google.com/p/android/issues/detail?id=235461 + var emulator_dir = path.dirname(shelljs.which('emulator')); var args = ['-avd', emulatorId, '-port', port]; // Don't wait for it to finish, since the emulator will probably keep running for a long time. child_process - .spawn('emulator', args, { stdio: 'inherit', detached: true }) + .spawn('emulator', args, { stdio: 'inherit', detached: true, cwd: emulator_dir }) .unref(); // wait for emulator to start @@ -294,7 +382,7 @@ module.exports.wait_for_boot = function(emulator_id, time_remaining) { module.exports.create_image = function(name, target) { console.log('Creating new avd named ' + name); if (target) { - return spawn('android', ['create', 'avd', '--name', name, '--target', target]) + return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', target]) .then(null, function(error) { console.error('ERROR : Failed to create emulator image : '); console.error(' Do you have the latest android targets including ' + target + '?'); @@ -302,7 +390,8 @@ module.exports.create_image = function(name, target) { }); } else { console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.'); - return spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]) + // TODO: there's a more robust method for finding targets in android_sdk.js + return superspawn.spawn('android', ['create', 'avd', '--name', name, '--target', this.list_targets()[0]]) .then(function() { // TODO: This seems like another error case, even though it always happens. console.error('ERROR : Unable to create an avd emulator, no targets found.'); diff --git a/StoneIsland/platforms/android/cordova/lib/list-devices b/StoneIsland/platforms/android/cordova/lib/list-devices index fa84d7f6..8e22c7f2 100755 --- a/StoneIsland/platforms/android/cordova/lib/list-devices +++ b/StoneIsland/platforms/android/cordova/lib/list-devices @@ -22,7 +22,7 @@ var devices = require('./device'); // Usage support for when args are given -require('../lib/check_reqs').check_android().then(function() { +require('./check_reqs').check_android().then(function() { devices.list().done(function(device_list) { device_list && device_list.forEach(function(dev) { console.log(dev); diff --git a/StoneIsland/platforms/android/cordova/lib/list-emulator-images b/StoneIsland/platforms/android/cordova/lib/list-emulator-images index 03c827fe..25e5c81a 100755 --- a/StoneIsland/platforms/android/cordova/lib/list-emulator-images +++ b/StoneIsland/platforms/android/cordova/lib/list-emulator-images @@ -22,7 +22,7 @@ var emulators = require('./emulator'); // Usage support for when args are given -require('../lib/check_reqs').check_android().then(function() { +require('./check_reqs').check_android().then(function() { emulators.list_images().done(function(emulator_list) { emulator_list && emulator_list.forEach(function(emu) { console.log(emu.name); diff --git a/StoneIsland/platforms/android/cordova/lib/list-started-emulators b/StoneIsland/platforms/android/cordova/lib/list-started-emulators index a890dec6..43ebda25 100755 --- a/StoneIsland/platforms/android/cordova/lib/list-started-emulators +++ b/StoneIsland/platforms/android/cordova/lib/list-started-emulators @@ -22,7 +22,7 @@ var emulators = require('./emulator'); // Usage support for when args are given -require('../lib/check_reqs').check_android().then(function() { +require('./check_reqs').check_android().then(function() { emulators.list_started().done(function(emulator_list) { emulator_list && emulator_list.forEach(function(emu) { console.log(emu); diff --git a/StoneIsland/platforms/android/cordova/lib/prepare.js b/StoneIsland/platforms/android/cordova/lib/prepare.js index 10a69ea3..504eb612 100644 --- a/StoneIsland/platforms/android/cordova/lib/prepare.js +++ b/StoneIsland/platforms/android/cordova/lib/prepare.js @@ -48,6 +48,7 @@ module.exports.prepare = function (cordovaProject, options) { .then(function () { updateIcons(cordovaProject, path.relative(cordovaProject.root, self.locations.res)); updateSplashes(cordovaProject, path.relative(cordovaProject.root, self.locations.res)); + updateFileResources(cordovaProject, path.relative(cordovaProject.root, self.locations.root)); }) .then(function () { events.emit('verbose', 'Prepared android project successfully'); @@ -72,6 +73,7 @@ module.exports.clean = function (options) { cleanWww(projectRoot, self.locations); cleanIcons(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res)); cleanSplashes(projectRoot, projectConfig, path.relative(projectRoot, self.locations.res)); + cleanFileResources(projectRoot, projectConfig, path.relative(projectRoot, self.locations.root)); }); }; @@ -401,6 +403,44 @@ function mapImageResources(rootDir, subDir, type, resourceName) { return pathMap; } + +function updateFileResources(cordovaProject, platformDir) { + var files = cordovaProject.projectConfig.getFileResources('android'); + + // if there are resource-file elements in config.xml + if (files.length === 0) { + events.emit('verbose', 'This app does not have additional resource files defined'); + return; + } + + var resourceMap = {}; + files.forEach(function(res) { + var targetPath = path.join(platformDir, res.target); + resourceMap[targetPath] = res.src; + }); + + events.emit('verbose', 'Updating resource files at ' + platformDir); + FileUpdater.updatePaths( + resourceMap, { rootDir: cordovaProject.root }, logFileOp); +} + + +function cleanFileResources(projectRoot, projectConfig, platformDir) { + var files = projectConfig.getFileResources('android'); + if (files.length > 0) { + events.emit('verbose', 'Cleaning resource files at ' + platformDir); + + var resourceMap = {}; + files.forEach(function(res) { + var filePath = path.join(platformDir, res.target); + resourceMap[filePath] = null; + }); + + FileUpdater.updatePaths( + resourceMap, { rootDir: projectRoot, all: true}, logFileOp); + } +} + /** * Gets and validates 'AndroidLaunchMode' prepference from config.xml. Returns * preference value and warns if it doesn't seems to be valid |
