diff --git a/bin/lib/check_reqs.js b/bin/lib/check_reqs.js index 8e57a119..40f3ade3 100644 --- a/bin/lib/check_reqs.js +++ b/bin/lib/check_reqs.js @@ -19,7 +19,7 @@ under the License. */ -var shell = require('shelljs'), +var shelljs = require('shelljs'), child_process = require('child_process'), Q = require('q'), path = require('path'), @@ -27,92 +27,157 @@ var shell = require('shelljs'), which = require('which'), ROOT = path.join(__dirname, '..', '..'); +var isWindows = process.platform == 'win32'; + +function forgivingWhichSync(cmd) { + try { + return which.sync(cmd); + } catch (e) { + return ''; + } +} + +function tryCommand(cmd, errMsg) { + var d = Q.defer(); + child_process.exec(cmd, function(err, stdout, stderr) { + if (err) d.reject(new Error(errMsg)); + else d.resolve(stdout); + }); + return d.promise; +} + // Get valid target from framework/project.properties module.exports.get_target = function() { if(fs.existsSync(path.join(ROOT, 'framework', 'project.properties'))) { - var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties')); + var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'framework', 'project.properties')); return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', ''); } else if (fs.existsSync(path.join(ROOT, 'project.properties'))) { // if no target found, we're probably in a project and project.properties is in ROOT. // this is called on the project itself, and can support Google APIs AND Vanilla Android - var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) || - shell.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties')); + var target = shelljs.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties')) || + shelljs.grep(/target=Google Inc.:Google APIs:[\d+]/, path.join(ROOT, 'project.properties')); if(target == "" || !target) { // Try Google Glass APIs - target = shell.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties')); + target = shelljs.grep(/target=Google Inc.:Glass Development Kit Preview:[\d+]/, path.join(ROOT, 'project.properties')); } return target.split('=')[1].replace('\n', '').replace('\r', ''); } } -// Returns a promise. -module.exports.sdk_dir = function() { - var d = Q.defer(); - which('android', function(err, path) { - if (err) { - d.reject(new Error('ERROR: Cannot find Android SDK. android command not found.')); - } else { - var toolsDir = path.substring(0, path.lastIndexOf('/')); - if (toolsDir.substring(toolsDir.length-6) != "/tools") { - d.reject(new Error('ERROR: Cannot find Android SDK. android command not found in tools dir.')); - } - d.resolve(toolsDir.substring(0, toolsDir.length-6)); - } - }); - return d.promise; +// 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.'); +}; + +// 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(); }; -// Returns a promise. -module.exports.check_ant = function() { - var d = Q.defer(); - child_process.exec('ant -version', function(err, stdout, stderr) { - if (err) d.reject(new Error('ERROR : executing command \'ant\', make sure you have ant installed and added to your path.')); - else d.resolve(); - }); - return d.promise; -} - // Returns a promise. module.exports.check_java = function() { - var d = Q.defer(); - child_process.exec('java -version', function(err, stdout, stderr) { - if(err) { - var msg = - 'Failed to run \'java -version\', make sure your java environment is set up\n' + - 'including JDK and JRE.\n' + - 'Your JAVA_HOME variable is ' + process.env.JAVA_HOME + '\n'; - d.reject(new Error(msg + err)); + var javacPath = forgivingWhichSync('javac'); + var hasJavaHome = !!process.env['JAVA_HOME']; + return Q().then(function() { + if (hasJavaHome) { + // Windows java installer doesn't add javac to PATH, nor set JAVA_HOME (ugh). + if (!javacPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['JAVA_HOME'], 'bin'); + } + } else { + if (javacPath) { + // 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') + .then(function(stdout) { + process.env['JAVA_HOME'] = stdout.trim(); + }); + } else { + // See if we can derive it from javac's location. + var maybeJavaHome = path.dirname(path.dirname(javacPath)); + 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'); + } + } + } else if (isWindows) { + // Try to auto-detect java in the default install paths. + var firstJdkDir = + shelljs.ls(process.env['ProgramFiles'] + '\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files\\java\\jdk*')[0] || + shelljs.ls('C:\\Program Files (x86)\\java\\jdk*')[0]; + if (firstJdkDir) { + // shelljs always uses / in paths. + firstJdkDir = firstJdkDir.replace(/\//g, path.sep); + if (!javacPath) { + process.env['PATH'] += path.delimiter + path.join(firstJdkDir, 'bin'); + } + process.env['JAVA_HOME'] = firstJdkDir; + } + } } - else d.resolve(); + }).then(function() { + var msg = + 'Failed to run "java -version", make sure your java environment is set up\n' + + 'including JDK and JRE.\n' + + 'Your JAVA_HOME variable is: ' + process.env['JAVA_HOME']; + return tryCommand('java -version', msg) + }).then(function() { + msg = 'Failed to run "javac -version", make sure you have a Java JDK (not just a JRE) installed.'; + return tryCommand('javac -version', msg) }); - return d.promise; } // Returns a promise. module.exports.check_android = function() { - var valid_target = this.get_target(); - var d = Q.defer(); - child_process.exec('android list targets', function(err, stdout, stderr) { - if (err) d.reject(stderr); - else d.resolve(stdout); + return Q().then(function() { + var androidCmdPath = forgivingWhichSync('android'); + var adbInPath = !!forgivingWhichSync('adb'); + var hasAndroidHome = !!process.env['ANDROID_HOME'] && fs.existsSync(process.env['ANDROID_HOME']); + if (hasAndroidHome && !androidCmdPath) { + process.env['PATH'] += path.delimiter + path.join(process.env['ANDROID_HOME'], 'tools'); + } + if (androidCmdPath && !hasAndroidHome) { + var parentDir = path.dirname(androidCmdPath); + if (path.basename(parentDir) == 'tools') { + process.env['ANDROID_HOME'] = path.dirname(parentDir); + hasAndroidHome = true; + } + } + 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.'); + } + if (!fs.existsSync(process.env['ANDROID_HOME'])) { + throw new Error('ANDROID_HOME is set to a non-existant path: ' + process.env['ANDROID_HOME']); + } + // Check that the target sdk level is installed. + return module.exports.check_android_target(module.exports.get_target()); }); +}; - return d.promise.then(function(output) { +module.exports.check_android_target = function(valid_target) { + var msg = 'Failed to run "android". Make sure you have the latest Android SDK installed, and that the "android" command (inside the tools/ folder) is added to your PATH.'; + return tryCommand('android list targets', msg) + .then(function(output) { if (!output.match(valid_target)) { - return Q.reject(new Error('Please install Android target ' + valid_target.split('-')[1] + ' (the Android newest SDK). Make sure you have the latest Android tools installed as well. Run \"android\" from your command-line to install/update any missing SDKs or tools.')); - } - return Q(); - }, function(stderr) { - if (stderr.match(/command\snot\sfound/) || stderr.match(/is not recognized as an internal or external command/)) { - 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. Error: ' + stderr )); + throw new Error('Please install Android target "' + valid_target + '".\n' + + 'Hint: Run "android" from your command-line to open the SDK manager.'); } }); -} +}; // Returns a promise. module.exports.run = function() { - return Q.all([this.check_ant(), this.check_java(), this.check_android()]); + return Q.all([this.check_java(), this.check_android()]); } diff --git a/bin/lib/create.js b/bin/lib/create.js index 485c60eb..f20c6a01 100755 --- a/bin/lib/create.js +++ b/bin/lib/create.js @@ -83,16 +83,47 @@ function copyJsAndLibrary(projectPath, shared, projectName) { } } -function runAndroidUpdate(projectPath, target_api, shared) { - var targetFrameworkDir = getFrameworkDir(projectPath, shared); - return exec('android update project --subprojects --path "' + projectPath + '" --target ' + target_api + ' --library "' + path.relative(projectPath, targetFrameworkDir) + '"'); +function extractSubProjectPaths(data) { + var ret = {}; + var r = /^\s*android\.library\.reference\.\d+=(.*)(?:\s|$)/mg + var m; + while (m = r.exec(data)) { + ret[m[1]] = 1; + } + return Object.keys(ret); } -function copyAntRules(projectPath) { - var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); - if (fs.existsSync(path.join(srcDir, 'custom_rules.xml'))) { - shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); +function writeProjectProperties(projectPath, target_api, shared) { + var dstPath = path.join(projectPath, 'project.properties'); + var templatePath = path.join(ROOT, 'bin', 'templates', 'project', 'project.properties'); + var srcPath = fs.existsSync(dstPath) ? dstPath : templatePath; + var data = fs.readFileSync(srcPath, 'utf8'); + data = data.replace(/^target=.*/m, 'target=' + target_api); + var subProjects = extractSubProjectPaths(data); + subProjects = subProjects.filter(function(p) { + return !(/^CordovaLib$/m.exec(p) || + /[\\\/]cordova-android[\\\/]framework$/m.exec(p) || + /^(\.\.[\\\/])+framework$/m.exec(p) + ); + }); + subProjects.unshift(shared ? path.relative(projectPath, path.join(ROOT, 'framework')) : 'CordovaLib'); + data = data.replace(/^\s*android\.library\.reference\.\d+=.*\n/mg, ''); + if (!/\n$/.exec(data)) { + data += '\n'; } + for (var i = 0; i < subProjects.length; ++i) { + data += 'android.library.reference.' + (i+1) + '=' + subProjects[i] + '\n'; + } + fs.writeFileSync(dstPath, data); +} + +function copyBuildRules(projectPath) { + var srcDir = path.join(ROOT, 'bin', 'templates', 'project'); + shell.cp('-f', path.join(srcDir, 'custom_rules.xml'), projectPath); + + shell.cp('-f', path.join(srcDir, 'build.gradle'), projectPath); + shell.cp('-f', path.join(srcDir, 'libraries.gradle'), projectPath); + shell.cp('-f', path.join(srcDir, 'settings.gradle'), projectPath); } function copyScripts(projectPath) { @@ -206,7 +237,7 @@ exports.createProject = function(project_path, package_name, project_name, proje }) // Check that requirements are met and proper targets are installed .then(function() { - check_reqs.run(); + return check_reqs.run(); }).then(function() { // Log the given values for the project console.log('Creating Cordova project for the Android platform:'); @@ -222,6 +253,7 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.cp('-r', path.join(project_template_dir, 'assets'), project_path); shell.cp('-r', path.join(project_template_dir, 'res'), project_path); shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res')); + shell.cp(path.join(project_template_dir, 'gitignore'), path.join(project_path, '.gitignore')); shell.cp('-f', path.join(project_template_dir, 'build.gradle'), project_path); shell.cp('-f', path.join(project_template_dir, 'libraries.gradle'), project_path); @@ -263,10 +295,10 @@ exports.createProject = function(project_path, package_name, project_name, proje shell.sed('-i', /__PACKAGE__/, package_name, manifest_path); shell.sed('-i', /__APILEVEL__/, target_api.split('-')[1], manifest_path); copyScripts(project_path); - copyAntRules(project_path); + copyBuildRules(project_path); }); // Link it to local android install. - return runAndroidUpdate(project_path, target_api, use_shared_project); + writeProjectProperties(project_path, target_api); }).then(function() { console.log('Project successfully created.'); }); @@ -289,22 +321,20 @@ function extractProjectNameFromManifest(projectPath) { } // Returns a promise. -exports.updateProject = function(projectPath) { - var version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); +exports.updateProject = function(projectPath, shared) { + var newVersion = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim(); // Check that requirements are met and proper targets are installed return check_reqs.run() .then(function() { var projectName = extractProjectNameFromManifest(projectPath); var target_api = check_reqs.get_target(); - copyJsAndLibrary(projectPath, false, projectName); + copyJsAndLibrary(projectPath, shared, projectName); copyScripts(projectPath); - copyAntRules(projectPath); + copyBuildRules(projectPath); removeDebuggableFromManifest(projectPath); - return runAndroidUpdate(projectPath, target_api, false) - .then(function() { - console.log('Android project is now at version ' + version); - console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.'); - }); + writeProjectProperties(projectPath, target_api, shared); + console.log('Android project is now at version ' + newVersion); + console.log('If you updated from a pre-3.2.0 version and use an IDE, we now require that you import the "CordovaLib" library project.'); }); }; diff --git a/bin/node_modules/which/package.json b/bin/node_modules/which/package.json index c71cabb5..6c5ccb34 100644 --- a/bin/node_modules/which/package.json +++ b/bin/node_modules/which/package.json @@ -27,5 +27,5 @@ }, "homepage": "https://github.com/isaacs/node-which", "_id": "which@1.0.5", - "_from": "which@^1.0.5" + "_from": "which@" } diff --git a/bin/templates/cordova/build b/bin/templates/cordova/build index a38f3b63..3c3aee4e 100755 --- a/bin/templates/cordova/build +++ b/bin/templates/cordova/build @@ -24,13 +24,17 @@ 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().then(function() { - return build.run(args[2]); - }).done(null, function(err) { + reqs.run().done(function() { + return build.run(args.slice(2)); + }, function(err) { console.error(err); process.exit(2); }); diff --git a/bin/templates/cordova/clean b/bin/templates/cordova/clean index 4e0808bf..d9a7d490 100755 --- a/bin/templates/cordova/clean +++ b/bin/templates/cordova/clean @@ -19,18 +19,26 @@ under the License. */ -var clean = require('./lib/clean'), +var build = require('./lib/build'), reqs = require('./lib/check_reqs'), args = process.argv; +var path = require('path'); -// Usage support for when args are given -if(args.length > 2) { - clean.help(); +// Support basic help commands +if(args[2] == '--help' || + args[2] == '/?' || + args[2] == '-h' || + args[2] == 'help' || + args[2] == '-help' || + args[2] == '/help') { + console.log('Usage: ' + path.relative(process.cwd(), process.argv[1])); + console.log('Cleans the project directory.'); + process.exit(0); } else { reqs.run().done(function() { - return clean.run(); + return build.runClean(args.slice(2)); }, function(err) { - console.error('ERROR: ' + err); + console.error(err); process.exit(2); }); } diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js index 5f100e21..7d8828db 100644 --- a/bin/templates/cordova/lib/build.js +++ b/bin/templates/cordova/lib/build.js @@ -26,79 +26,330 @@ var shell = require('shelljs'), fs = require('fs'), which = require('which'), ROOT = path.join(__dirname, '..', '..'); +var check_reqs = require('./check_reqs'); +var LOCAL_PROPERTIES_TEMPLATE = + '# This file is automatically generated.\n' + + '# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n'; -module.exports.getAntArgs = function(cmd) { - var args = [cmd, '-f', path.join(ROOT, 'build.xml')]; - 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. - */ -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.'); - } - var ret = Q(); - return ret.then(function() { - return spawn('ant', args); - }); -} - -/* - * 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?) - */ -module.exports.get_apk = function() { - var binDir = path.join(ROOT, 'bin'); - 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); +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; - }); - 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; + }).map(function(p) { return p.p; }); + return candidates; } else { - console.error('ERROR : unable to find project ' + binDir + ' directory, could not locate .apk'); + 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')); +} + +function extractProjectNameFromManifest(projectPath) { + var manifestPath = path.join(projectPath, 'AndroidManifest.xml'); + var manifestData = fs.readFileSync(manifestPath, 'utf8'); + var m = / 6 seconds + args.push('-Dorg.gradle.daemon=true'); + // Excluding lint: 6s-> 1.6s + for (var i = 0; i < lintSteps.length; ++i) { + args.push('-x', lintSteps[i]); + } + // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet): + // args.push('-Dorg.gradle.parallel=true'); + return args; + }, + + prepEnv: function() { + return check_reqs.check_gradle() + .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. + var projectPath = ROOT; + // check_reqs ensures that this is set. + var sdkDir = process.env['ANDROID_HOME']; + var wrapperDir = path.join(sdkDir, 'tools', 'templates', 'gradle', 'wrapper'); + if (process.platform == 'win32') { + shell.cp('-f', path.join(wrapperDir, 'gradlew.bat'), projectPath); + } else { + shell.cp('-f', path.join(wrapperDir, 'gradlew'), projectPath); + } + shell.rm('-rf', path.join(projectPath, 'gradle', 'wrapper')); + shell.mkdir('-p', path.join(projectPath, 'gradle')); + shell.cp('-r', path.join(wrapperDir, 'gradle', 'wrapper'), path.join(projectPath, 'gradle')); + }); + }, + + /* + * 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); + }); + }, + + clean: function() { + var builder = this; + var wrapper = path.join(ROOT, 'gradlew'); + var args = builder.getArgs('clean'); + return Q().then(function() { + return spawn(wrapper, args); + }); + }, + + // 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; + } + }, + + none: { + prepEnv: function() { + return Q(); + }, + build: function() { + console.log('Skipping build...'); + return Q(); + }, + clean: function() { + return Q(); + }, + } +}; + +function parseOpts(options) { + // Backwards-compatibility: Allow a single string argument + if (typeof options == "string") options = [options]; + + var ret = { + buildType: 'debug', + buildMethod: process.env['ANDROID_BUILD'] || 'ant' + }; + + // 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': + ret.buildType = option; + break; + case 'ant': + case 'gradle': + ret.buildMethod = option; + break; + case 'nobuild' : + ret.buildMethod = 'none'; + break; + default : + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } + } else { + return Q.reject('Build option \'' + options[i] + '\' not recognized.'); + } + } + return ret; +} + +/* + * Builds the project with the specifed options + * Returns a promise. + */ +module.exports.runClean = function(options) { + var opts = parseOpts(options); + var builder = builders[opts.buildMethod]; + return builder.prepEnv() + .then(function() { + return builder.clean(); + }).then(function() { + shell.rm('-rf', path.join(ROOT, 'out')); + }); +}; + +/* + * Builds the project with the specifed options + * Returns a promise. + */ +module.exports.run = function(options) { + var opts = parseOpts(options); + + var builder = builders[opts.buildMethod]; + return builder.prepEnv() + .then(function() { + return builder.build(opts.buildType); + }).then(function(apkFiles) { + // TODO: Rather than copy apks to out, it might be better to + // just write out what the last .apk build was. These files + // are used by get_apk(). + var outputDir = path.join(ROOT, 'out'); + shell.mkdir('-p', outputDir); + 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(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); + } + // TODO: Use build_type here. + 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); }; diff --git a/bin/templates/cordova/lib/clean.js b/bin/templates/cordova/lib/clean.js deleted file mode 100644 index 0a2e0ce0..00000000 --- a/bin/templates/cordova/lib/clean.js +++ /dev/null @@ -1,39 +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 build = require('./build'), - spawn = require('./spawn'), - path = require('path'); - -/* - * Cleans the project using ant - * Returns a promise. - */ -module.exports.run = function() { - var args = build.getAntArgs('clean'); - return spawn('ant', args); -} - -module.exports.help = function() { - console.log('Usage: ' + path.relative(process.cwd(), process.argv[1])); - console.log('Cleans the project directory.'); - process.exit(0); -} diff --git a/bin/templates/project/gitignore b/bin/templates/project/gitignore new file mode 100644 index 00000000..a1c8ff71 --- /dev/null +++ b/bin/templates/project/gitignore @@ -0,0 +1,14 @@ +# Non-project-specific build files: +build.xml +local.properties +/gradlew +/gradlew.bat +/gradle +# Ant builds +ant-built +ant-gen +# Eclipse builds +gen +out +# Gradle builds +/build diff --git a/bin/templates/project/project.properties b/bin/templates/project/project.properties new file mode 100644 index 00000000..ddd3a060 --- /dev/null +++ b/bin/templates/project/project.properties @@ -0,0 +1,15 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +android.library.reference.1=CordovaLib +# Project target. +target=This_gets_replaced diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml index 6dec1aec..e058a420 100755 --- a/test/AndroidManifest.xml +++ b/test/AndroidManifest.xml @@ -255,5 +255,15 @@ + + + + + + diff --git a/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java b/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java index aea06afe..381e0edd 100644 --- a/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java +++ b/test/src/org/apache/cordova/test/junit/IntentUriOverrideTest.java @@ -25,6 +25,7 @@ public class IntentUriOverrideTest extends ActivityInstrumentationTestCase2