refactor: use target SDK of built APK to determine best emulator (#1267)

* refactor(emulator): require emulatorId in emulator.run

* refactor: use effective targetSdk to find best emulator
This commit is contained in:
Raphael von der Grün
2021-07-12 09:48:36 +02:00
committed by GitHub
parent fb36e03aeb
commit 70a1eff705
6 changed files with 85 additions and 70 deletions
+9 -18
View File
@@ -25,7 +25,6 @@ var Adb = require('./Adb');
var events = require('cordova-common').events;
var CordovaError = require('cordova-common').CordovaError;
var android_sdk = require('./android_sdk');
var check_reqs = require('./check_reqs');
var which = require('which');
// constants
@@ -135,18 +134,19 @@ module.exports.list_images = function () {
};
/**
* Will return the closest avd to the projects target
* Returns the best image (if any) for given target.
*
* @param {Number} project_target Android targetSDK API level
* @return {{name: string} | undefined} the closest avd to the given target
* or undefined if no avds exist.
* Returns a promise.
*/
module.exports.best_image = function () {
module.exports.best_image = function (project_target) {
return this.list_images().then(function (images) {
// Just return undefined if there is no images
if (images.length === 0) return;
var closest = 9999;
var best = images[0];
var project_target = parseInt(check_reqs.get_target().replace('android-', ''));
for (var i in images) {
var target = images[i].target;
if (target && target.indexOf('API level') > -1) {
@@ -189,28 +189,19 @@ module.exports.get_available_port = function () {
/*
* Starts an emulator with the given ID,
* and returns the started ID of that emulator.
* If no ID is given it will use the first image available,
* if no image is available it will error out (maybe create one?).
* If no boot timeout is given or the value is negative it will wait forever for
* the emulator to boot
*
* Returns a promise.
*/
module.exports.start = function (emulator_ID, boot_timeout) {
module.exports.start = function (emulatorId, boot_timeout) {
var self = this;
return Promise.resolve().then(function () {
if (emulator_ID) return Promise.resolve(emulator_ID);
if (!emulatorId) {
throw new CordovaError('No emulator ID given');
}
return self.best_image().then(function (best) {
if (best && best.name) {
events.emit('warn', 'No emulator specified, defaulting to ' + best.name);
return best.name;
}
return Promise.reject(new CordovaError('No emulator images (avds) found'));
});
}).then(function (emulatorId) {
return self.get_available_port().then(function (port) {
// Figure out the directory the emulator binary runs in, and set the cwd to that directory.
// Workaround for https://code.google.com/p/android/issues/detail?id=235461
+7 -4
View File
@@ -56,10 +56,6 @@ function formatResolvedTarget ({ id, type }) {
* @return {Promise}
*/
module.exports.run = async function (runOptions = {}) {
const spec = buildTargetSpec(runOptions);
const resolvedTarget = await target.resolve(spec);
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);
const { packageType, buildType } = build.parseBuildOptions(runOptions, null, this.root);
// Android app bundles cannot be deployed directly to the device
@@ -68,6 +64,13 @@ module.exports.run = async function (runOptions = {}) {
}
const buildResults = this._builder.fetchBuildResults(buildType);
if (buildResults.apkPaths.length === 0) {
throw new CordovaError('Could not find any APKs to deploy');
}
const targetSpec = buildTargetSpec(runOptions);
const resolvedTarget = await target.resolve(targetSpec, buildResults);
events.emit('log', `Deploying to ${formatResolvedTarget(resolvedTarget)}`);
if (resolvedTarget.type === 'emulator') {
await emulator.wait_for_boot(resolvedTarget.id);
+30 -9
View File
@@ -19,6 +19,7 @@
const path = require('path');
const { inspect } = require('util');
const execa = require('execa');
const Adb = require('./Adb');
const build = require('./build');
const emulator = require('./emulator');
@@ -35,6 +36,7 @@ const EXEC_KILL_SIGNAL = 'SIGKILL';
* @typedef { 'device' | 'emulator' } TargetType
* @typedef { { id: string, type: TargetType } } Target
* @typedef { { id?: string, type?: TargetType } } TargetSpec
* @typedef { { apkPaths: string[] } } BuildResults
*/
/**
@@ -73,30 +75,49 @@ async function isEmulatorName (name) {
}
/**
* @param {TargetSpec?} spec
* @param {TargetSpec} spec
* @param {BuildResults} buildResults
* @return {Promise<Target>}
*/
async function resolveToOfflineEmulator (spec = {}) {
if (spec.type === 'device') return null;
if (spec.id && !(await isEmulatorName(spec.id))) return null;
async function resolveToOfflineEmulator ({ id: avdName, type }, { apkPaths }) {
if (type === 'device') 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);
if (avdName) {
if (!await isEmulatorName(avdName)) return null;
} else {
events.emit('verbose', 'Looking for emulator image that best matches the target API');
const targetSdk = await getTargetSdkFromApk(apkPaths[0]);
const bestImage = await emulator.best_image(targetSdk);
if (!bestImage) return null;
avdName = bestImage.name;
}
// try to start an emulator with name avdName
const emulatorId = await emulator.start(avdName);
return { id: emulatorId, type: 'emulator' };
}
async function getTargetSdkFromApk (apkPath) {
const { stdout: targetSdkStr } = await execa('apkanalyzer', [
'manifest', 'target-sdk', apkPath
]);
return Number(targetSdkStr);
}
/**
* @param {TargetSpec?} spec
* @param {BuildResults} buildResults
* @return {Promise<Target & {arch: string}>}
*/
exports.resolve = async (spec = {}) => {
exports.resolve = async (spec, buildResults) => {
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
const resolvedTarget =
(await resolveToOnlineTarget(spec)) ||
(await resolveToOfflineEmulator(spec));
(await resolveToOfflineEmulator(spec, buildResults));
if (!resolvedTarget) {
throw new CordovaError(`Could not find target matching ${inspect(spec)}`);