microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fix-ts-error1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/android/androidPlatform.ts

389lines · 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 semver from "semver";
5import * as nls from "vscode-nls";
6import { MobilePlatformDeps, TargetType } from "../generalPlatform";
7import { IAndroidRunOptions, PlatformType } from "../launchArgs";
8import { Package } from "../../common/node/package";
9import { OutputVerifier, PatternToFailure } from "../../common/outputVerifier";
10import { TelemetryHelper } from "../../common/telemetryHelper";
11import { CommandExecutor } from "../../common/commandExecutor";
12import { InternalErrorCode } from "../../common/error/internalErrorCode";
13import { ErrorHelper } from "../../common/error/errorHelper";
14import { notNullOrUndefined } from "../../common/utils";
15import { PromiseUtil } from "../../common/node/promise";
16import { GeneralMobilePlatform } from "../generalMobilePlatform";
17import { ProjectVersionHelper } from "../../common/projectVersionHelper";
18import { LogCatMonitorManager } from "./logCatMonitorManager";
19import { AndroidTarget, AndroidTargetManager } from "./androidTargetManager";
20import { AdbHelper, AndroidAPILevel } from "./adb";
21import { LogCatMonitor } from "./logCatMonitor";
22import { PackageNameResolver } from "./packageNameResolver";
23
24nls.config({
25 messageFormat: nls.MessageFormat.bundle,
26 bundleFormat: nls.BundleFormat.standalone,
27})();
28const localize = nls.loadMessageBundle();
29
30/**
31 * Android specific platform implementation for debugging RN applications.
32 */
33export class AndroidPlatform extends GeneralMobilePlatform {
34 // We should add the common Android build/run errors we find to this list
35 private static RUN_ANDROID_FAILURE_PATTERNS: PatternToFailure[] = [
36 {
37 pattern: "Failed to install on any devices",
38 errorCode: InternalErrorCode.AndroidCouldNotInstallTheAppOnAnyAvailibleDevice,
39 },
40 {
41 pattern: "com.android.ddmlib.ShellCommandUnresponsiveException",
42 errorCode: InternalErrorCode.AndroidShellCommandTimedOut,
43 },
44 {
45 pattern: "Android project not found",
46 errorCode: InternalErrorCode.AndroidProjectNotFound,
47 },
48 {
49 pattern: "error: more than one device/emulator",
50 errorCode: InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator,
51 },
52 {
53 pattern: /^Error: Activity class {.*} does not exist\.$/m,
54 errorCode: InternalErrorCode.AndroidFailedToLaunchTheSpecifiedActivity,
55 },
56 ];
57
58 private static RUN_ANDROID_SUCCESS_PATTERNS: string[] = [
59 "BUILD SUCCESSFUL",
60 "Starting the app",
61 "Starting: Intent",
62 ];
63
64 private packageName: string;
65 private adbHelper: AdbHelper;
66 private logCatMonitor: LogCatMonitor | null = null;
67 private needsToLaunchApps: boolean = false;
68
69 protected targetManager: AndroidTargetManager;
70 protected target?: AndroidTarget;
71
72 // We set remoteExtension = null so that if there is an instance of androidPlatform that wants to have it's custom remoteExtension it can. This is specifically useful for tests.
73 constructor(protected runOptions: IAndroidRunOptions, platformDeps: MobilePlatformDeps = {}) {
74 super(runOptions, platformDeps);
75 this.adbHelper = new AdbHelper(
76 this.runOptions.projectRoot,
77 runOptions.nodeModulesRoot,
78 this.logger,
79 );
80 this.targetManager = new AndroidTargetManager(this.adbHelper);
81 }
82
83 public showDevMenu(deviceId?: string): Promise<void> {
84 return this.adbHelper.showDevMenu(deviceId);
85 }
86
87 public reloadApp(deviceId?: string): Promise<void> {
88 return this.adbHelper.reloadApp(deviceId);
89 }
90
91 public async getTarget(): Promise<AndroidTarget> {
92 if (!this.target) {
93 const onlineTargets = await this.adbHelper.getOnlineTargets();
94 const target = await this.getTargetFromRunArgs();
95 if (target) {
96 this.target = target;
97 } else {
98 const onlineTargetsBySpecifiedType = onlineTargets.filter(target => {
99 switch (this.runOptions.target) {
100 case TargetType.Simulator:
101 return target.isVirtualTarget;
102 case TargetType.Device:
103 return !target.isVirtualTarget;
104 case undefined:
105 case "":
106 return true;
107 default:
108 return target.id === this.runOptions.target;
109 }
110 });
111 if (onlineTargetsBySpecifiedType.length) {
112 this.target = AndroidTarget.fromInterface(onlineTargetsBySpecifiedType[0]);
113 } else if (onlineTargets.length) {
114 this.logger.warning(
115 localize(
116 "ThereIsNoOnlineTargetWithSpecifiedTargetType",
117 "There is no any online target with specified target type '{0}'. Continue with any online target.",
118 this.runOptions.target,
119 ),
120 );
121 this.target = AndroidTarget.fromInterface(onlineTargets[0]);
122 } else {
123 throw ErrorHelper.getInternalError(
124 InternalErrorCode.AndroidThereIsNoAnyOnlineDebuggableTarget,
125 );
126 }
127 }
128 }
129 return this.target;
130 }
131
132 public async runApp(shouldLaunchInAllDevices: boolean = false): Promise<void> {
133 let extProps: any = {
134 platform: {
135 value: PlatformType.Android,
136 isPii: false,
137 },
138 };
139
140 if (this.runOptions.isDirect) {
141 extProps.isDirect = {
142 value: true,
143 isPii: false,
144 };
145 this.projectObserver?.updateRNAndroidHermesProjectState(true);
146 }
147
148 extProps = TelemetryHelper.addPlatformPropertiesToTelemetryProperties(
149 this.runOptions,
150 this.runOptions.reactNativeVersions,
151 extProps,
152 );
153
154 await TelemetryHelper.generate("AndroidPlatform.runApp", extProps, async () => {
155 const env = GeneralMobilePlatform.getEnvArgument(
156 process.env,
157 this.runOptions.env,
158 this.runOptions.envFile,
159 );
160
161 if (
162 !semver.valid(
163 this.runOptions.reactNativeVersions.reactNativeVersion,
164 ) /* Custom RN implementations should support this flag*/ ||
165 semver.gte(
166 this.runOptions.reactNativeVersions.reactNativeVersion,
167 AndroidPlatform.NO_PACKAGER_VERSION,
168 ) ||
169 ProjectVersionHelper.isCanaryVersion(
170 this.runOptions.reactNativeVersions.reactNativeVersion,
171 )
172 ) {
173 this.runArguments.push("--no-packager");
174 }
175
176 const mainActivity = GeneralMobilePlatform.getOptFromRunArgs(
177 this.runArguments,
178 "--main-activity",
179 );
180
181 if (mainActivity) {
182 this.adbHelper.setLaunchActivity(mainActivity);
183 } else if (notNullOrUndefined(this.runOptions.debugLaunchActivity)) {
184 this.runArguments.push("--main-activity", this.runOptions.debugLaunchActivity);
185 this.adbHelper.setLaunchActivity(this.runOptions.debugLaunchActivity);
186 }
187
188 const runAndroidSpawn = new CommandExecutor(
189 this.runOptions.nodeModulesRoot,
190 this.projectPath,
191 this.logger,
192 ).spawnReactCommand("run-android", this.runArguments, { env });
193 const output = new OutputVerifier(
194 () => Promise.resolve(AndroidPlatform.RUN_ANDROID_SUCCESS_PATTERNS),
195 () => Promise.resolve(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS),
196 PlatformType.Android,
197 ).process(runAndroidSpawn);
198
199 let devicesIdsForLaunch: string[] = [];
200 const onlineTargetsIds = (await this.adbHelper.getOnlineTargets()).map(
201 target => target.id,
202 );
203 let targetId: string | undefined;
204 try {
205 try {
206 await output;
207 } finally {
208 targetId = await this.getTargetIdForRunApp(onlineTargetsIds);
209 this.packageName = await this.getPackageName();
210 devicesIdsForLaunch = [targetId];
211 }
212 } catch (error) {
213 if (!targetId) {
214 targetId = await this.getTargetIdForRunApp(onlineTargetsIds);
215 }
216 if (
217 error.message ===
218 ErrorHelper.getInternalError(
219 InternalErrorCode.AndroidMoreThanOneDeviceOrEmulator,
220 ).message &&
221 onlineTargetsIds.length >= 1 &&
222 targetId
223 ) {
224 /* If it failed due to multiple devices, we'll apply this workaround to make it work anyways */
225 this.needsToLaunchApps = true;
226 devicesIdsForLaunch = shouldLaunchInAllDevices ? onlineTargetsIds : [targetId];
227 } else {
228 throw error;
229 }
230 }
231
232 await PromiseUtil.forEach(devicesIdsForLaunch, deviceId =>
233 this.launchAppWithADBReverseAndLogCat(deviceId),
234 );
235 });
236 }
237
238 public async enableJSDebuggingMode(): Promise<void> {
239 return this.adbHelper.switchDebugMode(
240 this.runOptions.projectRoot,
241 this.packageName,
242 true,
243 (await this.getTarget()).id,
244 this.getAppIdSuffixFromRunArgumentsIfExists(),
245 );
246 }
247
248 public async disableJSDebuggingMode(): Promise<void> {
249 return this.adbHelper.switchDebugMode(
250 this.runOptions.projectRoot,
251 this.packageName,
252 false,
253 (await this.getTarget()).id,
254 this.getAppIdSuffixFromRunArgumentsIfExists(),
255 );
256 }
257
258 public prewarmBundleCache(): Promise<void> {
259 return this.packager.prewarmBundleCache(PlatformType.Android);
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 } else {
268 if (this.runOptions.variant) {
269 runArguments.push("--variant", this.runOptions.variant);
270 }
271 if (this.runOptions.target) {
272 if (
273 this.runOptions.target !== TargetType.Device &&
274 this.runOptions.target !== TargetType.Simulator
275 ) {
276 runArguments.push("--deviceId", this.runOptions.target);
277 }
278 }
279 }
280
281 return runArguments;
282 }
283
284 public dispose(): void {
285 if (this.logCatMonitor) {
286 LogCatMonitorManager.delMonitor(this.logCatMonitor.deviceId);
287 this.logCatMonitor = null;
288 }
289 }
290
291 public async getTargetFromRunArgs(): Promise<AndroidTarget | undefined> {
292 if (this.runOptions.runArguments && this.runOptions.runArguments.length) {
293 const deviceId = GeneralMobilePlatform.getOptFromRunArgs(
294 this.runOptions.runArguments,
295 "--deviceId",
296 );
297 if (deviceId) {
298 return new AndroidTarget(true, this.adbHelper.isVirtualTarget(deviceId), deviceId);
299 }
300 }
301 return undefined;
302 }
303
304 private async getTargetIdForRunApp(onlineTargetsIds: string[]): Promise<string> {
305 let deviceId: string | undefined;
306 if (this.runOptions.runArguments && this.runOptions.runArguments.length) {
307 deviceId = GeneralMobilePlatform.getOptFromRunArgs(
308 this.runOptions.runArguments,
309 "--deviceId",
310 );
311 }
312 return deviceId
313 ? deviceId
314 : this.runOptions.target &&
315 this.runOptions.target !== TargetType.Simulator &&
316 this.runOptions.target !== TargetType.Device &&
317 onlineTargetsIds.find(id => id === this.runOptions.target)
318 ? this.runOptions.target
319 : (await this.getTarget()).id;
320 }
321
322 private getAppIdSuffixFromRunArgumentsIfExists(): string | undefined {
323 const appIdSuffixIndex = this.runArguments.indexOf("--appIdSuffix");
324 if (appIdSuffixIndex > -1) {
325 return this.runArguments[appIdSuffixIndex + 1];
326 }
327 return undefined;
328 }
329
330 private async launchAppWithADBReverseAndLogCat(deviceId: string): Promise<void> {
331 await this.configureADBReverseWhenApplicable(deviceId);
332 if (this.needsToLaunchApps) {
333 await this.adbHelper.launchApp(this.runOptions.projectRoot, this.packageName, deviceId);
334 }
335 return this.startMonitoringLogCat(deviceId, this.runOptions.logCatArguments);
336 }
337
338 private async configureADBReverseWhenApplicable(deviceId: string): Promise<void> {
339 // For other emulators and devices we try to enable adb reverse
340 const apiVersion = await this.adbHelper.apiVersion(deviceId);
341 if (apiVersion >= AndroidAPILevel.LOLLIPOP) {
342 // If we support adb reverse
343 try {
344 void this.adbHelper.reverseAdb(deviceId, Number(this.runOptions.packagerPort));
345 } catch (error) {
346 // "adb reverse" command could work incorrectly with remote devices, then skip the error and try to go on
347 if (
348 this.adbHelper.isRemoteTarget(deviceId) &&
349 error.message.includes(AndroidPlatform.RUN_ANDROID_FAILURE_PATTERNS[3].pattern)
350 ) {
351 this.logger.warning(error.message);
352 } else {
353 throw error;
354 }
355 }
356 } else {
357 this.logger.warning(
358 localize(
359 "DeviceSupportsOnlyAPILevel",
360 "Device {0} supports only API Level {1}. \n Level {2} is needed to support port forwarding via adb reverse. \n For debugging to work you'll need <Shake or press menu button> for the dev menu, \n go into <Dev Settings> and configure <Debug Server host & port for Device> to be \n an IP address of your computer that the Device can reach. More info at: \n https://facebook.github.io/react-native/docs/debugging.html#debugging-react-native-apps",
361 deviceId,
362 apiVersion,
363 AndroidAPILevel.LOLLIPOP,
364 ),
365 );
366 }
367 }
368
369 private async getPackageName(): Promise<string> {
370 const appName = await new Package(this.runOptions.projectRoot).name();
371 return new PackageNameResolver(appName).resolvePackageName(this.runOptions.projectRoot);
372 }
373
374 private startMonitoringLogCat(deviceId: string, logCatArguments: string[]): void {
375 LogCatMonitorManager.delMonitor(deviceId); // Stop previous logcat monitor if it's running
376
377 // this.logCatMonitor can be mutated, so we store it locally too
378 this.logCatMonitor = new LogCatMonitor(deviceId, this.adbHelper, logCatArguments);
379 LogCatMonitorManager.addMonitor(this.logCatMonitor);
380 this.logCatMonitor
381 .start() // The LogCat will continue running forever, so we don't wait for it
382 .catch(error => {
383 this.logger.warning(error);
384 this.logger.warning(
385 localize("ErrorWhileMonitoringLogCat", "Error while monitoring LogCat"),
386 );
387 }); // The LogCatMonitor failing won't stop the debugging experience
388 }
389}