Update adbkit and logcat to maintained librarys

Summary:
We were using an old unmaintained/abandonned library for communicating with adb server https://github.com/openstf/adbkit

This was giving me issues i couldnt figure out when running flipper server.

There is a popular fork written in typescript here https://github.com/DeviceFarmer/adbkit but it uses blue bird for promises.

There is a fork of the fork here which i have chosen to use which is the same api as above but with es6 promises, https://github.com/UrielCh/adbkit.

Both forks have a slightly different api to the original. In the original library there was a single client and any command directed at a particular device had a serial as the first argument

In the new libraries you create a DeviceClient where the serial is baked in and you don't need to supply this argument every time

allow-large-files

Reviewed By: lblasa

Differential Revision: D45569652

fbshipit-source-id: 2a23c0eaa12feaebdccadb3d343e087c0d5708d5
This commit is contained in:
Andrey Goncharov
2023-05-18 09:56:41 -07:00
committed by Facebook GitHub Bot
parent e2d5cc4607
commit 898e9c3b07
10 changed files with 262 additions and 134 deletions

View File

@@ -8,7 +8,7 @@
*/
import CertificateProvider from '../../utils/CertificateProvider';
import {Client} from 'adbkit';
import {Client} from '@u4/adbkit';
import * as androidUtil from './androidContainerUtility';
import {csrFileName, extractAppNameFromCSR} from '../../utils/certificateUtils';
@@ -98,7 +98,7 @@ export default class AndroidCertificateProvider extends CertificateProvider {
const appName = await extractAppNameFromCSR(csr);
const deviceId = await this.getTargetDeviceId(appName, destination, csr);
await androidUtil.push(
this.adb,
this.adb.getDevice(deviceId),
deviceId,
appName,
destination + filename,
@@ -113,7 +113,7 @@ export default class AndroidCertificateProvider extends CertificateProvider {
csr: string,
): Promise<{isMatch: boolean; foundCsr: string}> {
const deviceCsr = await androidUtil.pull(
this.adb,
this.adb.getDevice(deviceId),
deviceId,
processName,
directory + csrFileName,

View File

@@ -7,8 +7,7 @@
* @format
*/
import adb, {util, Client as ADBClient, PullTransfer} from 'adbkit';
import {Reader} from 'adbkit-logcat';
import adb, {DeviceClient as ADBDeviceClient, PullTransfer} from '@u4/adbkit';
import {createWriteStream} from 'fs';
import type {DeviceDebugData, DeviceType} from 'flipper-common';
import {spawn} from 'child_process';
@@ -27,10 +26,8 @@ export default class AndroidDevice
extends ServerDevice
implements DebuggableDevice
{
adb: ADBClient;
pidAppMapping: {[key: number]: string} = {};
private adbClient: ADBDeviceClient;
private recordingProcess?: Promise<string>;
reader?: Reader;
readonly logListener: AndroidLogListener;
readonly crashWatcher: AndroidCrashWatcher;
@@ -39,7 +36,7 @@ export default class AndroidDevice
serial: string,
deviceType: DeviceType,
title: string,
adb: ADBClient,
adb: ADBDeviceClient,
abiList: Array<string>,
sdkVersion: string,
specs: DeviceSpec[] = [],
@@ -58,13 +55,12 @@ export default class AndroidDevice
screenshotAvailable: false,
},
});
this.adb = adb;
this.adbClient = adb;
this.logListener = new AndroidLogListener(
() => this.connected,
(logEntry) => this.addLogEntry(logEntry),
this.adb,
this.serial,
this.adbClient,
);
// It is OK not to await the start of the log listener. We just spawn it and handle errors internally.
this.logListener
@@ -87,9 +83,7 @@ export default class AndroidDevice
reverse(ports: number[]): Promise<void> {
return Promise.all(
ports.map((port) =>
this.adb.reverse(this.serial, `tcp:${port}`, `tcp:${port}`),
),
ports.map((port) => this.adbClient.reverse(`tcp:${port}`, `tcp:${port}`)),
).then(() => {
return;
});
@@ -103,13 +97,13 @@ export default class AndroidDevice
async navigateToLocation(location: string) {
const shellCommand = `am start ${encodeURI(location)}`;
this.adb.shell(this.serial, shellCommand);
this.adbClient.shell(shellCommand);
}
async screenshot(): Promise<Buffer> {
return new Promise((resolve, reject) => {
this.adb
.screencap(this.serial)
this.adbClient
.screencap()
.then((stream) => {
const chunks: Array<Buffer> = [];
stream
@@ -129,7 +123,7 @@ export default class AndroidDevice
console.debug('AndroidDevice.setIntoPermissiveMode', this.serial);
try {
try {
await this.adb.root(this.serial);
await this.adbClient.root();
} catch (e) {
if (
!(e instanceof Error) ||
@@ -179,15 +173,15 @@ export default class AndroidDevice
}
async executeShell(command: string): Promise<string> {
return await this.adb
.shell(this.serial, command)
return await this.adbClient
.shell(command)
.then(adb.util.readAll)
.then((output: Buffer) => output.toString().trim());
}
private async executeShellOrDie(command: string | string[]): Promise<void> {
const output = await this.adb
.shell(this.serial, command)
const output = await this.adbClient
.shell(command)
.then(adb.util.readAll)
.then((output: Buffer) => output.toString().trim());
if (output) {
@@ -196,16 +190,16 @@ export default class AndroidDevice
}
private async getSdkVersion(): Promise<number> {
return await this.adb
.shell(this.serial, 'getprop ro.build.version.sdk')
return await this.adbClient
.shell('getprop ro.build.version.sdk')
.then(adb.util.readAll)
.then((output) => Number(output.toString().trim()));
}
private async isValidFile(filePath: string): Promise<boolean> {
const sdkVersion = await this.getSdkVersion();
const fileSize = await this.adb
.shell(this.serial, `ls -l "${filePath}"`)
const fileSize = await this.adbClient
.shell(`ls -l "${filePath}"`)
.then(adb.util.readAll)
.then((output: Buffer) => output.toString().trim().split(' '))
.then((x) => x.filter(Boolean))
@@ -222,7 +216,7 @@ export default class AndroidDevice
let newSize: string | undefined;
try {
const sizeString = (
await adb.util.readAll(await this.adb.shell(this.serial, 'wm size'))
await adb.util.readAll(await this.adbClient.shell('wm size'))
).toString();
const size = sizeString.split(' ').slice(-1).pop()?.split('x');
if (size && size.length === 2) {
@@ -239,8 +233,8 @@ export default class AndroidDevice
}
const sizeArg = newSize ? `--size ${newSize}` : '';
const cmd = `screenrecord ${sizeArg} "${recordingLocation}"`;
this.recordingProcess = this.adb
.shell(this.serial, cmd)
this.recordingProcess = this.adbClient
.shell(cmd)
.then(adb.util.readAll)
.then(async (output) => {
const isValid = await this.isValidFile(recordingLocation);
@@ -254,8 +248,7 @@ export default class AndroidDevice
.then(
(_) =>
new Promise(async (resolve, reject) => {
const stream: PullTransfer = await this.adb.pull(
this.serial,
const stream: PullTransfer = await this.adbClient.pull(
recordingLocation,
);
stream.on('end', resolve as () => void);
@@ -274,14 +267,14 @@ export default class AndroidDevice
if (!recordingProcess) {
return Promise.reject(new Error('Recording was not properly started'));
}
await this.adb.shell(this.serial, `pkill -l2 screenrecord`);
await this.adbClient.shell(`pkill -l2 screenrecord`);
const destination = await recordingProcess;
this.recordingProcess = undefined;
return destination;
}
async forwardPort(local: string, remote: string): Promise<boolean> {
return this.adb.forward(this.serial, local, remote);
return this.adbClient.forward(local, remote);
}
disconnect() {
@@ -293,7 +286,7 @@ export default class AndroidDevice
async installApp(apkPath: string) {
console.log(`Installing app with adb ${apkPath}`);
await this.adb.install(this.serial, apkPath);
await this.adbClient.install(apkPath);
}
async readFlipperFolderForAllApps(): Promise<DeviceDebugData[]> {
@@ -301,9 +294,9 @@ export default class AndroidDevice
'AndroidDevice.readFlipperFolderForAllApps',
this.info.serial,
);
const output = await this.adb
.shell(this.info.serial, 'pm list packages -3 -e')
.then(util.readAll)
const output = await this.adbClient
.shell('pm list packages -3 -e')
.then(adb.util.readAll)
.then((buffer) => buffer.toString());
const appIds = output
@@ -322,7 +315,7 @@ export default class AndroidDevice
const appsCommandsResults = await Promise.all(
appIds.map(async (appId): Promise<DeviceDebugData | undefined> => {
const sonarDirFilePaths = await executeCommandAsApp(
this.adb,
this.adbClient,
this.info.serial,
appId,
`find /data/data/${appId}/files/sonar -type f`,
@@ -369,7 +362,7 @@ export default class AndroidDevice
return {
path: filePath,
data: await pull(
this.adb,
this.adbClient,
this.info.serial,
appId,
filePath,
@@ -379,7 +372,7 @@ export default class AndroidDevice
);
const sonarDirContentWithStatsCommandPromise = executeCommandAsApp(
this.adb,
this.adbClient,
this.info.serial,
appId,
`ls -al /data/data/${appId}/files/sonar`,

View File

@@ -7,8 +7,8 @@
* @format
*/
import type {Client as ADBClient} from 'adbkit';
import {Priority} from 'adbkit-logcat';
import type {DeviceClient as ADBClient} from '@u4/adbkit';
import {Priority, Reader} from '@u4/adbkit-logcat';
import {DeviceLogEntry, DeviceLogLevel} from 'flipper-common';
import {DeviceListener} from '../../utils/DeviceListener';
@@ -17,14 +17,13 @@ export class AndroidLogListener extends DeviceListener {
isDeviceConnected: () => boolean,
private onNewLogEntry: (logEntry: DeviceLogEntry) => void,
private readonly adb: ADBClient,
private readonly serial: string,
) {
super(isDeviceConnected);
}
protected async startListener() {
const reader = await this.adb.openLogcat(this.serial, {
const reader = (await this.adb.openLogcat({
clear: true,
});
})) as Reader;
let gracefulShutdown = false;
let lastKnownError: Error | undefined;

View File

@@ -9,7 +9,7 @@
import {DeviceType} from 'flipper-common';
import AndroidDevice from './AndroidDevice';
import {Client as ADBClient} from 'adbkit';
import {DeviceClient} from '@u4/adbkit';
import {FlipperServerImpl} from '../../FlipperServerImpl';
export default class KaiOSDevice extends AndroidDevice {
@@ -18,7 +18,7 @@ export default class KaiOSDevice extends AndroidDevice {
serial: string,
deviceType: DeviceType,
title: string,
adb: ADBClient,
adb: DeviceClient,
abiList: Array<string>,
sdkVersion: string,
) {

View File

@@ -9,8 +9,7 @@
import {reportPlatformFailures} from 'flipper-common';
import {execFile} from 'promisify-child-process';
import adbConfig from './adbConfig';
import adbkit, {Client} from 'adbkit';
import adbkit, {Client as ADBClient} from '@u4/adbkit';
import path from 'path';
type Config = {
@@ -23,7 +22,7 @@ type Config = {
export async function initializeAdbClient(
config: Config,
): Promise<Client | void> {
): Promise<ADBClient | void> {
const adbClient = await reportPlatformFailures(
createClient(config),
'createADBClient',
@@ -39,10 +38,10 @@ export async function initializeAdbClient(
/* Adbkit will attempt to start the adb server if it's not already running,
however, it sometimes fails with ENOENT errors. So instead, we start it
manually before requesting a client. */
async function createClient(config: Config): Promise<Client> {
return reportPlatformFailures<Client>(
async function createClient(config: Config): Promise<ADBClient> {
return reportPlatformFailures<ADBClient>(
startAdbServer(config.androidHome).then(() =>
adbkit.createClient(adbConfig(config.adbKitSettings)),
adbkit.createClient(config.adbKitSettings),
),
'createADBClient.shell',
);

View File

@@ -8,7 +8,7 @@
*/
import {UnsupportedError} from 'flipper-common';
import adbkit, {Client} from 'adbkit';
import adbkit, {DeviceClient} from '@u4/adbkit';
const allowedAppNameRegex = /^[\w.-]+$/;
const appNotApplicationRegex = /not an application/;
@@ -23,7 +23,7 @@ export type FilePath = string;
export type FileContent = string;
export async function push(
client: Client,
client: DeviceClient,
deviceId: string,
app: string,
filepath: string,
@@ -36,7 +36,7 @@ export async function push(
}
export async function pull(
client: Client,
client: DeviceClient,
deviceId: string,
app: string,
path: string,
@@ -81,7 +81,7 @@ class RunAsError extends Error {
}
function _push(
client: Client,
deviceClient: DeviceClient,
deviceId: string,
app: AppName,
filename: FilePath,
@@ -91,12 +91,12 @@ function _push(
// TODO: this is sensitive to escaping issues, can we leverage client.push instead?
// https://www.npmjs.com/package/adbkit#pushing-a-file-to-all-connected-devices
const command = `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`;
return executeCommandAsApp(client, deviceId, app, command)
return executeCommandAsApp(deviceClient, deviceId, app, command)
.then((_) => undefined)
.catch((error) => {
if (error instanceof RunAsError) {
// Fall back to running the command directly. This will work if adb is running as root.
executeCommandWithSu(client, deviceId, app, command, error);
executeCommandWithSu(deviceClient, deviceId, app, command, error);
return undefined;
}
throw error;
@@ -104,24 +104,32 @@ function _push(
}
function _pull(
client: Client,
deviceClient: DeviceClient,
deviceId: string,
app: AppName,
path: FilePath,
): Promise<string> {
const command = `cat '${path}'`;
return executeCommandAsApp(client, deviceId, app, command).catch((error) => {
if (error instanceof RunAsError) {
// Fall back to running the command directly. This will work if adb is running as root.
return executeCommandWithSu(client, deviceId, app, command, error);
}
throw error;
});
return executeCommandAsApp(deviceClient, deviceId, app, command).catch(
(error) => {
if (error instanceof RunAsError) {
// Fall back to running the command directly. This will work if adb is running as root.
return executeCommandWithSu(
deviceClient,
deviceId,
app,
command,
error,
);
}
throw error;
},
);
}
// Keep this method private since it relies on pre-validated arguments
export function executeCommandAsApp(
client: Client,
client: DeviceClient,
deviceId: string,
app: string,
command: string,
@@ -136,7 +144,7 @@ export function executeCommandAsApp(
}
async function executeCommandWithSu(
client: Client,
client: DeviceClient,
deviceId: string,
app: string,
command: string,
@@ -151,14 +159,14 @@ async function executeCommandWithSu(
}
function _executeCommandWithRunner(
client: Client,
client: DeviceClient,
deviceId: string,
app: string,
command: string,
runner: string,
): Promise<string> {
return client
.shell(deviceId, `echo '${command}' | ${runner}`)
.shell(`echo '${command}' | ${runner}`)
.then(adbkit.util.readAll)
.then((buffer) => buffer.toString())
.then((output) => {

View File

@@ -10,7 +10,7 @@
import AndroidDevice from './AndroidDevice';
import KaiOSDevice from './KaiOSDevice';
import child_process from 'child_process';
import {Client as ADBClient, Device} from 'adbkit';
import {Client as ADBClient, Device} from '@u4/adbkit';
import {join} from 'path';
import {FlipperServerImpl} from '../../FlipperServerImpl';
import {notNull} from '../../utils/typeUtils';
@@ -26,10 +26,7 @@ export class AndroidDeviceManager {
this.certificateProvider = new AndroidCertificateProvider(this.adbClient);
}
private createDevice(
adbClient: ADBClient,
device: Device,
): Promise<AndroidDevice | undefined> {
private createDevice(device: Device): Promise<AndroidDevice | undefined> {
return new Promise(async (resolve, reject) => {
const type =
device.type !== 'device' || device.id.startsWith('emulator')
@@ -37,7 +34,9 @@ export class AndroidDeviceManager {
: 'physical';
try {
const props = await adbClient.getProperties(device.id);
const deviceClient = device.getClient();
const props = await deviceClient.getProperties();
try {
let name = props['ro.product.model'];
const abiString = props['ro.product.cpu.abilist'] || '';
@@ -56,7 +55,7 @@ export class AndroidDeviceManager {
device.id,
type,
name,
adbClient,
deviceClient,
abiList,
sdkVersion,
);
@@ -195,7 +194,7 @@ export class AndroidDeviceManager {
const devices = await this.adbClient.listDevices();
for (const device of devices) {
if (device.type !== 'offline') {
this.registerDevice(this.adbClient, device);
this.registerDevice(device);
} else {
this.handleOfflineDevice(device);
}
@@ -237,7 +236,7 @@ export class AndroidDeviceManager {
return;
}
if (device.type !== 'offline') {
this.registerDevice(this.adbClient, device);
this.registerDevice(device);
} else {
this.handleOfflineDevice(device);
}
@@ -247,7 +246,7 @@ export class AndroidDeviceManager {
if (device.type === 'offline') {
this.flipperServer.unregisterDevice(device.id);
} else {
this.registerDevice(this.adbClient, device);
this.registerDevice(device);
}
});
@@ -273,8 +272,8 @@ export class AndroidDeviceManager {
);
}
private async registerDevice(adbClient: ADBClient, deviceData: Device) {
const androidDevice = await this.createDevice(adbClient, deviceData);
private async registerDevice(deviceData: Device) {
const androidDevice = await this.createDevice(deviceData);
if (!androidDevice) {
return;
}