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) => {
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,
);

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
// reasons ...
whichPromise('emulator')
.catch(() => `${process.env.ANDROID_HOME || ''}/tools/emulator`)
.then(emulatorPath => {
if (emulatorPath) {
const child = spawn(emulatorPath, [`@${name}`], {

View File

@@ -48,8 +48,10 @@ function createDevice(
});
}
export async function getActiveAndroidDevices(): Promise<Array<BaseDevice>> {
const client = await getAdbClient();
export async function getActiveAndroidDevices(
store: Store,
): Promise<Array<BaseDevice>> {
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();
});
};

View File

@@ -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<StoreState, Actions, any, any>(
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(<AppFrame />, 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;

View File

@@ -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;

View File

@@ -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<void>;
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<boolean> {
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

View File

@@ -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<Client>;
export function getAdbClient(): Promise<Client> {
export function getAdbClient(store: Store): Promise<Client> {
if (!instance) {
instance = reportPlatformFailures(createClient(), 'createADBClient');
instance = reportPlatformFailures(createClient(store), 'createADBClient');
}
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,
however, it sometimes fails with ENOENT errors. So instead, we start it
manually before requesting a client. */
function createClient(): Promise<Client> {
const adbPath = process.env.ANDROID_HOME
? `${process.env.ANDROID_HOME}/platform-tools/adb`
: 'adb';
function createClient(store: Store): Promise<Client> {
const androidHome = store.getState().settingsState.androidHome;
const adbPath = path.resolve(androidHome, 'platform-tools/adb');
return reportPlatformFailures<Client>(
promisify(child_process.exec)(`${adbPath} start-server`).then(() =>
adbkit.createClient(adbConfig()),

View File

@@ -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<string> {
return validateAppName(app).then(validApp =>
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
* 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<FileContent> {
}
export function _push(
client: Client,
deviceId: string,
app: AppName,
filename: FilePath,
@@ -55,6 +55,7 @@ export function _push(
): Promise<void> {
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<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
function executeCommandAsApp(
client: Client,
deviceId: string,
app: string,
command: string,
): Promise<string> {
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 => {

View File

@@ -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<Array<BaseDevice>> {
const androidDevices = await getActiveAndroidDevices();
export async function listDevices(store: Store): Promise<Array<BaseDevice>> {
const androidDevices = await getActiveAndroidDevices(store);
const iOSDevices: BaseDevice[] = await getActiveDevicesAndSimulators();
return iOSDevices.concat(androidDevices);
}

View File

@@ -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)) {