refactor: unify target resolution for devices & emulators (#1101)

* refactor: unify target resolution for devices & emulators
* fix: use unified target methods in platform-centric bins
This commit is contained in:
Raphael von der Grün
2021-04-09 08:37:56 +02:00
committed by GitHub
parent c774bf3311
commit c04ea9b1c0
11 changed files with 339 additions and 466 deletions
-48
View File
@@ -1,48 +0,0 @@
/*
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.
*/
var build = require('./build');
var Adb = require('./Adb');
var CordovaError = require('cordova-common').CordovaError;
/**
* Returns a promise for the list of the device ID's found
*/
module.exports.list = async () => {
return (await Adb.devices())
.filter(id => !id.startsWith('emulator-'));
};
module.exports.resolveTarget = function (target) {
return this.list().then(function (device_list) {
if (!device_list || !device_list.length) {
return Promise.reject(new CordovaError('Failed to deploy to device, no devices found.'));
}
// default device
target = target || device_list[0];
if (device_list.indexOf(target) < 0) {
return Promise.reject(new CordovaError('ERROR: Unable to find target \'' + target + '\'.'));
}
return build.detectArchitecture(target).then(function (arch) {
return { target: target, arch: arch, isEmulator: false };
});
});
};
-19
View File
@@ -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 };
});
});
};
+5 -5
View File
@@ -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);
});
+6 -6
View File
@@ -19,21 +19,21 @@
under the License.
*/
const { install } = require('./target');
var emulator = require('./emulator');
var args = process.argv;
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);
});
+7 -5
View File
@@ -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);
+21 -57
View File
@@ -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);
}
+83 -3
View File
@@ -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,