From 4bf705a3d39b34400388265381a9975b246e3779 Mon Sep 17 00:00:00 2001 From: Vladimir Kotikov Date: Wed, 27 May 2015 12:41:07 +0300 Subject: [PATCH] CB-8954 Adds `requirements` command support to check_reqs module --- bin/lib/check_reqs.js | 128 +++++++++++++++++++++++++++++++++--------- bin/lib/create.js | 2 +- 2 files changed, 103 insertions(+), 27 deletions(-) diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 92ddc19a..46c3213f 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -40,11 +40,13 @@ function forgivingWhichSync(cmd) { } } -function tryCommand(cmd, errMsg) { +function tryCommand(cmd, errMsg, catchStderr) { var d = Q.defer(); child_process.exec(cmd, function(err, stdout, stderr) { if (err) d.reject(new Error(errMsg)); - else d.resolve(stdout); + // Sometimes it is necessary to return an stderr instead of stdout in case of success, since + // some commands prints theirs output to stderr instead of stdout. 'javac' is the example + else d.resolve((catchStderr ? stderr : stdout).trim()); }); return d.promise; } @@ -70,18 +72,25 @@ module.exports.get_target = function() { // 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.'); + return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.') + .then(function (output) { + // Parse Ant version from command output + return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; + }); }; // Returns a promise. Called only by build and clean commands. module.exports.check_gradle = function() { var sdkDir = process.env['ANDROID_HOME']; - var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); - if (!fs.existsSync(wrapperDir)) { - return Q.reject(new Error('Could not find gradle wrapper within android sdk. Might need to update your Android SDK.\n' + - 'Looked here: ' + wrapperDir)); - } - return Q.when(); + var message = 'Could not find gradle wrapper within Android SDK. '; + if (!sdkDir) return Q.reject(message + 'Might need to install Android SDK or set up \'ADROID_HOME\' env variable.'); + var wrapper = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper', 'gradlew'); + return tryCommand(wrapper + ' -v', message + 'Might need to update your Android SDK.\n' + + 'Looked here: ' + path.basename(wrapper)) + .then(function (output) { + // Parse Gradle version from command output + return/^gradle ((?:\d+\.)+(?:\d+))/gim.exec(output)[1]; + }); }; // Returns a promise. @@ -96,9 +105,10 @@ 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', 'Failed to run: /usr/libexec/java_home') + return tryCommand('/usr/libexec/java_home', msg) .then(function(stdout) { process.env['JAVA_HOME'] = stdout.trim(); }); @@ -109,7 +119,7 @@ module.exports.check_java = function() { if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) { process.env['JAVA_HOME'] = maybeJavaHome; } else { - throw new Error('Could not find JAVA_HOME. Try setting the environment variable manually'); + throw new Error(msg); } } } else if (isWindows) { @@ -140,7 +150,11 @@ module.exports.check_java = function() { } return tryCommand('java -version', msg) .then(function() { - return tryCommand('javac -version', msg); + // 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) { + return /^javac ((?:\d+\.)+(?:\d+))/i.exec(output)[1]; }); }); }; @@ -196,20 +210,22 @@ module.exports.check_android = function() { process.env['ANDROID_HOME'] = grandParentDir; hasAndroidHome = true; } else { - throw new Error('ANDROID_HOME is not set and no "tools" directory found at ' + parentDir); + throw new Error('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 && !adbInPath) { process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools'); } if (!process.env['ANDROID_HOME']) { - throw new Error('ANDROID_HOME is not set and "android" command not in your PATH. You must fulfill at least one of these conditions.'); + throw new Error('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' + + 'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.'); } if (!fs.existsSync(process.env['ANDROID_HOME'])) { - throw new Error('ANDROID_HOME is set to a non-existant path: ' + process.env['ANDROID_HOME']); + throw new Error('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] + + '\nTry update it manually to point to valid SDK directory.'); } - // Check that the target sdk level is installed. - return module.exports.check_android_target(module.exports.get_target()); }); }; @@ -223,27 +239,87 @@ module.exports.check_android_target = function(valid_target) { // android-L // Google Inc.:Google APIs:20 // Google Inc.:Glass Development Kit Preview:20 + if (!valid_target) 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) { - if (output.split('\n').indexOf(valid_target) == -1) { - var androidCmd = module.exports.getAbsoluteAndroidCmd(); - throw new Error('Please install Android target: "' + valid_target + '".\n\n' + - 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' + - 'You will require:\n' + - '1. "SDK Platform" for ' + valid_target + '\n' + - '2. "Android SDK Platform-tools (latest)\n' + - '3. "Android SDK Build-tools" (latest)'); + var targets = output.split('\n'); + if (targets.indexOf(valid_target) >= 0) { + return targets; } + + var androidCmd = module.exports.getAbsoluteAndroidCmd(); + throw new Error('Please install Android target: "' + valid_target + '".\n\n' + + 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' + + 'You will require:\n' + + '1. "SDK Platform" for ' + valid_target + '\n' + + '2. "Android SDK Platform-tools (latest)\n' + + '3. "Android SDK Build-tools" (latest)'); }); }; // Returns a promise. module.exports.run = function() { - return Q.all([this.check_java(), this.check_android()]) + return Q.all([this.check_java(), this.check_android(), this.check_android_target()]) .then(function() { console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']); console.log('JAVA_HOME=' + process.env['JAVA_HOME']); }); }; +/** + * Object thar represents one of requirements for current platform. + * @param {String} id The unique identifier for this requirements. + * @param {String} name The name of requirements. Human-readable field. + * @param {String} version The version of requirement installed. In some cases could be an array of strings + * (for example, check_android_target returns an array of android targets installed) + * @param {Boolean} installed Indicates whether the requirement is installed or not + */ +var Requirement = function (id, name, version, installed) { + this.id = id; + this.name = name; + this.installed = installed || false; + this.metadata = { + version: version, + }; +}; + +/** + * Methods that runs all checks one by one and returns a result of checks + * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method + * + * @return Promise Array of requirements. Due to implementation, promise is always fulfilled. + */ +module.exports.check_all = function() { + + var requirements = [ + new Requirement('java', 'Java JDK'), + new Requirement('androidSdk', 'Android SDK'), + new Requirement('androidTarget', 'Android target'), + new Requirement('gradle', 'Gradle') + ]; + + var checkFns = [ + this.check_java, + this.check_android, + this.check_android_target, + this.check_gradle + ]; + + // Then execute requirement checks one-by-one + return checkFns.reduce(function (promise, checkFn, idx) { + // Update each requirement with results + var requirement = requirements[idx]; + return promise.then(checkFn) + .then(function (version) { + requirement.installed = true; + requirement.metadata.version = version; + }, function (err) { + requirement.metadata.reason = err; + }); + }, Q()) + .then(function () { + // When chain is completed, return requirements array to upstream API + return requirements; + }); +}; diff --git a/bin/lib/create.js b/bin/lib/create.js index c7f5656a..08158701 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -128,7 +128,7 @@ function copyScripts(projectPath) { // Copy in the new ones. shell.cp('-r', srcScriptsDir, projectPath); shell.cp('-r', path.join(ROOT, 'bin', 'node_modules'), destScriptsDir); - shell.cp(path.join(ROOT, 'bin', 'check_reqs'), path.join(destScriptsDir, 'check_reqs')); + shell.cp(path.join(ROOT, 'bin', 'check_reqs*'), destScriptsDir); shell.cp(path.join(ROOT, 'bin', 'lib', 'check_reqs.js'), path.join(projectPath, 'cordova', 'lib', 'check_reqs.js')); shell.cp(path.join(ROOT, 'bin', 'android_sdk_version'), path.join(destScriptsDir, 'android_sdk_version')); shell.cp(path.join(ROOT, 'bin', 'lib', 'android_sdk_version.js'), path.join(projectPath, 'cordova', 'lib', 'android_sdk_version.js'));