Use idb describe to enumerate devices
Summary: This works for forwarded as well as local devices. There is a bunch more unification work that needs to be done here as we currently have three completely different ways of querying iOS devices. More on that next week. Reviewed By: jknoxville Differential Revision: D30308405 fbshipit-source-id: c58ac73e971ce2cc4da92e9508bc05dff9c1a95a
This commit is contained in:
committed by
Facebook GitHub Bot
parent
83978f5989
commit
e880059167
@@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {queryTargetsWithoutXcodeDependency} from '../iOSContainerUtility';
|
import {queryTargetsWithoutXcodeDependency} from '../iOSContainerUtility';
|
||||||
|
import {idbDescribeTargets} from '../iOSContainerUtility';
|
||||||
|
|
||||||
test('uses idbcompanion command for queryTargetsWithoutXcodeDependency', async () => {
|
test('uses idbcompanion command for queryTargetsWithoutXcodeDependency', async () => {
|
||||||
const mockedExec = jest.fn((_) =>
|
const mockedExec = jest.fn((_) =>
|
||||||
@@ -41,3 +42,31 @@ test('do not call idbcompanion if the path does not exist', async () => {
|
|||||||
);
|
);
|
||||||
expect(mockedExec).toHaveBeenCalledTimes(0);
|
expect(mockedExec).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parses idb describe output', async () => {
|
||||||
|
const mockedExec = jest.fn((_) =>
|
||||||
|
Promise.resolve({
|
||||||
|
stdout: `{"udid": "deafbeed", "type": "simulator", "name": "name", "state": "Booted"}
|
||||||
|
{"udid": "f4ceb00c", "type": "physical", "name": "ponzicoin-miner", "state": "Booted"}
|
||||||
|
{"udid": "ded", "type": "physical", "name": "350ppm", "state": "Shutdown"}
|
||||||
|
`,
|
||||||
|
stderr: '',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const targets = await idbDescribeTargets('/usr/bin/idb', mockedExec);
|
||||||
|
expect(mockedExec).toBeCalledWith('/usr/bin/idb describe --json');
|
||||||
|
expect(targets).toMatchInlineSnapshot(`
|
||||||
|
Array [
|
||||||
|
Object {
|
||||||
|
"name": "name",
|
||||||
|
"type": "emulator",
|
||||||
|
"udid": "deafbeed",
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
"name": "ponzicoin-miner",
|
||||||
|
"type": "physical",
|
||||||
|
"udid": "f4ceb00c",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|||||||
@@ -32,15 +32,18 @@ const mutex = new Mutex();
|
|||||||
type IdbTarget = {
|
type IdbTarget = {
|
||||||
name: string;
|
name: string;
|
||||||
udid: string;
|
udid: string;
|
||||||
state: string;
|
state: 'Booted' | 'Shutdown';
|
||||||
type: string;
|
type: string | DeviceType;
|
||||||
|
target_type?: string | DeviceType;
|
||||||
os_version: string;
|
os_version: string;
|
||||||
architecture: string;
|
architecture: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DeviceType = 'physical' | 'emulator';
|
||||||
|
|
||||||
export type DeviceTarget = {
|
export type DeviceTarget = {
|
||||||
udid: string;
|
udid: string;
|
||||||
type: 'physical' | 'emulator';
|
type: DeviceType;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,22 +75,7 @@ export async function queryTargetsWithoutXcodeDependency(
|
|||||||
): Promise<Array<DeviceTarget>> {
|
): Promise<Array<DeviceTarget>> {
|
||||||
if (await isAvailableFunc(idbCompanionPath)) {
|
if (await isAvailableFunc(idbCompanionPath)) {
|
||||||
return safeExecFunc(`${idbCompanionPath} --list 1 --only device`)
|
return safeExecFunc(`${idbCompanionPath} --list 1 --only device`)
|
||||||
.then(({stdout}) =>
|
.then(({stdout}) => parseIdbTargets(stdout!.toString()))
|
||||||
// It is safe to assume this to be non-null as it only turns null
|
|
||||||
// if the output redirection is misconfigured:
|
|
||||||
// https://stackoverflow.com/questions/27786228/node-child-process-spawn-stdout-returning-as-null
|
|
||||||
stdout!
|
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((line) => JSON.parse(line))
|
|
||||||
.filter(({type}: IdbTarget) => type !== 'simulator')
|
|
||||||
.map<DeviceTarget>((target: IdbTarget) => {
|
|
||||||
return {udid: target.udid, type: 'physical', name: target.name};
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then((devices) => {
|
.then((devices) => {
|
||||||
if (devices.length > 0 && !isPhysicalDeviceEnabled) {
|
if (devices.length > 0 && !isPhysicalDeviceEnabled) {
|
||||||
// TODO: Show a notification to enable the toggle or integrate Doctor to better suggest this advice.
|
// TODO: Show a notification to enable the toggle or integrate Doctor to better suggest this advice.
|
||||||
@@ -112,6 +100,60 @@ export async function queryTargetsWithoutXcodeDependency(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseIdbTargets(lines: string): Array<DeviceTarget> {
|
||||||
|
return lines
|
||||||
|
.trim()
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line) => JSON.parse(line))
|
||||||
|
.filter(({state}: IdbTarget) => state.toLocaleLowerCase() === 'booted')
|
||||||
|
.map<IdbTarget>(({type, target_type, ...rest}: IdbTarget) => ({
|
||||||
|
type: (type || target_type) === 'simulator' ? 'emulator' : 'physical',
|
||||||
|
...rest,
|
||||||
|
}))
|
||||||
|
.map<DeviceTarget>((target: IdbTarget) => ({
|
||||||
|
udid: target.udid,
|
||||||
|
type: target.type as DeviceType,
|
||||||
|
name: target.name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbDescribeTargets(
|
||||||
|
idbPath: string,
|
||||||
|
safeExecFunc: (
|
||||||
|
command: string,
|
||||||
|
) => Promise<{stdout: string; stderr: string} | Output> = safeExec,
|
||||||
|
): Promise<Array<DeviceTarget>> {
|
||||||
|
return safeExecFunc(`${idbPath} describe --json`)
|
||||||
|
.then(({stdout}) => parseIdbTargets(stdout!.toString()))
|
||||||
|
.catch((e: Error) => {
|
||||||
|
// idb describe doesn't work when multiple devices are available.
|
||||||
|
// That's expected behavior and we can skip the log.
|
||||||
|
if (!e.message.includes('multiple companions')) {
|
||||||
|
console.warn('Failed to query idb for targets:', e);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbListTargets(
|
||||||
|
idbPath: string,
|
||||||
|
safeExecFunc: (
|
||||||
|
command: string,
|
||||||
|
) => Promise<{stdout: string; stderr: string} | Output> = safeExec,
|
||||||
|
): Promise<Array<DeviceTarget>> {
|
||||||
|
return safeExecFunc(`${idbPath} list-targets --json`)
|
||||||
|
.then(({stdout}) =>
|
||||||
|
// See above.
|
||||||
|
parseIdbTargets(stdout!.toString()),
|
||||||
|
)
|
||||||
|
.catch((e: Error) => {
|
||||||
|
console.warn('Failed to query idb for targets:', e);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function targets(
|
async function targets(
|
||||||
idbPath: string,
|
idbPath: string,
|
||||||
isPhysicalDeviceEnabled: boolean,
|
isPhysicalDeviceEnabled: boolean,
|
||||||
@@ -140,28 +182,12 @@ async function targets(
|
|||||||
// Flipper with Simulators without it.
|
// Flipper with Simulators without it.
|
||||||
// But idb is MUCH more CPU efficient than instruments, so
|
// But idb is MUCH more CPU efficient than instruments, so
|
||||||
// when installed, use it.
|
// when installed, use it.
|
||||||
|
// TODO: Move idb availability check up.
|
||||||
if (await memoize(isAvailable)(idbPath)) {
|
if (await memoize(isAvailable)(idbPath)) {
|
||||||
return safeExec(`${idbPath} list-targets --json`)
|
// We want to get both `idb describe` and `idb list-targets` outputs
|
||||||
.then(({stdout}) =>
|
// as in certain setups devices may not show up on one but the other.
|
||||||
// It is safe to assume this to be non-null as it only turns null
|
const targets = await idbDescribeTargets(idbPath);
|
||||||
// if the output redirection is misconfigured:
|
return targets.concat(await idbListTargets(idbPath));
|
||||||
// https://stackoverflow.com/questions/27786228/node-child-process-spawn-stdout-returning-as-null
|
|
||||||
stdout!
|
|
||||||
.toString()
|
|
||||||
.trim()
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((line) => JSON.parse(line))
|
|
||||||
.filter(({type}: IdbTarget) => type !== 'simulator')
|
|
||||||
.map<DeviceTarget>((target: IdbTarget) => {
|
|
||||||
return {udid: target.udid, type: 'physical', name: target.name};
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch((e: Error) => {
|
|
||||||
console.warn('Failed to query idb for targets:', e);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await killOrphanedInstrumentsProcesses();
|
await killOrphanedInstrumentsProcesses();
|
||||||
return safeExec('instruments -s devices')
|
return safeExec('instruments -s devices')
|
||||||
|
|||||||
Reference in New Issue
Block a user