mirror of
https://github.com/apache/cordova-android.git
synced 2025-01-18 22:52:54 +08:00
a9d4d4ebd2
* 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>
174 lines
5.9 KiB
JavaScript
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');
|
|
};
|