diff --git a/bin/templates/cordova/build b/bin/templates/cordova/build index 2d589018..367bf7d2 100755 --- a/bin/templates/cordova/build +++ b/bin/templates/cordova/build @@ -24,12 +24,16 @@ var build = require('./lib/build'), args = process.argv; // Support basic help commands -if(args[2] == '--help' || args[2] == '/?' || args[2] == '-h' || - args[2] == 'help' || args[2] == '-help' || args[2] == '/help') { +if(args[2] == '--help' || + args[2] == '/?' || + args[2] == '-h' || + args[2] == 'help' || + args[2] == '-help' || + args[2] == '/help') { build.help(); } else { reqs.run().done(function() { - return build.run(args[2]); + return build.run.call(build.run, args.slice(2)); }, function(err) { console.error(err); process.exit(2); diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 5134f412..3d0ae872 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -27,39 +27,164 @@ var shell = require('shelljs'), ROOT = path.join(__dirname, '..', '..'); var check_reqs = require('./check_reqs'); +// Globals +var build_type, + build_method; + +function find_files(directory, predicate) { + if (fs.existsSync(directory)) { + var candidates = fs.readdirSync(directory).filter(predicate).map(function(p) { + p = path.join(directory, p); + return { p: p, t: fs.statSync(p).mtime }; + }).sort(function(a,b) { + return a.t > b.t ? -1 : + a.t < b.t ? 1 : 0; + }).map(function(p) { return p.p; }); + return candidates; + } else { + console.error('ERROR : unable to find project ' + directory + ' directory, could not locate .apk'); + process.exit(2); + } +} function hasCustomRules() { return fs.existsSync(path.join(ROOT, 'custom_rules.xml')); } -module.exports.getAntArgs = function(cmd) { - var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; - // custom_rules.xml is required for incremental builds. - if (hasCustomRules()) { - args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); + +module.exports.builders = { + ant: { + getArgs: function(cmd) { + var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; + // custom_rules.xml is required for incremental builds. + if (hasCustomRules()) { + args.push('-Dout.dir=ant-build', '-Dgen.absolute.dir=ant-gen'); + } + try { + // Specify sdk dir in case local properties are missing + args.push('-Dsdk.dir='+path.join(which.sync('android'), '../..')); + } catch(e) { + // Can't find android; don't push arg: assume all is okay + } + return args; + }, + + /* + * Builds the project with ant. + * Returns a promise. + */ + build: function(build_type) { + var builder = this; + var args = builder.getArgs(build_type == "debug" ? 'debug' : 'release'); + return Q().then(function() { + return spawn('ant', args); + }).then(function() { + return builder.getOutputFiles(); + }); + }, + + // Find the recently-generated output APK files + // Ant only generates one output file; return it. + getOutputFiles: function() { + var binDir; + if(hasCustomRules()) { + binDir = path.join(ROOT, 'ant-build'); + } else { + binDir = path.join(ROOT, 'bin'); + } + var candidates = find_files(binDir, function(candidate) { return path.extname(candidate) == '.apk'; }); + if (candidates.length === 0) { + console.error('ERROR : No .apk found in ' + binDir + ' directory'); + process.exit(2); + } + console.log('Using apk: ' + candidates[0]); + return [candidates[0]]; + } + }, + gradle: { + getArgs: function(cmd) { + var args = [cmd, '-b', path.join(ROOT, 'build.gradle')]; + return args; + }, + + /* + * Builds the project with gradle. + * Returns a promise. + */ + build: function(build_type) { + var builder = this; + var wrapper = path.join(ROOT, 'gradlew'); + var args = builder.getArgs('build'); + return Q().then(function() { + return spawn(wrapper, args); + }).then(function() { + return builder.getOutputFiles(build_type); + }); + }, + + // Find the recently-generated output APK files + // Gradle can generate multiple output files; return all of them. + getOutputFiles: function(build_type) { + var binDir = path.join(ROOT, 'build', 'apk'); + var candidates = find_files(binDir, function(candidate) { + // Need to choose between release and debug .apk. + if (build_type === 'debug') { + return (path.extname(candidate) == '.apk' && candidate.indexOf('-debug-') >= 0); + } + if (build_type === 'release') { + return (path.extname(candidate) == '.apk' && candidate.indexOf('-release-') >= 0); + } + return path.extname(candidate) == '.apk'; + }); + return candidates; + } } - return args; }; /* - * Builds the project with ant. + * Builds the project with the specifed options * Returns a promise. */ -module.exports.run = function(build_type) { - //default build type - build_type = typeof build_type !== 'undefined' ? build_type : "--debug"; - var args = module.exports.getAntArgs('debug'); - switch(build_type) { - case '--debug' : - break; - case '--release' : - args[0] = 'release'; - break; - case '--nobuild' : - console.log('Skipping build...'); - return Q(); - default : - return Q.reject('Build option \'' + build_type + '\' not recognized.'); +module.exports.run = function(options) { + + // Backwards-compatibility: Allow a single string argument + if (typeof options == "string") options = [options]; + + // Iterate through command line options + for (var i=0; options && (i < options.length); ++i) { + if (options[i].substring && options[i].substring(0,2) == "--") { + var option = options[i].substring(2); + switch(option) { + case 'debug': + case 'release': + if (build_type) { + return Q.reject('Multiple build types (' + build_type + ' and ' + option + ') specified.'); + } + build_type = option; + break; + case 'ant': + case 'gradle': + if (build_method) { + return Q.reject('Multiple build methods (' + build_method + ' and ' + option + ') specified.'); + } + build_method = option; + break; + case 'nobuild' : + console.log('Skipping build...'); + return Q(); + default : + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } + } else { + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } } + // Defaults + build_type = build_type || "debug"; + build_method = build_method || process.env.ANDROID_BUILD || "ant"; + + // Get the builder + var builder = module.exports.builders[build_method]; + // Without our custom_rules.xml, we need to clean before building. var ret; if (!hasCustomRules()) { @@ -68,50 +193,48 @@ module.exports.run = function(build_type) { } else { ret = check_reqs.check_ant(); } + + // Return a promise for the actual build return ret.then(function() { - return spawn('ant', args); + return builder.build.call(builder, build_type); + }).then(function(apkFiles) { + var outputDir = path.join(ROOT, 'out'); + try { + fs.mkdirSync(outputDir); + } catch (e) { + if (e.code != "EEXIST") { + throw e; + } + } + for (var i=0; i < apkFiles.length; ++i) { + shell.cp('-f', apkFiles[i], path.join(outputDir, path.basename(apkFiles[i]))); + } }); -} +}; /* * Gets the path to the apk file, if not such file exists then * the script will error out. (should we error or just return undefined?) + * This is called by the run script to install the apk to the device */ -module.exports.get_apk = function() { - var binDir = ''; - if(!hasCustomRules()) { - binDir = path.join(ROOT, 'bin'); - } else { - binDir = path.join(ROOT, 'ant-build'); - } - if (fs.existsSync(binDir)) { - var candidates = fs.readdirSync(binDir).filter(function(p) { - // Need to choose between release and debug .apk. - return path.extname(p) == '.apk'; - }).map(function(p) { - p = path.join(binDir, p); - return { p: p, t: fs.statSync(p).mtime }; - }).sort(function(a,b) { - return a.t > b.t ? -1 : - a.t < b.t ? 1 : 0; - }); - if (candidates.length === 0) { - console.error('ERROR : No .apk found in ' + binDir + ' directory'); - process.exit(2); - } - console.log('Using apk: ' + candidates[0].p); - return candidates[0].p; - } else { - console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk'); +module.exports.get_apk = function(build_type) { + var outputDir = path.join(ROOT, 'out'); + var candidates = find_files(outputDir, function() { return true; }); + if (candidates.length === 0) { + console.error('ERROR : No .apk found in ' + outputDir + ' directory'); process.exit(2); } -} + console.log('Using apk: ' + candidates[0]); + return candidates[0]; +}; module.exports.help = function() { console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [build_type]'); console.log('Build Types : '); - console.log(' \'--debug\': Default build, will build project in using ant debug'); - console.log(' \'--release\': will build project using ant release'); + console.log(' \'--debug\': Default build, will build project in debug mode'); + console.log(' \'--release\': will build project for release'); + console.log(' \'--ant\': Default build, will build project with ant'); + console.log(' \'--gradle\': will build project with gradle'); console.log(' \'--nobuild\': will skip build process (can be used with run command)'); process.exit(0); -} +};