From 89149751449a14eb04926c54d2809e2af5d29af6 Mon Sep 17 00:00:00 2001 From: Kuba Date: Thu, 5 Sep 2019 02:56:43 +0200 Subject: [PATCH 01/10] feat: launch android emulator in run-android --- .../src/commands/runAndroid/index.ts | 6 +- .../commands/runAndroid/runOnAllDevices.ts | 15 ++++- .../commands/runAndroid/tryLaunchEmulator.ts | 61 +++++++++++++++++++ 3 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts diff --git a/packages/platform-android/src/commands/runAndroid/index.ts b/packages/platform-android/src/commands/runAndroid/index.ts index 373e582c7..d8b65b61e 100644 --- a/packages/platform-android/src/commands/runAndroid/index.ts +++ b/packages/platform-android/src/commands/runAndroid/index.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. * */ - import path from 'path'; import execa from 'execa'; import chalk from 'chalk'; @@ -119,7 +118,7 @@ function getPackageNameWithSuffix( } // Builds the app and runs it on a connected emulator / device. -function buildAndRun(args: Flags) { +async function buildAndRun(args: Flags) { process.chdir(path.join(args.root, 'android')); const cmd = process.platform.startsWith('win') ? 'gradlew.bat' : './gradlew'; @@ -135,7 +134,6 @@ function buildAndRun(args: Flags) { args.appIdSuffix, packageName, ); - const adbPath = getAdbPath(); if (args.deviceId) { return runOnSpecificDevice( @@ -146,7 +144,7 @@ function buildAndRun(args: Flags) { adbPath, ); } else { - return runOnAllDevices( + return await runOnAllDevices( args, cmd, packageNameWithSuffix, diff --git a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts index a36c0f408..306c2ee59 100644 --- a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts +++ b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts @@ -12,6 +12,7 @@ import {logger, CLIError} from '@react-native-community/cli-tools'; import adb from './adb'; import tryRunAdbReverse from './tryRunAdbReverse'; import tryLaunchAppOnDevice from './tryLaunchAppOnDevice'; +import tryLaunchEmulator from './tryLaunchEmulator'; import {Flags} from '.'; function getTaskNames( @@ -27,13 +28,24 @@ function toPascalCase(value: string) { return value[0].toUpperCase() + value.slice(1); } -function runOnAllDevices( +async function runOnAllDevices( args: Flags, cmd: string, packageNameWithSuffix: string, packageName: string, adbPath: string, ) { + const devices = adb.getDevices(adbPath); + if (devices.length === 0) { + logger.info('Trying to launch emulator...'); + const result = await tryLaunchEmulator(adbPath); + if (result) { + logger.info('Emulator launch succeeded!'); + } else { + logger.warn('Emulator launch failed.'); + } + } + try { const tasks = args.tasks || ['install' + toPascalCase(args.variant)]; const gradleArgs = getTaskNames(args.appFolder, tasks); @@ -51,7 +63,6 @@ function runOnAllDevices( } catch (error) { throw createInstallError(error); } - const devices = adb.getDevices(adbPath); (devices.length > 0 ? devices : [undefined]).forEach( (device: string | void) => { diff --git a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts new file mode 100644 index 000000000..d507bd033 --- /dev/null +++ b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts @@ -0,0 +1,61 @@ +import {execFileSync, spawn} from 'child_process'; +import Adb from './adb'; + +const getEmulators = () => { + try { + const emulatorsOutput = execFileSync('emulator', ['-list-avds']).toString(); + return emulatorsOutput.split('\n').filter(name => name !== ''); + } catch { + return []; + } +}; + +const launchEmulator = async (emulatorName: string, adbPath: string) => { + return new Promise((resolve, reject) => { + const cp = spawn('emulator', [`@${emulatorName}`], { + detached: true, + stdio: ['ignore', 'pipe', 'ignore'], + }); + + // Reject command after timeout + const rejectTimeout = setTimeout(() => { + cp.stdout.destroy(); + reject(); + }, 30 * 1000); + + // When emulator is started from snapshot, it does not emit boot completed message. + // It starts immediately so we can check if device is present + setTimeout(() => { + if (Adb.getDevices(adbPath).length > 0) { + resolve(); + } + }, 5000); + + cp.unref(); + + cp.stdout.addListener('data', message => { + if (message.toString().indexOf('boot completed') >= 0) { + clearTimeout(rejectTimeout); + cp.stdout.destroy(); + resolve(); + } + }); + + cp.on('close', () => { + reject(); + }); + }); +}; + +export default async function tryLaunchEmulator(adbPath: string) { + const emulators = getEmulators(); + if (emulators.length > 0) { + try { + await launchEmulator(emulators[0], adbPath); + return true; + } catch (e) { + console.log('Emulator error', e); + } + } + return false; +} From 0f0b69aadb25583b29b33497886ae6f7e3b5917a Mon Sep 17 00:00:00 2001 From: Kuba Date: Thu, 5 Sep 2019 03:12:13 +0200 Subject: [PATCH 02/10] test: fix tests due to changes --- .../runAndroid/__mocks__/tryLaunchEmulator.ts | 1 + .../__tests__/runOnAllDevices.test.ts | 26 +++++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 packages/platform-android/src/commands/runAndroid/__mocks__/tryLaunchEmulator.ts diff --git a/packages/platform-android/src/commands/runAndroid/__mocks__/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/__mocks__/tryLaunchEmulator.ts new file mode 100644 index 000000000..44746ea90 --- /dev/null +++ b/packages/platform-android/src/commands/runAndroid/__mocks__/tryLaunchEmulator.ts @@ -0,0 +1 @@ +export default async () => true; diff --git a/packages/platform-android/src/commands/runAndroid/__tests__/runOnAllDevices.test.ts b/packages/platform-android/src/commands/runAndroid/__tests__/runOnAllDevices.test.ts index daafc407b..830b2c56a 100644 --- a/packages/platform-android/src/commands/runAndroid/__tests__/runOnAllDevices.test.ts +++ b/packages/platform-android/src/commands/runAndroid/__tests__/runOnAllDevices.test.ts @@ -14,6 +14,7 @@ jest.mock('child_process', () => ({ })); jest.mock('../getAdbPath'); +jest.mock('../tryLaunchEmulator'); const {execFileSync} = require('child_process'); describe('--appFolder', () => { @@ -21,18 +22,17 @@ describe('--appFolder', () => { jest.clearAllMocks(); }); - it('uses task "install[Variant]" as default task', () => { + it('uses task "install[Variant]" as default task', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ variant: 'debug', }); - expect(execFileSync.mock.calls[0][1]).toContain('installDebug'); }); - it('uses appFolder and default variant', () => { + it('uses appFolder and default variant', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ appFolder: 'someApp', variant: 'debug', }); @@ -40,9 +40,9 @@ describe('--appFolder', () => { expect(execFileSync.mock.calls[0][1]).toContain('someApp:installDebug'); }); - it('uses appFolder and custom variant', () => { + it('uses appFolder and custom variant', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ appFolder: 'anotherApp', variant: 'staging', }); @@ -52,9 +52,9 @@ describe('--appFolder', () => { ); }); - it('uses only task argument', () => { + it('uses only task argument', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ tasks: ['someTask'], variant: 'debug', }); @@ -62,9 +62,9 @@ describe('--appFolder', () => { expect(execFileSync.mock.calls[0][1]).toContain('someTask'); }); - it('uses appFolder and custom task argument', () => { + it('uses appFolder and custom task argument', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ appFolder: 'anotherApp', tasks: ['someTask'], variant: 'debug', @@ -73,9 +73,9 @@ describe('--appFolder', () => { expect(execFileSync.mock.calls[0][1]).toContain('anotherApp:someTask'); }); - it('uses multiple tasks', () => { + it('uses multiple tasks', async () => { // @ts-ignore - runOnAllDevices({ + await runOnAllDevices({ appFolder: 'app', tasks: ['clean', 'someTask'], }); From 0b3e57461e2b2abdfe10fd3511adb67629bcfb85 Mon Sep 17 00:00:00 2001 From: Kuba Date: Thu, 5 Sep 2019 22:47:29 +0200 Subject: [PATCH 03/10] refactor: address CR comments & improvements --- .../commands/runAndroid/runOnAllDevices.ts | 12 +++- .../commands/runAndroid/tryLaunchEmulator.ts | 55 +++++++++++++------ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts index 306c2ee59..a0cb51de4 100644 --- a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts +++ b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts @@ -39,10 +39,16 @@ async function runOnAllDevices( if (devices.length === 0) { logger.info('Trying to launch emulator...'); const result = await tryLaunchEmulator(adbPath); - if (result) { - logger.info('Emulator launch succeeded!'); + if (result.success) { + logger.info('Emulator launched!'); } else { - logger.warn('Emulator launch failed.'); + logger.error('Emulator launch failed.'); + if (result.error !== undefined) { + logger.error(result.error); + } + logger.warn( + 'Please launch an emulator manually or connect a device. Otherwise app launch may fail.', + ); } } diff --git a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts index d507bd033..986b77be7 100644 --- a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts +++ b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts @@ -1,9 +1,9 @@ -import {execFileSync, spawn} from 'child_process'; +import execa from 'execa'; import Adb from './adb'; const getEmulators = () => { try { - const emulatorsOutput = execFileSync('emulator', ['-list-avds']).toString(); + const emulatorsOutput = execa.sync('emulator', ['-list-avds']).stdout; return emulatorsOutput.split('\n').filter(name => name !== ''); } catch { return []; @@ -12,50 +12,69 @@ const getEmulators = () => { const launchEmulator = async (emulatorName: string, adbPath: string) => { return new Promise((resolve, reject) => { - const cp = spawn('emulator', [`@${emulatorName}`], { + const cp = execa('emulator', [`@${emulatorName}`], { detached: true, stdio: ['ignore', 'pipe', 'ignore'], }); + const timeout = 30; + // Reject command after timeout const rejectTimeout = setTimeout(() => { - cp.stdout.destroy(); - reject(); - }, 30 * 1000); + cleanup(); + reject(`Could not start emulator within ${timeout} seconds.`); + }, timeout * 1000); // When emulator is started from snapshot, it does not emit boot completed message. - // It starts immediately so we can check if device is present - setTimeout(() => { + // It starts immediately so we can check if device is present after some short delay + const snapshotStartTimeout = setTimeout(() => { if (Adb.getDevices(adbPath).length > 0) { + cleanup(); resolve(); } }, 5000); + const cleanup = () => { + clearTimeout(rejectTimeout); + clearTimeout(snapshotStartTimeout); + cp.stdout.destroy(); + }; + cp.unref(); cp.stdout.addListener('data', message => { - if (message.toString().indexOf('boot completed') >= 0) { - clearTimeout(rejectTimeout); - cp.stdout.destroy(); + if (message.toString().includes('boot completed')) { + cleanup(); resolve(); } }); - cp.on('close', () => { - reject(); + cp.on('exit', () => { + cleanup(); + reject('Emulator exited before boot.'); + }); + + cp.on('error', error => { + cleanup(); + reject(error.message); }); }); }; -export default async function tryLaunchEmulator(adbPath: string) { +export default async function tryLaunchEmulator( + adbPath: string, +): Promise<{success: boolean; error?: string}> { const emulators = getEmulators(); if (emulators.length > 0) { try { await launchEmulator(emulators[0], adbPath); - return true; - } catch (e) { - console.log('Emulator error', e); + return {success: true}; + } catch (error) { + return {success: false, error}; } } - return false; + return { + success: false, + error: 'No emulators found as an output of `emulator -list-avds`', + }; } From 06e0129061f3c129d5a1447e380778a28e7545c7 Mon Sep 17 00:00:00 2001 From: Kuba Date: Thu, 5 Sep 2019 22:59:25 +0200 Subject: [PATCH 04/10] fix: refetch devices info after emualtor launch --- .../src/commands/runAndroid/runOnAllDevices.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts index a0cb51de4..bbf513043 100644 --- a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts +++ b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts @@ -35,12 +35,13 @@ async function runOnAllDevices( packageName: string, adbPath: string, ) { - const devices = adb.getDevices(adbPath); + let devices = adb.getDevices(adbPath); if (devices.length === 0) { logger.info('Trying to launch emulator...'); const result = await tryLaunchEmulator(adbPath); if (result.success) { logger.info('Emulator launched!'); + devices = adb.getDevices(adbPath); } else { logger.error('Emulator launch failed.'); if (result.error !== undefined) { From bb5d5d3d2a1ebc160e7ff89bb554dca298c1a273 Mon Sep 17 00:00:00 2001 From: Kuba Date: Mon, 9 Sep 2019 09:47:02 +0200 Subject: [PATCH 05/10] fix: remove redundant async/await --- packages/platform-android/src/commands/runAndroid/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/index.ts b/packages/platform-android/src/commands/runAndroid/index.ts index d8b65b61e..a9fa9db00 100644 --- a/packages/platform-android/src/commands/runAndroid/index.ts +++ b/packages/platform-android/src/commands/runAndroid/index.ts @@ -118,7 +118,7 @@ function getPackageNameWithSuffix( } // Builds the app and runs it on a connected emulator / device. -async function buildAndRun(args: Flags) { +function buildAndRun(args: Flags) { process.chdir(path.join(args.root, 'android')); const cmd = process.platform.startsWith('win') ? 'gradlew.bat' : './gradlew'; @@ -144,7 +144,7 @@ async function buildAndRun(args: Flags) { adbPath, ); } else { - return await runOnAllDevices( + return runOnAllDevices( args, cmd, packageNameWithSuffix, From 53a89079a8cd45f643c3b97688efb5b78f1b2738 Mon Sep 17 00:00:00 2001 From: Kuba Date: Wed, 11 Sep 2019 14:48:30 +0200 Subject: [PATCH 06/10] fix: run emulator from android home path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Michał Pierzchała --- .../src/commands/runAndroid/tryLaunchEmulator.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts index 986b77be7..1681f0cd3 100644 --- a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts +++ b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts @@ -12,10 +12,16 @@ const getEmulators = () => { const launchEmulator = async (emulatorName: string, adbPath: string) => { return new Promise((resolve, reject) => { - const cp = execa('emulator', [`@${emulatorName}`], { - detached: true, - stdio: ['ignore', 'pipe', 'ignore'], - }); + const cp = execa( + process.env.ANDROID_HOME + ? `${process.env.ANDROID_HOME}/emulator/emulator` + : 'emulator', + [`@${emulatorName}`], + { + detached: true, + stdio: ['ignore', 'pipe', 'ignore'], + }, + ); const timeout = 30; From d759f1a91764913dc6a77d5f942c53fe3a3f61a3 Mon Sep 17 00:00:00 2001 From: Kuba Date: Wed, 11 Sep 2019 15:44:14 +0200 Subject: [PATCH 07/10] refactor: use interval to check if emulator is booted --- .../commands/runAndroid/tryLaunchEmulator.ts | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts index 1681f0cd3..143c6dcba 100644 --- a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts +++ b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts @@ -19,10 +19,10 @@ const launchEmulator = async (emulatorName: string, adbPath: string) => { [`@${emulatorName}`], { detached: true, - stdio: ['ignore', 'pipe', 'ignore'], + stdio: 'ignore', }, ); - + cp.unref(); const timeout = 30; // Reject command after timeout @@ -31,30 +31,18 @@ const launchEmulator = async (emulatorName: string, adbPath: string) => { reject(`Could not start emulator within ${timeout} seconds.`); }, timeout * 1000); - // When emulator is started from snapshot, it does not emit boot completed message. - // It starts immediately so we can check if device is present after some short delay - const snapshotStartTimeout = setTimeout(() => { + const bootCheckInterval = setInterval(() => { if (Adb.getDevices(adbPath).length > 0) { cleanup(); resolve(); } - }, 5000); + }, 1000); const cleanup = () => { clearTimeout(rejectTimeout); - clearTimeout(snapshotStartTimeout); - cp.stdout.destroy(); + clearInterval(bootCheckInterval); }; - cp.unref(); - - cp.stdout.addListener('data', message => { - if (message.toString().includes('boot completed')) { - cleanup(); - resolve(); - } - }); - cp.on('exit', () => { cleanup(); reject('Emulator exited before boot.'); From 7019639fcfe67c1ede06a983eeec59574f316c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 11 Sep 2019 20:22:34 +0200 Subject: [PATCH 08/10] fix emulator path --- .../commands/runAndroid/tryLaunchEmulator.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts index 143c6dcba..9bdcf504c 100644 --- a/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts +++ b/packages/platform-android/src/commands/runAndroid/tryLaunchEmulator.ts @@ -1,9 +1,13 @@ import execa from 'execa'; import Adb from './adb'; +const emulatorCommand = process.env.ANDROID_HOME + ? `${process.env.ANDROID_HOME}/emulator/emulator` + : 'emulator'; + const getEmulators = () => { try { - const emulatorsOutput = execa.sync('emulator', ['-list-avds']).stdout; + const emulatorsOutput = execa.sync(emulatorCommand, ['-list-avds']).stdout; return emulatorsOutput.split('\n').filter(name => name !== ''); } catch { return []; @@ -12,16 +16,10 @@ const getEmulators = () => { const launchEmulator = async (emulatorName: string, adbPath: string) => { return new Promise((resolve, reject) => { - const cp = execa( - process.env.ANDROID_HOME - ? `${process.env.ANDROID_HOME}/emulator/emulator` - : 'emulator', - [`@${emulatorName}`], - { - detached: true, - stdio: 'ignore', - }, - ); + const cp = execa(emulatorCommand, [`@${emulatorName}`], { + detached: true, + stdio: 'ignore', + }); cp.unref(); const timeout = 30; From 20bdb48889e1fa3a004264e2646d7fcc44a60f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 11 Sep 2019 20:22:52 +0200 Subject: [PATCH 09/10] adjust wording --- .../src/commands/runAndroid/runOnAllDevices.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts index bbf513043..64eed5874 100644 --- a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts +++ b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts @@ -37,16 +37,15 @@ async function runOnAllDevices( ) { let devices = adb.getDevices(adbPath); if (devices.length === 0) { - logger.info('Trying to launch emulator...'); + logger.info('Launching emulator...'); const result = await tryLaunchEmulator(adbPath); if (result.success) { - logger.info('Emulator launched!'); + logger.info('Successfully launched emulator.'); devices = adb.getDevices(adbPath); } else { - logger.error('Emulator launch failed.'); - if (result.error !== undefined) { - logger.error(result.error); - } + logger.error( + `Failed to launch emulator. Reason: ${chalk.dim(result.error || '')}.`, + ); logger.warn( 'Please launch an emulator manually or connect a device. Otherwise app launch may fail.', ); From aae4e6ba861aa8a0276441c8472a23f5fe5fd9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 11 Sep 2019 20:26:00 +0200 Subject: [PATCH 10/10] moar adjustments --- .../platform-android/src/commands/runAndroid/runOnAllDevices.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts index 64eed5874..e9779d7ba 100644 --- a/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts +++ b/packages/platform-android/src/commands/runAndroid/runOnAllDevices.ts @@ -47,7 +47,7 @@ async function runOnAllDevices( `Failed to launch emulator. Reason: ${chalk.dim(result.error || '')}.`, ); logger.warn( - 'Please launch an emulator manually or connect a device. Otherwise app launch may fail.', + 'Please launch an emulator manually or connect a device. Otherwise app may fail to launch.', ); } }