Skip to content

Commit

Permalink
fix(list): handle errors with devices/virtual devices
Browse files Browse the repository at this point in the history
When listing native targets, we don't want the command to fail if a
specific type can't be resolved. For example, if Xcode isn't installed
or if the Android AVD folder can't be found.

Instead of the whole command failing, just print the error to stderr and
keep the output the same, even if it means no targets can be found.

fixes #49
  • Loading branch information
imhoffd committed Jun 17, 2019
1 parent f99f742 commit 9c2375d
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 66 deletions.
19 changes: 17 additions & 2 deletions src/android/list.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { serializeError } from '../errors';
import { Targets, formatTargets } from '../utils/list';

import { getDeviceTargets, getVirtualTargets } from './utils/list';
Expand All @@ -10,8 +11,22 @@ export async function run(args: readonly string[]): Promise<void> {
export async function list(args: readonly string[]): Promise<Targets> {
const sdk = await getSDK();
const [ devices, virtualDevices ] = await Promise.all([
getDeviceTargets(sdk),
getVirtualTargets(sdk),
(async () => {
try {
return await getDeviceTargets(sdk);
} catch (e) {
process.stderr.write(`Error with Android device targets: ${serializeError(e)}`);
return [];
}
})(),
(async () => {
try {
return await getVirtualTargets(sdk);
} catch (e) {
process.stderr.write(`Error with Android virtual targets: ${serializeError(e)}`);
return [];
}
})(),
]);

return { devices, virtualDevices };
Expand Down
4 changes: 2 additions & 2 deletions src/android/sdk-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type Platform = Required<APILevel>;

interface SDKInfo {
root: string;
avdHome: string;
avdHome?: string;
platforms: Platform[];
tools: SDKPackage[];
}
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function run(args: readonly string[]): Promise<void> {
function formatSDKInfo(sdk: SDKInfo): string {
return `
SDK Location: ${sdk.root}
AVD Home: ${sdk.avdHome}
AVD Home${sdk.avdHome ? `: ${sdk.avdHome}` : ` (!): not found`}
${sdk.platforms.map(platform => `${formatPlatform(platform)}\n\n`).join('\n')}
Tools:
Expand Down
34 changes: 26 additions & 8 deletions src/android/utils/avd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Debug from 'debug';
import * as pathlib from 'path';

import { ASSETS_PATH } from '../../constants';
import { AVDException, ERR_INVALID_SKIN, ERR_INVALID_SYSTEM_IMAGE, ERR_MISSING_SYSTEM_IMAGE, ERR_SDK_UNSATISFIED_PACKAGES, ERR_UNSUITABLE_API_INSTALLATION, ERR_UNSUPPORTED_API_LEVEL } from '../../errors';
import { AVDException, ERR_AVD_HOME_NOT_FOUND, ERR_INVALID_SKIN, ERR_INVALID_SYSTEM_IMAGE, ERR_MISSING_SYSTEM_IMAGE, ERR_SDK_UNSATISFIED_PACKAGES, ERR_UNSUITABLE_API_INSTALLATION, ERR_UNSUPPORTED_API_LEVEL, SDKException } from '../../errors';
import { readINI, writeINI } from '../../utils/ini';
import { sort } from '../../utils/object';

Expand Down Expand Up @@ -87,11 +87,18 @@ export const isAVDConfigINI = (o: any): o is AVDConfigINI => o

export async function getAVDINIs(sdk: SDK): Promise<[string, AVDINI][]> {
const debug = Debug(`${modulePrefix}:${getAVDINIs.name}`);
const contents = await readdir(sdk.avdHome);

const { avdHome } = sdk;

if (!avdHome) {
throw new SDKException(`No valid Android AVD home found.`, ERR_AVD_HOME_NOT_FOUND);
}

const contents = await readdir(avdHome);

const iniFilePaths = contents
.filter(f => pathlib.extname(f) === '.ini')
.map(f => pathlib.resolve(sdk.avdHome, f));
.map(f => pathlib.resolve(avdHome, f));

debug('Discovered AVD ini files: %O', iniFilePaths);

Expand Down Expand Up @@ -197,26 +204,37 @@ export async function getDefaultAVD(sdk: SDK, avds: readonly AVD[]): Promise<AVD
}

export async function createAVD(sdk: SDK, schematic: AVDSchematic): Promise<AVD> {
const { avdHome } = sdk;
const { id, ini, configini } = schematic;

await mkdirp(pathlib.join(sdk.avdHome, `${id}.avd`));
if (!avdHome) {
throw new SDKException(`No valid Android AVD home found.`, ERR_AVD_HOME_NOT_FOUND);
}

await mkdirp(pathlib.join(avdHome, `${id}.avd`));

await Promise.all([
writeINI(pathlib.join(sdk.avdHome, `${id}.ini`), ini),
writeINI(pathlib.join(sdk.avdHome, `${id}.avd`, 'config.ini'), configini),
writeINI(pathlib.join(avdHome, `${id}.ini`), ini),
writeINI(pathlib.join(avdHome, `${id}.avd`, 'config.ini'), configini),
]);

return getAVDFromConfigINI(pathlib.join(sdk.avdHome, `${id}.ini`), ini, configini);
return getAVDFromConfigINI(pathlib.join(avdHome, `${id}.ini`), ini, configini);
}

export async function createAVDSchematic(sdk: SDK, partialSchematic: PartialAVDSchematic): Promise<AVDSchematic> {
const { avdHome } = sdk;

if (!avdHome) {
throw new SDKException(`No valid Android AVD home found.`, ERR_AVD_HOME_NOT_FOUND);
}

const sysimage = findPackageBySchemaPath(sdk.packages || [], new RegExp(`^system-images;${partialSchematic.ini.target}`));

if (!sysimage) {
throw new AVDException(`Cannot create AVD schematic for ${partialSchematic.id}: missing system image.`, ERR_MISSING_SYSTEM_IMAGE);
}

const avdpath = pathlib.join(sdk.avdHome, `${partialSchematic.id}.avd`);
const avdpath = pathlib.join(avdHome, `${partialSchematic.id}.avd`);
const skinpath = getSkinPathByName(sdk, partialSchematic.configini['skin.name']);
const sysdir = pathlib.relative(sdk.root, sysimage.location);
const [ , , tagid ] = sysimage.path.split(';');
Expand Down
36 changes: 8 additions & 28 deletions src/android/utils/list.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,24 @@
import * as Debug from 'debug';

import { Target } from '../../utils/list';

import { Device, getDevices } from './adb';
import { AVD, getDefaultAVD, getInstalledAVDs } from './avd';
import { SDK } from './sdk';

const modulePrefix = 'native-run:android:utils:list';

export async function getDeviceTargets(sdk: SDK): Promise<Target[]> {
const debug = Debug(`${modulePrefix}:${getDeviceTargets.name}`);

try {
return (await getDevices(sdk))
.filter(device => device.type === 'hardware')
.map(deviceToTarget);
} catch (e) {
debug('Error getting device targets: %O', e);
}

return [];
return (await getDevices(sdk))
.filter(device => device.type === 'hardware')
.map(deviceToTarget);
}

export async function getVirtualTargets(sdk: SDK): Promise<Target[]> {
const debug = Debug(`${modulePrefix}:${getVirtualTargets.name}`);

try {
const avds = await getInstalledAVDs(sdk);
const defaultAvd = await getDefaultAVD(sdk, avds);

if (!avds.includes(defaultAvd)) {
avds.push(defaultAvd);
}
const avds = await getInstalledAVDs(sdk);
const defaultAvd = await getDefaultAVD(sdk, avds);

return avds.map(avdToTarget);
} catch (e) {
debug('Error getting virtual targets: %O', e);
if (!avds.includes(defaultAvd)) {
avds.push(defaultAvd);
}

return [];
return avds.map(avdToTarget);
}

export function deviceToTarget(device: Device): Target {
Expand Down
17 changes: 9 additions & 8 deletions src/android/utils/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Debug from 'debug';
import * as os from 'os';
import * as pathlib from 'path';

import { ERR_AVD_HOME_NOT_FOUND, ERR_EMULATOR_HOME_NOT_FOUND, ERR_SDK_NOT_FOUND, ERR_SDK_PACKAGE_NOT_FOUND, SDKException } from '../../../errors';
import { ERR_EMULATOR_HOME_NOT_FOUND, ERR_SDK_NOT_FOUND, ERR_SDK_PACKAGE_NOT_FOUND, SDKException } from '../../../errors';
import { isDir } from '../../../utils/fs';

import { getAPILevelFromPackageXml, getNameFromPackageXml, getPathFromPackageXml, getVersionFromPackageXml, readPackageXml } from './xml';
Expand All @@ -20,7 +20,7 @@ export const SDK_DIRECTORIES: ReadonlyMap<NodeJS.Platform, string[] | undefined>
export interface SDK {
readonly root: string;
readonly emulatorHome: string;
readonly avdHome: string;
readonly avdHome?: string;
packages?: SDKPackage[];
}

Expand Down Expand Up @@ -173,13 +173,13 @@ export async function resolveEmulatorHome(): Promise<string> {
const debug = Debug(`${modulePrefix}:${resolveEmulatorHome.name}`);
debug('Looking for $ANDROID_EMULATOR_HOME');

// Try $ANDROID_EMULATOR_HOME
if (process.env.ANDROID_EMULATOR_HOME && await isDir(process.env.ANDROID_EMULATOR_HOME)) {
debug('Using $ANDROID_EMULATOR_HOME at %s', process.env.$ANDROID_EMULATOR_HOME);
return process.env.ANDROID_EMULATOR_HOME;
}

// Try $HOME/.android/
debug('Looking at $HOME/.android');

const homeEmulatorHome = pathlib.join(homedir, '.android');

if (await isDir(homeEmulatorHome)) {
Expand All @@ -190,25 +190,26 @@ export async function resolveEmulatorHome(): Promise<string> {
throw new SDKException(`No valid Android Emulator home found.`, ERR_EMULATOR_HOME_NOT_FOUND);
}

export async function resolveAVDHome(): Promise<string> {
export async function resolveAVDHome(): Promise<string | undefined> {
const debug = Debug(`${modulePrefix}:${resolveAVDHome.name}`);

debug('Looking for $ANDROID_AVD_HOME');

// Try $ANDROID_AVD_HOME
if (process.env.ANDROID_AVD_HOME && await isDir(process.env.ANDROID_AVD_HOME)) {
debug('Using $ANDROID_AVD_HOME at %s', process.env.$ANDROID_AVD_HOME);
return process.env.ANDROID_AVD_HOME;
}

// Try $HOME/.android/avd/
debug('Looking at $HOME/.android/avd');

const homeAvdHome = pathlib.join(homedir, '.android', 'avd');

if (await isDir(homeAvdHome)) {
debug('Using $HOME/.android/avd/ at %s', homeAvdHome);
return homeAvdHome;
}

throw new SDKException(`No valid Android AVD home found.`, ERR_AVD_HOME_NOT_FOUND);
debug('No valid AVD home found.');
}

export function supplementProcessEnv(sdk: SDK): NodeJS.ProcessEnv {
Expand Down
39 changes: 21 additions & 18 deletions src/ios/list.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import * as Debug from 'debug';
import { DeviceValues } from 'node-ioslib';

import { serializeError } from '../errors';
import { Target, Targets, formatTargets } from '../utils/list';

import { getConnectedDevices } from './utils/device';
import { Simulator, getSimulators } from './utils/simulator';

const debug = Debug('native-run:ios:list');

export async function run(args: readonly string[]): Promise<void> {
process.stdout.write(`\n${formatTargets(args, await list(args))}\n`);
}

export async function list(args: readonly string[]): Promise<Targets> {
const devicesPromise = getConnectedDevices()
.then(devices => devices.map(deviceToTarget))
.catch(err => {
debug('There was an error getting the iOS device list: %O', err);
return [];
});

const simulatorsPromise = getSimulators()
.then(simulators => simulators.map(simulatorToTarget))
.catch(err => {
debug('There was an error getting the iOS simulator list: %O', err);
return [];
});

const [ devices, virtualDevices ] = await Promise.all([devicesPromise, simulatorsPromise]);
const [ devices, virtualDevices ] = await Promise.all([
(async () => {
try {
const devices = await getConnectedDevices();
return devices.map(deviceToTarget);
} catch (e) {
process.stderr.write(`Error with iOS device targets: ${serializeError(e)}`);
return [];
}
})(),
(async () => {
try {
const simulators = await getSimulators();
return simulators.map(simulatorToTarget);
} catch (e) {
process.stderr.write(`Error with iOS virtual targets: ${serializeError(e)}`);
return [];
}
})(),
]);

return { devices, virtualDevices };
}
Expand Down

0 comments on commit 9c2375d

Please sign in to comment.