commonroutesServer/www/platforms/android/cordova/lib/check_reqs.js
www/platforms/android/cordova/lib/check_reqs.jsRaw
1#!/usr/bin/env node
2
3/*
4 Licensed to the Apache Software Foundation (ASF) under one
5 or more contributor license agreements. See the NOTICE file
6 distributed with this work for additional information
7 regarding copyright ownership. The ASF licenses this file
8 to you under the Apache License, Version 2.0 (the
9 "License"); you may not use this file except in compliance
10 with the License. You may obtain a copy of the License at
11
12 http://www.apache.org/licenses/LICENSE-2.0
13
14 Unless required by applicable law or agreed to in writing,
15 software distributed under the License is distributed on an
16 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 KIND, either express or implied. See the License for the
18 specific language governing permissions and limitations
19 under the License.
20*/
21
22/* jshint sub:true */
23
24var shelljs = require('shelljs'),
25 child_process = require('child_process'),
26 Q = require('q'),
27 path = require('path'),
28 fs = require('fs'),
29 ROOT = path.join(__dirname, '..', '..');
30var CordovaError = require('cordova-common').CordovaError;
31
32var isWindows = process.platform == 'win32';
33
34function forgivingWhichSync(cmd) {
35 try {
36 return fs.realpathSync(shelljs.which(cmd));
37 } catch (e) {
38 return '';
39 }
40}
41
42function tryCommand(cmd, errMsg, catchStderr) {
43 var d = Q.defer();
44 child_process.exec(cmd, function(err, stdout, stderr) {
45 if (err) d.reject(new CordovaError(errMsg));
46 // Sometimes it is necessary to return an stderr instead of stdout in case of success, since
47 // some commands prints theirs output to stderr instead of stdout. 'javac' is the example
48 else d.resolve((catchStderr ? stderr : stdout).trim());
49 });
50 return d.promise;
51}
52
53// Get valid target from framework/project.properties
54module.exports.get_target = function() {
55 function extractFromFile(filePath) {
56 var target = shelljs.grep(/\btarget=/, filePath);
57 if (!target) {
58 throw new Error('Could not find android target within: ' + filePath);
59 }
60 return target.split('=')[1].trim();
61 }
62 if (fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) {
63 return extractFromFile(path.join(ROOT, 'framework', 'project.properties'));
64 }
65 if (fs.existsSync(path.join(ROOT, 'project.properties'))) {
66 // if no target found, we're probably in a project and project.properties is in ROOT.
67 return extractFromFile(path.join(ROOT, 'project.properties'));
68 }
69 throw new Error('Could not find android target. File missing: ' + path.join(ROOT, 'project.properties'));
70};
71
72// Returns a promise. Called only by build and clean commands.
73module.exports.check_ant = function() {
74 return tryCommand('ant -version', 'Failed to run "ant -version", make sure you have ant installed and added to your PATH.')
75 .then(function (output) {
76 // Parse Ant version from command output
77 return /version ((?:\d+\.)+(?:\d+))/i.exec(output)[1];
78 });
79};
80
81// Returns a promise. Called only by build and clean commands.
82module.exports.check_gradle = function() {
83 var sdkDir = process.env['ANDROID_HOME'];
84 if (!sdkDir)
85 return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Could not find Android SDK directory.\n' +
86 'Might need to install Android SDK or set up \'ANDROID_HOME\' env variable.'));
87
88 var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper');
89 if (!fs.existsSync(wrapperDir)) {
90 return Q.reject(new CordovaError('Could not find gradle wrapper within Android SDK. Might need to update your Android SDK.\n' +
91 'Looked here: ' + wrapperDir));
92 }
93 return Q.when();
94};
95
96// Returns a promise.
97module.exports.check_java = function() {
98 var javacPath = forgivingWhichSync('javac');
99 var hasJavaHome = !!process.env['JAVA_HOME'];
100 return Q().then(function() {
101 if (hasJavaHome) {
102 // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh).
103 if (!javacPath) {
104 process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin');
105 }
106 } else {
107 if (javacPath) {
108 var msg = 'Failed to find \'JAVA_HOME\' environment variable. Try setting setting it manually.';
109 // OS X has a command for finding JAVA_HOME.
110 if (fs.existsSync('/usr/libexec/java_home')) {
111 return tryCommand('/usr/libexec/java_home', msg)
112 .then(function(stdout) {
113 process.env['JAVA_HOME'] = stdout.trim();
114 });
115 } else {
116 // See if we can derive it from javac's location.
117 // fs.realpathSync is require on Ubuntu, which symplinks from /usr/bin -> JDK
118 var maybeJavaHome = path.dirname(path.dirname(javacPath));
119 if (fs.existsSync(path.join(maybeJavaHome, 'lib', 'tools.jar'))) {
120 process.env['JAVA_HOME'] = maybeJavaHome;
121 } else {
122 throw new CordovaError(msg);
123 }
124 }
125 } else if (isWindows) {
126 // Try to auto-detect java in the default install paths.
127 var oldSilent = shelljs.config.silent;
128 shelljs.config.silent = true;
129 var firstJdkDir =
130 shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] ||
131 shelljs.ls('C:\\Program Files\\java\\jdk*')[0] ||
132 shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0];
133 shelljs.config.silent = oldSilent;
134 if (firstJdkDir) {
135 // shelljs always uses / in paths.
136 firstJdkDir = firstJdkDir.replace(/\//g, path.sep);
137 if (!javacPath) {
138 process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin');
139 }
140 process.env['JAVA_HOME'] = firstJdkDir;
141 }
142 }
143 }
144 }).then(function() {
145 var msg =
146 'Failed to run "javac -version", make sure that you have a JDK installed.\n' +
147 'You can get it from: http://www.oracle.com/technetwork/java/javase/downloads.\n';
148 if (process.env['JAVA_HOME']) {
149 msg += 'Your JAVA_HOME is invalid: ' + process.env['JAVA_HOME'] + '\n';
150 }
151 // We use tryCommand with catchStderr = true, because
152 // javac writes version info to stderr instead of stdout
153 return tryCommand('javac -version', msg, true)
154 .then(function (output) {
155 var match = /javac ((?:\d+\.)+(?:\d+))/i.exec(output);
156 return match && match[1];
157 });
158 });
159};
160
161// Returns a promise.
162module.exports.check_android = function() {
163 return Q().then(function() {
164 var androidCmdPath = forgivingWhichSync('android');
165 var adbInPath = !!forgivingWhichSync('adb');
166 var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']);
167 function maybeSetAndroidHome(value) {
168 if (!hasAndroidHome && fs.existsSync(value)) {
169 hasAndroidHome = true;
170 process.env['ANDROID_HOME'] = value;
171 }
172 }
173 if (!hasAndroidHome && !androidCmdPath) {
174 if (isWindows) {
175 // Android Studio 1.0 installer
176 maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'sdk'));
177 maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'sdk'));
178 // Android Studio pre-1.0 installer
179 maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-studio', 'sdk'));
180 maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-studio', 'sdk'));
181 // Stand-alone installer
182 maybeSetAndroidHome(path.join(process.env['LOCALAPPDATA'], 'Android', 'android-sdk'));
183 maybeSetAndroidHome(path.join(process.env['ProgramFiles'], 'Android', 'android-sdk'));
184 } else if (process.platform == 'darwin') {
185 // Android Studio 1.0 installer
186 maybeSetAndroidHome(path.join(process.env['HOME'], 'Library', 'Android', 'sdk'));
187 // Android Studio pre-1.0 installer
188 maybeSetAndroidHome('/Applications/Android Studio.app/sdk');
189 // Stand-alone zip file that user might think to put under /Applications
190 maybeSetAndroidHome('/Applications/android-sdk-macosx');
191 maybeSetAndroidHome('/Applications/android-sdk');
192 }
193 if (process.env['HOME']) {
194 // Stand-alone zip file that user might think to put under their home directory
195 maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk-macosx'));
196 maybeSetAndroidHome(path.join(process.env['HOME'], 'android-sdk'));
197 }
198 }
199 if (hasAndroidHome && !androidCmdPath) {
200 process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools');
201 }
202 if (androidCmdPath && !hasAndroidHome) {
203 var parentDir = path.dirname(androidCmdPath);
204 var grandParentDir = path.dirname(parentDir);
205 if (path.basename(parentDir) == 'tools') {
206 process.env['ANDROID_HOME'] = path.dirname(parentDir);
207 hasAndroidHome = true;
208 } else if (fs.existsSync(path.join(grandParentDir, 'tools', 'android'))) {
209 process.env['ANDROID_HOME'] = grandParentDir;
210 hasAndroidHome = true;
211 } else {
212 throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
213 'Detected \'android\' command at ' + parentDir + ' but no \'tools\' directory found near.\n' +
214 'Try reinstall Android SDK or update your PATH to include path to valid SDK directory.');
215 }
216 }
217 if (hasAndroidHome && !adbInPath) {
218 process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'platform-tools');
219 }
220 if (!process.env['ANDROID_HOME']) {
221 throw new CordovaError('Failed to find \'ANDROID_HOME\' environment variable. Try setting setting it manually.\n' +
222 'Failed to find \'android\' command in your \'PATH\'. Try update your \'PATH\' to include path to valid SDK directory.');
223 }
224 if (!fs.existsSync(process.env['ANDROID_HOME'])) {
225 throw new CordovaError('\'ANDROID_HOME\' environment variable is set to non-existent path: ' + process.env['ANDROID_HOME'] +
226 '\nTry update it manually to point to valid SDK directory.');
227 }
228 });
229};
230
231module.exports.getAbsoluteAndroidCmd = function () {
232 var cmd = forgivingWhichSync('android');
233 if (process.platform === 'win32') {
234 return '"' + cmd + '"';
235 }
236 return cmd.replace(/(\s)/g, '\\$1');
237};
238
239module.exports.check_android_target = function(originalError) {
240 // valid_target can look like:
241 // android-19
242 // android-L
243 // Google Inc.:Google APIs:20
244 // Google Inc.:Glass Development Kit Preview:20
245 var valid_target = module.exports.get_target();
246 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.';
247 return tryCommand('android list targets --compact', msg)
248 .then(function(output) {
249 var targets = output.split('\n');
250 if (targets.indexOf(valid_target) >= 0) {
251 return targets;
252 }
253
254 var androidCmd = module.exports.getAbsoluteAndroidCmd();
255 var msg = 'Please install Android target: "' + valid_target + '".\n\n' +
256 'Hint: Open the SDK manager by running: ' + androidCmd + '\n' +
257 'You will require:\n' +
258 '1. "SDK Platform" for ' + valid_target + '\n' +
259 '2. "Android SDK Platform-tools (latest)\n' +
260 '3. "Android SDK Build-tools" (latest)';
261 if (originalError) {
262 msg = originalError + '\n' + msg;
263 }
264 throw new CordovaError(msg);
265 });
266};
267
268// Returns a promise.
269module.exports.run = function() {
270 return Q.all([this.check_java(), this.check_android()])
271 .then(function() {
272 console.log('ANDROID_HOME=' + process.env['ANDROID_HOME']);
273 console.log('JAVA_HOME=' + process.env['JAVA_HOME']);
274 });
275};
276
277/**
278 * Object thar represents one of requirements for current platform.
279 * @param {String} id The unique identifier for this requirements.
280 * @param {String} name The name of requirements. Human-readable field.
281 * @param {String} version The version of requirement installed. In some cases could be an array of strings
282 * (for example, check_android_target returns an array of android targets installed)
283 * @param {Boolean} installed Indicates whether the requirement is installed or not
284 */
285var Requirement = function (id, name, version, installed) {
286 this.id = id;
287 this.name = name;
288 this.installed = installed || false;
289 this.metadata = {
290 version: version,
291 };
292};
293
294/**
295 * Methods that runs all checks one by one and returns a result of checks
296 * as an array of Requirement objects. This method intended to be used by cordova-lib check_reqs method
297 *
298 * @return Promise<Requirement[]> Array of requirements. Due to implementation, promise is always fulfilled.
299 */
300module.exports.check_all = function() {
301
302 var requirements = [
303 new Requirement('java', 'Java JDK'),
304 new Requirement('androidSdk', 'Android SDK'),
305 new Requirement('androidTarget', 'Android target'),
306 new Requirement('gradle', 'Gradle')
307 ];
308
309 var checkFns = [
310 this.check_java,
311 this.check_android,
312 this.check_android_target,
313 this.check_gradle
314 ];
315
316 // Then execute requirement checks one-by-one
317 return checkFns.reduce(function (promise, checkFn, idx) {
318 // Update each requirement with results
319 var requirement = requirements[idx];
320 return promise.then(checkFn)
321 .then(function (version) {
322 requirement.installed = true;
323 requirement.metadata.version = version;
324 }, function (err) {
325 requirement.metadata.reason = err instanceof Error ? err.message : err;
326 });
327 }, Q())
328 .then(function () {
329 // When chain is completed, return requirements array to upstream API
330 return requirements;
331 });
332};