Skip to content
Permalink
Browse files
refactor: unify target resolution for devices & emulators (#1101)
* refactor: unify target resolution for devices & emulators
* fix: use unified target methods in platform-centric bins
  • Loading branch information
raphinesse committed Apr 9, 2021
1 parent c774bf3 commit c04ea9b1c0bfaf79409de73adcfdaffc5997b8de
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 465 deletions.

This file was deleted.

@@ -20,7 +20,6 @@
const execa = require('execa');
const fs = require('fs-extra');
var android_versions = require('android-versions');
var build = require('./build');
var path = require('path');
var Adb = require('./Adb');
var events = require('cordova-common').events;
@@ -349,21 +348,3 @@ module.exports.wait_for_boot = function (emulator_id, time_remaining) {
}
});
};

module.exports.resolveTarget = function (target) {
return this.list_started().then(function (emulator_list) {
if (emulator_list.length < 1) {
return Promise.reject(new CordovaError('No running Android emulators found, please start an emulator before deploying your project.'));
}

// default emulator
target = target || emulator_list[0];
if (emulator_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('Unable to find target \'' + target + '\'. Failed to deploy to emulator.'));
}

return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: true };
});
});
};
@@ -19,21 +19,21 @@
under the License.
*/

const { install } = require('./target');
var device = require('./device');
const { resolve, install } = require('./target');

var args = process.argv;
const targetSpec = { type: 'device' };

if (args.length > 2) {
var install_target;
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
targetSpec.id = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}

