microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.8.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/ios/iOSTargetManager.ts

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