From aeb0b5f317ba8a66b5fa16e09a9557193f671ac3 Mon Sep 17 00:00:00 2001 From: Lawrence Lomax Date: Mon, 31 Jan 2022 07:23:29 -0800 Subject: [PATCH] Extract getSimulators to IOSBridge Summary: Extracts `getSimulator` interrnals to `SimctlBridge`. This allows this functionality to be used independently of things like the the flipper server. For now this just moves the functionality, but future diffs will build on top of this. Reviewed By: passy Differential Revision: D33842986 fbshipit-source-id: bae26a9bd5c21c9813f8a2b10c3b3e3efc1c5929 --- .../src/devices/ios/IOSBridge.tsx | 47 +++++++++++++ .../devices/ios/__tests__/iOSDevice.node.tsx | 18 +++++ .../src/devices/ios/iOSDeviceManager.tsx | 70 ++++--------------- 3 files changed, 78 insertions(+), 57 deletions(-) diff --git a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx index 6d6ca2956..df4e7f816 100644 --- a/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx +++ b/desktop/flipper-server-core/src/devices/ios/IOSBridge.tsx @@ -9,6 +9,7 @@ import fs from 'fs-extra'; import child_process from 'child_process'; +import type {IOSDeviceParams} from 'flipper-common'; import {DeviceType} from 'flipper-common'; import {v1 as uuid} from 'uuid'; import path from 'path'; @@ -21,6 +22,15 @@ export const ERR_NO_IDB_OR_XCODE_AVAILABLE = export const ERR_PHYSICAL_DEVICE_LOGS_WITHOUT_IDB = 'Cannot provide logs from a physical device without idb.'; +// eslint-disable-next-line @typescript-eslint/naming-convention +type iOSSimulatorDevice = { + state: 'Booted' | 'Shutdown' | 'Shutting Down'; + availability?: string; + isAvailable?: 'YES' | 'NO' | true | false; + name: string; + udid: string; +}; + export interface IOSBridge { startLogListener: ( udid: string, @@ -115,12 +125,49 @@ export class SimctlBridge implements IOSBridge { ); } + async getActiveDevices(bootedOnly: boolean): Promise> { + return execFile('xcrun', [ + 'simctl', + ...getDeviceSetPath(), + 'list', + 'devices', + '--json', + ]) + .then(({stdout}) => JSON.parse(stdout!.toString()).devices) + .then((simulatorDevices: Array) => { + const simulators = Object.values(simulatorDevices).flat(); + return simulators + .filter( + (simulator) => + (!bootedOnly || simulator.state === 'Booted') && + isSimulatorAvailable(simulator), + ) + .map((simulator) => { + return { + ...simulator, + type: 'emulator', + } as IOSDeviceParams; + }); + }); + } + async launchSimulator(udid: string): Promise { await execFile('xcrun', ['simctl', ...getDeviceSetPath(), 'boot', udid]); await execFile('open', ['-a', 'simulator']); } } +function isSimulatorAvailable(simulator: iOSSimulatorDevice): boolean { + // For some users "availability" is set, for others it's "isAvailable" + // It's not clear which key is set, so we are checking both. + // We've also seen isAvailable return "YES" and true, depending on version. + return ( + simulator.availability === '(available)' || + simulator.isAvailable === 'YES' || + simulator.isAvailable === true + ); +} + async function isAvailable(idbPath: string): Promise { if (!idbPath) { return false; diff --git a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx index ddef8270b..0d29e4c88 100644 --- a/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx +++ b/desktop/flipper-server-core/src/devices/ios/__tests__/iOSDevice.node.tsx @@ -18,7 +18,17 @@ import { setFlipperServerConfig, } from '../../../FlipperServerConfig'; +let fakeSimctlBridge: any; +let hasCalledSimctlActiveDevices = false; + beforeEach(() => { + hasCalledSimctlActiveDevices = false; + fakeSimctlBridge = { + getActiveDevices: async (_bootedOnly: boolean) => { + hasCalledSimctlActiveDevices = true; + return []; + }, + }; setFlipperServerConfig(getRenderHostInstance().serverConfig); }); @@ -73,11 +83,13 @@ test('test getAllPromisesForQueryingDevices when xcode detected', () => { ); flipperServer.ios.iosBridge = {} as IOSBridge; (flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings; + flipperServer.ios.simctlBridge = fakeSimctlBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( true, false, ); expect(promises.length).toEqual(2); + expect(hasCalledSimctlActiveDevices).toEqual(true); }); test('test getAllPromisesForQueryingDevices when xcode is not detected', () => { @@ -87,11 +99,13 @@ test('test getAllPromisesForQueryingDevices when xcode is not detected', () => { ); flipperServer.ios.iosBridge = {} as IOSBridge; (flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings; + flipperServer.ios.simctlBridge = fakeSimctlBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( false, true, ); expect(promises.length).toEqual(1); + expect(hasCalledSimctlActiveDevices).toEqual(false); }); test('test getAllPromisesForQueryingDevices when xcode and idb are both unavailable', () => { @@ -101,11 +115,13 @@ test('test getAllPromisesForQueryingDevices when xcode and idb are both unavaila ); flipperServer.ios.iosBridge = {} as IOSBridge; (flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings; + flipperServer.ios.simctlBridge = fakeSimctlBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( false, false, ); expect(promises.length).toEqual(0); + expect(hasCalledSimctlActiveDevices).toEqual(false); }); test('test getAllPromisesForQueryingDevices when both idb and xcode are available', () => { @@ -115,9 +131,11 @@ test('test getAllPromisesForQueryingDevices when both idb and xcode are availabl ); flipperServer.ios.iosBridge = {} as IOSBridge; (flipperServer.ios as any).idbConfig = getFlipperServerConfig().settings; + flipperServer.ios.simctlBridge = fakeSimctlBridge; const promises = flipperServer.ios.getAllPromisesForQueryingDevices( true, true, ); expect(promises.length).toEqual(2); + expect(hasCalledSimctlActiveDevices).toEqual(false); }); diff --git a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx index b10e1338f..099105641 100644 --- a/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx +++ b/desktop/flipper-server-core/src/devices/ios/iOSDeviceManager.tsx @@ -11,14 +11,13 @@ import {ChildProcess} from 'child_process'; import type {IOSDeviceParams} from 'flipper-common'; import path from 'path'; import childProcess from 'child_process'; -import {exec, execFile} from 'promisify-child-process'; +import {exec} from 'promisify-child-process'; import iosUtil from './iOSContainerUtility'; import IOSDevice from './IOSDevice'; import { ERR_NO_IDB_OR_XCODE_AVAILABLE, IOSBridge, makeIOSBridge, - getDeviceSetPath, SimctlBridge, } from './IOSBridge'; import {FlipperServerImpl} from '../../FlipperServerImpl'; @@ -27,26 +26,6 @@ import {getFlipperServerConfig} from '../../FlipperServerConfig'; import {IdbConfig, setIdbConfig} from './idbConfig'; import {assertNotNull} from 'flipper-server-core/src/comms/Utilities'; -// eslint-disable-next-line @typescript-eslint/naming-convention -type iOSSimulatorDevice = { - state: 'Booted' | 'Shutdown' | 'Shutting Down'; - availability?: string; - isAvailable?: 'YES' | 'NO' | true | false; - name: string; - udid: string; -}; - -function isAvailable(simulator: iOSSimulatorDevice): boolean { - // For some users "availability" is set, for others it's "isAvailable" - // It's not clear which key is set, so we are checking both. - // We've also seen isAvailable return "YES" and true, depending on version. - return ( - simulator.availability === '(available)' || - simulator.isAvailable === 'YES' || - simulator.isAvailable === true - ); -} - export class IOSDeviceManager { private portForwarders: Array = []; private idbConfig?: IdbConfig; @@ -214,41 +193,18 @@ export class IOSDeviceManager { } getSimulators(bootedOnly: boolean): Promise> { - return execFile('xcrun', [ - 'simctl', - ...getDeviceSetPath(), - 'list', - 'devices', - '--json', - ]) - .then(({stdout}) => JSON.parse(stdout!.toString()).devices) - .then((simulatorDevices: Array) => { - const simulators = Object.values(simulatorDevices).flat(); - return simulators - .filter( - (simulator) => - (!bootedOnly || simulator.state === 'Booted') && - isAvailable(simulator), - ) - .map((simulator) => { - return { - ...simulator, - type: 'emulator', - } as IOSDeviceParams; - }); - }) - .catch((e: Error) => { - console.warn('Failed to query simulators:', e); - if (e.message.includes('Xcode license agreements')) { - this.flipperServer.emit('notification', { - type: 'error', - title: 'Xcode license requires approval', - description: - 'The Xcode license agreement has changed. You need to either open Xcode and agree to the terms or run `sudo xcodebuild -license` in a Terminal to allow simulators to work with Flipper.', - }); - } - return Promise.resolve([]); - }); + return this.simctlBridge.getActiveDevices(bootedOnly).catch((e: Error) => { + console.warn('Failed to query simulators:', e); + if (e.message.includes('Xcode license agreements')) { + this.flipperServer.emit('notification', { + type: 'error', + title: 'Xcode license requires approval', + description: + 'The Xcode license agreement has changed. You need to either open Xcode and agree to the terms or run `sudo xcodebuild -license` in a Terminal to allow simulators to work with Flipper.', + }); + } + return Promise.resolve([]); + }); } private queryDevicesForever() {