microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/removeNode10TodoComments

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/ios/iOSTargetManager.ts

291lines · 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, IMobileTarget, 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: IMobileTarget) =>
185 (filter ? filter(el as IDebuggableIOSTarget) : true) &&
186 (system === IOSTargetManager.ANY_SYSTEM
187 ? true
188 : (el as IDebuggableIOSTarget).system === system),
189 )) as IDebuggableIOSTarget | undefined;
190 }
191 return;
192 }
193
194 protected async selectSystem(
195 filter?: (el: IDebuggableIOSTarget) => boolean,
196 ): Promise<string | undefined> {
197 const targets = (await this.getTargetList(
198 filter as ((el: IMobileTarget) => boolean) | undefined,
199 )) as IDebuggableIOSTarget[];
200 // If we select only from devices, we should not select system
201 if (!targets.find(target => target.isVirtualTarget)) {
202 return IOSTargetManager.ANY_SYSTEM;
203 }
204 const names: Set<string> = new Set(targets.map(target => target.system));
205 const systemsList = Array.from(names);
206 let result: string | undefined = systemsList[0];
207 if (systemsList.length > 1) {
208 const quickPickOptions: QuickPickOptions = {
209 ignoreFocusOut: true,
210 canPickMany: false,
211 placeHolder: localize(
212 "SelectIOSSystemVersion",
213 "Select system version of iOS target",
214 ),
215 };
216 result = await window.showQuickPick(systemsList, quickPickOptions);
217 }
218 return result?.toString();
219 }
220
221 protected async launchSimulator(
222 virtualTarget: IDebuggableIOSTarget,
223 ): Promise<IOSTarget | undefined> {
224 return new Promise<IOSTarget | undefined>((resolve, reject) => {
225 let emulatorLaunchFailed = false;
226 const emulatorProcess = this.childProcess.spawn(
227 IOSTargetManager.XCRUN_COMMAND,
228 [IOSTargetManager.SIMCTL_COMMAND, IOSTargetManager.BOOT_COMMAND, virtualTarget.id],
229 {
230 detached: true,
231 },
232 true,
233 );
234 emulatorProcess.spawnedProcess.unref();
235 emulatorProcess.outcome.catch(e => {
236 emulatorLaunchFailed = true;
237 this.logger.error(
238 localize(
239 "ErrorWhileLaunchingSimulator",
240 "Error while launching simulator {0} : {1}",
241 `${virtualTarget.name}(${virtualTarget.id})`,
242 e,
243 ),
244 );
245 reject(e);
246 });
247
248 const condition = async () => {
249 if (emulatorLaunchFailed)
250 throw new Error("iOS simulator launch failed unexpectedly");
251 await this.collectTargets(TargetType.Simulator);
252 const onlineTarget = (await this.getTargetList()).find(
253 target => target.id === virtualTarget.id && target.isOnline,
254 );
255 return onlineTarget ? true : null;
256 };
257
258 void PromiseUtil.waitUntil<boolean>(
259 condition,
260 1000,
261 IOSTargetManager.SIMULATOR_START_TIMEOUT * 1000,
262 ).then(
263 isBooted => {
264 if (isBooted) {
265 virtualTarget.isOnline = true;
266 this.logger.info(
267 localize(
268 "SimulatorLaunched",
269 "Launched simulator {0}",
270 virtualTarget.name,
271 ),
272 );
273 resolve(IOSTarget.fromInterface(virtualTarget));
274 } else {
275 reject(
276 new Error(
277 `Virtual device launch finished with an exception: ${localize(
278 "SimulatorStartWarning",
279 "Could not start the simulator {0} within {1} seconds.",
280 virtualTarget.name,
281 IOSTargetManager.SIMULATOR_START_TIMEOUT,
282 )}`,
283 ),
284 );
285 }
286 },
287 () => {},
288 );
289 });
290 }
291}
292