Skip to content

Commit

Permalink
Merge pull request #1262 from flexn-io/fix/android_dev_management
Browse files Browse the repository at this point in the history
Android device management revamp
  • Loading branch information
mihaiblaga89 authored Dec 6, 2023
2 parents 51f3f1c + ac42ac0 commit 9fd9d0b
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 79 deletions.
3 changes: 1 addition & 2 deletions packages/app-harness/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@
},
"devDependencies": {
"@flexn/assets-renative-outline": "0.3.2",
"@flexn/graybox": "0.21.1",
"@rnv/core": "1.0.0-rc.1",
"@rnv/engine-lightning": "1.0.0-rc.1",
"@rnv/engine-rn": "1.0.0-rc.1",
"@rnv/engine-rn-electron": "1.0.0-rc.1",
"@rnv/engine-rn-next": "1.0.0-rc.1",
"@rnv/engine-rn-tvos": "1.0.0-rc.1",
"@rnv/engine-rn-web": "1.0.0-rc.1",
"@rnv/template-starter": "1.0.0-rc.1",
"@rnv/template-starter": "^1.0.0-rc.1",
"@types/react": "18.2.22",
"@types/react-dom": "18.2.7",
"@types/react-native": "0.72.2",
Expand Down
12 changes: 7 additions & 5 deletions packages/engine-rn-tvos/src/tasks/task.rnv.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import {
executeOrSkipTask,
shouldSkipTask,
} from '@rnv/core';
import { packageAndroid, runAndroid } from '@rnv/sdk-android';
import { runXcodeProject, getDeviceToRunOn } from '@rnv/sdk-apple';
import { packageAndroid, runAndroid, getAndroidDeviceToRunOn } from '@rnv/sdk-android';
import { runXcodeProject, getIosDeviceToRunOn } from '@rnv/sdk-apple';
import { startBundlerIfRequired, waitForBundlerIfRequired } from '@rnv/sdk-react-native';

export const taskRnvRun: RnvTaskFn = async (c, parentTask, originTask) => {
Expand All @@ -34,21 +34,23 @@ export const taskRnvRun: RnvTaskFn = async (c, parentTask, originTask) => {
switch (platform) {
case ANDROID_TV:
case FIRE_TV:
// eslint-disable-next-line no-case-declarations
const runDevice = await getAndroidDeviceToRunOn(c);
if (!c.program.only) {
await startBundlerIfRequired(c, TASK_RUN, originTask);
if (bundleAssets) {
await packageAndroid(c);
}
await runAndroid(c);
await runAndroid(c, runDevice!);
if (!bundleAssets) {
logSummary('BUNDLER STARTED');
}
return waitForBundlerIfRequired(c);
}
return runAndroid(c);
return runAndroid(c, runDevice!);
case TVOS:
// eslint-disable-next-line no-case-declarations
const runDeviceArgs = await getDeviceToRunOn(c);
const runDeviceArgs = await getIosDeviceToRunOn(c);
if (!c.program.only) {
await startBundlerIfRequired(c, TASK_RUN, originTask);
await runXcodeProject(c, runDeviceArgs);
Expand Down
12 changes: 7 additions & 5 deletions packages/engine-rn/src/tasks/task.rnv.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
getConfigProp,
logSummary,
} from '@rnv/core';
import { packageAndroid, runAndroid } from '@rnv/sdk-android';
import { runXcodeProject, getDeviceToRunOn } from '@rnv/sdk-apple';
import { packageAndroid, runAndroid, getAndroidDeviceToRunOn } from '@rnv/sdk-android';
import { runXcodeProject, getIosDeviceToRunOn } from '@rnv/sdk-apple';
import { startBundlerIfRequired, waitForBundlerIfRequired } from '@rnv/sdk-react-native';

export const taskRnvRun: RnvTaskFn = async (c, parentTask, originTask) => {
Expand All @@ -37,7 +37,7 @@ export const taskRnvRun: RnvTaskFn = async (c, parentTask, originTask) => {
case IOS:
case MACOS:
// eslint-disable-next-line no-case-declarations
const runDeviceArgs = await getDeviceToRunOn(c);
const runDeviceArgs = await getIosDeviceToRunOn(c);
if (!c.program.only) {
await startBundlerIfRequired(c, TASK_RUN, originTask);
await runXcodeProject(c, runDeviceArgs);
Expand All @@ -51,18 +51,20 @@ export const taskRnvRun: RnvTaskFn = async (c, parentTask, originTask) => {
case ANDROID_TV:
case FIRE_TV:
case ANDROID_WEAR:
// eslint-disable-next-line no-case-declarations
const runDevice = await getAndroidDeviceToRunOn(c);
if (!c.program.only) {
await startBundlerIfRequired(c, TASK_RUN, originTask);
if (bundleAssets || platform === ANDROID_WEAR) {
await packageAndroid(c);
}
await runAndroid(c);
await runAndroid(c, runDevice!);
if (!bundleAssets) {
logSummary('BUNDLER STARTED');
}
return waitForBundlerIfRequired(c);
}
return runAndroid(c);
return runAndroid(c, runDevice!);
default:
return logErrorPlatform(c);
}
Expand Down
28 changes: 21 additions & 7 deletions packages/sdk-android/src/deviceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const launchAndroidSimulator = async (
if (isIndependentThread) {
execCLI(c, CLI_ANDROID_EMULATOR, `-avd "${actualTarget}"`, {

Check warning

Code scanning / CodeQL

Unsafe shell command constructed from library input Medium

This string concatenation which depends on
library input
is later used in a
shell command
.
This string concatenation which depends on
library input
is later used in a
shell command
.
This string concatenation which depends on
library input
is later used in a
shell command
.
detached: isIndependentThread,
silent: true,
}).catch((err) => {
if (err.includes && err.includes('WHPX')) {
logWarning(err);
Expand All @@ -95,6 +96,7 @@ export const launchAndroidSimulator = async (
}
return execCLI(c, CLI_ANDROID_EMULATOR, `-avd "${actualTarget}"`, {

Check warning

Code scanning / CodeQL

Unsafe shell command constructed from library input Medium

This string concatenation which depends on
library input
is later used in a
shell command
.
This string concatenation which depends on
library input
is later used in a
shell command
.
This string concatenation which depends on
library input
is later used in a
shell command
.
detached: isIndependentThread,
silent: true,
});
}
return Promise.reject('No simulator -t target name specified!');
Expand Down Expand Up @@ -467,7 +469,7 @@ const _parseDevicesResult = async (
}
}

if (avdsString) {
if (avdsString && !deviceOnly) {
const avdLines = avdsString.trim().split(/\r?\n/);
logDebug('_parseDevicesResult 7', { avdLines });

Expand Down Expand Up @@ -507,7 +509,11 @@ const _parseDevicesResult = async (
}

if (avdDetails) {
devices.push(device);
// exclude duplicate sims (running ones + avdconfig)
const potentialDuplicate = devices.find((v) => v.name === device.name);
if (!potentialDuplicate || potentialDuplicate.isDevice) {
devices.push(device);
}
}
})
);
Expand Down Expand Up @@ -618,7 +624,7 @@ const waitForEmulatorToBeReady = (c: RnvContext, emulator: string) =>
return res;
});

export const checkForActiveEmulator = (c: RnvContext) =>
export const checkForActiveEmulator = (c: RnvContext, emulatorName?: string) =>
new Promise<AndroidDevice | undefined>((resolve, reject) => {
logTask('checkForActiveEmulator');
const { platform } = c;
Expand All @@ -637,11 +643,19 @@ export const checkForActiveEmulator = (c: RnvContext) =>
running = true;
getAndroidTargets(c, false, true, false)
.then(async (v) => {
logDebug('Available devices after filtering', v);
if (v.length > 0) {
logSuccess(`Found active emulator! ${chalk().white(v[0].udid)}. Will use it`);
const simsOnly = v.filter((device) => !device.isDevice);
logDebug('Available devices after filtering', simsOnly);
if (emulatorName) {
const found = simsOnly.find((v) => v.name === emulatorName);
if (found) {
logSuccess(`Found active emulator! ${chalk().white(found.udid)}. Will use it`);
clearInterval(poll);
resolve(found);
}
} else if (simsOnly.length > 0) {
logSuccess(`Found active emulator! ${chalk().white(simsOnly[0].udid)}. Will use it`);
clearInterval(poll);
resolve(v[0]);
resolve(simsOnly[0]);
} else {
logRaw(`looking for active emulators: attempt ${attempts}/${maxAttempts}`);
attempts++;
Expand Down
104 changes: 52 additions & 52 deletions packages/sdk-android/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
logTask,
logWarning,
logDebug,
logInfo,
logSuccess,
logRaw,
logError,
Expand Down Expand Up @@ -54,7 +53,7 @@ import {
import { parseGradleWrapperSync } from './gradleWrapperParser';
import { parseValuesStringsSync, injectPluginXmlValuesSync, parseValuesColorsSync } from './xmlValuesParser';
import { ejectGradleProject } from './ejector';
import { Context } from './types';
import { AndroidDevice, Context } from './types';
import {
resetAdb,
getAndroidTargets,
Expand All @@ -75,63 +74,59 @@ export const packageAndroid = async (_c: Context) => {
return true;
};

export const runAndroid = async (c: Context) => {
const { target } = c.program;
const { platform } = c;
export const getAndroidDeviceToRunOn = async (c: Context) => {
const defaultTarget = c.runtime.target;
logTask('runAndroid', `target:${target} default:${defaultTarget}`);
logTask('getAndroidDeviceToRunOn', `default:${defaultTarget}`);

if (!platform) return;
if (!c.platform) return;

const { target, device } = c.program;
const { platform } = c;

await resetAdb(c);

if (target && net.isIP(target.split(':')[0])) {
if (target && typeof target === 'string' && net.isIP(target.split(':')[0])) {
await connectToWifiDevice(c, target);
}

let devicesAndEmulators;
try {
devicesAndEmulators = await getAndroidTargets(c, false, false, c.program.device !== undefined);
} catch (e) {
return Promise.reject(e);
}
const devicesAndEmulators = await getAndroidTargets(c, false, false, !!device);

const activeDevices = devicesAndEmulators.filter((d) => d.isActive);
const inactiveDevices = devicesAndEmulators.filter((d) => !d.isActive);

const askWhereToRun = async () => {
if (activeDevices.length === 0 && inactiveDevices.length > 0) {
// No device active, but there are emulators created
const devicesString = composeDevicesArray(inactiveDevices);
const choices = devicesString;
const response = await inquirerPrompt({
name: 'chosenEmulator',
type: 'list',
message: 'What emulator would you like to start?',
choices,
});
if (response.chosenEmulator) {
await launchAndroidSimulator(c, response.chosenEmulator, true);
const devices = await checkForActiveEmulator(c);
await runReactNativeAndroid(c, platform, devices);
if (activeDevices.length || inactiveDevices.length) {
// No device active and device param is passed, exiting
if (c.program.device && !activeDevices.length) {
return logError('No active devices found, please connect one or remove the device argument', true);
}
} else if (activeDevices.length > 1) {
const devicesString = composeDevicesArray(activeDevices);
const choices = devicesString;

const activeString = composeDevicesArray(activeDevices);
const inactiveString = composeDevicesArray(inactiveDevices);

const choices = [...activeString, ...inactiveString];
const response = await inquirerPrompt({
name: 'chosenEmulator',
type: 'list',
message: 'Where would you like to run your app?',
message: 'What emulator would you like to start?',
choices,
});

if (response.chosenEmulator) {
const dev = activeDevices.find((d) => d.name === response.chosenEmulator);
await runReactNativeAndroid(c, platform, dev);
if (dev) return dev;

await launchAndroidSimulator(c, response.chosenEmulator, true);
const device = await checkForActiveEmulator(c, response.chosenEmulator);
return device;
}
} else {
if (c.program.device) {
return logError('No active devices found, please connect one or remove the device argument', true);
}
await askForNewEmulator(c, platform);
const devices = await checkForActiveEmulator(c);
await runReactNativeAndroid(c, platform, devices);
const device = await checkForActiveEmulator(c);
return device;
}
};

Expand All @@ -141,41 +136,46 @@ export const runAndroid = async (c: Context) => {
const foundDevice = devicesAndEmulators.find((d) => d.udid.includes(target) || d.name.includes(target));
if (foundDevice) {
if (foundDevice.isActive) {
await runReactNativeAndroid(c, platform, foundDevice);
} else {
await launchAndroidSimulator(c, foundDevice, true);
const device = await checkForActiveEmulator(c);
await runReactNativeAndroid(c, platform, device);
return foundDevice;
}
} else {
await askWhereToRun();
await launchAndroidSimulator(c, foundDevice, true);
const device = await checkForActiveEmulator(c, foundDevice.name);
return device;
}
} else if (activeDevices.length === 1) {
// Only one that is active, running on that one
const dv = activeDevices[0];
logInfo(`Found device ${dv.name}:${dv.udid}!`);
await runReactNativeAndroid(c, platform, dv);
logDebug('Target not found, asking where to run');
return askWhereToRun();
} else if (defaultTarget) {
// neither a target nor an active device is found, revert to default target if available
logDebug('Default target used', defaultTarget);
const foundDevice = devicesAndEmulators.find(
(d) => d.udid.includes(defaultTarget) || d.name.includes(defaultTarget)
);

if (!foundDevice) {
logDebug('Target not provided, asking where to run');
await askWhereToRun();
} else {
return askWhereToRun();
} else if (!foundDevice.isActive) {
await launchAndroidSimulator(c, foundDevice, true);
const device = await checkForActiveEmulator(c);
await runReactNativeAndroid(c, platform, device);
const device = await checkForActiveEmulator(c, foundDevice.name);
return device;
}
return foundDevice;
} else {
// we don't know what to do, ask the user
logDebug('Target not provided, asking where to run');
await askWhereToRun();
return askWhereToRun();
}
};

export const runAndroid = async (c: Context, device: AndroidDevice) => {
logTask('runAndroid', `target:${device.udid}`);
const { platform } = c;

if (!platform) return;

await runReactNativeAndroid(c, platform, device);
};

const _checkSigningCerts = async (c: Context) => {
logTask('_checkSigningCerts');
const signingConfig = getConfigProp(c, c.platform, 'signingConfig', 'Debug');
Expand Down
10 changes: 5 additions & 5 deletions packages/sdk-apple/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createRnvApi, createRnvContext } from '@rnv/core';
import type { PromptParams } from "@rnv/core";
import { getDeviceToRunOn } from '../runner';
import { getIosDeviceToRunOn } from '../runner';
import { simctlSimJson, xctraceDevices } from '../__mocks__/data';

beforeEach(() => {
Expand All @@ -14,7 +14,7 @@ afterEach(() => {
jest.clearAllMocks();
});

describe('getDeviceToRunOn', () => {
describe('getIosDeviceToRunOn', () => {
it('should return a device to run on with pick', async () => {
const ctx = getContext();

Expand All @@ -39,7 +39,7 @@ describe('getDeviceToRunOn', () => {
}
});

const deviceArgs = await getDeviceToRunOn(ctx);
const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(deviceArgs).toBe('--simulator iPhone\\ 14');
});
Expand All @@ -65,7 +65,7 @@ describe('getDeviceToRunOn', () => {
}
});

const deviceArgs = await getDeviceToRunOn(ctx);
const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(deviceArgs).toBe('--simulator iPhone\\ SE\\ (3rd\\ generation)');
});
Expand All @@ -80,7 +80,7 @@ describe('getDeviceToRunOn', () => {
.mockReturnValueOnce(Promise.resolve(xctraceDevices))
.mockReturnValueOnce(Promise.resolve(JSON.stringify(simctlSimJson)));

const deviceArgs = await getDeviceToRunOn(ctx);
const deviceArgs = await getIosDeviceToRunOn(ctx);
expect(executeAsync).toHaveBeenCalledTimes(2);
expect(deviceArgs).toContain('--simulator');
// expect(deviceArgs).toBe('--simulator iPhone\\ 14\\ Plus'); // FIXME: This is failing
Expand Down
4 changes: 2 additions & 2 deletions packages/sdk-apple/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ const copyAppleAssets = (c: Context, platform: RnvPlatform, appFolderName: strin
resolve();
});

export const getDeviceToRunOn = async (c: Context) => {
logTask('getDeviceToRunOn');
export const getIosDeviceToRunOn = async (c: Context) => {
logTask('getIosDeviceToRunOn');

if (!c.platform) return;

Expand Down
Loading

0 comments on commit 9fd9d0b

Please sign in to comment.