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 2015/04/02 01:55:23 UTC

android commit: CB-8484 Add signing flags to `build` and `run` scripts

Repository: cordova-android
Updated Branches:
  refs/heads/master 51adf8191 -> ad1c3d243


CB-8484 Add signing flags to `build` and `run` scripts

Parameters for creating signed archives can be specified using command line or build.json file as part of the --buildConfig argument.
close #164


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

Branch: refs/heads/master
Commit: ad1c3d24386edcd7f1c240a7d5d06355b9680c53
Parents: 51adf81
Author: Nikhil Khandelwal <ni...@microsoft.com>
Authored: Tue Mar 10 18:13:13 2015 -0700
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Wed Apr 1 19:53:56 2015 -0400

----------------------------------------------------------------------
 bin/templates/cordova/lib/build.js | 193 +++++++++++++++++++++++++++-----
 bin/templates/cordova/lib/run.js   |   2 +-
 2 files changed, 168 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-android/blob/ad1c3d24/bin/templates/cordova/lib/build.js
----------------------------------------------------------------------
diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js
index 75dc0c7..42be23e 100644
--- a/bin/templates/cordova/lib/build.js
+++ b/bin/templates/cordova/lib/build.js
@@ -31,9 +31,12 @@ var shell   = require('shelljs'),
 var check_reqs = require('./check_reqs');
 var exec  = require('./exec');
 
-var LOCAL_PROPERTIES_TEMPLATE =
+
+var SIGNING_PROPERTIES = '-signing.properties';
+var MARKER = 'YOUR CHANGES WILL BE ERASED!';
+var TEMPLATE =
     '# This file is automatically generated.\n' +
-    '# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n';
+    '# Do not modify this file -- ' + MARKER + '\n';
 
 function findApks(directory) {
     var ret = [];
@@ -56,6 +59,14 @@ function sortFilesByDate(files) {
     }).map(function(p) { return p.p; });
 }
 
