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/iOSPlatform.ts

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