You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2014/08/19 18:03:25 UTC

[19/19] android commit: Merge branch 'master' into 4.0.x (build & create script updates)

Merge branch 'master' into 4.0.x (build & create script updates)

Conflicts:
	bin/lib/check_reqs.js
	bin/lib/create.js
	bin/node_modules/which/package.json
	bin/templates/cordova/lib/build.js


Project: http://git-wip-us.apache.org/repos/asf/cordova-android/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-android/commit/4c1942e3
Tree: http://git-wip-us.apache.org/repos/asf/cordova-android/tree/4c1942e3
Diff: http://git-wip-us.apache.org/repos/asf/cordova-android/diff/4c1942e3

Branch: refs/heads/4.0.x
Commit: 4c1942e3fe57d2898a7eba7341fb22a485bd339c
Parents: a7ccb92 71e72f2
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Aug 19 12:02:36 2014 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Aug 19 12:02:36 2014 -0400

----------------------------------------------------------------------
 bin/lib/check_reqs.js                           | 175 ++++++---
 bin/lib/create.js                               |  68 +++-
 bin/node_modules/which/package.json             |   2 +-
 bin/templates/cordova/build                     |  14 +-
 bin/templates/cordova/clean                     |  20 +-
 bin/templates/cordova/lib/build.js              | 357 ++++++++++++++++---
 bin/templates/cordova/lib/clean.js              |  39 --
 bin/templates/project/gitignore                 |  14 +
 bin/templates/project/project.properties        |  15 +
 test/AndroidManifest.xml                        |  10 +
 .../test/junit/IntentUriOverrideTest.java       |   1 +
 11 files changed, 537 insertions(+), 178 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4c1942e3/bin/lib/create.js
----------------------------------------------------------------------
diff --cc bin/lib/create.js
index 485c60e,0e929e3..f20c6a0
--- a/bin/lib/create.js
+++ b/bin/lib/create.js
@@@ -222,17 -246,8 +253,18 @@@ exports.createProject = function(projec
              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);
 +            shell.cp('-f', path.join(project_template_dir, 'settings.gradle'), project_path);
 +            check_reqs.sdk_dir().then(function(dir) {
 +                console.log("Copying Gradle wrapper from " + dir);
 +                copyGradleWrapper(dir, project_path);
 +            }).catch(function(err) {
 +                console.log("Cannot find Android SDK. Not installing Gradle wrapper.");
 +            });
 +
              // Manually create directories that would be empty within the template (since git doesn't track directories).
              shell.mkdir(path.join(project_path, 'libs'));
  

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/4c1942e3/bin/templates/cordova/lib/build.js
----------------------------------------------------------------------
diff --cc bin/templates/cordova/lib/build.js
index 5f100e2,1f4810d..7d8828d
--- a/bin/templates/cordova/lib/build.js
+++ b/bin/templates/cordova/lib/build.js
@@@ -24,46 -24,306 +24,307 @@@ var shell   = require('shelljs')
      Q       = require('q'),
      path    = require('path'),
      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
+ 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'));
+ }
+ 
+ function extractProjectNameFromManifest(projectPath) {
+     var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
+     var manifestData = fs.readFileSync(manifestPath, 'utf8');
+     var m = /<activity[\s\S]*?android:name\s*=\s*"(.*?)"/i.exec(manifestData);
+     if (!m) {
+         throw new Error('Could not find activity name in ' + manifestPath);
+     }
+     return m[1];
+ }
+ 
+ function extractSubProjectPaths() {
+     var data = fs.readFileSync(path.join(ROOT, 'project.properties'), 'utf8');
+     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);
+ }
+ 
+ var 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');
+             }
+             return args;
+         },
+ 
+         prepEnv: function() {
+             return check_reqs.check_ant()
+             .then(function() {
+                 // Copy in build.xml 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 sdkDir = process.env['ANDROID_HOME'];
+                 var buildTemplate = fs.readFileSync(path.join(sdkDir, 'tools', 'lib', 'build.template'), 'utf8');
+                 function writeBuildXml(projectPath) {
+                     var newData = buildTemplate.replace('PROJECT_NAME', extractProjectNameFromManifest(ROOT));
+                     fs.writeFileSync(path.join(projectPath, 'build.xml'), newData);
+                     if (!fs.existsSync(path.join(projectPath, 'local.properties'))) {
+                         fs.writeFileSync(path.join(projectPath, 'local.properties'), LOCAL_PROPERTIES_TEMPLATE);
+                     }
+                 }
+                 var subProjects = extractSubProjectPaths();
+                 writeBuildXml(ROOT);
+                 for (var i = 0; i < subProjects.length; ++i) {
+                     writeBuildXml(path.join(ROOT, subProjects[i]));
+                 }
+             });
+         },
+ 
+         /*
+          * Builds the project with ant.
+          * Returns a promise.
+          */
+         build: function(build_type) {
+             // Without our custom_rules.xml, we need to clean before building.
+             var ret = Q();
+             if (!hasCustomRules()) {
+                 // clean will call check_ant() for us.
+                 ret = this.clean();
+             }
+ 
+             var builder = this;
+             var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
+             return check_reqs.check_ant()
+             .then(function() {
+                 return spawn('ant', args);
+             }).then(function() {
+                 return builder.getOutputFiles();
+             });
+         },
+ 
+         clean: function() {
+             var args = this.getArgs('clean');
+             return check_reqs.check_ant()
+             .then(function() {
+                 return spawn('ant', args);
+             });
+         },
+ 
+         // 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 lintSteps = [
+                 'lint',
+                 'lintVitalRelease',
+                 'compileLint',
+                 'copyReleaseLint',
+                 'copyDebugLint'
+             ];
+             var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
+             // 10 seconds -> 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();
+         },
      }
-     return args;
  };
  
+ 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 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.');
-     }
-     var ret = Q();
-     return ret.then(function() {
-         return spawn('ant', args);
+ 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