+function isAutoGenerated(file) {
+    if(fs.existsSync(file)) {
+        var fileContents = fs.readFileSync(file, 'utf8');
+        return fileContents.indexOf(MARKER) > 0;
+    }
+    return false;
+}
+
 function findOutputApksHelper(dir, build_type, arch) {
     var ret = findApks(dir).filter(function(candidate) {
         // Need to choose between release and debug .apk.
@@ -121,16 +132,19 @@ function readProjectProperties() {
 
 var builders = {
     ant: {
-        getArgs: function(cmd) {
+        getArgs: function(cmd, opts) {
             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');
             }
+            if(opts.packageInfo) {
+                args.push('-propertyfile=' + path.join(ROOT, opts.buildType + SIGNING_PROPERTIES));
+            }
             return args;
         },
 
-        prepEnv: function() {
+        prepEnv: function(opts) {
             return check_reqs.check_ant()
             .then(function() {
                 // Copy in build.xml on each build so that:
@@ -142,7 +156,7 @@ var builders = {
                     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);
+                        fs.writeFileSync(path.join(projectPath, 'local.properties'), TEMPLATE);
                     }
                 }
                 writeBuildXml(ROOT);
@@ -154,6 +168,14 @@ var builders = {
                 if (propertiesObj.systemLibs.length > 0) {
                     throw new Error('Project contains at least one plugin that requires a system library. This is not supported with ANT. Please build using gradle.');
                 }
+
+                var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+                var propertiesFilePath = path.join(ROOT, propertiesFile);
+                if (opts.packageInfo) {
+                    fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+                } else if(isAutoGenerated(propertiesFilePath)) {
+                    shell.rm('-f', propertiesFilePath);
+                }
             });
         },
 
@@ -161,23 +183,24 @@ var builders = {
          * Builds the project with ant.
          * Returns a promise.
          */
-        build: function(build_type) {
+        build: function(opts) {
             // 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();
+                ret = this.clean(opts);
             }
 
-            var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release');
+            var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
             return check_reqs.check_ant()
             .then(function() {
+                console.log('Executing: ant ' + args.join(' '));
                 return spawn('ant', args);
             });
         },
 
-        clean: function() {
-            var args = this.getArgs('clean');
+        clean: function(opts) {
+            var args = this.getArgs('clean', opts);
             return check_reqs.check_ant()
             .then(function() {
                 return spawn('ant', args);
@@ -190,20 +213,20 @@ var builders = {
         }
     },
     gradle: {
-        getArgs: function(cmd, arch, extraArgs) {
+        getArgs: function(cmd, opts) {
             if (cmd == 'release') {
                 cmd = 'cdvBuildRelease';
             } else if (cmd == 'debug') {
                 cmd = 'cdvBuildDebug';
             }
             var args = [cmd, '-b', path.join(ROOT, 'build.gradle')];
-            if (arch) {
-                args.push('-PcdvBuildArch=' + arch);
+            if (opts.arch) {
+                args.push('-PcdvBuildArch=' + opts.arch);
             }
 
             // 10 seconds -> 6 seconds
             args.push('-Dorg.gradle.daemon=true');
-            args.push.apply(args, extraArgs);
+            args.push.apply(args, opts.extraArgs);
             // Shaves another 100ms, but produces a "try at own risk" warning. Not worth it (yet):
             // args.push('-Dorg.gradle.parallel=true');
             return args;
@@ -268,7 +291,7 @@ var builders = {
             fs.writeFileSync(path.join(projectPath, 'build.gradle'), buildGradle);
         },
 
-        prepEnv: function() {
+        prepEnv: function(opts) {
             var self = this;
             return check_reqs.check_gradle()
             .then(function() {
@@ -297,6 +320,14 @@ var builders = {
                 var distributionUrl = 'distributionUrl=http\\://services.gradle.org/distributions/gradle-2.2.1-all.zip';
                 var gradleWrapperPropertiesPath = path.join(projectPath, 'gradle', 'wrapper', 'gradle-wrapper.properties');
                 shell.sed('-i', distributionUrlRegex, distributionUrl, gradleWrapperPropertiesPath);
+
+                var propertiesFile = opts.buildType + SIGNING_PROPERTIES;
+                var propertiesFilePath = path.join(ROOT, propertiesFile);
+                if (opts.packageInfo) {
+                    fs.writeFileSync(propertiesFilePath, TEMPLATE + opts.packageInfo.toProperties());
+                } else if (isAutoGenerated(propertiesFilePath)) {
+                    shell.rm('-f', propertiesFilePath);
+                }
             });
         },
 
@@ -304,19 +335,19 @@ var builders = {
          * Builds the project with gradle.
          * Returns a promise.
          */
-        build: function(build_type, arch, extraArgs) {
+        build: function(opts) {
             var wrapper = path.join(ROOT, 'gradlew');
-            var args = this.getArgs(build_type == 'debug' ? 'debug' : 'release', arch, extraArgs);
+            var args = this.getArgs(opts.buildType == 'debug' ? 'debug' : 'release', opts);
             return Q().then(function() {
                 console.log('Running: ' + wrapper + ' ' + args.join(' '));
                 return spawn(wrapper, args);
             });
         },
 
-        clean: function(extraArgs) {
+        clean: function(opts) {
             var builder = this;
             var wrapper = path.join(ROOT, 'gradlew');
-            var args = builder.getArgs('clean', null, extraArgs);
+            var args = builder.getArgs('clean', opts);
             return Q().then(function() {
                 console.log('Running: ' + wrapper + ' ' + args.join(' '));
                 return spawn(wrapper, args);
@@ -346,6 +377,10 @@ var builders = {
     }
 };
 
+module.exports.isBuildFlag = function(flag) {
+    return /^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=|keystore=|alias=|password=|storePassword=|keystoreType=|buildConfig=)/.exec(flag);
+};
+
 function parseOpts(options, resolvedTarget) {
     // Backwards-compatibility: Allow a single string argument
     if (typeof options == 'string') options = [options];
@@ -360,9 +395,16 @@ function parseOpts(options, resolvedTarget) {
     var multiValueArgs = {
       'versionCode': true,
       'minSdkVersion': true,
-      'gradleArg': true
+      'gradleArg': true,
+      'keystore' : true,
+      'alias' : true,
+      'password' : true,
+      'storePassword' : true,
+      'keystoreType' : true,
+      'buildConfig' : true
     };
-
+    var packageArgs = {};
+    var buildConfig;
     // Iterate through command line options
     for (var i=0; options && (i < options.length); ++i) {
         if (/^--/.exec(options[i])) {
@@ -402,6 +444,18 @@ function parseOpts(options, resolvedTarget) {
                 case 'gradleArg':
                     ret.extraArgs.push(flagValue);
                     break;
+                case 'keystore':
+                    packageArgs.keystore = path.relative(ROOT, path.resolve(flagValue));
+                    break;
+                case 'alias':
+                case 'storePassword':
+                case 'password':
+                case 'keystoreType':
+                    packageArgs[flagName] = flagValue;
+                    break;
+                case 'buildConfig':
+                    buildConfig = flagValue;
+                    break;
                 default :
                     console.warn('Build option --\'' + flagName + '\' not recognized (ignoring).');
             }
@@ -410,6 +464,36 @@ function parseOpts(options, resolvedTarget) {
         }
     }
 
+    // If some values are not specified as command line arguments - use build config to supplement them.
+    // Command line arguemnts have precedence over build config.
+    if (buildConfig) {
+        console.log(path.resolve(buildConfig));
+        if (!fs.existsSync(buildConfig)) {
+            throw new Error('Specified build config file does not exist: ' + buildConfig);
+        }
+        console.log('Reading build config file: '+ buildConfig);
+        var config = JSON.parse(fs.readFileSync(buildConfig, 'utf8'));
+        if (config.android && config.android[ret.buildType]) {
+            var androidInfo = config.android[ret.buildType];
+            if(androidInfo.keystore) {
+                packageArgs.keystore = packageArgs.keystore || path.relative(ROOT, path.join(path.dirname(buildConfig), androidInfo.keystore));
+            }
+
+            ['alias', 'storePassword', 'password','keystoreType'].forEach(function (key){
+                packageArgs[key] = packageArgs[key] || androidInfo[key];
+            });
+        }
+    }
+    if (packageArgs.keystore && packageArgs.alias) {
+        ret.packageInfo = new PackageInfo(packageArgs.keystore, packageArgs.alias, packageArgs.storePassword,
+            packageArgs.password, packageArgs.keystoreType);
+    }
+
+    if(!ret.packageInfo) {
+        if(Object.keys(packageArgs).length > 0) {
+            console.warn('\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
+        }
+    }
     ret.arch = resolvedTarget && resolvedTarget.arch;
 
     return ret;
@@ -422,11 +506,18 @@ function parseOpts(options, resolvedTarget) {
 module.exports.runClean = function(options) {
     var opts = parseOpts(options);
     var builder = builders[opts.buildMethod];
-    return builder.prepEnv()
+    return builder.prepEnv(opts)
     .then(function() {
-        return builder.clean(opts.extraArgs);
+        return builder.clean(opts);
     }).then(function() {
         shell.rm('-rf', path.join(ROOT, 'out'));
+
+        ['debug', 'release'].forEach(function(config) {
+            var propertiesFilePath = path.join(ROOT, config + SIGNING_PROPERTIES);
+            if(isAutoGenerated(propertiesFilePath)){
+                shell.rm('-f', propertiesFilePath);
+            }
+        });
     });
 };
 
@@ -437,13 +528,13 @@ module.exports.runClean = function(options) {
 module.exports.run = function(options, optResolvedTarget) {
     var opts = parseOpts(options, optResolvedTarget);
     var builder = builders[opts.buildMethod];
-    return builder.prepEnv()
+    return builder.prepEnv(opts)
     .then(function() {
         if (opts.prepEnv) {
             console.log('Build file successfully prepared.');
             return;
         }
-        return builder.build(opts.buildType, opts.arch, opts.extraArgs)
+        return builder.build(opts)
         .then(function() {
             var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
             console.log('Built the following apk(s):');
@@ -530,8 +621,51 @@ module.exports.findBestApkForArchitecture = function(buildResults, arch) {
     throw new Error('Could not find apk architecture: ' + arch + ' build-type: ' + buildResults.buildType);
 };
 
+function PackageInfo(keystore, alias, storePassword, password, keystoreType) {
+    this.keystore = {
+        'name': 'key.store',
+        'value': keystore
+    };
+    this.alias = {
+        'name': 'key.alias',
+        'value': alias
+    };
+    if (storePassword) {
+        this.storePassword = {
+            'name': 'key.store.password',
+            'value': storePassword
+        };
+    }
+    if (password) {
+        this.password = {
+            'name': 'key.alias.password',
+            'value': password
+        };
+    }
+    if (keystoreType) {
+        this.keystoreType = {
+            'name': 'key.store.type',
+            'value': keystoreType
+        };
+    }
+}
+
+PackageInfo.prototype = {
+    toProperties: function() {
+        var self = this;
+        var result = '';
+        Object.keys(self).forEach(function(key) {
+            result += self[key].name;
+            result += '=';
+            result += self[key].value.replace(/\\/g, '\\\\');
+            result += '\n';
+        });
+        return result;
+    }
+};
+
 module.exports.help = function() {
-    console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags]');
+    console.log('Usage: ' + path.relative(process.cwd(), path.join(ROOT, 'cordova', 'build')) + ' [flags] [Signed APK flags]');
     console.log('Flags:');
     console.log('    \'--debug\': will build project in debug mode (default)');
     console.log('    \'--release\': will build project for release');
@@ -542,5 +676,12 @@ module.exports.help = function() {
     console.log('    \'--versionCode=#\': Override versionCode for this build. Useful for uploading multiple APKs. Requires --gradle.');
     console.log('    \'--minSdkVersion=#\': Override minSdkVersion for this build. Useful for uploading multiple APKs. Requires --gradle.');
     console.log('    \'--gradleArg=<gradle command line arg>\': Extra args to pass to the gradle command. Use one flag per arg. Ex. --gradleArg=-PcdvBuildMultipleApks=true');
+    console.log('');
+    console.log('Signed APK flags (overwrites debug/release-signing.proprties) :');
+    console.log('    \'--keystore=<path to keystore>\': Key store used to build a signed archive. (Required)');
+    console.log('    \'--alias=\': Alias for the key store. (Required)');
+    console.log('    \'--storePassword=\': Password for the key store. (Optional - prompted)');
+    console.log('    \'--password=\': Password for the key. (Optional - prompted)');
+    console.log('    \'--keystoreType\': Type of the keystore. (Optional)');
     process.exit(0);
 };

http://git-wip-us.apache.org/repos/asf/cordova-android/blob/ad1c3d24/bin/templates/cordova/lib/run.js
----------------------------------------------------------------------
diff --git a/bin/templates/cordova/lib/run.js b/bin/templates/cordova/lib/run.js
index d18c02e..7f15448 100644
--- a/bin/templates/cordova/lib/run.js
+++ b/bin/templates/cordova/lib/run.js
@@ -41,7 +41,7 @@ var path  = require('path'),
     var list = false;
 
     for (var i=2; i<args.length; i++) {
-        if (/^--(debug|release|ant|gradle|nobuild|versionCode=|minSdkVersion=|gradleArg=)/.exec(args[i])) {
+        if (build.isBuildFlag(args[i])) {
             buildFlags.push(args[i]);
         } else if (args[i] == '--device') {
             install_target = '--device';


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org