microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
test-microbuild1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/ios/iOSTargetManager.ts

287lines · 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 * as nls from "vscode-nls";
5import { QuickPickOptions, window } from "vscode";
6import { ChildProcess } from "../../common/node/childProcess";
7import { PromiseUtil } from "../../common/node/promise";
8import { IDebuggableMobileTarget, MobileTarget } from "../mobileTarget";
9import { MobileTargetManager } from "../mobileTargetManager";
10import { OutputChannelLogger } from "../log/OutputChannelLogger";
11import { TargetType } from "../generalPlatform";
12
13nls.config({
14 messageFormat: nls.MessageFormat.bundle,
15 bundleFormat: nls.BundleFormat.standalone,
16})();
17const localize = nls.loadMessageBundle();
18
19export interface IDebuggableIOSTarget extends IDebuggableMobileTarget {
20 name: string;
21 system: string;
22}
23
24export class IOSTarget extends MobileTarget implements IDebuggableIOSTarget {
25 protected _system: string;
26 protected _name: string;
27
28 public static fromInterface(obj: IDebuggableIOSTarget): IOSTarget {
29 return new IOSTarget(obj.isOnline, obj.isVirtualTarget, obj.id, obj.name, obj.system);
30 }
31
32 constructor(
33 isOnline: boolean,
34 isVirtualTarget: boolean,
35 id: string,
36 name: string,
37 system: string,
38 ) {
39 super(isOnline, isVirtualTarget, id, name);
40 this._system = system;
41 }
42
43 get system(): string {
44 return this._system;
45 }
46
47 get name(): string {
48 return this._name;
49 }
50
51 set name(value: string) {
52 this._name = value;
53 }
54}
55
56export class IOSTargetManager extends MobileTargetManager {
57 private static readonly XCRUN_COMMAND = "xcrun";
58 private static readonly SIMCTL_COMMAND = "simctl";
59 private static readonly BOOT_COMMAND = `boot`;
60 private static readonly SIMULATORS_LIST_COMMAND = `${IOSTargetManager.XCRUN_COMMAND} ${IOSTargetManager.SIMCTL_COMMAND} list devices available --json`;
61 private static readonly ALL_DEVICES_LIST_COMMAND = `${IOSTargetManager.XCRUN_COMMAND} xctrace list devices`;
62 private static readonly BOOTED_STATE = "Booted";
63 private static readonly SIMULATOR_START_TIMEOUT = 120;
64 private static readonly ANY_SYSTEM = "AnySystem";
65
66 private childProcess: ChildProcess = new ChildProcess();
67 private logger: OutputChannelLogger = OutputChannelLogger.getChannel(
68 OutputChannelLogger.MAIN_CHANNEL_NAME,
69 true,
70 );
71 protected targets?: IDebuggableIOSTarget[];
72
73 public async collectTargets(targetType?: TargetType): Promise<void> {
74 this.targets = [];
75 if (targetType === undefined || targetType === TargetType.Simulator) {
76 const simulators = JSON.parse(
77 await this.childProcess.execToString(`${IOSTargetManager.SIMULATORS_LIST_COMMAND}`),
78 );
79 Object.keys(simulators.devices).forEach(rawSystem => {
80 const temp = rawSystem.split(".").slice(-1)[0].split("-"); // "com.apple.CoreSimulator.SimRuntime.iOS-11-4" -> ["iOS", "11", "4"]
81 const system = `${temp[0]} ${temp.slice(1).join(".")}`; // ["iOS", "11", "4"] -> iOS 11.4
82 simulators.devices[rawSystem].forEach((device: any) => {
83 // Now we support selection only for iOS system
84 if (system.includes("iOS")) {
85 this.targets?.push({
86 id: device.udid,
87 name: device.name,
88 system,
89 isVirtualTarget: true,
90 isOnline: device.state === IOSTargetManager.BOOTED_STATE,
91 });
92 }
93 });
94 });
95 }
96
97 if (targetType === undefined || targetType === TargetType.Device) {
98 const allDevicesOutput = await this.childProcess.execToString(
99 `${IOSTargetManager.ALL_DEVICES_LIST_COMMAND}`,
100 );
101 // Output example:
102 // == Devices ==
103 // sierra (EFDAAD01-E1A3-5F00-A357-665B501D5520)
104 // My iPhone (14.4.2) (33n546e591e707bd64c718bfc1bf3e8b7c16bfc9)
105 //
106 // == Simulators ==
107 // Apple TV (14.5) (417BDFD8-6E22-4F87-BCAA-19C241AC9548)
108 // Apple TV 4K (2nd generation) (14.5) (925E6E38-0D7B-45E9-ADE0-89C20779D467)
109 // ...
110 const lines = allDevicesOutput
111 .split("\n")
112 .map(line => line.trim())
113 .filter(line => !!line);
114 const firstDevicesIndex = lines.indexOf("== Devices ==") + 1;
115 const lastDevicesIndex = lines.indexOf("== Simulators ==") - 1;
116 for (let i = firstDevicesIndex; i <= lastDevicesIndex; i++) {
117 const line = lines[i];
118 const params = line
119 .split(" ")
120 .map(el => el.trim())
121 .filter(el => !!el);
122 // Add only devices with system version
123 if (
124 params[params.length - 1].match(/\(.+\)/) &&
125 params[params.length - 2].match(/\(.+\)/)
126 ) {
127 this.targets.push({
128 id: params[params.length - 1].replace(/\(|\)/g, "").trim(),
129 name: params.slice(0, params.length - 2).join(" "),
130 system: params[params.length - 2].replace(/\(|\)/g, "").trim(),
131 isVirtualTarget: false,
132 isOnline: true,
133 });
134 }
135 }
136 }
137 }
138
139 public async selectAndPrepareTarget(
140 filter?: (el: IDebuggableIOSTarget) => boolean,
141 ): Promise<IOSTarget | undefined> {
142 const selectedTarget = await this.startSelection(filter);
143 if (selectedTarget) {
144 return !selectedTarget.isOnline && selectedTarget.isVirtualTarget
145 ? this.launchSimulator(selectedTarget)
146 : IOSTarget.fromInterface(selectedTarget);
147 }
148 return undefined;
149 }
150
151 public async isVirtualTarget(targetString: string): Promise<boolean> {
152 try {
153 if (targetString === TargetType.Device) {
154 return false;
155 } else if (targetString === TargetType.Simulator) {
156 return true;
157 }
158 const target = (
159 await this.getTargetList(
160 target => target.id === targetString || target.name === targetString,
161 )
162 )[0];
163 if (target) {
164 return target.isVirtualTarget;
165 }
166 throw Error("There is no any target with specified target string");
167 } catch {
168 throw new Error(
169 localize(
170 "CouldNotRecognizeTargetType",
171 "Could not recognize type of the target {0}",
172 targetString,
173 ),
174 );
175 }
176 }
177
178 protected async startSelection(
179 filter?: (el: IDebuggableIOSTarget) => boolean,
180 ): Promise<IDebuggableIOSTarget | undefined> {
181 const system = await this.selectSystem(filter);
182 if (system) {
183 return (await this.selectTarget(
184 (el: IDebuggableIOSTarget) =>
185 (filter ? filter(el) : true) &&
186 (system === IOSTargetManager.ANY_SYSTEM ? true : el.system === system),
187 )) as IDebuggableIOSTarget | undefined;
188 }
189 return;
190 }
191
192 protected async selectSystem(
193 filter?: (el: IDebuggableIOSTarget) => boolean,
194 ): Promise<string | undefined> {
195 const targets = (await this.getTargetList(filter)) as IDebuggableIOSTarget[];
196 // If we select only from devices, we should not select system
197 if (!targets.find(target => target.isVirtualTarget)) {
198 return IOSTargetManager.ANY_SYSTEM;
199 }
200 const names: Set<string> = new Set(targets.map(target => target.system));
201 const systemsList = Array.from(names);
202 let result: string | undefined = systemsList[0];
203 if (systemsList.length > 1) {
204 const quickPickOptions: QuickPickOptions = {
205 ignoreFocusOut: true,
206 canPickMany: false,
207 placeHolder: localize(
208 "SelectIOSSystemVersion",
209 "Select system version of iOS target",
210 ),
211 };
212 result = await window.showQuickPick(systemsList, quickPickOptions);
213 }
214 return result?.toString();
215 }
216
217 protected async launchSimulator(
218 virtualTarget: IDebuggableIOSTarget,
219 ): Promise<IOSTarget | undefined> {
220 return new Promise<IOSTarget | undefined>((resolve, reject) => {
221 let emulatorLaunchFailed = false;
222 const emulatorProcess = this.childProcess.spawn(
223 IOSTargetManager.XCRUN_COMMAND,
224 [IOSTargetManager.SIMCTL_COMMAND, IOSTargetManager.BOOT_COMMAND, virtualTarget.id],
225 {
226 detached: true,
227 },
228 true,
229 );
230 emulatorProcess.spawnedProcess.unref();
231 emulatorProcess.outcome.catch(e => {
232 emulatorLaunchFailed = true;
233 this.logger.error(
234 localize(
235 "ErrorWhileLaunchingSimulator",
236 "Error while launching simulator {0} : {1}",
237 `${virtualTarget.name}(${virtualTarget.id})`,
238 e,
239 ),
240 );
241 reject(e);
242 });
243
244 const condition = async () => {
245 if (emulatorLaunchFailed)
246 throw new Error("iOS simulator launch failed unexpectedly");
247 await this.collectTargets(TargetType.Simulator);
248 const onlineTarget = (await this.getTargetList()).find(
249 target => target.id === virtualTarget.id && target.isOnline,
250 );
251 return onlineTarget ? true : null;
252 };
253
254 void PromiseUtil.waitUntil<boolean>(
255 condition,
256 1000,
257 IOSTargetManager.SIMULATOR_START_TIMEOUT * 1000,
258 ).then(
259 isBooted => {
260 if (isBooted) {
261 virtualTarget.isOnline = true;
262 this.logger.info(
263 localize(
264 "SimulatorLaunched",
265 "Launched simulator {0}",
266 virtualTarget.name,
267 ),
268 );
269 resolve(IOSTarget.fromInterface(virtualTarget));
270 } else {
271 reject(
272 new Error(
273 `Virtual device launch finished with an exception: ${localize(
274 "SimulatorStartWarning",
275 "Could not start the simulator {0} within {1} seconds.",
276 virtualTarget.name,
277 IOSTargetManager.SIMULATOR_START_TIMEOUT,
278 )}`,
279 ),
280 );
281 }
282 },
283 () => {},
284 );
285 });
286 }
287}
288