cordova-android/lib/target.js
エリス a9d4d4ebd2
feat!: bump Gradle 7.6 & AGP 7.4.2 (#1539)
* feat: bump gradle 7.6
* feat: bump android gradle plugin 7.3.1
* feat: bump android gradle plugin 7.4.2
* fix!: move android package name to build.gradle namespace
* fix!: remove deprecated package name from AndroidManifest
* fix: package name
* fix: rename CordovaGradleConfigParser's _save to write
* test: fix CordovaGradleConfigParser related specs
* fix: test refactoring for gradle namespace
* fix: accidental variable naming mixing

---------

Co-authored-by: Norman Breau <norman@nbsolutions.ca>
2023-04-12 14:39:47 +09:00

174 lines
5.9 KiB
JavaScript

/*
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 { inspect } = require('util');
const execa = require('execa');
const Adb = require('./Adb');
const build = require('./build');
const emulator = require('./emulator');
const { compareBy } = require('./utils');
const { retryPromise } = require('./retry');
const { events, CordovaError } = require('cordova-common');
const INSTALL_COMMAND_TIMEOUT = 5 * 60 * 1000;
const NUM_INSTALL_RETRIES = 3;
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
*/
/**
* 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
* @param {BuildResults} buildResults
* @return {Promise<Target>}
*/
async function resolveToOfflineEmulator ({ id: avdName, type }, { apkPaths }) {
if (type === 'device') return null;
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, buildResults) => {
events.emit('verbose', `Trying to find target matching ${inspect(spec)}`);
const resolvedTarget =
(await resolveToOnlineTarget(spec)) ||
(await resolveToOfflineEmulator(spec, buildResults));
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 }, { manifest, buildResults, cordovaGradleConfigParser }) {
const apk_path = build.findBestApkForArchitecture(buildResults, arch);
const pkgName = cordovaGradleConfigParser.getPackageName();
const launchName = pkgName + '/.' + manifest.getActivity().getName();
events.emit('log', 'Using apk: ' + apk_path);
events.emit('log', 'Package name: ' + pkgName);
events.emit('verbose', `Installing app on target ${target}`);
async function doInstall (execOptions = {}) {
try {
await Adb.install(target, apk_path, { replace: true, execOptions });
} catch (error) {
// CB-9557 CB-10157 only uninstall and reinstall app if the one that
// is already installed on device was signed w/different certificate
if (!/INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES/.test(error.toString())) throw error;
events.emit('warn', 'Uninstalling app from device and reinstalling it again because the ' +
'installed app already signed with different key');
// This promise is always resolved, even if 'adb uninstall' fails to uninstall app
// or the app doesn't installed at all, so no error catching needed.
await Adb.uninstall(target, pkgName);
await Adb.install(target, apk_path, { replace: true });
}
}
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,
killSignal: EXEC_KILL_SIGNAL
}));
} else {
await doInstall();
}
events.emit('log', 'INSTALL SUCCESS');
events.emit('verbose', 'Unlocking screen...');
await Adb.shell(target, 'input keyevent 82');
await Adb.start(target, launchName);
events.emit('log', 'LAUNCH SUCCESS');
};