diff --git a/packages/expo-cli/src/commands/index.ts b/packages/expo-cli/src/commands/index.ts index 5160286f10..d9cc6c1b07 100644 --- a/packages/expo-cli/src/commands/index.ts +++ b/packages/expo-cli/src/commands/index.ts @@ -22,6 +22,7 @@ const COMMANDS = [ require('./publish-modify'), require('./push-creds'), require('./register'), + require('./run'), require('./send'), require('./start'), require('./upgrade'), diff --git a/packages/expo-cli/src/commands/run/buildAndroidClientAsync.ts b/packages/expo-cli/src/commands/run/buildAndroidClientAsync.ts new file mode 100644 index 0000000000..e35b420081 --- /dev/null +++ b/packages/expo-cli/src/commands/run/buildAndroidClientAsync.ts @@ -0,0 +1,73 @@ +import { AndroidConfig } from '@expo/config-plugins'; +import spawnAsync from '@expo/spawn-async'; +import { Android } from '@expo/xdl'; +import ora from 'ora'; +import path from 'path'; + +import Log from '../../log'; +import { prebuildAsync } from '../eject/prebuildAsync'; + +type Options = { + buildVariant: string; +}; + +function capitalize(name: string) { + return name.charAt(0).toUpperCase() + name.slice(1); +} + +function getGradleTask(buildVariant: string): string { + return `install${capitalize(buildVariant)}`; +} + +export default async function buildAndroidClientAsync( + projectRoot: string, + options: Options +): Promise { + const devices = await Android.getAllAvailableDevicesAsync(); + const device = devices.length > 1 ? await Android.promptForDeviceAsync(devices) : devices[0]; + if (!device) { + return; + } + const bootedDevice = await Android.attemptToStartEmulatorOrAssertAsync(device); + if (!bootedDevice) { + return; + } + + Log.log('Building app...'); + + let androidProjectPath; + try { + androidProjectPath = await AndroidConfig.Paths.getProjectPathOrThrowAsync(projectRoot); + } catch { + // If the project doesn't have native code, prebuild it... + await prebuildAsync(projectRoot, { + install: true, + platforms: ['android'], + }); + androidProjectPath = await AndroidConfig.Paths.getProjectPathOrThrowAsync(projectRoot); + } + + const gradlew = path.join( + androidProjectPath, + process.platform === 'win32' ? 'gradlew.bat' : 'gradlew' + ); + + await spawnAsync(gradlew, [getGradleTask(options.buildVariant)], { + cwd: androidProjectPath, + stdio: 'inherit', + }); + + const spinner = ora('Starting the development client...').start(); + try { + await Android.openProjectAsync({ + projectRoot, + shouldPrompt: false, + devClient: true, + }); + } catch (error) { + spinner.fail(); + throw error; + } + + spinner.succeed(); +} diff --git a/packages/expo-cli/src/commands/run/index.ts b/packages/expo-cli/src/commands/run/index.ts new file mode 100644 index 0000000000..def447f43c --- /dev/null +++ b/packages/expo-cli/src/commands/run/index.ts @@ -0,0 +1,27 @@ +import { Command } from 'commander'; + +import CommandError from '../../CommandError'; +import buildAndroidClientAsync from './buildAndroidClientAsync'; + +type Options = { + platform?: string; + buildVariant?: string; +}; + +async function runAndroidAsync(projectRoot: string, options: Options) { + if (typeof options.buildVariant !== 'string') { + throw new CommandError('--build-variant must be a string'); + } + await buildAndroidClientAsync(projectRoot, { + buildVariant: options.buildVariant, + }); +} + +export default function (program: Command) { + program + .command('run:android [path]') + .helpGroup('experimental') + .description('Build a development client and run it in on a device.') + .option('--build-variant [name]', '(Android) build variant', 'release') + .asyncActionProjectDir(runAndroidAsync); +} diff --git a/packages/xdl/src/Android.ts b/packages/xdl/src/Android.ts index 37e657a6f3..d750d45218 100644 --- a/packages/xdl/src/Android.ts +++ b/packages/xdl/src/Android.ts @@ -548,7 +548,7 @@ async function _openUrlAsync({ return openProject; } -async function attemptToStartEmulatorOrAssertAsync(device: Device): Promise { +export async function attemptToStartEmulatorOrAssertAsync(device: Device): Promise { // TODO: Add a light-weight method for checking since a device could disconnect. if (!(await isDeviceBootedAsync(device))) { @@ -995,7 +995,7 @@ function nameStyleForDevice(device: Device) { return (text: string) => chalk.bold(chalk.gray(text)); } -async function promptForDeviceAsync(devices: Device[]): Promise { +export async function promptForDeviceAsync(devices: Device[]): Promise { // TODO: provide an option to add or download more simulators // Pause interactions on the TerminalUI