You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by br...@apache.org on 2019/08/08 16:53:19 UTC
[cordova-android] branch master updated: feat: Build app bundles
(.aab files) (#764)
This is an automated email from the ASF dual-hosted git repository.
brodybits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-android.git
The following commit(s) were added to refs/heads/master by this push:
new bd1697d feat: Build app bundles (.aab files) (#764)
bd1697d is described below
commit bd1697dbd27411d8b12d9ad0902babaaae327efa
Author: Norman Breau <no...@normanbreau.com>
AuthorDate: Thu Aug 8 13:53:10 2019 -0300
feat: Build app bundles (.aab files) (#764)
* (android) Added android bundle support
with some corrected tests
added bundle specific output
* with --packageType flag to have consistency with cordova-ios
* warn about missing required signing params only if at least one signing param is present
* produce error on run if packageType = bundle
* added comments relating to shelljs as suggested
* unit test case added by @brodybits - Chris Brody
* Filled in error message and unit test spec
Primary author: @breautek - Norman Breau <no...@normanbreau.com>
Co-authored-by: Norman Breau <no...@normanbreau.com>
Co-authored-by: Chris Brody <ch...@brody.consulting>
---
bin/templates/cordova/Api.js | 4 +-
bin/templates/cordova/lib/PackageType.js | 25 ++++++++
bin/templates/cordova/lib/build.js | 53 +++++++++++++---
.../cordova/lib/builders/ProjectBuilder.js | 73 ++++++++++++++++++----
bin/templates/cordova/lib/run.js | 9 +++
spec/unit/builders/ProjectBuilder.spec.js | 28 +++++++++
spec/unit/run.spec.js | 12 ++++
7 files changed, 182 insertions(+), 22 deletions(-)
diff --git a/bin/templates/cordova/Api.js b/bin/templates/cordova/Api.js
index 30b1fbe..dd4a209 100644
--- a/bin/templates/cordova/Api.js
+++ b/bin/templates/cordova/Api.js
@@ -302,12 +302,12 @@ Api.prototype.build = function (buildOptions) {
return require('./lib/build').run.call(self, buildOptions);
}).then(function (buildResults) {
// Cast build result to array of build artifacts
- return buildResults.apkPaths.map(function (apkPath) {
+ return buildResults.paths.map(function (apkPath) {
return {
buildType: buildResults.buildType,
buildMethod: buildResults.buildMethod,
path: apkPath,
- type: 'apk'
+ type: path.extname(apkPath).replace(/\./g, '')
};
});
});
diff --git a/bin/templates/cordova/lib/PackageType.js b/bin/templates/cordova/lib/PackageType.js
new file mode 100644
index 0000000..fd129f1
--- /dev/null
+++ b/bin/templates/cordova/lib/PackageType.js
@@ -0,0 +1,25 @@
+/**
+ 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.
+*/
+
+const PackageType = {
+ APK: 'apk',
+ BUNDLE: 'bundle'
+};
+
+module.exports = PackageType;
diff --git a/bin/templates/cordova/lib/build.js b/bin/templates/cordova/lib/build.js
index dec218e..0695539 100644
--- a/bin/templates/cordova/lib/build.js
+++ b/bin/templates/cordova/lib/build.js
@@ -30,6 +30,7 @@ var builders = require('./builders/builders');
var events = require('cordova-common').events;
var spawn = require('cordova-common').superspawn.spawn;
var CordovaError = require('cordova-common').CordovaError;
+var PackageType = require('./PackageType');
module.exports.parseBuildOptions = parseOpts;
function parseOpts (options, resolvedTarget, projectRoot) {
@@ -45,7 +46,8 @@ function parseOpts (options, resolvedTarget, projectRoot) {
alias: String,
storePassword: String,
password: String,
- keystoreType: String
+ keystoreType: String,
+ packageType: String
}, {}, options.argv, 0);
// Android Studio Build method is the default
@@ -68,14 +70,14 @@ function parseOpts (options, resolvedTarget, projectRoot) {
if (options.argv.keystore) { packageArgs.keystore = path.relative(projectRoot, path.resolve(options.argv.keystore)); }
- ['alias', 'storePassword', 'password', 'keystoreType'].forEach(function (flagName) {
+ ['alias', 'storePassword', 'password', 'keystoreType', 'packageType'].forEach(function (flagName) {
if (options.argv[flagName]) { packageArgs[flagName] = options.argv[flagName]; }
});
var buildConfig = options.buildConfig;
// If some values are not specified as command line arguments - use build config to supplement them.
- // Command line arguemnts have precedence over build config.
+ // Command line arguments have precedence over build config.
if (buildConfig) {
if (!fs.existsSync(buildConfig)) {
throw new Error('Specified build config file does not exist: ' + buildConfig);
@@ -93,7 +95,7 @@ function parseOpts (options, resolvedTarget, projectRoot) {
events.emit('log', 'Reading the keystore from: ' + packageArgs.keystore);
}
- ['alias', 'storePassword', 'password', 'keystoreType'].forEach(function (key) {
+ ['alias', 'storePassword', 'password', 'keystoreType', 'packageType'].forEach(function (key) {
packageArgs[key] = packageArgs[key] || androidInfo[key];
});
}
@@ -105,11 +107,38 @@ function parseOpts (options, resolvedTarget, projectRoot) {
}
if (!ret.packageInfo) {
- if (Object.keys(packageArgs).length > 0) {
+ // The following loop is to decide whether to print a warning about generating a signed archive
+ // We only want to produce a warning if they are using a config property that is related to signing, but
+ // missing the required properties for signing. We don't want to produce a warning if they are simply
+ // using a build property that isn't related to signing, such as --packageType
+ let shouldWarn = false;
+ const signingKeys = ['keystore', 'alias', 'storePassword', 'password', 'keystoreType'];
+
+ for (let key in packageArgs) {
+ if (!shouldWarn && signingKeys.indexOf(key) > -1) {
+ // If we enter this condition, we have a key used for signing a build,
+ // but we are missing some required signing properties
+ shouldWarn = true;
+ }
+ }
+
+ if (shouldWarn) {
events.emit('warn', '\'keystore\' and \'alias\' need to be specified to generate a signed archive.');
}
}
+ if (packageArgs.packageType) {
+ const VALID_PACKAGE_TYPES = [PackageType.APK, PackageType.BUNDLE];
+ if (VALID_PACKAGE_TYPES.indexOf(packageArgs.packageType) === -1) {
+ events.emit('warn', '"' + packageArgs.packageType + '" is an invalid packageType. Valid values are: ' + VALID_PACKAGE_TYPES.join(', ') + '\nDefaulting packageType to ' + PackageType.APK);
+ ret.packageType = PackageType.APK;
+ } else {
+ ret.packageType = packageArgs.packageType;
+ }
+ } else {
+ ret.packageType = PackageType.APK;
+ }
+
return ret;
}
@@ -148,10 +177,17 @@ module.exports.run = function (options, optResolvedTarget) {
return;
}
return builder.build(opts).then(function () {
- var apkPaths = builder.findOutputApks(opts.buildType, opts.arch);
- events.emit('log', 'Built the following apk(s): \n\t' + apkPaths.join('\n\t'));
+ var paths;
+ if (opts.packageType === PackageType.BUNDLE) {
+ paths = builder.findOutputBundles(opts.buildType);
+ events.emit('log', 'Built the following bundle(s): \n\t' + paths.join('\n\t'));
+ } else {
+ paths = builder.findOutputApks(opts.buildType, opts.arch);
+ events.emit('log', 'Built the following apk(s): \n\t' + paths.join('\n\t'));
+ }
+
return {
- apkPaths: apkPaths,
+ paths: paths,
buildType: opts.buildType
};
});
@@ -273,6 +309,7 @@ module.exports.help = function () {
console.log(' \'--maxSdkVersion=#\': Override maxSdkVersion for this build. (Not Recommended)');
console.log(' \'--targetSdkVersion=#\': Override targetSdkVersion for this build.');
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(' \'--packageType=<apk|bundle>\': Builds an APK or a bundle');
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)');
diff --git a/bin/templates/cordova/lib/builders/ProjectBuilder.js b/bin/templates/cordova/lib/builders/ProjectBuilder.js
index f2a0066..35ce906 100644
--- a/bin/templates/cordova/lib/builders/ProjectBuilder.js
+++ b/bin/templates/cordova/lib/builders/ProjectBuilder.js
@@ -27,6 +27,7 @@ var spawn = require('cordova-common').superspawn.spawn;
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var check_reqs = require('../check_reqs');
+var PackageType = require('../PackageType');
const compareFunc = require('compare-func');
const MARKER = 'YOUR CHANGES WILL BE ERASED!';
@@ -38,23 +39,37 @@ const TEMPLATE =
class ProjectBuilder {
constructor (rootDirectory) {
this.root = rootDirectory || path.resolve(__dirname, '../../..');
- this.binDir = path.join(this.root, 'app', 'build', 'outputs', 'apk');
+ this.apkDir = path.join(this.root, 'app', 'build', 'outputs', 'apk');
+ this.aabDir = path.join(this.root, 'app', 'build', 'outputs', 'bundle');
}
getArgs (cmd, opts) {
- if (cmd === 'release') {
- cmd = 'cdvBuildRelease';
- } else if (cmd === 'debug') {
- cmd = 'cdvBuildDebug';
- }
+ let args;
+ if (opts.packageType === PackageType.BUNDLE) {
+ let buildCmd;
+ if (cmd === 'release') {
+ buildCmd = ':app:bundleRelease';
+ } else if (cmd === 'debug') {
+ buildCmd = ':app:bundleDebug';
+ }
- let args = [cmd, '-b', path.join(this.root, 'build.gradle')];
+ args = [buildCmd, '-b', path.join(this.root, 'build.gradle')];
+ } else {
+ let buildCmd;
+ if (cmd === 'release') {
+ buildCmd = 'cdvBuildRelease';
+ } else if (cmd === 'debug') {
+ buildCmd = 'cdvBuildDebug';
+ }
- if (opts.arch) {
- args.push('-PcdvBuildArch=' + opts.arch);
- }
+ args = [buildCmd, '-b', path.join(this.root, 'build.gradle')];
- args.push.apply(args, opts.extraArgs);
+ if (opts.arch) {
+ args.push('-PcdvBuildArch=' + opts.arch);
+ }
+
+ args.push.apply(args, opts.extraArgs);
+ }
return args;
}
@@ -288,7 +303,11 @@ class ProjectBuilder {
}
findOutputApks (build_type, arch) {
- return findOutputApksHelper(this.binDir, build_type, arch).sort(apkSorter);
+ return findOutputApksHelper(this.apkDir, build_type, arch).sort(apkSorter);
+ }
+
+ findOutputBundles (build_type) {
+ return findOutputBundlesHelper(this.aabDir, build_type);
}
fetchBuildResults (build_type, arch) {
@@ -359,6 +378,36 @@ function findOutputApksHelper (dir, build_type, arch) {
return ret;
}
+// This method was a copy of findOutputApksHelper and modified to look for bundles
+// While replacing shell with fs-extra, it might be a good idea to see if we can
+// generalise these findOutput methods.
+function findOutputBundlesHelper (dir, build_type) {
+ // This is an unused variable that was copied from findOutputApksHelper
+ // we are pretty sure it was meant to reset shell.config.silent back to
+ // the original value. However shell is planned to be replaced,
+ // it was left as is to avoid unintended consequences.
+ const shellSilent = shell.config.silent;
+ shell.config.silent = true;
+
+ // list directory recursively
+ const ret = shell.ls('-R', dir).map(function (file) {
+ return path.join(dir, file); // ls does not include base directory
+ }).filter(function (file) {
+ return file.match(/\.aab?$/i); // find all bundles
+ }).filter(function (candidate) {
+ // Need to choose between release and debug bundle.
+ if (build_type === 'debug') {
+ return /debug/.exec(candidate);
+ }
+ if (build_type === 'release') {
+ return /release/.exec(candidate);
+ }
+ return true;
+ });
+
+ return ret;
+}
+
function isAutoGenerated (file) {
return fs.existsSync(file) && fs.readFileSync(file, 'utf8').indexOf(MARKER) > 0;
}
diff --git a/bin/templates/cordova/lib/run.js b/bin/templates/cordova/lib/run.js
index 221467b..f1c70f2 100644
--- a/bin/templates/cordova/lib/run.js
+++ b/bin/templates/cordova/lib/run.js
@@ -23,6 +23,7 @@ var path = require('path');
var emulator = require('./emulator');
var device = require('./device');
var Q = require('q');
+var PackageType = require('./PackageType');
var events = require('cordova-common').events;
function getInstallTarget (runOptions) {
@@ -104,6 +105,14 @@ module.exports.run = function (runOptions) {
return new Promise((resolve) => {
const builder = require('./builders/builders').getBuilder();
const buildOptions = require('./build').parseBuildOptions(runOptions, null, self.root);
+
+ // Android app bundles cannot be deployed directly to the device
+ if (buildOptions.packageType === PackageType.BUNDLE) {
+ const packageTypeErrorMessage = 'Package type "bundle" is not supported during cordova run.';
+ events.emit('error', packageTypeErrorMessage);
+ throw packageTypeErrorMessage;
+ }
+
resolve(builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
}).then(function (buildResults) {
if (resolvedTarget && resolvedTarget.isEmulator) {
diff --git a/spec/unit/builders/ProjectBuilder.spec.js b/spec/unit/builders/ProjectBuilder.spec.js
index 6dfd05f..44769b5 100644
--- a/spec/unit/builders/ProjectBuilder.spec.js
+++ b/spec/unit/builders/ProjectBuilder.spec.js
@@ -66,6 +66,34 @@ describe('ProjectBuilder', () => {
expect(args[0]).toBe('cdvBuildDebug');
});
+ it('should set apk release', () => {
+ const args = builder.getArgs('release', {
+ packageType: 'apk'
+ });
+ expect(args[0]).withContext(args).toBe('cdvBuildRelease');
+ });
+
+ it('should set apk debug', () => {
+ const args = builder.getArgs('debug', {
+ packageType: 'apk'
+ });
+ expect(args[0]).withContext(args).toBe('cdvBuildDebug');
+ });
+
+ it('should set bundle release', () => {
+ const args = builder.getArgs('release', {
+ packageType: 'bundle'
+ });
+ expect(args[0]).withContext(args).toBe(':app:bundleRelease');
+ });
+
+ it('should set bundle debug', () => {
+ const args = builder.getArgs('debug', {
+ packageType: 'bundle'
+ });
+ expect(args[0]).withContext(args).toBe(':app:bundleDebug');
+ });
+
it('should add architecture if it is passed', () => {
const arch = 'unittest';
const args = builder.getArgs('debug', { arch });
diff --git a/spec/unit/run.spec.js b/spec/unit/run.spec.js
index 61a843c..76081eb 100644
--- a/spec/unit/run.spec.js
+++ b/spec/unit/run.spec.js
@@ -196,6 +196,18 @@ describe('run', () => {
expect(emulatorSpyObj.install).toHaveBeenCalledWith(emulatorTarget, { apkPaths: [], buildType: 'debug' });
});
});
+
+ it('should fail with the error message if --packageType=bundle setting is used', () => {
+ const deviceList = ['testDevice1', 'testDevice2'];
+ getInstallTargetSpy.and.returnValue(null);
+
+ deviceSpyObj.list.and.returnValue(Promise.resolve(deviceList));
+
+ return run.run({ argv: ['--packageType=bundle'] }).then(
+ () => fail('Expected error to be thrown'),
+ err => expect(err).toContain('Package type "bundle" is not supported during cordova run.')
+ );
+ });
});
describe('help', () => {
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org