microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
8f322bcd71555e02f0b00a89a99da351ef8412b7

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/ios/iOSContainerUtility.ts

257lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import { ChildProcess } from "../../common/node/childProcess";
5import { PromiseUtil } from "../../common/node/promise";
6import { OutputChannelLogger } from "../log/OutputChannelLogger";
7import * as fs from "fs";
8import * as path from "path";
9import { IDebuggableMobileTarget } from "../mobileTarget";
10import { logger } from "vscode-debugadapter/lib/logger";
11
12/**
13 * @preserve
14 * Start region: the code is borrowed from https://github.com/facebook/flipper/blob/c2848df7f210c363113797c0f2e3db8c5d4fd49f/desktop/app/src/server/devices/ios/iOSContainerUtility.tsx
15 *
16 * Copyright (c) Facebook, Inc. and its affiliates.
17 *
18 * This source code is licensed under the MIT license found in the
19 * LICENSE file in the root directory of this source tree.
20 *
21 * @format
22 */
23
24export const idbPath = "/usr/local/bin/idb";
25// Use debug to get helpful logs when idb fails
26const idbLogLevel = "DEBUG";
27
28type IdbTarget = {
29 name: string;
30 udid: string;
31 state: string;
32 type: string;
33 target_type?: string;
34 os_version: string;
35 architecture: string;
36};
37
38export type DeviceTarget = IDebuggableMobileTarget;
39
40export const isIdbAvailable = PromiseUtil.promiseCacheDecorator<boolean>(isAvailable);
41
42function isAvailable(): Promise<boolean> {
43 if (!idbPath) {
44 return Promise.resolve(false);
45 }
46 return fs.promises
47 .access(idbPath, fs.constants.X_OK)
48 .then(() => true)
49 .catch(() => false);
50}
51
52export async function isXcodeDetected(): Promise<boolean> {
53 return new ChildProcess()
54 .execToString("xcode-select -p")
55 .then(stdout => {
56 return fs.existsSync(stdout.trim());
57 })
58 .catch(_ => false);
59}
60
61async function queryTargetsWithoutXcodeDependency(
62 idbCompanionPath: string,
63 isAvailableFunc: (idbPath: string) => Promise<boolean>,
64): Promise<Array<DeviceTarget>> {
65 if (await isAvailableFunc(idbCompanionPath)) {
66 return new ChildProcess()
67 .execToString(`${idbCompanionPath} --list 1 --only device`)
68 .then(stdout => parseIdbTargets(stdout))
69 .catch((e: Error) => {
70 logger.warn(
71 `Failed to query idb_companion --list 1 --only device for physical targets: ${e}`,
72 );
73 return [];
74 });
75 } else {
76 logger.warn(
77 `Unable to locate idb_companion in ${idbCompanionPath}. Try running sudo yum install -y fb-idb`,
78 );
79 return [];
80 }
81}
82
83function parseIdbTargets(lines: string): Array<DeviceTarget> {
84 return lines
85 .trim()
86 .split("\n")
87 .map(line => line.trim())
88 .filter(Boolean)
89 .map(line => JSON.parse(line))
90 .filter(({ state }: IdbTarget) => state.toLocaleLowerCase() === "booted")
91 .map<IdbTarget>(({ type, target_type, ...rest }: IdbTarget) => ({
92 type: (type || target_type) === "simulator" ? "emulator" : "physical",
93 ...rest,
94 }))
95 .map<DeviceTarget>((target: IdbTarget) => ({
96 id: target.udid,
97 isVirtualTarget: target.type === "emulator",
98 name: target.name,
99 isOnline: true,
100 }));
101}
102
103export async function idbListTargets(idbPath: string): Promise<Array<DeviceTarget>> {
104 return new ChildProcess()
105 .execToString(`${idbPath} list-targets --json`)
106 .then(stdout =>
107 // See above.
108 parseIdbTargets(stdout),
109 )
110 .catch((e: Error) => {
111 logger.warn(`Failed to query idb for targets: ${e}`);
112 return [];
113 });
114}
115
116async function targets(): Promise<Array<DeviceTarget>> {
117 if (process.platform !== "darwin") {
118 return [];
119 }
120 const isXcodeInstalled = await isXcodeDetected();
121 if (!isXcodeInstalled) {
122 const idbCompanionPath = path.dirname(idbPath) + "/idb_companion";
123 return queryTargetsWithoutXcodeDependency(idbCompanionPath, isAvailable);
124 }
125
126 // Not all users have idb installed because you can still use
127 // Flipper with Simulators without it.
128 // But idb is MUCH more CPU efficient than xcrun, so
129 // when installed, use it. This still holds true
130 // with the move from instruments to xcrun.
131 // TODO: Move idb availability check up.
132 if (await isIdbAvailable()) {
133 return await idbListTargets(idbPath);
134 } else {
135 return new ChildProcess()
136 .execToString("xcrun xctrace list devices")
137 .then(stdout => {
138 const targets: DeviceTarget[] = [];
139 const lines = stdout
140 .split("\n")
141 .map(line => line.trim())
142 .filter(line => !!line);
143 const firstDevicesIndex = lines.findIndex(line => line === "== Devices ==") + 1;
144 const lastDevicesIndex = lines.findIndex(line => line === "== Simulators ==") - 1;
145 for (let i = firstDevicesIndex; i <= lastDevicesIndex; i++) {
146 const line = lines[i];
147 const params = line
148 .split(" ")
149 .map(el => el.trim())
150 .filter(el => !!el);
151 // Add only devices with system version
152 if (
153 params[params.length - 1].match(/\(.+\)/) &&
154 params[params.length - 2].match(/\(.+\)/)
155 ) {
156 targets.push({
157 id: params[params.length - 1].replace(/\(|\)/g, "").trim(),
158 name: params.slice(0, params.length - 2).join(" "),
159 isVirtualTarget: false,
160 isOnline: true,
161 });
162 }
163 }
164 return targets;
165 })
166 .catch(e => {
167 logger.warn(`Failed to query for devices using xctrace: ${e}`);
168 return [];
169 });
170 }
171}
172
173async function push(
174 udid: string,
175 src: string,
176 bundleId: string,
177 dst: string,
178 logger?: OutputChannelLogger,
179): Promise<void> {
180 const cp = new ChildProcess();
181 await checkIdbIsInstalled();
182 return wrapWithErrorMessage(
183 cp
184 .execToString(
185 `${idbPath} --log ${idbLogLevel} file push --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`,
186 )
187 .then(() => {
188 return;
189 })
190 .catch(e => handleMissingIdb(e)),
191 logger,
192 );
193}
194
195async function pull(
196 udid: string,
197 src: string,
198 bundleId: string,
199 dst: string,
200 logger?: OutputChannelLogger,
201): Promise<void> {
202 const cp = new ChildProcess();
203 await checkIdbIsInstalled();
204 return wrapWithErrorMessage(
205 cp
206 .execToString(
207 `${idbPath} --log ${idbLogLevel} file pull --udid ${udid} --bundle-id ${bundleId} '${src}' '${dst}'`,
208 )
209 .then(() => {
210 return;
211 })
212 .catch(e => handleMissingIdb(e)),
213 logger,
214 );
215}
216
217export async function checkIdbIsInstalled(): Promise<void> {
218 const isInstalled = await isIdbAvailable();
219 if (!isInstalled) {
220 throw new Error(
221 `idb is required to use iOS devices. Please install it with instructions from https://github.com/facebook/idb.`,
222 );
223 }
224}
225
226// The fb-internal idb binary is a shim that downloads the proper one on first run. It requires sudo to do so.
227// If we detect this, Tell the user how to fix it.
228function handleMissingIdb(e: Error): void {
229 if (e.message && e.message.includes("sudo: no tty present and no askpass program specified")) {
230 throw new Error(
231 `idb doesn't appear to be installed. Run "${idbPath} list-targets" to fix this.`,
232 );
233 }
234 throw e;
235}
236
237function wrapWithErrorMessage<T>(p: Promise<T>, logger?: OutputChannelLogger): Promise<T> {
238 return p.catch((e: Error) => {
239 logger?.error(e.message);
240 // Give the user instructions. Don't embed the error because it's unique per invocation so won't be deduped.
241 throw new Error(
242 "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.",
243 );
244 });
245}
246
247export default {
248 isAvailable,
249 targets,
250 push,
251 pull,
252};
253
254/**
255 * @preserve
256 * End region: https://github.com/facebook/flipper/blob/c2848df7f210c363113797c0f2e3db8c5d4fd49f/desktop/app/src/server/devices/ios/iOSContainerUtility.tsx
257 */