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
This commit is contained in:
John Knox
2019-10-07 08:49:05 -07:00
committed by Facebook Github Bot
parent 85c0ec0d13
commit 729e74f2fc
11 changed files with 72 additions and 44 deletions

View File

@@ -281,7 +281,7 @@ async function startFlipper(userArguments: UserArguments) {
> = [ > = [
async (userArguments: UserArguments) => { async (userArguments: UserArguments) => {
if (userArguments.listDevices) { if (userArguments.listDevices) {
const devices = await listDevices(); const devices = await listDevices(store);
const mapped = devices.map(device => { const mapped = devices.map(device => {
return { return {
os: device.os, os: device.os,
@@ -306,7 +306,7 @@ async function startFlipper(userArguments: UserArguments) {
async (userArguments: UserArguments, store: Store) => { async (userArguments: UserArguments, store: Store) => {
const {device: selectedDeviceID} = userArguments; const {device: selectedDeviceID} = userArguments;
if (selectedDeviceID) { if (selectedDeviceID) {
const devices = await listDevices(); const devices = await listDevices(store);
const matchedDevice = devices.find( const matchedDevice = devices.find(
device => device.serial === selectedDeviceID, device => device.serial === selectedDeviceID,
); );

View File

@@ -47,7 +47,6 @@ class DevicesButton extends Component<Props> {
// On Linux, you must run the emulator from the directory it's in because // On Linux, you must run the emulator from the directory it's in because
// reasons ... // reasons ...
whichPromise('emulator') whichPromise('emulator')
.catch(() => `${process.env.ANDROID_HOME || ''}/tools/emulator`)
.then(emulatorPath => { .then(emulatorPath => {
if (emulatorPath) { if (emulatorPath) {
const child = spawn(emulatorPath, [`@${name}`], { const child = spawn(emulatorPath, [`@${name}`], {

View File

@@ -48,8 +48,10 @@ function createDevice(
}); });
} }
export async function getActiveAndroidDevices(): Promise<Array<BaseDevice>> { export async function getActiveAndroidDevices(
const client = await getAdbClient(); store: Store,
): Promise<Array<BaseDevice>> {
const client = await getAdbClient(store);
const androidDevices = await client.listDevices(); const androidDevices = await client.listDevices();
return await Promise.all( return await Promise.all(
androidDevices.map(device => createDevice(client, device)), androidDevices.map(device => createDevice(client, device)),
@@ -102,7 +104,7 @@ export default (store: Store, logger: Logger) => {
); );
}); });
getAdbClient() getAdbClient(store)
.then(client => { .then(client => {
client client
.trackDevices() .trackDevices()
@@ -233,7 +235,7 @@ export default (store: Store, logger: Logger) => {
// cleanup method // cleanup method
return () => return () =>
getAdbClient().then(client => { getAdbClient(store).then(client => {
client.kill(); client.kill();
}); });
}; };

View File

@@ -16,7 +16,7 @@ import BugReporter from './fb-stubs/BugReporter';
import setupPrefetcher from './fb-stubs/Prefetcher'; import setupPrefetcher from './fb-stubs/Prefetcher';
import {createStore} from 'redux'; import {createStore} from 'redux';
import {persistStore} from 'redux-persist'; 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 dispatcher from './dispatcher/index';
import TooltipProvider from './ui/components/TooltipProvider'; import TooltipProvider from './ui/components/TooltipProvider';
import config from './utils/processConfig'; import config from './utils/processConfig';
@@ -27,6 +27,7 @@ import fbConfig from './fb-stubs/config';
import {isFBEmployee} from './utils/fbEmployee'; import {isFBEmployee} from './utils/fbEmployee';
import WarningEmployee from './chrome/WarningEmployee'; import WarningEmployee from './chrome/WarningEmployee';
import React from 'react'; import React from 'react';
import path from 'path';
const store = createStore<StoreState, Actions, any, any>( const store = createStore<StoreState, Actions, any, any>(
reducers, 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() { function init() {
ReactDOM.render(<AppFrame />, document.getElementById('root')); ReactDOM.render(<AppFrame />, document.getElementById('root'));
initLauncherHooks(config(), store); initLauncherHooks(config(), store);
@@ -85,6 +101,8 @@ function init() {
// rehydrate app state before exposing init // rehydrate app state before exposing init
persistStore(store, undefined, () => { persistStore(store, undefined, () => {
// Make sure process state is set before dispatchers run
setProcessState(store);
dispatcher(store, logger); dispatcher(store, logger);
// make init function callable from outside // make init function callable from outside
window.Flipper.init = init; window.Flipper.init = init;

View File

@@ -64,7 +64,7 @@ class Server extends EventEmitter {
super(); super();
this.logger = logger; this.logger = logger;
this.connections = new Map(); this.connections = new Map();
this.certificateProvider = new CertificateProvider(this, logger); this.certificateProvider = new CertificateProvider(this, logger, store);
this.connectionTracker = new ConnectionTracker(logger); this.connectionTracker = new ConnectionTracker(logger);
this.secureServer = null; this.secureServer = null;
this.insecureServer = null; this.insecureServer = null;

View File

@@ -21,6 +21,7 @@ import {getAdbClient} from './adbClient';
import * as androidUtil from './androidContainerUtility'; import * as androidUtil from './androidContainerUtility';
import os from 'os'; import os from 'os';
import {Client as ADBClient} from 'adbkit'; import {Client as ADBClient} from 'adbkit';
import {Store} from '../reducers/index';
const tmpFile = promisify(tmp.file) as ( const tmpFile = promisify(tmp.file) as (
options?: FileOptions, options?: FileOptions,
@@ -77,9 +78,9 @@ export default class CertificateProvider {
certificateSetup: Promise<void>; certificateSetup: Promise<void>;
server: Server; server: Server;
constructor(server: Server, logger: Logger) { constructor(server: Server, logger: Logger, store: Store) {
this.logger = logger; this.logger = logger;
this.adb = getAdbClient(); this.adb = getAdbClient(store);
this.certificateSetup = reportPlatformFailures( this.certificateSetup = reportPlatformFailures(
this.ensureServerCertExists(), this.ensureServerCertExists(),
'ensureServerCertExists', 'ensureServerCertExists',
@@ -200,9 +201,15 @@ export default class CertificateProvider {
const deviceIdPromise = appNamePromise.then(app => const deviceIdPromise = appNamePromise.then(app =>
this.getTargetAndroidDeviceId(app, destination, csr), this.getTargetAndroidDeviceId(app, destination, csr),
); );
return Promise.all([deviceIdPromise, appNamePromise]).then( return Promise.all([deviceIdPromise, appNamePromise, this.adb]).then(
([deviceId, appName]) => ([deviceId, appName, adbClient]) =>
androidUtil.push(deviceId, appName, destination + filename, contents), androidUtil.push(
adbClient,
deviceId,
appName,
destination + filename,
contents,
),
); );
} }
if (os === 'iOS' || os === 'windows' || os == 'MacOS') { if (os === 'iOS' || os === 'windows' || os == 'MacOS') {
@@ -343,8 +350,15 @@ export default class CertificateProvider {
processName: string, processName: string,
csr: string, csr: string,
): Promise<boolean> { ): Promise<boolean> {
return androidUtil return this.adb
.pull(deviceId, processName, directory + csrFileName) .then(adbClient =>
androidUtil.pull(
adbClient,
deviceId,
processName,
directory + csrFileName,
),
)
.then(deviceCsr => { .then(deviceCsr => {
// Santitize both of the string before comparation // Santitize both of the string before comparation
// The csr string extraction on client side return string in both way // The csr string extraction on client side return string in both way

View File

@@ -10,13 +10,15 @@ import child_process from 'child_process';
import promiseRetry from 'promise-retry'; import promiseRetry from 'promise-retry';
import adbConfig from '../utils/adbConfig'; import adbConfig from '../utils/adbConfig';
import adbkit, {Client} from 'adbkit'; import adbkit, {Client} from 'adbkit';
import {Store} from '../reducers/index';
import path from 'path';
const MAX_RETRIES = 5; const MAX_RETRIES = 5;
let instance: Promise<Client>; let instance: Promise<Client>;
export function getAdbClient(): Promise<Client> { export function getAdbClient(store: Store): Promise<Client> {
if (!instance) { if (!instance) {
instance = reportPlatformFailures(createClient(), 'createADBClient'); instance = reportPlatformFailures(createClient(store), 'createADBClient');
} }
return instance; return instance;
} }
@@ -24,10 +26,9 @@ export function getAdbClient(): Promise<Client> {
/* Adbkit will attempt to start the adb server if it's not already running, /* 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 however, it sometimes fails with ENOENT errors. So instead, we start it
manually before requesting a client. */ manually before requesting a client. */
function createClient(): Promise<Client> { function createClient(store: Store): Promise<Client> {
const adbPath = process.env.ANDROID_HOME const androidHome = store.getState().settingsState.androidHome;
? `${process.env.ANDROID_HOME}/platform-tools/adb` const adbPath = path.resolve(androidHome, 'platform-tools/adb');
: 'adb';
return reportPlatformFailures<Client>( return reportPlatformFailures<Client>(
promisify(child_process.exec)(`${adbPath} start-server`).then(() => promisify(child_process.exec)(`${adbPath} start-server`).then(() =>
adbkit.createClient(adbConfig()), adbkit.createClient(adbConfig()),

View File

@@ -11,8 +11,10 @@ import {
_push, _push,
_pull, _pull,
} from './androidContainerUtilityInternal'; } from './androidContainerUtilityInternal';
import {Client} from 'adbkit';
export function push( export function push(
client: Client,
deviceId: string, deviceId: string,
app: string, app: string,
filepath: string, filepath: string,
@@ -21,20 +23,21 @@ export function push(
return validateAppName(app).then(validApp => return validateAppName(app).then(validApp =>
validateFilePath(filepath).then(validFilepath => validateFilePath(filepath).then(validFilepath =>
validateFileContent(contents).then(validContent => validateFileContent(contents).then(validContent =>
_push(deviceId, validApp, validFilepath, validContent), _push(client, deviceId, validApp, validFilepath, validContent),
), ),
), ),
); );
} }
export function pull( export function pull(
client: Client,
deviceId: string, deviceId: string,
app: string, app: string,
path: string, path: string,
): Promise<string> { ): Promise<string> {
return validateAppName(app).then(validApp => return validateAppName(app).then(validApp =>
validateFilePath(path).then(validPath => validateFilePath(path).then(validPath =>
_pull(deviceId, validApp, validPath), _pull(client, deviceId, validApp, validPath),
), ),
); );
} }

View File

@@ -10,9 +10,8 @@
* opaque types will ensure the commands are only ever run on validated * opaque types will ensure the commands are only ever run on validated
* arguments. * arguments.
*/ */
import {getAdbClient} from './adbClient';
import {UnsupportedError} from './metrics'; import {UnsupportedError} from './metrics';
import adbkit from 'adbkit'; import adbkit, {Client} from 'adbkit';
const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/; const allowedAppNameRegex = /^[a-zA-Z0-9._\-]+$/;
const appNotDebuggableRegex = /debuggable/; const appNotDebuggableRegex = /debuggable/;
@@ -48,6 +47,7 @@ export function validateFileContent(content: string): Promise<FileContent> {
} }
export function _push( export function _push(
client: Client,
deviceId: string, deviceId: string,
app: AppName, app: AppName,
filename: FilePath, filename: FilePath,
@@ -55,6 +55,7 @@ export function _push(
): Promise<void> { ): Promise<void> {
console.debug(`Deploying ${filename} to ${deviceId}:${app}`, logTag); console.debug(`Deploying ${filename} to ${deviceId}:${app}`, logTag);
return executeCommandAsApp( return executeCommandAsApp(
client,
deviceId, deviceId,
app, app,
`echo "${contents}" > '${filename}' && chmod 644 '${filename}'`, `echo "${contents}" > '${filename}' && chmod 644 '${filename}'`,
@@ -62,23 +63,23 @@ export function _push(
} }
export function _pull( export function _pull(
client: Client,
deviceId: string, deviceId: string,
app: AppName, app: AppName,
path: FilePath, path: FilePath,
): Promise<string> { ): Promise<string> {
return executeCommandAsApp(deviceId, app, `cat '${path}'`); return executeCommandAsApp(client, deviceId, app, `cat '${path}'`);
} }
// Keep this method private since it relies on pre-validated arguments // Keep this method private since it relies on pre-validated arguments
function executeCommandAsApp( function executeCommandAsApp(
client: Client,
deviceId: string, deviceId: string,
app: string, app: string,
command: string, command: string,
): Promise<string> { ): Promise<string> {
return getAdbClient() return client
.then(client => .shell(deviceId, `echo '${command}' | run-as '${app}'`)
client.shell(deviceId, `echo '${command}' | run-as '${app}'`),
)
.then(adbkit.util.readAll) .then(adbkit.util.readAll)
.then(buffer => buffer.toString()) .then(buffer => buffer.toString())
.then(output => { .then(output => {

View File

@@ -7,9 +7,10 @@
import {getActiveAndroidDevices} from '../dispatcher/androidDevice'; import {getActiveAndroidDevices} from '../dispatcher/androidDevice';
import {getActiveDevicesAndSimulators} from '../dispatcher/iOSDevice'; import {getActiveDevicesAndSimulators} from '../dispatcher/iOSDevice';
import BaseDevice from '../devices/BaseDevice'; import BaseDevice from '../devices/BaseDevice';
import {Store} from '../reducers/index';
export async function listDevices(): Promise<Array<BaseDevice>> { export async function listDevices(store: Store): Promise<Array<BaseDevice>> {
const androidDevices = await getActiveAndroidDevices(); const androidDevices = await getActiveAndroidDevices(store);
const iOSDevices: BaseDevice[] = await getActiveDevicesAndSimulators(); const iOSDevices: BaseDevice[] = await getActiveDevicesAndSimulators();
return iOSDevices.concat(androidDevices); return iOSDevices.concat(androidDevices);
} }

View File

@@ -10,17 +10,6 @@ const os = require('os');
const fs = require('fs'); const fs = require('fs');
module.exports = function(argv) { 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 // ensure .flipper folder and config exist
const flipperDir = path.join(os.homedir(), '.flipper'); const flipperDir = path.join(os.homedir(), '.flipper');
if (!fs.existsSync(flipperDir)) { if (!fs.existsSync(flipperDir)) {