// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
/* eslint-disable */
/* eslint-enable prettier/prettier*/
import * as fs from "fs";
import * as path from "path";
import { logger } from "vscode-debugadapter/lib/logger";
import { ChildProcess } from "../../common/node/childProcess";
import { PromiseUtil } from "../../common/node/promise";
import { OutputChannelLogger } from "../log/OutputChannelLogger";
import { IDebuggableMobileTarget } from "../mobileTarget";
/**
* @preserve
* Start region: the code is borrowed from https://github.com/facebook/flipper/blob/c2848df7f210c363113797c0f2e3db8c5d4fd49f/desktop/app/src/server/devices/ios/iOSContainerUtility.tsx
*
* 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
*/
export const idbPath = "/usr/local/bin/idb";
// Use debug to get helpful logs when idb fails
const idbLogLevel = "DEBUG";
type IdbTarget = {
name: string;
udid: string;
state: string;
type: string;
target_type?: string;
os_version: string;
architecture: string;
};
export type DeviceTarget = IDebuggableMobileTarget;
export const isIdbAvailable = PromiseUtil.promiseCacheDecorator<boolean>(isAvailable);
function isAvailable(): Promise<boolean> {
if (!idbPath) {
return Promise.resolve(false);
}
return fs.promises
.access(idbPath, fs.constants.X_OK)
.then(() => true)
.catch(() => false);
}
export async function isXcodeDetected(): Promise<boolean> {
return new ChildProcess()
.execToString("xcode-select -p")
.then(stdout => {
return fs.existsSync(stdout.trim());
})
.catch(_ => false);
}
async function queryTargetsWithoutXcodeDependency(
idbCompanionPath: string,
isAvailableFunc: (idbPath: string) => Promise<boolean>,
): Promise<Array<DeviceTarget>> {
if (await isAvailableFunc(idbCompanionPath)) {
return new ChildProcess()
.execToString(`${idbCompanionPath} --list 1 --only device`)
.then(stdout => parseIdbTargets(stdout))
.catch((e: Error) => {
logger.warn(
`Failed to query idb_companion --list 1 --only device for physical targets: ${e}`,
);
return [];
});
} else {
logger.warn(
`Unable to locate idb_companion in ${idbCompanionPath}. Try running sudo yum install -y fb-idb`,
);
return [];
}
}
function parseIdbTargets(lines: string): Array<DeviceTarget> {
return lines
.trim()
.split("\n")
.map(line => line.trim())
.filter(Boolean)
.map(line => JSON.parse(line))
.filter(({ state }: IdbTarget) => state.toLocaleLowerCase() === "booted")
.map<IdbTarget>(({ type, target_type, ...rest }: IdbTarget) => ({
type: (type || target_type) === "simulator" ? "emulator" : "physical",
...rest,
}))
.map<DeviceTarget>((target: IdbTarget) => ({
id: target.udid,
isVirtualTarget: target.type === "emulator",
name: target.name,
isOnline: true,
}));
}
export async function idbListTargets(idbPath: string): Promise<Array<DeviceTarget>> {
return new ChildProcess()
.execToString(`${idbPath} list-targets --json`)
.then(stdout =>
// See above.
parseIdbTargets(stdout),
)
.catch((e: Error) => {
logger.warn(`Failed to query idb for targets: ${e}`);
return [];
});
}
async function targets(): Promise<Array<DeviceTarget>> {
if (process.platform !== "darwin") {
return [];
}
const isXcodeInstalled = await isXcodeDetected();
if (!isXcodeInstalled) {
const idbCompanionPath = path.dirname(idbPath) + "/idb_companion";
return queryTargetsWithoutXcodeDependency(idbCompanionPath, isAvailable);
}
// Not all users have idb installed because you can still use
// Flipper with Simulators without it.
// But idb is MUCH more CPU efficient than xcrun, so
// when installed, use it. This still holds true
// with the move from instruments to xcrun.
// TODO: Move idb availability check up.
return (await isIdbAvailable())
? await idbListTargets(idbPath)
: new ChildProcess()
.execToString("xcrun xctrace list devices")
.then(stdout => {
const targets: DeviceTarget[] = [];
const lines = stdout
.split("\n")
.map(line => line.trim())
.filter(line => !!line);
const firstDevicesIndex = lines.indexOf("== Devices ==") + 1;
const lastDevicesIndex = lines.indexOf("== Simulators ==") - 1;
for (let i = firstDevicesIndex; i <= lastDevicesIndex; i++) {
const line = lines[i];
const params = line
.split(" ")
.map(el => el.trim())
.filter(el => !!el);
// Add only devices with system version
if (
params[params.length - 1].match(/\(.+\)/) &&
params[params.length - 2].match(/\(.+\)/)
) {
targets.push({
id: params[params.length - 1].replace(/\(|\)/g, "").trim(),
name: params.slice(0, params.length - 2).join(" "),
isVirtualTarget: false,
isOnline: true,
});
}
}
return targets;
})
.catch(e => {
logger.warn(`Failed to query for devices using xctrace: ${e}`);
return [];
});
}
async function push(
udid: string,
src: string,
bundleId: string,
dst: string,
logger?: OutputChannelLogger,
): Promise<void> {
const cp = new ChildProcess();
await checkIdbIsInstalled();
return wrapWithErrorMessage(
cp
.execToString(
`${idbPath} --log ${idbLogLevel} file push --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`,
)
.then(() => {
return;
})
.catch(e => handleMissingIdb(e)),
logger,
);
}
async function pull(
udid: string,
src: string,
bundleId: string,
dst: string,
logger?: OutputChannelLogger,
): Promise<void> {
const cp = new ChildProcess();
await checkIdbIsInstalled();
return wrapWithErrorMessage(
cp
.execToString(
`${idbPath} --log ${idbLogLevel} file pull --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`,
)
.then(() => {
return;
})
.catch(e => handleMissingIdb(e)),
logger,
);
}
export async function checkIdbIsInstalled(): Promise<void> {
const isInstalled = await isIdbAvailable();
if (!isInstalled) {
throw new Error(
`idb is required to use iOS devices. Please install it with instructions from https://github.com/facebook/idb.`,
);
}
}
// The fb-internal 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<T>(p: Promise<T>, logger?: OutputChannelLogger): Promise<T> {
return p.catch((e: Error) => {
logger?.error(e.message);
// 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 https://github.com/microsoft/vscode-react-native.",
);
});
}
export default {
isAvailable,
targets,
push,
pull,
};
/**
* @preserve
* End region: https://github.com/facebook/flipper/blob/c2848df7f210c363113797c0f2e3db8c5d4fd49f/desktop/app/src/server/devices/ios/iOSContainerUtility.tsx
*/microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
src/extension/ios/iOSContainerUtility.ts
258lines · modepreview