Make iOS device detection faster

Summary:
I noticed that Android device detection is _much_ faster than iOS, so I tried to optimize it a bit

1. Removed a test run on `instruments -s`. That command is really slow (easily 5 secs), and the check has become redundant since Doctor already does a similar check
2. When querying for devices, it tries to fetch emulated and physical devices in parallel, but only processes the results after both have finished. However, finding simulators is almost instant, while querying the physical devices takes ~5 seconds ore more. So in this diff we process the found devices till in parallel, rather than waiting until both have arrived

This diff reduces the time until the ios simulator + FB app is detected from 28 to 4 seconds on my machine.

Changelog: Improved the startup sequence for emulated iOS devices, so that devices and apps connect a lot faster after starting Flipper

Reviewed By: jknoxville

Differential Revision: D21907597

fbshipit-source-id: 73edf0b04c4ad8367e04557e33f4c0d9e9bcd710
This commit is contained in:
Michel Weststrate
2020-06-08 03:43:14 -07:00
committed by Facebook GitHub Bot
parent b75113a7f1
commit b94e426261

View File

@@ -19,8 +19,8 @@ const execFile = child_process.execFile;
import iosUtil from '../utils/iOSContainerUtility'; import iosUtil from '../utils/iOSContainerUtility';
import IOSDevice from '../devices/IOSDevice'; import IOSDevice from '../devices/IOSDevice';
import isProduction from '../utils/isProduction'; import isProduction from '../utils/isProduction';
import GK from '../fb-stubs/GK';
import {registerDeviceCallbackOnPlugins} from '../utils/onRegisterDevice'; import {registerDeviceCallbackOnPlugins} from '../utils/onRegisterDevice';
type iOSSimulatorDevice = { type iOSSimulatorDevice = {
state: 'Booted' | 'Shutdown' | 'Shutting Down'; state: 'Booted' | 'Shutdown' | 'Shutting Down';
availability?: string; availability?: string;
@@ -31,6 +31,8 @@ type iOSSimulatorDevice = {
type IOSDeviceParams = {udid: string; type: DeviceType; name: string}; type IOSDeviceParams = {udid: string; type: DeviceType; name: string};
const exec = promisify(child_process.exec);
let portForwarders: Array<ChildProcess> = []; let portForwarders: Array<ChildProcess> = [];
function isAvailable(simulator: iOSSimulatorDevice): boolean { function isAvailable(simulator: iOSSimulatorDevice): boolean {
@@ -73,20 +75,36 @@ if (typeof window !== 'undefined') {
}); });
} }
async function queryDevices(store: Store, logger: Logger): Promise<void> { async function queryDevices(store: Store, logger: Logger): Promise<any> {
if (!(await checkIfDevicesCanBeQueryied(store))) { return Promise.all([
return; checkXcodeVersionMismatch(store),
} getActiveSimulators().then((devices) => {
await checkXcodeVersionMismatch(store); processDevices(store, logger, devices, 'emulator');
}),
getActiveDevices().then((devices) => {
processDevices(store, logger, devices, 'physical');
}),
]);
}
function processDevices(
store: Store,
logger: Logger,
activeDevices: IOSDeviceParams[],
type: 'physical' | 'emulator',
) {
const {connections} = store.getState(); const {connections} = store.getState();
const currentDeviceIDs: Set<string> = new Set( const currentDeviceIDs: Set<string> = new Set(
connections.devices connections.devices
.filter((device) => device instanceof IOSDevice) .filter(
(device) =>
device instanceof IOSDevice &&
device.deviceType === type &&
!device.isArchived,
)
.map((device) => device.serial), .map((device) => device.serial),
); );
return Promise.all([getActiveSimulators(), getActiveDevices()])
.then(([a, b]) => a.concat(b))
.then((activeDevices) => {
for (const {udid, type, name} of activeDevices) { for (const {udid, type, name} of activeDevices) {
if (currentDeviceIDs.has(udid)) { if (currentDeviceIDs.has(udid)) {
currentDeviceIDs.delete(udid); currentDeviceIDs.delete(udid);
@@ -121,7 +139,6 @@ async function queryDevices(store: Store, logger: Logger): Promise<void> {
payload: currentDeviceIDs, payload: currentDeviceIDs,
}); });
} }
});
} }
function getActiveSimulators(): Promise<Array<IOSDeviceParams>> { function getActiveSimulators(): Promise<Array<IOSDeviceParams>> {
@@ -180,7 +197,6 @@ async function checkXcodeVersionMismatch(store: Store) {
if (xcodeVersionMismatchFound) { if (xcodeVersionMismatchFound) {
return; return;
} }
const exec = promisify(child_process.exec);
try { try {
let {stdout: xcodeCLIVersion} = await exec('xcode-select -p'); let {stdout: xcodeCLIVersion} = await exec('xcode-select -p');
xcodeCLIVersion = xcodeCLIVersion.trim(); xcodeCLIVersion = xcodeCLIVersion.trim();
@@ -211,35 +227,8 @@ async function checkXcodeVersionMismatch(store: Store) {
console.error(e); console.error(e);
} }
} }
let canQueryDevices: boolean | undefined = undefined;
async function checkIfDevicesCanBeQueryied(store: Store): Promise<boolean> {
if (canQueryDevices !== undefined) {
return canQueryDevices;
}
try {
const exec = promisify(child_process.exec);
// make sure we can use instruments (it will throw otherwise)
await exec('instruments -s devices');
return (canQueryDevices = true);
} catch (e) {
store.dispatch({
type: 'SERVER_ERROR',
payload: {
message:
'It looks like XCode was not installed properly. Further actions are required if you want to use an iOS emulator.',
details:
"You might want to run 'sudo xcode-select -s /Applications/Xcode.app/Contents/Developer'",
error: e,
},
});
return (canQueryDevices = false);
}
}
async function isXcodeDetected(): Promise<boolean> { async function isXcodeDetected(): Promise<boolean> {
return promisify(child_process.exec)('xcode-select -p') return exec('xcode-select -p')
.then((_) => true) .then((_) => true)
.catch((_) => false); .catch((_) => false);
} }
@@ -266,16 +255,11 @@ export default (store: Store, logger: Logger) => {
if (!store.getState().settingsState.enableIOS) { if (!store.getState().settingsState.enableIOS) {
return; return;
} }
isXcodeDetected() isXcodeDetected().then((isDetected) => {
.then((isDetected) => {
store.dispatch(setXcodeDetected(isDetected)); store.dispatch(setXcodeDetected(isDetected));
return isDetected;
})
.then((isDetected) => {
if (isDetected) { if (isDetected) {
startDevicePortForwarders(); startDevicePortForwarders();
return queryDevicesForever(store, logger); return queryDevicesForever(store, logger);
} }
return Promise.resolve();
}); });
}; };