Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: unify target resolution for devices & emulators #1101

Merged
merged 2 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 0 additions & 48 deletions bin/templates/cordova/lib/device.js

This file was deleted.

19 changes: 0 additions & 19 deletions bin/templates/cordova/lib/emulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 };
});
});
};
10 changes: 5 additions & 5 deletions bin/templates/cordova/lib/install-device
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
10 changes: 5 additions & 5 deletions bin/templates/cordova/lib/install-emulator
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
12 changes: 7 additions & 5 deletions bin/templates/cordova/lib/list-devices
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
78 changes: 21 additions & 57 deletions bin/templates/cordova/lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}

/**
Expand All @@ -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);

Expand All @@ -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);
}

Expand Down
86 changes: 83 additions & 3 deletions bin/templates/cordova/lib/target.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down
Loading