From 729e74f2fc1da0b9c0441263d681abd18d2e3e13 Mon Sep 17 00:00:00 2001 From: John Knox Date: Mon, 7 Oct 2019 08:49:05 -0700 Subject: [PATCH] Switch to using settings for android sdk location Summary: A settings screen has been added where android home can be set. This changes the downstream code to use this value rather than the `env.PATH` variable. Reviewed By: passy Differential Revision: D17713288 fbshipit-source-id: 51551652c9c2f468e1117c18785123348e4b4576 --- headless/index.tsx | 4 +-- src/chrome/DevicesButton.tsx | 1 - src/dispatcher/androidDevice.tsx | 10 ++++--- src/init.tsx | 20 ++++++++++++- src/server.tsx | 2 +- src/utils/CertificateProvider.tsx | 28 ++++++++++++++----- src/utils/adbClient.tsx | 13 +++++---- src/utils/androidContainerUtility.tsx | 7 +++-- src/utils/androidContainerUtilityInternal.tsx | 15 +++++----- src/utils/listDevices.tsx | 5 ++-- static/setup.js | 11 -------- 11 files changed, 72 insertions(+), 44 deletions(-) diff --git a/headless/index.tsx b/headless/index.tsx index 49d125029..a86d55b66 100644 --- a/headless/index.tsx +++ b/headless/index.tsx @@ -281,7 +281,7 @@ async function startFlipper(userArguments: UserArguments) { > = [ async (userArguments: UserArguments) => { if (userArguments.listDevices) { - const devices = await listDevices(); + const devices = await listDevices(store); const mapped = devices.map(device => { return { os: device.os, @@ -306,7 +306,7 @@ async function startFlipper(userArguments: UserArguments) { async (userArguments: UserArguments, store: Store) => { const {device: selectedDeviceID} = userArguments; if (selectedDeviceID) { - const devices = await listDevices(); + const devices = await listDevices(store); const matchedDevice = devices.find( device => device.serial === selectedDeviceID, ); diff --git a/src/chrome/DevicesButton.tsx b/src/chrome/DevicesButton.tsx index bf9185cae..7003e64b4 100644 --- a/src/chrome/DevicesButton.tsx +++ b/src/chrome/DevicesButton.tsx @@ -47,7 +47,6 @@ class DevicesButton extends Component { // On Linux, you must run the emulator from the directory it's in because // reasons ... whichPromise('emulator') - .catch(() => `${process.env.ANDROID_HOME || ''}/tools/emulator`) .then(emulatorPath => { if (emulatorPath) { const child = spawn(emulatorPath, [`@${name}`], { diff --git a/src/dispatcher/androidDevice.tsx b/src/dispatcher/androidDevice.tsx index a3bf64dae..22e2f9fc6 100644 --- a/src/dispatcher/androidDevice.tsx +++ b/src/dispatcher/androidDevice.tsx @@ -48,8 +48,10 @@ function createDevice( }); } -export async function getActiveAndroidDevices(): Promise> { - const client = await getAdbClient(); +export async function getActiveAndroidDevices( + store: Store, +): Promise> { + const client = await getAdbClient(store); const androidDevices = await client.listDevices(); return await Promise.all( androidDevices.map(device => createDevice(client, device)), @@ -102,7 +104,7 @@ export default (store: Store, logger: Logger) => { ); }); - getAdbClient() + getAdbClient(store) .then(client => { client .trackDevices() @@ -233,7 +235,7 @@ export default (store: Store, logger: Logger) => { // cleanup method return () => - getAdbClient().then(client => { + getAdbClient(store).then(client => { client.kill(); }); }; diff --git a/src/init.tsx b/src/init.tsx index 17c52f9e0..b55093cd3 100644 --- a/src/init.tsx +++ b/src/init.tsx @@ -16,7 +16,7 @@ import BugReporter from './fb-stubs/BugReporter'; import setupPrefetcher from './fb-stubs/Prefetcher'; import {createStore} from 'redux'; import {persistStore} from 'redux-persist'; -import reducers, {Actions, State as StoreState} from './reducers/index'; +import reducers, {Store, Actions, State as StoreState} from './reducers/index'; import dispatcher from './dispatcher/index'; import TooltipProvider from './ui/components/TooltipProvider'; import config from './utils/processConfig'; @@ -27,6 +27,7 @@ import fbConfig from './fb-stubs/config'; import {isFBEmployee} from './utils/fbEmployee'; import WarningEmployee from './chrome/WarningEmployee'; import React from 'react'; +import path from 'path'; const store = createStore( reducers, @@ -72,6 +73,21 @@ const AppFrame = () => { ); }; +function setProcessState(store: Store) { + const androidHome = store.getState().settingsState.androidHome; + + if (!process.env.ANDROID_HOME) { + process.env.ANDROID_HOME = androidHome; + } + + // emulator/emulator is more reliable than tools/emulator, so prefer it if + // it exists + process.env.PATH = + ['emulator', 'tools', 'platform-tools'] + .map(directory => path.resolve(androidHome, directory)) + .join(':') + `:${process.env.PATH}`; +} + function init() { ReactDOM.render(, document.getElementById('root')); initLauncherHooks(config(), store); @@ -85,6 +101,8 @@ function init() { // rehydrate app state before exposing init persistStore(store, undefined, () => { + // Make sure process state is set before dispatchers run + setProcessState(store); dispatcher(store, logger); // make init function callable from outside window.Flipper.init = init; diff --git a/src/server.tsx b/src/server.tsx index 1161273d2..7691bfd68 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -64,7 +64,7 @@ class Server extends EventEmitter { super(); this.logger = logger; this.connections = new Map(); - this.certificateProvider = new CertificateProvider(this, logger); + this.certificateProvider = new CertificateProvider(this, logger, store); this.connectionTracker = new ConnectionTracker(logger); this.secureServer = null; this.insecureServer = null; diff --git a/src/utils/CertificateProvider.tsx b/src/utils/CertificateProvider.tsx index de0be0fb1..197881704 100644 --- a/src/utils/CertificateProvider.tsx +++ b/src/utils/CertificateProvider.tsx @@ -21,6 +21,7 @@ import {getAdbClient} from './adbClient'; import * as androidUtil from './androidContainerUtility'; import os from 'os'; import {Client as ADBClient} from 'adbkit'; +import {Store} from '../reducers/index'; const tmpFile = promisify(tmp.file) as ( options?: FileOptions, @@ -77,9 +78,9 @@ export default class CertificateProvider { certificateSetup: Promise; server: Server; - constructor(server: Server, logger: Logger) { + constructor(server: Server, logger: Logger, store: Store) { this.logger = logger; - this.adb = getAdbClient(); + this.adb = getAdbClient(store); this.certificateSetup = reportPlatformFailures( this.ensureServerCertExists(), 'ensureServerCertExists', @@ -200,9 +201,15 @@ export default class CertificateProvider { const deviceIdPromise = appNamePromise.then(app => this.getTargetAndroidDeviceId(app, destination, csr), ); - return Promise.all([deviceIdPromise, appNamePromise]).then( - ([deviceId, appName]) => - androidUtil.push(deviceId, appName, destination + filename, contents), + return Promise.all([deviceIdPromise, appNamePromise, this.adb]).then( + ([deviceId, appName, adbClient]) => + androidUtil.push( + adbClient, + deviceId, + appName, + destination + filename, + contents, + ), ); } if (os === 'iOS' || os === 'windows' || os == 'MacOS') { @@ -343,8 +350,15 @@ export default class CertificateProvider { processName: string, csr: string, ): Promise { - return androidUtil - .pull(deviceId, processName, directory + csrFileName) + return this.adb + .then(adbClient => + androidUtil.pull( + adbClient, + deviceId, + processName, + directory + csrFileName, + ), + ) .then(deviceCsr => { // Santitize both of the string before comparation // The csr string extraction on client side return string in both way diff --git a/src/utils/adbClient.tsx b/src/utils/adbClient.tsx index 14db1e6d4..f3e641aa2 100644 --- a/src/utils/adbClient.tsx +++ b/src/utils/adbClient.tsx @@ -10,13 +10,15 @@ import child_process from 'child_process'; import promiseRetry from 'promise-retry'; import adbConfig from '../utils/adbConfig'; import adbkit, {Client} from 'adbkit'; +import {Store} from '../reducers/index'; +import path from 'path'; const MAX_RETRIES = 5; let instance: Promise; -export function getAdbClient(): Promise { +export function getAdbClient(store: Store): Promise { if (!instance) { - instance = reportPlatformFailures(createClient(), 'createADBClient'); + instance = reportPlatformFailures(createClient(store), 'createADBClient'); } return instance; } @@ -24,10 +26,9 @@ export function getAdbClient(): Promise { /* Adbkit will attempt to start the adb server if it's not already running, however, it sometimes fails with ENOENT errors. So instead, we start it manually before requesting a client. */ -function createClient(): Promise { - const adbPath = process.env.ANDROID_HOME - ? `${process.env.ANDROID_HOME}/platform-tools/adb` - : 'adb'; +function createClient(store: Store): Promise { + const androidHome = store.getState().settingsState.androidHome; + const adbPath = path.resolve(androidHome, 'platform-tools/adb'); return reportPlatformFailures( promisify(child_process.exec)(`${adbPath} start-server`).then(() => adbkit.createClient(adbConfig()), diff --git a/src/utils/androidContainerUtility.tsx b/src/utils/androidContainerUtility.tsx index da1455c77..df571eb1b 100644 --- a/src/utils/androidContainerUtility.tsx +++ b/src/utils/androidContainerUtility.tsx @@ -11,8 +11,10 @@ import { _push, _pull, } from './androidContainerUtilityInternal'; +import {Client} from 'adbkit'; export function push( + client: Client, deviceId: string, app: string, filepath: string, @@ -21,20 +23,21 @@ export function push( return validateAppName(app).then(validApp => validateFilePath(filepath).then(validFilepath => validateFileContent(contents).then(validContent => - _push(deviceId, validApp, validFilepath, validContent), + _push(client, deviceId, validApp, validFilepath, validContent), ), ), ); } export function pull( + client: Client, deviceId: string, app: string, path: string, ): Promise { return validateAppName(app).then(validApp => validateFilePath(path).then(validPath => - _pull(deviceId, validApp, validPath), + _pull(client, deviceId, validApp, validPath), ), ); } diff --git a/src/utils/androidContainerUtilityInternal.tsx b/src/utils/androidContainerUtilityInternal.tsx index 7d0e71280..2045c6318 100644 --- a/src/utils/androidContainerUtilityInternal.tsx +++ b/src/utils/androidContainerUtilityInternal.tsx @@ -10,9 +10,8 @@ * opaque types will ensure the commands are only ever run on validated * arguments. */ -import {getAdbClient} from './adbClient'; import {UnsupportedError} from './metrics'; -import adbkit from 'adbkit'; +import adbkit, {Client} from 'adbkit'; const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/; const appNotDebuggableRegex = /debuggable/; @@ -48,6 +47,7 @@ export function validateFileContent(content: string): Promise { } export function _push( + client: Client, deviceId: string, app: AppName, filename: FilePath, @@ -55,6 +55,7 @@ export function _push( ): Promise { console.debug(`Deploying ${filename} to ${deviceId}:${app}`, logTag); return executeCommandAsApp( + client, deviceId, app, `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`, @@ -62,23 +63,23 @@ export function _push( } export function _pull( + client: Client, deviceId: string, app: AppName, path: FilePath, ): Promise { - return executeCommandAsApp(deviceId, app, `cat '${path}'`); + return executeCommandAsApp(client, deviceId, app, `cat '${path}'`); } // Keep this method private since it relies on pre-validated arguments function executeCommandAsApp( + client: Client, deviceId: string, app: string, command: string, ): Promise { - return getAdbClient() - .then(client => - client.shell(deviceId, `echo '${command}' | run-as '${app}'`), - ) + return client + .shell(deviceId, `echo '${command}' | run-as '${app}'`) .then(adbkit.util.readAll) .then(buffer => buffer.toString()) .then(output => { diff --git a/src/utils/listDevices.tsx b/src/utils/listDevices.tsx index 744e8b3c7..66d94e704 100644 --- a/src/utils/listDevices.tsx +++ b/src/utils/listDevices.tsx @@ -7,9 +7,10 @@ import {getActiveAndroidDevices} from '../dispatcher/androidDevice'; import {getActiveDevicesAndSimulators} from '../dispatcher/iOSDevice'; import BaseDevice from '../devices/BaseDevice'; +import {Store} from '../reducers/index'; -export async function listDevices(): Promise> { - const androidDevices = await getActiveAndroidDevices(); +export async function listDevices(store: Store): Promise> { + const androidDevices = await getActiveAndroidDevices(store); const iOSDevices: BaseDevice[] = await getActiveDevicesAndSimulators(); return iOSDevices.concat(androidDevices); } diff --git a/static/setup.js b/static/setup.js index c528e3ad6..a0a9089e8 100644 --- a/static/setup.js +++ b/static/setup.js @@ -10,17 +10,6 @@ const os = require('os'); const fs = require('fs'); module.exports = function(argv) { - if (!process.env.ANDROID_HOME) { - process.env.ANDROID_HOME = '/opt/android_sdk'; - } - - // emulator/emulator is more reliable than tools/emulator, so prefer it if - // it exists - process.env.PATH = - ['emulator', 'tools', 'platform-tools'] - .map(directory => `${process.env.ANDROID_HOME}/${directory}`) - .join(':') + `:${process.env.PATH}`; - // ensure .flipper folder and config exist const flipperDir = path.join(os.homedir(), '.flipper'); if (!fs.existsSync(flipperDir)) {