Detect Physical iOS device without Xcode
Summary: This diff adds the support of detecting physical device in Flipper even if the xcode is not installed and there is no cli tool installed. See the demo. Reviewed By: timur-valiev Differential Revision: D26816588 fbshipit-source-id: 5f052998fcbe5c51385222d16df0e1855177b552
This commit is contained in:
committed by
Facebook GitHub Bot
parent
11879c127b
commit
a2d559c8c0
@@ -425,7 +425,10 @@ export default class CertificateProvider {
|
||||
return Promise.resolve(matches[1]);
|
||||
}
|
||||
return iosUtil
|
||||
.targets(this.store.getState().settingsState.idbPath)
|
||||
.targets(
|
||||
this.store.getState().settingsState.idbPath,
|
||||
this.store.getState().settingsState.enablePhysicalIOS,
|
||||
)
|
||||
.then((targets) => {
|
||||
if (targets.length === 0) {
|
||||
throw new Error('No iOS devices found');
|
||||
|
||||
@@ -11,7 +11,7 @@ import fs from 'fs';
|
||||
import child_process from 'child_process';
|
||||
|
||||
export interface IOSBridge {
|
||||
startLogListener: (
|
||||
startLogListener?: (
|
||||
udid: string,
|
||||
) => child_process.ChildProcessWithoutNullStreams;
|
||||
}
|
||||
@@ -35,7 +35,7 @@ const LOG_EXTRA_ARGS = [
|
||||
'--info',
|
||||
];
|
||||
|
||||
function idbStartLogListener(
|
||||
export function idbStartLogListener(
|
||||
idbPath: string,
|
||||
udid: string,
|
||||
): child_process.ChildProcessWithoutNullStreams {
|
||||
@@ -46,7 +46,7 @@ function idbStartLogListener(
|
||||
);
|
||||
}
|
||||
|
||||
function xcrunStartLogListener(udid: string) {
|
||||
export function xcrunStartLogListener(udid: string) {
|
||||
const deviceSetPath = process.env.DEVICE_SET_PATH
|
||||
? ['--set', process.env.DEVICE_SET_PATH]
|
||||
: [];
|
||||
@@ -67,8 +67,14 @@ function xcrunStartLogListener(udid: string) {
|
||||
|
||||
export async function makeIOSBridge(
|
||||
idbPath: string,
|
||||
isXcodeDetected: boolean,
|
||||
isAvailableFn: (idbPath: string) => Promise<boolean> = isAvailable,
|
||||
): Promise<IOSBridge> {
|
||||
if (!isXcodeDetected) {
|
||||
// iOS Physical Device can still get detected without Xcode. In this case there is no way to setup log listener yet.
|
||||
// This will not be the case, idb team is working on making idb log work without XCode atleast for physical device.
|
||||
return {};
|
||||
}
|
||||
if (await isAvailableFn(idbPath)) {
|
||||
return {
|
||||
startLogListener: idbStartLogListener.bind(null, idbPath),
|
||||
|
||||
@@ -14,9 +14,11 @@ import {mocked} from 'ts-jest/utils';
|
||||
jest.mock('child_process');
|
||||
const spawn = mocked(childProcess.spawn);
|
||||
|
||||
test('uses xcrun with no idb', async () => {
|
||||
const ib = await makeIOSBridge('');
|
||||
ib.startLogListener('deadbeef');
|
||||
test('uses xcrun with no idb when xcode is detected', async () => {
|
||||
const ib = await makeIOSBridge('', true);
|
||||
expect(ib.startLogListener).toBeDefined();
|
||||
|
||||
ib.startLogListener!('deadbeef');
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'xcrun',
|
||||
@@ -37,9 +39,11 @@ test('uses xcrun with no idb', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('uses idb when present', async () => {
|
||||
const ib = await makeIOSBridge('/usr/local/bin/idb', async (_) => true);
|
||||
ib.startLogListener('deadbeef');
|
||||
test('uses idb when present and xcode detected', async () => {
|
||||
const ib = await makeIOSBridge('/usr/local/bin/idb', true, async (_) => true);
|
||||
expect(ib.startLogListener).toBeDefined();
|
||||
|
||||
ib.startLogListener!('deadbeef');
|
||||
|
||||
expect(spawn).toHaveBeenCalledWith(
|
||||
'/usr/local/bin/idb',
|
||||
@@ -58,3 +62,8 @@ test('uses idb when present', async () => {
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
test('uses no log listener when xcode is not detected', async () => {
|
||||
const ib = await makeIOSBridge('', false);
|
||||
expect(ib.startLogListener).toBeUndefined();
|
||||
});
|
||||
|
||||
43
desktop/app/src/utils/__tests__/iOSContainerUtility.node.tsx
Normal file
43
desktop/app/src/utils/__tests__/iOSContainerUtility.node.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {queryTargetsWithoutXcodeDependency} from '../iOSContainerUtility';
|
||||
|
||||
test('uses idbcompanion command for queryTargetsWithoutXcodeDependency', async () => {
|
||||
const mockedExec = jest.fn((_) =>
|
||||
Promise.resolve({
|
||||
stdout: '{"udid": "udid", "type": "physical", "name": "name"}',
|
||||
stderr: '{ "msg": "mocked stderr"}',
|
||||
}),
|
||||
);
|
||||
await queryTargetsWithoutXcodeDependency(
|
||||
'idbCompanionPath',
|
||||
true,
|
||||
(_) => Promise.resolve(true),
|
||||
mockedExec,
|
||||
);
|
||||
|
||||
expect(mockedExec).toBeCalledWith('idbCompanionPath --list 1 --only device');
|
||||
});
|
||||
|
||||
test('do not call idbcompanion if the path does not exist', async () => {
|
||||
const mockedExec = jest.fn((_) =>
|
||||
Promise.resolve({
|
||||
stdout: '{"udid": "udid", "type": "physical", "name": "name"}',
|
||||
stderr: '{"msg": "mocked stderr"}',
|
||||
}),
|
||||
);
|
||||
await queryTargetsWithoutXcodeDependency(
|
||||
'idbCompanionPath',
|
||||
true,
|
||||
(_) => Promise.resolve(false),
|
||||
mockedExec,
|
||||
);
|
||||
expect(mockedExec).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
@@ -14,6 +14,11 @@ import {reportPlatformFailures} from './metrics';
|
||||
import {promises, constants} from 'fs';
|
||||
import memoize from 'lodash.memoize';
|
||||
import {notNull} from './typeUtils';
|
||||
import {promisify} from 'util';
|
||||
import child_process from 'child_process';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
// Use debug to get helpful logs when idb fails
|
||||
const idbLogLevel = 'DEBUG';
|
||||
@@ -54,10 +59,79 @@ function safeExec(
|
||||
.then((release) => unsafeExec(command).finally(release));
|
||||
}
|
||||
|
||||
async function targets(idbPath: string): Promise<Array<DeviceTarget>> {
|
||||
export async function queryTargetsWithoutXcodeDependency(
|
||||
idbCompanionPath: string,
|
||||
isPhysicalDeviceEnabled: boolean,
|
||||
isAvailableFunc: (idbPath: string) => Promise<boolean>,
|
||||
safeExecFunc: (
|
||||
command: string,
|
||||
) => Promise<{stdout: string; stderr: string} | Output>,
|
||||
): Promise<Array<DeviceTarget>> {
|
||||
if (await isAvailableFunc(idbCompanionPath)) {
|
||||
return safeExecFunc(`${idbCompanionPath} --list 1 --only device`)
|
||||
.then(({stdout}) =>
|
||||
// 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) => {
|
||||
if (devices.length > 0 && !isPhysicalDeviceEnabled) {
|
||||
// TODO: Show a notification to enable the toggle or integrate Doctor to better suggest this advice.
|
||||
console.warn(
|
||||
'You are trying to connect Physical Device. Please enable the toggle "Enable physical iOS device" from the setting screen.',
|
||||
);
|
||||
}
|
||||
return devices;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.warn(
|
||||
'Failed to query idb_companion --list 1 --only device for physical targets:',
|
||||
e,
|
||||
);
|
||||
return [];
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
`Unable to locate idb_companion in ${idbCompanionPath}. Try running sudo yum install -y fb-idb`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function targets(
|
||||
idbPath: string,
|
||||
isPhysicalDeviceEnabled: boolean,
|
||||
): Promise<Array<DeviceTarget>> {
|
||||
if (process.platform !== 'darwin') {
|
||||
return [];
|
||||
}
|
||||
const isXcodeInstalled = await isXcodeDetected();
|
||||
if (!isXcodeInstalled) {
|
||||
if (!isPhysicalDeviceEnabled) {
|
||||
// TODO: Show a notification to enable the toggle or integrate Doctor to better suggest this advice.
|
||||
console.warn(
|
||||
'You are trying to connect Physical Device. Please enable the toggle "Enable physical iOS device" from the setting screen.',
|
||||
);
|
||||
}
|
||||
const idbCompanionPath = path.dirname(idbPath) + '/idb_companion';
|
||||
return queryTargetsWithoutXcodeDependency(
|
||||
idbCompanionPath,
|
||||
isPhysicalDeviceEnabled,
|
||||
isAvailable,
|
||||
safeExec,
|
||||
);
|
||||
}
|
||||
|
||||
// Not all users have idb installed because you can still use
|
||||
// Flipper with Simulators without it.
|
||||
@@ -185,9 +259,18 @@ function wrapWithErrorMessage<T>(p: Promise<T>): Promise<T> {
|
||||
});
|
||||
}
|
||||
|
||||
async function isXcodeDetected(): Promise<boolean> {
|
||||
return exec('xcode-select -p')
|
||||
.then(({stdout}) => {
|
||||
return fs.pathExists(stdout);
|
||||
})
|
||||
.catch((_) => false);
|
||||
}
|
||||
|
||||
export default {
|
||||
isAvailable,
|
||||
targets,
|
||||
push,
|
||||
pull,
|
||||
isXcodeDetected,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user