From 5723553fba3a9fabb65cca0a68fab56702207ce6 Mon Sep 17 00:00:00 2001 From: John Knox Date: Fri, 29 May 2020 05:58:18 -0700 Subject: [PATCH] Open-source idb interaction code Summary: I've outlined the tasks required to get iOS device support working for open source users [here](https://github.com/facebook/flipper/issues/262). This is the first step. It publishes the same code we use internally to GitHub, but in a state where it is only "available" for non-public builds. This will not change any behavior, but means that together with the community, we can start adapting it to suit everyone, and then eventually flip "available" to true for everyone. Reviewed By: passy Differential Revision: D21740193 fbshipit-source-id: 586c79ad850f67da330c10a007605ff25a187544 --- desktop/app/src/dispatcher/iOSDevice.tsx | 2 +- .../app/src/fb-stubs/iOSContainerUtility.tsx | 71 ---------- desktop/app/src/reducers/connections.tsx | 2 +- desktop/app/src/utils/CertificateProvider.tsx | 2 +- desktop/app/src/utils/iOSContainerUtility.tsx | 133 ++++++++++++++++++ 5 files changed, 136 insertions(+), 74 deletions(-) delete mode 100644 desktop/app/src/fb-stubs/iOSContainerUtility.tsx create mode 100644 desktop/app/src/utils/iOSContainerUtility.tsx diff --git a/desktop/app/src/dispatcher/iOSDevice.tsx b/desktop/app/src/dispatcher/iOSDevice.tsx index 4e0e1f287..1222b0690 100644 --- a/desktop/app/src/dispatcher/iOSDevice.tsx +++ b/desktop/app/src/dispatcher/iOSDevice.tsx @@ -16,7 +16,7 @@ import {promisify} from 'util'; import path from 'path'; import child_process from 'child_process'; const execFile = child_process.execFile; -import iosUtil from '../fb-stubs/iOSContainerUtility'; +import iosUtil from '../utils/iOSContainerUtility'; import IOSDevice from '../devices/IOSDevice'; import isProduction from '../utils/isProduction'; import GK from '../fb-stubs/GK'; diff --git a/desktop/app/src/fb-stubs/iOSContainerUtility.tsx b/desktop/app/src/fb-stubs/iOSContainerUtility.tsx deleted file mode 100644 index 004f07a99..000000000 --- a/desktop/app/src/fb-stubs/iOSContainerUtility.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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 {DeviceType} from '../devices/BaseDevice'; -import {exec} from 'promisify-child-process'; -import {notNull} from '../utils/typeUtils'; -import {killOrphanedInstrumentsProcesses} from '../utils/processCleanup'; - -const errorMessage = 'Physical iOS devices not yet supported'; - -export type DeviceTarget = { - udid: string; - type: DeviceType; - name: string; -}; - -function isAvailable(): boolean { - return false; -} - -async function targets(): Promise> { - await killOrphanedInstrumentsProcesses(); - const {stdout} = await exec('instruments -s devices'); - if (!stdout) { - return []; - } - return stdout - .toString() - .split('\n') - .map((line) => line.trim()) - .map((line) => /(.+) \([^(]+\) \[(.*)\]( \(Simulator\))?/.exec(line)) - .filter(notNull) - .filter( - ([_match, name, _udid, isSim]) => - !isSim && (name.includes('iPhone') || name.includes('iPad')), - ) - .map(([_match, name, udid]) => { - return {udid: udid, type: 'physical', name: name}; - }); -} - -function push( - _udid: string, - _src: string, - _bundleId: string, - _dst: string, -): Promise { - return Promise.reject(errorMessage); -} - -function pull( - _udid: string, - _src: string, - _bundleId: string, - _dst: string, -): Promise { - return Promise.reject(errorMessage); -} - -export default { - isAvailable: isAvailable, - targets: targets, - push: push, - pull: pull, -}; diff --git a/desktop/app/src/reducers/connections.tsx b/desktop/app/src/reducers/connections.tsx index 224601859..23f747a03 100644 --- a/desktop/app/src/reducers/connections.tsx +++ b/desktop/app/src/reducers/connections.tsx @@ -14,7 +14,7 @@ import MacDevice from '../devices/MacDevice'; import Client from '../Client'; import {UninitializedClient} from '../UninitializedClient'; import {isEqual} from 'lodash'; -import iosUtil from '../fb-stubs/iOSContainerUtility'; +import iosUtil from '../utils/iOSContainerUtility'; import {performance} from 'perf_hooks'; import isHeadless from '../utils/isHeadless'; import {Actions} from '.'; diff --git a/desktop/app/src/utils/CertificateProvider.tsx b/desktop/app/src/utils/CertificateProvider.tsx index f1a904197..afc202712 100644 --- a/desktop/app/src/utils/CertificateProvider.tsx +++ b/desktop/app/src/utils/CertificateProvider.tsx @@ -17,7 +17,7 @@ import { } from './openssl-wrapper-with-promises'; import path from 'path'; import tmp, {DirOptions, FileOptions} from 'tmp'; -import iosUtil from '../fb-stubs/iOSContainerUtility'; +import iosUtil from './iOSContainerUtility'; import {reportPlatformFailures} from './metrics'; import {getAdbClient} from './adbClient'; import * as androidUtil from './androidContainerUtility'; diff --git a/desktop/app/src/utils/iOSContainerUtility.tsx b/desktop/app/src/utils/iOSContainerUtility.tsx new file mode 100644 index 000000000..20b52bb9a --- /dev/null +++ b/desktop/app/src/utils/iOSContainerUtility.tsx @@ -0,0 +1,133 @@ +/** + * 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 child_process from 'child_process'; +import {promisify} from 'util'; +import {Mutex} from 'async-mutex'; +import {notNull} from './typeUtils'; +const unsafeExec = promisify(child_process.exec); +import {killOrphanedInstrumentsProcesses} from './processCleanup'; +import {reportPlatformFailures} from './metrics'; +import config from '../fb-stubs/config'; + +const idbPath = '/usr/local/bin/idb'; +// Use debug to get helpful logs when idb fails +const idbLogLevel = 'DEBUG'; +const operationPrefix = 'iosContainerUtility'; + +const mutex = new Mutex(); + +export type DeviceTarget = { + udid: string; + type: 'physical' | 'emulator'; + name: string; +}; + +function isAvailable(): boolean { + return config.isFBBuild; +} + +function safeExec(command: string): Promise<{stdout: string; stderr: string}> { + return mutex.acquire().then((release) => { + return unsafeExec(command).finally(release); + }); +} + +async function targets(): Promise> { + if (process.platform !== 'darwin') { + return []; + } + await killOrphanedInstrumentsProcesses(); + return safeExec('instruments -s devices').then(({stdout}) => + stdout + .toString() + .split('\n') + .map((line) => line.trim()) + .map((line) => /(.+) \([^(]+\) \[(.*)\]( \(Simulator\))?/.exec(line)) + .filter(notNull) + .filter( + ([_match, name, _udid, isSim]) => + !isSim && (name.includes('iPhone') || name.includes('iPad')), + ) + .map(([_match, name, udid]) => { + return {udid: udid, type: 'physical', name: name}; + }), + ); +} + +function push( + udid: string, + src: string, + bundleId: string, + dst: string, +): Promise { + return wrapWithErrorMessage( + reportPlatformFailures( + safeExec( + `${idbPath} --log ${idbLogLevel} file push --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`, + ) + .then(() => { + return; + }) + .catch(handleMissingIdb), + `${operationPrefix}:push`, + ), + ); +} + +function pull( + udid: string, + src: string, + bundleId: string, + dst: string, +): Promise { + return wrapWithErrorMessage( + reportPlatformFailures( + safeExec( + `${idbPath} --log ${idbLogLevel} file pull --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`, + ) + .then(() => { + return; + }) + .catch(handleMissingIdb), + `${operationPrefix}:pull`, + ), + ); +} + +// The idb binary is a shim that downloads the proper one on first run. It requires sudo to do so. +// If we detect this, Tell the user how to fix it. +function handleMissingIdb(e: Error): void { + if ( + e.message && + e.message.includes('sudo: no tty present and no askpass program specified') + ) { + throw new Error( + `idb doesn't appear to be installed. Run "${idbPath} list-targets" to fix this.`, + ); + } + throw e; +} + +function wrapWithErrorMessage(p: Promise): Promise { + return p.catch((e: Error) => { + console.error(e); + // Give the user instructions. Don't embed the error because it's unique per invocation so won't be deduped. + throw new Error( + "A problem with idb has ocurred. Please run `sudo rm -rf /tmp/idb*` and `sudo yum install -y fb-idb` to update it, if that doesn't fix it, post in Flipper Support.", + ); + }); +} + +export default { + isAvailable: isAvailable, + targets: targets, + push: push, + pull: pull, +};