diff --git a/bin/templates/cordova/lib/android_sdk.js b/bin/templates/cordova/lib/android_sdk.js index a1a806a6..58d301ef 100755 --- a/bin/templates/cordova/lib/android_sdk.js +++ b/bin/templates/cordova/lib/android_sdk.js @@ -1,3 +1,5 @@ + + /* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file @@ -66,38 +68,65 @@ module.exports.version_string_to_api_level = { '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); + return superspawn.spawn('android', ['list', 'targets']) + .then(function(stdout) { + var target_out = stdout.split('\n'); + var targets = []; + for (var i = target_out.length - 1; i >= 0; i--) { + if(target_out[i].match(/id:/)) { + targets.push(target_out[i].match(/"(.+)"/)[1]); + } + } + return targets; + }); }; -module.exports.list_targets_with_avdmanager = function() { - return superspawn.spawn('avdmanager', ['list', 'target']) - .then(parse_targets); +module.exports.list_targets_with_sdkmanager = function() { + return superspawn.spawn('sdkmanager', ['--list']) + .then(function(stdout) { + var parsing_installed_packages = false; + var lines = stdout.split('\n'); + var targets = []; + for (var i = 0, l = lines.length; i < l; i++) { + var line = lines[i]; + if (line.match(/Installed packages/)) { + parsing_installed_packages = true; + } else if (line.match(/Available Packages/) || line.match(/Available Updates/)) { + // we are done working through installed packages, exit + break; + } + if (parsing_installed_packages) { + // Match stock android platform + if (line.match(/platforms;android-\d+/)) { + targets.push(line.match(/(android-\d+)/)[1]); + } + // Match Google APIs + if (line.match(/addon-google_apis-google-\d+/)) { + var description = lines[i + 1]; + // munge description to match output from old android sdk tooling + var api_level = description.match(/Android (\d+)/); //[1]; + if (api_level) { + targets.push('Google Inc.:Google APIs:' + api_level[1]); + } + } + // TODO: match anything else? + } + } + return targets; + }); }; module.exports.list_targets = function() { - return module.exports.list_targets_with_avdmanager() + return module.exports.list_targets_with_android() .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(); + // there's a chance `android` no longer works. + // lets see if `sdkmanager` is available and we can figure it out + var avail_regex = /"?android"? command is no longer available/; + if (err.code && ((err.stdout && err.stdout.match(avail_regex)) || (err.stderr && err.stderr.match(avail_regex)))) { + return module.exports.list_targets_with_sdkmanager(); } else throw err; - }) - .then(function(targets) { + }).then(function(targets) { if (targets.length === 0) { return Q.reject(new Error('No android targets (SDKs) installed!')); } diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index bd613da2..e684e61f 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -35,7 +35,7 @@ function parseOpts(options, resolvedTarget, projectRoot) { options = options || {}; options.argv = nopt({ gradle: Boolean, - ant: Boolean, + studio: Boolean, prepenv: Boolean, versionCode: String, minSdkVersion: String, @@ -55,8 +55,8 @@ function parseOpts(options, resolvedTarget, projectRoot) { extraArgs: [] }; - if (options.argv.ant || options.argv.gradle) - ret.buildMethod = options.argv.ant ? 'ant' : 'gradle'; + if (options.argv.gradle || options.argv.studio) + ret.buildMethod = options.argv.studio ? 'studio' : 'gradle'; if (options.nobuild) ret.buildMethod = 'none'; diff --git a/bin/templates/cordova/lib/builders/GenericBuilder.js b/bin/templates/cordova/lib/builders/GenericBuilder.js index 16a69f57..0ae6fcab 100644 --- a/bin/templates/cordova/lib/builders/GenericBuilder.js +++ b/bin/templates/cordova/lib/builders/GenericBuilder.js @@ -27,6 +27,7 @@ var CordovaError = require('cordova-common').CordovaError; function GenericBuilder (projectDir) { this.root = projectDir || path.resolve(__dirname, '../../..'); this.binDirs = { + studio: path.join(this.root, 'app', 'build', 'outputs', 'apk'), gradle: path.join(this.root, 'build', 'outputs', 'apk') }; } @@ -52,6 +53,7 @@ GenericBuilder.prototype.findOutputApks = function(build_type, arch) { var self = this; return Object.keys(this.binDirs) .reduce(function (result, builderName) { + console.log('builderName:'+ builderName); var binDir = self.binDirs[builderName]; return result.concat(findOutputApksHelper(binDir, build_type, builderName === 'ant' ? null : arch)); }, []) diff --git a/bin/templates/cordova/lib/builders/GradleBuilder.js b/bin/templates/cordova/lib/builders/GradleBuilder.js index 809d5f7d..8357055f 100644 --- a/bin/templates/cordova/lib/builders/GradleBuilder.js +++ b/bin/templates/cordova/lib/builders/GradleBuilder.js @@ -79,6 +79,10 @@ GradleBuilder.prototype.runGradleWrapper = function(gradle_cmd) { } }; +/* + * We need to kill this in a fire. + */ + GradleBuilder.prototype.readProjectProperties = function () { function findAllUniq(data, r) { var s = {}; @@ -117,6 +121,9 @@ GradleBuilder.prototype.prepBuildFiles = function() { var pluginBuildGradle = path.join(this.root, 'cordova', 'lib', 'plugin-build.gradle'); var propertiesObj = this.readProjectProperties(); var subProjects = propertiesObj.libs; + + // Check and copy the gradle file into the subproject. + // Called by the loop below this function def. var checkAndCopy = function(subProject, root) { var subProjectGradle = path.join(root, subProject, 'build.gradle'); // This is the future-proof way of checking if a file exists @@ -127,11 +134,16 @@ GradleBuilder.prototype.prepBuildFiles = function() { shell.cp('-f', pluginBuildGradle, subProjectGradle); } }; + + // Some dependencies on Android don't use gradle, or don't have default + // gradle files. This copies a dummy gradle file into them for (var i = 0; i < subProjects.length; ++i) { - if (subProjects[i] !== 'CordovaLib') { + if (subProjects[i] !== 'CordovaLib' && subProjects[i] !== 'app') { checkAndCopy(subProjects[i], this.root); } } + + var name = this.extractRealProjectNameFromManifest(); //Remove the proj.id/name- prefix from projects: https://issues.apache.org/jira/browse/CB-9149 var settingsGradlePaths = subProjects.map(function(p){ @@ -151,6 +163,11 @@ GradleBuilder.prototype.prepBuildFiles = function() { var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8'); var depsList = ''; var root = this.root; + + + // Cordova Plugins can be written as library modules that would use Cordova as a + // dependency. Because we need to make sure that Cordova is compiled only once for + // dexing, we make sure to exclude CordovaLib from these modules var insertExclude = function(p) { var gradlePath = path.join(root, p, 'build.gradle'); var projectGradleFile = fs.readFileSync(gradlePath, 'utf-8'); @@ -161,6 +178,7 @@ GradleBuilder.prototype.prepBuildFiles = function() { depsList +='\n'; } }; + subProjects.forEach(function(p) { console.log('Subproject Path: ' + p); var libName=p.replace(/[/\\]/g, ':').replace(name+'-',''); @@ -169,11 +187,14 @@ GradleBuilder.prototype.prepBuildFiles = function() { depsList += ' releaseCompile(project(path: "' + libName + '", configuration: "release"))'; insertExclude(p); }); + + // For why we do this mapping: https://issues.apache.org/jira/browse/CB-8390 var SYSTEM_LIBRARY_MAPPINGS = [ [/^\/?extras\/android\/support\/(.*)$/, 'com.android.support:support-$1:+'], [/^\/?google\/google_play_services\/libproject\/google-play-services_lib\/?$/, 'com.google.android.gms:play-services:+'] ]; + propertiesObj.systemLibs.forEach(function(p) { var mavenRef; // It's already in gradle form if it has two ':'s @@ -193,6 +214,9 @@ GradleBuilder.prototype.prepBuildFiles = function() { } depsList += ' compile "' + mavenRef + '"\n'; }); + + //This code is dangerous and actually writes gradle declarations directly into the build.gradle + //Try not to mess with this if possible buildGradle = buildGradle.replace(/(SUB-PROJECT DEPENDENCIES START)[\s\S]*(\/\/ SUB-PROJECT DEPENDENCIES END)/, '$1\n' + depsList + ' $2'); var includeList = ''; propertiesObj.gradleIncludes.forEach(function(includePath) { diff --git a/bin/templates/cordova/lib/builders/StudioBuilder.js b/bin/templates/cordova/lib/builders/StudioBuilder.js index 66ff0efa..2d3e3e51 100644 --- a/bin/templates/cordova/lib/builders/StudioBuilder.js +++ b/bin/templates/cordova/lib/builders/StudioBuilder.js @@ -37,7 +37,7 @@ var TEMPLATE = function StudioBuilder (projectRoot) { GenericBuilder.call(this, projectRoot); - this.binDirs = {gradle: this.binDirs.gradle}; + this.binDirs = {gradle: this.binDirs.studio}; } util.inherits(StudioBuilder, GenericBuilder); @@ -81,6 +81,9 @@ StudioBuilder.prototype.runGradleWrapper = function(gradle_cmd) { StudioBuilder.prototype.readProjectProperties = function () { + + console.log("Do we even have to do this?"); + function findAllUniq(data, r) { var s = {}; var m; @@ -145,10 +148,15 @@ StudioBuilder.prototype.prepBuildFiles = function() { return str; }); + + // We really shouldn't do this. // Write the settings.gradle file. - fs.writeFileSync(path.join(this.root, 'settings.gradle'), + /* + fs.writeFileSync(path.join(this.root, 'settings.gradle'), '// GENERATED FILE - DO NOT EDIT\n' + 'include ":"\n' + settingsGradlePaths.join('')); + */ + // Update dependencies within build.gradle. var buildGradle = fs.readFileSync(path.join(this.root, 'build.gradle'), 'utf8'); var depsList = ''; @@ -212,21 +220,6 @@ StudioBuilder.prototype.prepEnv = function(opts) { }).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); - } else { - shell.rm('-f', path.join(self.root, 'gradlew')); - shell.cp(path.join(wrapperDir, 'gradlew'), self.root); - } - 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. diff --git a/bin/templates/cordova/lib/device.js b/bin/templates/cordova/lib/device.js index 4b171db6..47828f7f 100644 --- a/bin/templates/cordova/lib/device.js +++ b/bin/templates/cordova/lib/device.js @@ -85,7 +85,7 @@ module.exports.install = function(target, buildResults) { return module.exports.resolveTarget(target); }).then(function(resolvedTarget) { var apk_path = build.findBestApkForArchitecture(buildResults, resolvedTarget.arch); - var manifest = new AndroidManifest(path.join(__dirname, '../../AndroidManifest.xml')); + var manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml')); var pkgName = manifest.getPackageId(); var launchName = pkgName + '/.' + manifest.getActivity().getName(); events.emit('log', 'Using apk: ' + apk_path); diff --git a/bin/templates/cordova/lib/emulator.js b/bin/templates/cordova/lib/emulator.js index 22209aa0..2f36b9a8 100644 --- a/bin/templates/cordova/lib/emulator.js +++ b/bin/templates/cordova/lib/emulator.js @@ -116,7 +116,7 @@ module.exports.list_images_using_avdmanager = function () { }; module.exports.list_images_using_android = function() { - return superspawn.spawn('android', ['list', 'avd']) + return superspawn.spawn('android', ['list', 'avds']) .then(function(output) { var response = output.split('\n'); var emulator_list = []; @@ -171,10 +171,20 @@ module.exports.list_images_using_android = function() { } */ module.exports.list_images = function() { - if (forgivingWhichSync('avdmanager')) { + if (forgivingWhichSync('android')) { + return module.exports.list_images_using_android() + .catch(function(err) { + // try to use `avdmanager` in case `android` reports it is no longer available. + // this likely means the target machine is using a newer version of + // the android sdk, and possibly `avdmanager` is available. + if (err.code == 1 && err.stdout.indexOf('android command is no longer available')) { + return module.exports.list_images_using_avdmanager(); + } else { + throw err; + } + }); + } else 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?'); @@ -218,7 +228,6 @@ 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 superspawn.spawn('android', ['list', 'targets'], {cwd: os.tmpdir()}) .then(function(output) { @@ -390,7 +399,6 @@ 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.'); - // 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.