device.resolveTarget(install_target).then(install).catch(err => {
resolve(targetSpec).then(install).catch(err => {
console.error('ERROR: ' + err);
process.exit(2);
});
@@ -19,21 +19,21 @@
under the License.
*/

const { install } = require('./target');
var emulator = require('./emulator');
const { resolve, install } = require('./target');

var args = process.argv;
const targetSpec = { type: 'emulator' };

var install_target;
if (args.length > 2) {
if (args[2].substring(0, 9) === '--target=') {
install_target = args[2].substring(9, args[2].length);
targetSpec.id = args[2].substring(9, args[2].length);
} else {
console.error('ERROR : argument \'' + args[2] + '\' not recognized.');
process.exit(2);
}
}

emulator.resolveTarget(install_target).then(install).catch(err => {
resolve(targetSpec).then(install).catch(err => {
console.error('ERROR: ' + err);
process.exit(2);
});
@@ -19,14 +19,16 @@
under the License.
*/

var devices = require('./device');
const { list } = require('./target');

// Usage support for when args are given
require('./check_reqs').check_android().then(function () {
devices.list().then(function (device_list) {
device_list && device_list.forEach(function (dev) {
console.log(dev);
});
list().then(targets => {
const deviceIds = targets
.filter(({ type }) => type === 'device')
.map(({ id }) => id);

console.log(deviceIds.join('\n'));
}, function (err) {
console.error('ERROR: ' + err);
process.exit(2);
@@ -19,22 +19,30 @@

var path = require('path');
var emulator = require('./emulator');
var device = require('./device');
const target = require('./target');
var PackageType = require('./PackageType');
const { CordovaError, events } = require('cordova-common');
const { events } = require('cordova-common');

function getInstallTarget (runOptions) {
var install_target;
/**
* Builds a target spec from a runOptions object
*
* @param {{target?: string, device?: boolean, emulator?: boolean}} runOptions
* @return {target.TargetSpec}
*/
function buildTargetSpec (runOptions) {
const spec = {};
if (runOptions.target) {
install_target = runOptions.target;
spec.id = runOptions.target;
} else if (runOptions.device) {
install_target = '--device';
spec.type = 'device';
} else if (runOptions.emulator) {
install_target = '--emulator';
spec.type = 'emulator';
}
return spec;
}

return install_target;
function formatResolvedTarget ({ id, type }) {
return `${type} ${id}`;
}

/**
@@ -51,55 +59,11 @@ module.exports.run = function (runOptions) {
runOptions = runOptions || {};

var self = this;
var install_target = getInstallTarget(runOptions);
const spec = buildTargetSpec(runOptions);

return target.resolve(spec).then(function (resolvedTarget) {
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);

return Promise.resolve().then(function () {
if (!install_target) {
// no target given, deploy to device if available, otherwise use the emulator.
return device.list().then(function (device_list) {
if (device_list.length > 0) {
events.emit('warn', 'No target specified, deploying to device \'' + device_list[0] + '\'.');
install_target = device_list[0];
} else {
events.emit('warn', 'No target specified and no devices found, deploying to emulator');
install_target = '--emulator';
}
});
}
}).then(function () {
if (install_target === '--device') {
return device.resolveTarget(null);
} else if (install_target === '--emulator') {
// Give preference to any already started emulators. Else, start one.
return emulator.list_started().then(function (started) {
return started && started.length > 0 ? started[0] : emulator.start();
}).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
// They specified a specific device/emulator ID.
return device.list().then(function (devices) {
if (devices.indexOf(install_target) > -1) {
return device.resolveTarget(install_target);
}
return emulator.list_started().then(function (started_emulators) {
if (started_emulators.indexOf(install_target) > -1) {
return emulator.resolveTarget(install_target);
}
return emulator.list_images().then(function (avds) {
// if target emulator isn't started, then start it.
for (var avd in avds) {
if (avds[avd].name === install_target) {
return emulator.start(install_target).then(function (emulatorId) {
return emulator.resolveTarget(emulatorId);
});
}
}
return Promise.reject(new CordovaError(`Target '${install_target}' not found, unable to run project`));
});
});
});
}).then(function (resolvedTarget) {
return new Promise((resolve) => {
const buildOptions = require('./build').parseBuildOptions(runOptions, null, self.root);

@@ -112,7 +76,7 @@ module.exports.run = function (runOptions) {

resolve(self._builder.fetchBuildResults(buildOptions.buildType, buildOptions.arch));
}).then(async function (buildResults) {
if (resolvedTarget && resolvedTarget.isEmulator) {
if (resolvedTarget.type === 'emulator') {
await emulator.wait_for_boot(resolvedTarget.id);
}

@@ -18,17 +18,97 @@
*/

const path = require('path');
const { inspect } = require('util');
const Adb = require('./Adb');
const build = require('./build');
const emulator = require('./emulator');
const AndroidManifest = require('./AndroidManifest');
const { compareBy } = require('./utils');
const { retryPromise } = require('./retry');
const { events } = require('cordova-common');
const { events, CordovaError } = require('cordova-common');

const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
const NUM_INSTALL_RETRIES = 3;
const EXEC_KILL_SIGNAL = 'SIGKILL';

exports.install = async function ({ target, arch, isEmulator }, buildResults) {
/**
* @typedef { 'device' | 'emulator' } TargetType
* @typedef { { id: string, type: TargetType } } Target
* @typedef { { id?: string, type?: TargetType } } TargetSpec
*/

/**
* Returns a list of available targets (connected devices & started emulators)
*
* @return {Promise<Target[]>}
*/
exports.list = async () => {
return (await Adb.devices())
.map(id => ({
id,
type: id.startsWith('emulator-') ? 'emulator' : 'device'
}));
};

/**
* @param {TargetSpec?} spec
* @return {Promise<Target>}
*/
async function resolveToOnlineTarget (spec = {}) {
const targetList = await exports.list();
if (targetList.length === 0) return null;

// Sort by type: devices first, then emulators.
targetList.sort(compareBy(t => t.type));

// Find first matching target for spec. {} matches any target.
return targetList.find(target =>
Object.keys(spec).every(k => spec[k] === target[k])
) || null;
}

async function isEmulatorName (name) {
const emus = await emulator.list_images();
return emus.some(avd => avd.name === name);
}

/**
* @param {TargetSpec?} spec
* @return {Promise<Target>}
*/
async function resolveToOfflineEmulator (spec = {}) {
if (spec.type === 'device') return null;
if (spec.id && !(await isEmulatorName(spec.id))) return null;

// try to start an emulator with name spec.id
// if spec.id is undefined, picks best match regarding target API
const emulatorId = await emulator.start(spec.id);

return { id: emulatorId, type: 'emulator' };
}

/**
* @param {TargetSpec?} spec
* @return {Promise<Target & {arch: string}>}
*/
exports.resolve = async (spec = {}) => {
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);

const resolvedTarget =
(await resolveToOnlineTarget(spec)) ||
(await resolveToOfflineEmulator(spec));

if (!resolvedTarget) {
throw new CordovaError(`Could not find target matching ${inspect(spec)}`);
}

return {
...resolvedTarget,
arch: await build.detectArchitecture(resolvedTarget.id)
};
};

exports.install = async function ({ id: target, arch, type }, buildResults) {
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
const manifest = new AndroidManifest(path.join(__dirname, '../../app/src/main/AndroidManifest.xml'));
const pkgName = manifest.getPackageId();
@@ -56,7 +136,7 @@ exports.install = async function ({ target, arch, isEmulator }, buildResults) {
}
}

if (isEmulator) {
if (type === 'emulator') {
// Work around sporadic emulator hangs: http://issues.apache.org/jira/browse/CB-9119
await retryPromise(NUM_INSTALL_RETRIES, () => doInstall({
timeout: INSTALL_COMMAND_TIMEOUT,

0 comments on commit c04ea9b

Please sign in to comment.