1import path from 'path'; 2 3import { resolveInstallApkNameAsync } from './resolveInstallApkName'; 4import { Options, ResolvedOptions, resolveOptionsAsync } from './resolveOptions'; 5import { Log } from '../../log'; 6import { assembleAsync, installAsync } from '../../start/platforms/android/gradle'; 7import { setNodeEnv } from '../../utils/nodeEnv'; 8import { ensurePortAvailabilityAsync } from '../../utils/port'; 9import { getSchemesForAndroidAsync } from '../../utils/scheme'; 10import { ensureNativeProjectAsync } from '../ensureNativeProject'; 11import { logProjectLogsLocation } from '../hints'; 12import { startBundlerAsync } from '../startBundler'; 13 14const debug = require('debug')('expo:run:android'); 15 16export async function runAndroidAsync(projectRoot: string, { install, ...options }: Options) { 17 // NOTE: This is a guess, the developer can overwrite with `NODE_ENV`. 18 setNodeEnv(options.variant === 'release' ? 'production' : 'development'); 19 require('@expo/env').load(projectRoot); 20 21 await ensureNativeProjectAsync(projectRoot, { platform: 'android', install }); 22 23 const props = await resolveOptionsAsync(projectRoot, options); 24 25 debug('Package name: ' + props.packageName); 26 Log.log('› Building app...'); 27 28 const androidProjectRoot = path.join(projectRoot, 'android'); 29 30 await assembleAsync(androidProjectRoot, { 31 variant: props.variant, 32 port: props.port, 33 appName: props.appName, 34 buildCache: props.buildCache, 35 }); 36 37 // Ensure the port hasn't become busy during the build. 38 if (props.shouldStartBundler && !(await ensurePortAvailabilityAsync(projectRoot, props))) { 39 props.shouldStartBundler = false; 40 } 41 42 const manager = await startBundlerAsync(projectRoot, { 43 port: props.port, 44 // If a scheme is specified then use that instead of the package name. 45 scheme: (await getSchemesForAndroidAsync(projectRoot))?.[0], 46 headless: !props.shouldStartBundler, 47 }); 48 49 await installAppAsync(androidProjectRoot, props); 50 51 await manager.getDefaultDevServer().openCustomRuntimeAsync( 52 'emulator', 53 { 54 applicationId: props.packageName, 55 }, 56 { device: props.device.device } 57 ); 58 59 if (props.shouldStartBundler) { 60 logProjectLogsLocation(); 61 } 62} 63 64async function installAppAsync(androidProjectRoot: string, props: ResolvedOptions) { 65 // Find the APK file path 66 const apkFile = await resolveInstallApkNameAsync(props.device.device, props); 67 68 if (apkFile) { 69 // Attempt to install the APK from the file path 70 const binaryPath = path.join(props.apkVariantDirectory, apkFile); 71 debug('Installing:', binaryPath); 72 await props.device.installAppAsync(binaryPath); 73 } else { 74 // If we cannot resolve the APK file path then we can attempt to install using Gradle. 75 // This offers more advanced resolution that we may not have first class support for. 76 Log.log('› Failed to locate binary file, installing with Gradle...'); 77 await installAsync(androidProjectRoot, { 78 variant: props.variant ?? 'debug', 79 appName: props.appName ?? 'app', 80 port: props.port, 81 }); 82 } 83} 84