microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.7.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/ios/iOSPlatform.ts

369lines · 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 path from "path";
5import * as semver from "semver";
6
7import { ChildProcess } from "../../common/node/childProcess";
8import { CommandExecutor } from "../../common/commandExecutor";
9import { GeneralMobilePlatform, MobilePlatformDeps, TargetType } from "../generalMobilePlatform";
10import { IIOSRunOptions, PlatformType } from "../launchArgs";
11import { PlistBuddy } from "./plistBuddy";
12import { IOSDebugModeManager } from "./iOSDebugModeManager";
13import { OutputVerifier, PatternToFailure } from "../../common/outputVerifier";
14import { TelemetryHelper } from "../../common/telemetryHelper";
15import { InternalErrorCode } from "../../common/error/internalErrorCode";
16import * as nls from "vscode-nls";
17import { AppLauncher } from "../appLauncher";
18import { IiOSSimulator, IOSSimulatorManager } from "./iOSSimulatorManager";
19nls.config({
20 messageFormat: nls.MessageFormat.bundle,
21 bundleFormat: nls.BundleFormat.standalone,
22})();
23const localize = nls.loadMessageBundle();
24
25export class IOSPlatform extends GeneralMobilePlatform {
26 public static DEFAULT_IOS_PROJECT_RELATIVE_PATH = "ios";
27
28 private plistBuddy = new PlistBuddy();
29 private targetType: TargetType = "simulator";
30 private iosProjectRoot: string;
31 private iosDebugModeManager: IOSDebugModeManager;
32 private simulatorManager: IOSSimulatorManager;
33
34 private defaultConfiguration: string = "Debug";
35 private configurationArgumentName: string = "--configuration";
36
37 // We should add the common iOS build/run errors we find to this list
38 private static RUN_IOS_FAILURE_PATTERNS: PatternToFailure[] = [
39 {
40 pattern: "No devices are booted",
41 errorCode: InternalErrorCode.IOSSimulatorNotLaunchable,
42 },
43 {
44 pattern: "FBSOpenApplicationErrorDomain",
45 errorCode: InternalErrorCode.IOSSimulatorNotLaunchable,
46 },
47 {
48 pattern: "ios-deploy",
49 errorCode: InternalErrorCode.IOSDeployNotFound,
50 },
51 ];
52
53 private static readonly RUN_IOS_SUCCESS_PATTERNS = ["BUILD SUCCEEDED"];
54
55 public async showDevMenu(appLauncher: AppLauncher): Promise<void> {
56 const worker = appLauncher.getAppWorker();
57 if (worker) {
58 worker.showDevMenuCommand();
59 }
60 }
61
62 public async reloadApp(appLauncher: AppLauncher): Promise<void> {
63 const worker = appLauncher.getAppWorker();
64 if (worker) {
65 worker.reloadAppCommand();
66 }
67 }
68
69 constructor(protected runOptions: IIOSRunOptions, platformDeps: MobilePlatformDeps = {}) {
70 super(runOptions, platformDeps);
71
72 this.simulatorManager = new IOSSimulatorManager();
73 this.runOptions.configuration = this.getConfiguration();
74
75 if (this.runOptions.iosRelativeProjectPath) {
76 // Deprecated option
77 this.logger.warning(
78 localize(
79 "iosRelativeProjectPathOptionIsDeprecatedUseRunArgumentsInstead",
80 "'iosRelativeProjectPath' option is deprecated. Please use 'runArguments' instead.",
81 ),
82 );
83 }
84
85 const iosProjectFolderPath = IOSPlatform.getOptFromRunArgs(
86 this.runArguments,
87 "--project-path",
88 false,
89 );
90 this.iosProjectRoot = path.join(
91 this.projectPath,
92 iosProjectFolderPath ||
93 this.runOptions.iosRelativeProjectPath ||
94 IOSPlatform.DEFAULT_IOS_PROJECT_RELATIVE_PATH,
95 );
96 const schemeFromArgs = IOSPlatform.getOptFromRunArgs(this.runArguments, "--scheme", false);
97 this.iosDebugModeManager = new IOSDebugModeManager(
98 this.iosProjectRoot,
99 this.projectPath,
100 schemeFromArgs ? schemeFromArgs : this.runOptions.scheme,
101 );
102
103 if (this.runArguments && this.runArguments.length > 0) {
104 this.targetType =
105 this.runArguments.indexOf(`--${IOSPlatform.deviceString}`) >= 0
106 ? IOSPlatform.deviceString
107 : IOSPlatform.simulatorString;
108 return;
109 }
110
111 if (
112 this.runOptions.target &&
113 this.runOptions.target !== IOSPlatform.simulatorString &&
114 this.runOptions.target !== IOSPlatform.deviceString
115 ) {
116 this.targetType = IOSPlatform.simulatorString;
117 return;
118 }
119
120 this.targetType = this.runOptions.target || IOSPlatform.simulatorString;
121 }
122
123 public async resolveVirtualDevice(target: string): Promise<IiOSSimulator | null> {
124 if (target === "simulator") {
125 const simulatorName = await this.simulatorManager.startSelection();
126 if (simulatorName) {
127 const simulator = this.simulatorManager.findSimulator(simulatorName);
128 if (simulator) {
129 GeneralMobilePlatform.removeRunArgument(this.runArguments, "--simulator", true);
130 GeneralMobilePlatform.setRunArgument(this.runArguments, "--udid", simulator.id);
131 }
132 return simulator;
133 } else {
134 return null;
135 }
136 } else if (!target.includes("device")) {
137 const simulators = await this.simulatorManager.collectSimulators();
138 let simulator = this.simulatorManager.getSimulatorById(target, simulators);
139 if (simulator) {
140 GeneralMobilePlatform.removeRunArgument(this.runArguments, "--simulator", false);
141 GeneralMobilePlatform.setRunArgument(this.runArguments, "--udid", simulator.id);
142 }
143 return null;
144 } else {
145 return null;
146 }
147 }
148
149 public async runApp(): Promise<void> {
150 let extProps = {
151 platform: {
152 value: PlatformType.iOS,
153 isPii: false,
154 },
155 };
156
157 extProps = TelemetryHelper.addPlatformPropertiesToTelemetryProperties(
158 this.runOptions,
159 this.runOptions.reactNativeVersions,
160 extProps,
161 );
162
163 await TelemetryHelper.generate("iOSPlatform.runApp", extProps, async () => {
164 // Compile, deploy, and launch the app on either a simulator or a device
165 const env = GeneralMobilePlatform.getEnvArgument(
166 process.env,
167 this.runOptions.env,
168 this.runOptions.envFile,
169 );
170
171 if (
172 !semver.valid(
173 this.runOptions.reactNativeVersions.reactNativeVersion,
174 ) /*Custom RN implementations should support this flag*/ ||
175 semver.gte(
176 this.runOptions.reactNativeVersions.reactNativeVersion,
177 IOSPlatform.NO_PACKAGER_VERSION,
178 )
179 ) {
180 this.runArguments.push("--no-packager");
181 }
182 // Since @react-native-community/cli@2.1.0 build output are hidden by default
183 // we are using `--verbose` to show it as it contains `BUILD SUCCESSFUL` and other patterns
184 if (semver.gte(this.runOptions.reactNativeVersions.reactNativeVersion, "0.60.0")) {
185 this.runArguments.push("--verbose");
186 }
187 const runIosSpawn = new CommandExecutor(
188 this.runOptions.nodeModulesRoot,
189 this.projectPath,
190 this.logger,
191 ).spawnReactCommand("run-ios", this.runArguments, { env });
192 await new OutputVerifier(
193 () =>
194 this.generateSuccessPatterns(
195 this.runOptions.reactNativeVersions.reactNativeVersion,
196 ),
197 () => Promise.resolve(IOSPlatform.RUN_IOS_FAILURE_PATTERNS),
198 PlatformType.iOS,
199 ).process(runIosSpawn);
200 });
201 }
202
203 public async enableJSDebuggingMode(): Promise<void> {
204 // Configure the app for debugging
205 if (this.targetType === IOSPlatform.deviceString) {
206 // Note that currently we cannot automatically switch the device into debug mode.
207 this.logger.info(
208 "Application is running on a device, please shake device and select 'Debug JS Remotely' to enable debugging.",
209 );
210 return;
211 }
212
213 // Wait until the configuration file exists, and check to see if debugging is enabled
214 const [debugModeEnabled, bundleId] = await Promise.all<boolean | string>([
215 this.iosDebugModeManager.getAppRemoteDebuggingSetting(
216 this.runOptions.configuration,
217 this.runOptions.productName,
218 ),
219 this.getBundleId(),
220 ]);
221 if (debugModeEnabled) {
222 return;
223 }
224 // Debugging must still be enabled
225 // We enable debugging by writing to a plist file that backs a NSUserDefaults object,
226 // but that file is written to by the app on occasion. To avoid races, we shut the app
227 // down before writing to the file.
228 const childProcess = new ChildProcess();
229 const output = await childProcess.execToString("xcrun simctl spawn booted launchctl list");
230 // Try to find an entry that looks like UIKitApplication:com.example.myApp[0x4f37]
231 const regex = new RegExp(`(\\S+${bundleId}\\S+)`);
232 const match = regex.exec(output);
233 // If we don't find a match, the app must not be running and so we do not need to close it
234 if (match) {
235 await childProcess.exec(`xcrun simctl spawn booted launchctl stop ${match[1]}`);
236 }
237 // Write to the settings file while the app is not running to avoid races
238 await this.iosDebugModeManager.setAppRemoteDebuggingSetting(
239 /*enable=*/ true,
240 this.runOptions.configuration,
241 this.runOptions.productName,
242 );
243 // Relaunch the app
244 return await this.runApp();
245 }
246
247 public async disableJSDebuggingMode(): Promise<void> {
248 if (this.targetType === IOSPlatform.deviceString) {
249 return;
250 }
251 return this.iosDebugModeManager.setAppRemoteDebuggingSetting(
252 /*enable=*/ false,
253 this.runOptions.configuration,
254 this.runOptions.productName,
255 );
256 }
257
258 public prewarmBundleCache(): Promise<void> {
259 return this.packager.prewarmBundleCache(PlatformType.iOS);
260 }
261
262 public getRunArguments(): string[] {
263 let runArguments: string[] = [];
264
265 if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
266 runArguments = this.runOptions.runArguments;
267 if (this.runOptions.scheme) {
268 const schemeFromArgs = IOSPlatform.getOptFromRunArgs(
269 runArguments,
270 "--scheme",
271 false,
272 );
273 if (!schemeFromArgs) {
274 runArguments.push("--scheme", this.runOptions.scheme);
275 } else {
276 this.logger.warning(
277 localize(
278 "iosSchemeParameterAlreadySetInRunArguments",
279 "'--scheme' is set as 'runArguments' configuration parameter value, 'scheme' configuration parameter value will be omitted",
280 ),
281 );
282 }
283 }
284 } else {
285 if (this.runOptions.target) {
286 runArguments.push(...this.handleTargetArg(this.runOptions.target));
287 }
288
289 if (this.runOptions.iosRelativeProjectPath) {
290 runArguments.push("--project-path", this.runOptions.iosRelativeProjectPath);
291 }
292
293 // provide any defined scheme
294 if (this.runOptions.scheme) {
295 runArguments.push("--scheme", this.runOptions.scheme);
296 }
297 }
298
299 return runArguments;
300 }
301
302 private handleTargetArg(target: string): string[] {
303 if (target === IOSPlatform.deviceString || target === IOSPlatform.simulatorString) {
304 return [`--${this.runOptions.target}`];
305 } else {
306 if (target.indexOf(IOSPlatform.deviceString) !== -1) {
307 const deviceArgs = target.split("=");
308 return deviceArgs[1]
309 ? [`--${IOSPlatform.deviceString}`, deviceArgs[1]]
310 : [`--${IOSPlatform.deviceString}`];
311 } else {
312 return [`--${IOSPlatform.simulatorString}`, `${this.runOptions.target}`];
313 }
314 }
315 }
316
317 private async generateSuccessPatterns(version: string): Promise<string[]> {
318 // Clone RUN_IOS_SUCCESS_PATTERNS to avoid its runtime mutation
319 let successPatterns = [...IOSPlatform.RUN_IOS_SUCCESS_PATTERNS];
320 if (this.targetType === IOSPlatform.deviceString) {
321 if (semver.gte(version, "0.60.0")) {
322 successPatterns.push("success Installed the app on the device");
323 } else {
324 successPatterns.push("INSTALLATION SUCCEEDED");
325 }
326 return successPatterns;
327 } else {
328 const bundleId = await this.getBundleId();
329 if (semver.gte(version, "0.60.0")) {
330 successPatterns.push(
331 `Launching "${bundleId}"\nsuccess Successfully launched the app `,
332 );
333 } else {
334 successPatterns.push(`Launching ${bundleId}\n${bundleId}: `);
335 }
336 return successPatterns;
337 }
338 }
339
340 private getConfiguration(): string {
341 return (
342 IOSPlatform.getOptFromRunArgs(this.runArguments, this.configurationArgumentName) ||
343 this.defaultConfiguration
344 );
345 }
346
347 private getBundleId(): Promise<string> {
348 let scheme = this.runOptions.scheme;
349 if (!scheme) {
350 const schemeFromArgs = IOSPlatform.getOptFromRunArgs(
351 this.runArguments,
352 "--scheme",
353 false,
354 );
355 if (schemeFromArgs) {
356 scheme = schemeFromArgs;
357 }
358 }
359 return this.plistBuddy.getBundleId(
360 this.iosProjectRoot,
361 this.projectPath,
362 PlatformType.iOS,
363 true,
364 this.runOptions.configuration,
365 this.runOptions.productName,
366 scheme,
367 );
368 }
369}
370