Skip to content

Commit

Permalink
run-android-on-specific-device
Browse files Browse the repository at this point in the history
Summary:
At the moment the run-android command from the react-native cli does run all available devices at once. However it would be extreme useful to have the option to choose only one specific device/simulator.
Therefore i've created a new flag for the command 'run-android': --deviceIdFromList 'deviceIdFromList' in order
to install and launch apps on a specific device/simulator from the command line.

'deviceIdFromList'  is the id listed on the output of the command 'adb devices'.

I've tested my code with the following commands:
react-native run-android --deviceIdFromList "Not existing id"
react-native run-android --deviceIdFromList
react-native run-android --deviceIdFromList "id of a simulator"
react-native run-android --deviceIdFromList "id of a device"

Output:
![not-existing-device](https://cloud.githubusercontent.com/assets/9102810/17931086/d843abc8-6a09-11e6-995d-8c737dd5ed5c.png)
![empty-flag](https://cloud.githubusercontent.com/assets/9102810/17931087/d8443930-6a09-11e6-94f3-d
Closes facebook#9568

Differential Revision: D4335133

Pulled By: mkonicek

fbshipit-source-id: a827628316be1b5751225851323b1131f451574c
  • Loading branch information
LearningDave authored and facebook-github-bot committed Dec 16, 2016
1 parent 4394419 commit 85c8333
Showing 1 changed file with 126 additions and 94 deletions.
220 changes: 126 additions & 94 deletions local-cli/runAndroid/runAndroid.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
*/
'use strict';

const adb = require('./adb');
const chalk = require('chalk');
const child_process = require('child_process');
const fs = require('fs');
const isPackagerRunning = require('../util/isPackagerRunning');
const path = require('path');
const isPackagerRunning = require('../util/isPackagerRunning');
const Promise = require('promise');
const adb = require('./adb');

// Verifies this is an Android project
function checkAndroid(root) {
Expand Down Expand Up @@ -76,9 +77,98 @@ function tryRunAdbReverse(device) {

// Builds the app and runs it on a connected emulator / device.
function buildAndRun(args) {
process.chdir(path.join(args.root, 'android'));
const cmd = process.platform.startsWith('win')
? 'gradlew.bat'
: './gradlew';

const packageName = fs.readFileSync(
'app/src/main/AndroidManifest.xml',
'utf8'
).match(/package="(.+?)"/)[1];

const adbPath = getAdbPath();
if (args.deviceId) {
runOnSpecificDevice(args, cmd, packageName, adbPath);
} else {
runOnAllDevices(args, cmd, packageName, adbPath);
}
}

function runOnSpecificDevice(args, gradlew, packageName, adbPath) {
let devices = adb.getDevices();
if (devices && devices.length > 0) {
if (devices.indexOf(args.deviceId) !== -1) {
buildApk(gradlew);
installAndLaunchOnDevice(args, args.deviceId, packageName, adbPath);
} else {
console.log('Could not find device with the id: "' + args.deviceId + '".');
console.log('Choose one of the following:');
console.log(devices);
}
} else {
console.log('No Android devices connected.');
}
}

function buildApk(gradlew) {
try {
adb.getDevices().map((device) => tryRunAdbReverse(device));
console.log(chalk.bold(
'Building the app...'
));

// using '-x lint' in order to ignore linting errors while building the apk
child_process.execFileSync(gradlew, ['build', '-x', 'lint'], {
stdio: [process.stdin, process.stdout, process.stderr],
});
} catch (e) {
console.log(chalk.red(
'Could not build the app on the device, read the error above for details.\n'
));
}
}

function tryInstallAppOnDevice(args, device) {
try {
const pathToApk = 'app/build/outputs/apk/app-debug.apk';
const adbPath = getAdbPath();
const adbArgs = ['-s', device, 'install', pathToApk];
console.log(chalk.bold(
`Installing the app on the device (cd android && adb -s ${device} install ${pathToApk}`
));
child_process.execFileSync(adbPath, adbArgs, {
stdio: [process.stdin, process.stdout, process.stderr],
});
} catch (e) {
console.log(e.message);
console.log(chalk.red(
'Could not install the app on the device, read the error above for details.\n'
));
}
}

function tryLaunchAppOnDevice(device, packageName, adbPath) {
try {
const adbArgs = ['-s', device, 'shell', 'am', 'start', '-n', packageName + '/.MainActivity'];
console.log(chalk.bold(
`Starting the app on ${device} (${adbPath} ${adbArgs.join(' ')})...`
));
child_process.spawnSync(adbPath, adbArgs, {stdio: 'inherit'});
} catch (e) {
console.log(chalk.red(
'adb invocation failed. Do you have adb in your PATH?'
));
}
}

function installAndLaunchOnDevice(args, selectedDevice, packageName, adbPath) {
tryRunAdbReverse(selectedDevice);
tryInstallAppOnDevice(args, selectedDevice);
tryLaunchAppOnDevice(selectedDevice, packageName, adbPath);
}

function runOnAllDevices(args, cmd, packageName, adbPath){
try {
const gradleArgs = [];
if (args.variant) {
gradleArgs.push('install' +
Expand All @@ -92,46 +182,15 @@ function buildAndRun(args) {
args.flavor[0].toUpperCase() + args.flavor.slice(1)
);
} else {
gradleArgs.push('install');
gradleArgs.push('installDebug');
}

// Append the build type to the current gradle install configuration.
// By default it will generate `installDebug`.
gradleArgs[0] =
gradleArgs[0] + args.configuration[0].toUpperCase() + args.configuration.slice(1);

// Get the Android project directory.
const androidProjectDir = path.join(args.root, 'android');

if (args.configuration.toUpperCase() === 'RELEASE') {
console.log(chalk.bold(
'Generating the bundle for the release build...'
));

child_process.execSync(
'react-native bundle ' +
'--platform android ' +
'--dev false ' +
'--entry-file index.android.js ' +
`--bundle-output ${androidProjectDir}/app/src/main/assets/index.android.bundle ` +
`--assets-dest ${androidProjectDir}/app/src/main/res/`,
{
stdio: [process.stdin, process.stdout, process.stderr],
}
);
if (args.installDebug) {
gradleArgs.push(args.installDebug);
}

// Change to the Android directory.
process.chdir(androidProjectDir);

// Get the gradle binary for the current platform.
const cmd = process.platform.startsWith('win')
? 'gradlew.bat'
: './gradlew';

console.log(chalk.bold(
'Building and installing the app on the device ' +
`(cd android && ${cmd} ${gradleArgs.join(' ')})...`
`Building and installing the app on the device (cd android && ${cmd} ${gradleArgs.join(' ')}...`
));

child_process.execFileSync(cmd, gradleArgs, {
Expand All @@ -149,50 +208,33 @@ function buildAndRun(args) {
// `console.log(e.stderr)`
return Promise.reject();
}

try {
const packageName = fs.readFileSync(
'app/src/main/AndroidManifest.xml',
'utf8'
).match(/package="(.+?)"/)[1];

const adbPath = getAdbPath();

const devices = adb.getDevices();

if (devices && devices.length > 0) {
devices.forEach((device) => {

const adbArgs =
['-s', device, 'shell', 'am', 'start', '-n', packageName + '/.' + args.mainActivity];

console.log(chalk.bold(
`Starting the app on ${device} (${adbPath} ${adbArgs.join(' ')})...`
));

child_process.spawnSync(adbPath, adbArgs, {stdio: 'inherit'});
tryRunAdbReverse(device);
tryLaunchAppOnDevice(device, packageName, adbPath);
});
} else {
// If we cannot execute based on adb devices output, fall back to
// shell am start
const fallbackAdbArgs = [
'shell', 'am', 'start', '-n', packageName + '/.MainActivity'
];
console.log(chalk.bold(
`Starting the app (${adbPath} ${fallbackAdbArgs.join(' ')}...`
));
child_process.spawnSync(adbPath, fallbackAdbArgs, {stdio: 'inherit'});
try {
// If we cannot execute based on adb devices output, fall back to
// shell am start
const fallbackAdbArgs = [
'shell', 'am', 'start', '-n', packageName + '/.MainActivity'
];
console.log(chalk.bold(
`Starting the app (${adbPath} ${fallbackAdbArgs.join(' ')}...`
));
child_process.spawnSync(adbPath, fallbackAdbArgs, {stdio: 'inherit'});
} catch (e) {
console.log(chalk.red(
'adb invocation failed. Do you have adb in your PATH?'
));
// stderr is automatically piped from the gradle process, so the user
// should see the error already, there is no need to do
// `console.log(e.stderr)`
return Promise.reject();
}
}

} catch (e) {
console.log(chalk.red(
'adb invocation failed. Do you have adb in your PATH?'
));
// stderr is automatically piped from the gradle process, so the user
// should see the error already, there is no need to do
// `console.log(e.stderr)`
return Promise.reject();
}
}

function startServerInNewWindow() {
Expand All @@ -206,9 +248,7 @@ function startServerInNewWindow() {

if (process.platform === 'darwin') {
if (yargV.open) {
return (
child_process.spawnSync('open', ['-a', yargV.open, launchPackagerScript], procConfig)
);
return child_process.spawnSync('open', ['-a', yargV.open, launchPackagerScript], procConfig);
}
return child_process.spawnSync('open', [launchPackagerScript], procConfig);

Expand All @@ -233,27 +273,19 @@ module.exports = {
description: 'builds your app and starts it on a connected Android emulator or device',
func: runAndroid,
options: [{
command: '--install-debug',
}, {
command: '--root [string]',
description:
'Override the root directory for the android build ' +
'(which contains the android directory)',
description: 'Override the root directory for the android build (which contains the android directory)',
default: '',
}, {
command: '--flavor [string]',
description: '--flavor has been deprecated. Use --variant instead',
}, {
command: '--configuration [string]',
description:
'You can use `Release` or `Debug`. ' +
'This creates a build based on the selected configuration. ' +
'If you want to use the `Release` configuration make sure you have the ' +
'`signingConfig` configured at `app/build.gradle`.',
default: 'Debug'
}, {
command: '--variant [string]',
}, {
command: '--main-activity [string]',
description: 'Name of the activity to start',
default: 'MainActivity'
command: '--deviceId [string]',
description: 'builds your app and starts it on a specific device/simulator with the ' +
'given device id (listed by running "adb devices" on the command line).',
}],
};
};

0 comments on commit 85c8333

Please sign in to comment.