microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.0.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/appLauncher.ts

376lines · 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 vscode from "vscode";
5import {Packager} from "../common/packager";
6import {RNPackageVersions} from "../common/projectVersionHelper";
7import {ExponentHelper} from "./exponent/exponentHelper";
8import {ReactDirManager} from "./reactDirManager";
9import {SettingsHelper} from "./settingsHelper";
10import {PackagerStatusIndicator} from "./packagerStatusIndicator";
11import {CommandExecutor} from "../common/commandExecutor";
12import {isNullOrUndefined} from "../common/utils";
13import {OutputChannelLogger} from "./log/OutputChannelLogger";
14import {MobilePlatformDeps, GeneralMobilePlatform} from "./generalMobilePlatform";
15import {PlatformResolver} from "./platformResolver";
16import {ProjectVersionHelper} from "../common/projectVersionHelper";
17import {TelemetryHelper} from "../common/telemetryHelper";
18import {ErrorHelper} from "../common/error/errorHelper";
19import {InternalErrorCode} from "../common/error/internalErrorCode";
20import {TargetPlatformHelper} from "../common/targetPlatformHelper";
21import {LogCatMonitor} from "./android/logCatMonitor";
22import {ProjectsStorage} from "./projectsStorage";
23import {ReactNativeCDPProxy} from "../cdp-proxy/reactNativeCDPProxy";
24import {generateRandomPortNumber} from "../common/extensionHelper";
25import {DEBUG_TYPES} from "./debugConfigurationProvider";
26import * as nls from "vscode-nls";
27import { MultipleLifetimesAppWorker } from "../debugger/appWorker";
28import { PlatformType } from "./launchArgs";
29import { LaunchScenariosManager } from "./launchScenariosManager";
30import { IVirtualDevice } from "./VirtualDeviceManager";
31nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
32const localize = nls.loadMessageBundle();
33
34export class AppLauncher {
35 private readonly cdpProxyPort: number;
36 private readonly cdpProxyHostAddress: string;
37
38 private appWorker: MultipleLifetimesAppWorker | null;
39 private packager: Packager;
40 private exponentHelper: ExponentHelper;
41 private reactDirManager: ReactDirManager;
42 private workspaceFolder: vscode.WorkspaceFolder;
43 private reactNativeVersions?: RNPackageVersions;
44 private rnCdpProxy: ReactNativeCDPProxy;
45 private logger: OutputChannelLogger = OutputChannelLogger.getMainChannel();
46 private logCatMonitor: LogCatMonitor | null = null;
47 private launchScenariosManager: LaunchScenariosManager;
48
49 public static getAppLauncherByProjectRootPath(projectRootPath: string): AppLauncher {
50 const appLauncher = ProjectsStorage.projectsCache[projectRootPath.toLowerCase()];
51 if (!appLauncher) {
52 throw new Error(`Could not find AppLauncher by the project root path ${projectRootPath}`);
53 }
54
55 return appLauncher;
56 }
57
58 constructor(reactDirManager: ReactDirManager, workspaceFolder: vscode.WorkspaceFolder) {
59 // constants definition
60 this.cdpProxyPort = generateRandomPortNumber();
61 this.cdpProxyHostAddress = "127.0.0.1"; // localhost
62
63 const rootPath = workspaceFolder.uri.fsPath;
64 this.launchScenariosManager = new LaunchScenariosManager(rootPath);
65 const projectRootPath = SettingsHelper.getReactNativeProjectRoot(rootPath);
66 this.exponentHelper = new ExponentHelper(rootPath, projectRootPath);
67 const packagerStatusIndicator: PackagerStatusIndicator = new PackagerStatusIndicator(rootPath);
68 this.packager = new Packager(rootPath, projectRootPath, SettingsHelper.getPackagerPort(workspaceFolder.uri.fsPath), packagerStatusIndicator);
69 this.packager.setExponentHelper(this.exponentHelper);
70 this.reactDirManager = reactDirManager;
71 this.workspaceFolder = workspaceFolder;
72 this.rnCdpProxy = new ReactNativeCDPProxy(
73 this.cdpProxyHostAddress,
74 this.cdpProxyPort
75 );
76 }
77
78 public getCdpProxyPort(): number {
79 return this.cdpProxyPort;
80 }
81
82 public getRnCdpProxy(): ReactNativeCDPProxy {
83 return this.rnCdpProxy;
84 }
85
86 public getPackager(): Packager {
87 return this.packager;
88 }
89
90 public getWorkspaceFolderUri(): vscode.Uri {
91 return this.workspaceFolder.uri;
92 }
93
94 public getWorkspaceFolder(): vscode.WorkspaceFolder {
95 return this.workspaceFolder;
96 }
97
98 public getReactNativeVersions(): RNPackageVersions | undefined {
99 return this.reactNativeVersions;
100 }
101
102 public getExponentHelper(): ExponentHelper {
103 return this.exponentHelper;
104 }
105
106 public getReactDirManager(): ReactDirManager {
107 return this.reactDirManager;
108 }
109
110 public setReactNativeVersions(reactNativeVersions: RNPackageVersions): void {
111 this.reactNativeVersions = reactNativeVersions;
112 }
113
114 public setAppWorker(appWorker: MultipleLifetimesAppWorker): void {
115 this.appWorker = appWorker;
116 }
117
118 public getAppWorker(): MultipleLifetimesAppWorker | null {
119 return this.appWorker;
120 }
121
122 public dispose(): void {
123 this.packager.getStatusIndicator().dispose();
124 this.packager.stop(true);
125 this.stopMonitoringLogCat();
126 }
127
128 public stopMonitoringLogCat(): void {
129 if (this.logCatMonitor) {
130 this.logCatMonitor.dispose();
131 this.logCatMonitor = null;
132 }
133 }
134
135 public openFileAtLocation(filename: string, lineNumber: number): Promise<void> {
136 return new Promise((resolve) => {
137 vscode.workspace.openTextDocument(vscode.Uri.file(filename))
138 .then((document: vscode.TextDocument) => {
139 vscode.window.showTextDocument(document)
140 .then((editor: vscode.TextEditor) => {
141 let range = editor.document.lineAt(lineNumber - 1).range;
142 editor.selection = new vscode.Selection(range.start, range.end);
143 editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
144 resolve();
145 });
146 });
147 });
148 }
149
150 public getPackagerPort(projectFolder: string): number {
151 return SettingsHelper.getPackagerPort(projectFolder);
152 }
153
154 public launch(launchArgs: any): Promise<any> {
155 let mobilePlatformOptions = this.requestSetup(launchArgs);
156
157 // We add the parameter if it's defined (adapter crashes otherwise)
158 if (!isNullOrUndefined(launchArgs.logCatArguments)) {
159 mobilePlatformOptions.logCatArguments = [this.parseLogCatArguments(launchArgs.logCatArguments)];
160 }
161
162 if (!isNullOrUndefined(launchArgs.variant)) {
163 mobilePlatformOptions.variant = launchArgs.variant;
164 }
165
166 if (!isNullOrUndefined(launchArgs.scheme)) {
167 mobilePlatformOptions.scheme = launchArgs.scheme;
168 }
169
170 if (!isNullOrUndefined(launchArgs.productName)) {
171 mobilePlatformOptions.productName = launchArgs.productName;
172 }
173
174 if (!isNullOrUndefined(launchArgs.launchActivity)) {
175 mobilePlatformOptions.debugLaunchActivity = launchArgs.launchActivity;
176 }
177
178 if (launchArgs.type === DEBUG_TYPES.REACT_NATIVE_DIRECT) {
179 mobilePlatformOptions.isDirect = true;
180 }
181
182 mobilePlatformOptions.packagerPort = SettingsHelper.getPackagerPort(launchArgs.cwd || launchArgs.program);
183 const platformDeps: MobilePlatformDeps = {
184 packager: this.packager,
185 };
186 const mobilePlatform = new PlatformResolver()
187 .resolveMobilePlatform(launchArgs.platform, mobilePlatformOptions, platformDeps);
188 return new Promise((resolve, reject) => {
189 let extProps: any = {
190 platform: {
191 value: launchArgs.platform,
192 isPii: false,
193 },
194 };
195
196 if (mobilePlatformOptions.isDirect) {
197 extProps.isDirect = {
198 value: true,
199 isPii: false,
200 };
201 }
202
203 return ProjectVersionHelper.getReactNativePackageVersionsFromNodeModules(mobilePlatformOptions.projectRoot, true)
204 .then(versions => {
205 mobilePlatformOptions.reactNativeVersions = versions;
206 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeVersion, "reactNativeVersion", extProps);
207 if (launchArgs.platform === PlatformType.Windows) {
208 if (ProjectVersionHelper.isVersionError(versions.reactNativeWindowsVersion)) {
209 throw ErrorHelper.getInternalError(InternalErrorCode.ReactNativeWindowsIsNotInstalled);
210 }
211 extProps = TelemetryHelper.addPropertyToTelemetryProperties(versions.reactNativeWindowsVersion, "reactNativeWindowsVersion", extProps);
212 }
213 TelemetryHelper.generate("launch", extProps, (generator) => {
214 generator.step("resolveEmulator");
215 return this.resolveAndSaveVirtualDevice(mobilePlatform, launchArgs, mobilePlatformOptions)
216 .then(() => mobilePlatform.beforeStartPackager())
217 .then(() => {
218 generator.step("checkPlatformCompatibility");
219 TargetPlatformHelper.checkTargetPlatformSupport(mobilePlatformOptions.platform);
220 })
221 .then(() => {
222 generator.step("startPackager");
223 return mobilePlatform.startPackager();
224 })
225 .then(() => {
226 // We've seen that if we don't prewarm the bundle cache, the app fails on the first attempt to connect to the debugger logic
227 // and the user needs to Reload JS manually. We prewarm it to prevent that issue
228 generator.step("prewarmBundleCache");
229 this.logger.info(localize("PrewarmingBundleCache", "Prewarming bundle cache. This may take a while ..."));
230 return mobilePlatform.prewarmBundleCache();
231 })
232 .then(() => {
233 generator.step("mobilePlatform.runApp").add("target", mobilePlatformOptions.target, false);
234 this.logger.info(localize("BuildingAndRunningApplication", "Building and running application."));
235 return mobilePlatform.runApp();
236 })
237 .then(() => {
238 if (mobilePlatformOptions.isDirect || !mobilePlatformOptions.enableDebug) {
239 if (mobilePlatformOptions.isDirect && launchArgs.platform === PlatformType.Android) {
240 generator.step("mobilePlatform.enableDirectDebuggingMode");
241 if (mobilePlatformOptions.enableDebug) {
242 this.logger.info(localize("PrepareHermesDebugging", "Prepare Hermes debugging (experimental)"));
243 } else {
244 this.logger.info(localize("PrepareHermesLaunch", "Prepare Hermes launch (experimental)"));
245 }
246 } else {
247 generator.step("mobilePlatform.disableJSDebuggingMode");
248 this.logger.info(localize("DisableJSDebugging", "Disable JS Debugging"));
249 }
250 return mobilePlatform.disableJSDebuggingMode();
251 }
252 generator.step("mobilePlatform.enableJSDebuggingMode");
253 this.logger.info(localize("EnableJSDebugging", "Enable JS Debugging"));
254 return mobilePlatform.enableJSDebuggingMode();
255 })
256 .then(resolve)
257 .catch(error => {
258 if (!mobilePlatformOptions.enableDebug && launchArgs.platform === PlatformType.iOS && launchArgs.type === DEBUG_TYPES.REACT_NATIVE) {
259 // If we disable debugging mode for iOS scenarios, we'll we ignore the error and run the 'run-ios' command anyway,
260 // since the error doesn't affects an application launch process
261 return resolve();
262 }
263 generator.addError(error);
264 this.logger.error(error);
265 reject(error);
266 });
267 });
268 })
269 .catch(error => {
270 if (error && error.errorCode) {
271 if (error.errorCode === InternalErrorCode.ReactNativePackageIsNotInstalled) {
272 TelemetryHelper.sendErrorEvent(
273 "ReactNativePackageIsNotInstalled",
274 ErrorHelper.getInternalError(InternalErrorCode.ReactNativePackageIsNotInstalled)
275 );
276 } else if (error.errorCode === InternalErrorCode.ReactNativeWindowsIsNotInstalled) {
277 TelemetryHelper.sendErrorEvent(
278 "ReactNativeWindowsPackageIsNotInstalled",
279 ErrorHelper.getInternalError(InternalErrorCode.ReactNativeWindowsIsNotInstalled)
280 );
281 }
282 }
283 this.logger.error(error);
284 reject(error);
285 });
286 });
287 }
288
289 private resolveAndSaveVirtualDevice(mobilePlatform: GeneralMobilePlatform, launchArgs: any, mobilePlatformOptions: any): Promise<void> {
290 if (launchArgs.target && (mobilePlatformOptions.platform === PlatformType.Android || mobilePlatformOptions.platform === PlatformType.iOS)) {
291 return mobilePlatform.resolveVirtualDevice(launchArgs.target)
292 .then((emulator: IVirtualDevice | null) => {
293 if (emulator) {
294 if (emulator.name && launchArgs.platform === PlatformType.Android) {
295 mobilePlatformOptions.target = emulator.id;
296 this.launchScenariosManager.updateLaunchScenario(launchArgs, {target: emulator.name});
297 }
298 if (launchArgs.platform === PlatformType.iOS) {
299 this.launchScenariosManager.updateLaunchScenario(launchArgs, {target: emulator.id});
300 }
301 launchArgs.target = emulator.id;
302 }
303 else if (mobilePlatformOptions.target.indexOf("device") < 0) {
304 this.cleanupTargetModifications(mobilePlatform, mobilePlatformOptions);
305 }
306 })
307 .catch(error => {
308 if (error && error.errorCode && error.errorCode === InternalErrorCode.VirtualDeviceSelectionError) {
309 TelemetryHelper.sendErrorEvent(
310 "VirtualDeviceSelectionError",
311 ErrorHelper.getInternalError(InternalErrorCode.VirtualDeviceSelectionError)
312 );
313
314 this.logger.warning(error);
315 this.logger.warning(localize("ContinueWithRnCliWorkflow", "Continue using standard RN CLI workflow."));
316
317 if (mobilePlatformOptions.target.indexOf("device") < 0) {
318 this.cleanupTargetModifications(mobilePlatform, mobilePlatformOptions);
319 }
320 return Promise.resolve();
321 }
322 else {
323 return Promise.reject(error);
324 }
325 });
326 }
327 return Promise.resolve();
328 }
329
330 private cleanupTargetModifications(mobilePlatform: GeneralMobilePlatform, mobilePlatformOptions: any) {
331 mobilePlatformOptions.target = "simulator";
332 mobilePlatform.runArguments = mobilePlatform.getRunArguments();
333 }
334
335 private requestSetup(args: any): any {
336 const workspaceFolder: vscode.WorkspaceFolder = <vscode.WorkspaceFolder>vscode.workspace.getWorkspaceFolder(vscode.Uri.file(args.cwd || args.program));
337 const projectRootPath = this.getProjectRoot(args);
338 let mobilePlatformOptions: any = {
339 workspaceRoot: workspaceFolder.uri.fsPath,
340 projectRoot: projectRootPath,
341 platform: args.platform,
342 env: args.env,
343 envFile: args.envFile,
344 target: args.target || "simulator",
345 enableDebug: args.enableDebug,
346 };
347
348 if (args.platform === PlatformType.Exponent) {
349 mobilePlatformOptions.expoHostType = args.expoHostType || "tunnel";
350 }
351
352 CommandExecutor.ReactNativeCommand = SettingsHelper.getReactNativeGlobalCommandName(workspaceFolder.uri);
353
354 if (!args.runArguments) {
355 let runArgs = SettingsHelper.getRunArgs(args.platform, args.target || "simulator", workspaceFolder.uri);
356 mobilePlatformOptions.runArguments = runArgs;
357 } else {
358 mobilePlatformOptions.runArguments = args.runArguments;
359 }
360
361 return mobilePlatformOptions;
362 }
363
364 private getProjectRoot(args: any): string {
365 return SettingsHelper.getReactNativeProjectRoot(args.cwd || args.program);
366 }
367
368 /**
369 * Parses log cat arguments to a string
370 */
371 private parseLogCatArguments(userProvidedLogCatArguments: any): string {
372 return Array.isArray(userProvidedLogCatArguments)
373 ? userProvidedLogCatArguments.join(" ") // If it's an array, we join the arguments
374 : userProvidedLogCatArguments; // If not, we leave it as-is
375 }
376}
377