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:
committed by
Facebook Github Bot
parent
85c0ec0d13
commit
729e74f2fc
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}`], {
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
20
src/init.tsx
20
src/init.tsx
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()),
|
||||||
|
|||||||
@@ -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),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 => {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user