microsoft/vscode-react-native
Publicmirrored from https://github.com/microsoft/vscode-react-nativeAvailable
src/extension/macos/macOSPlatform.ts
225lines · modeblame
341dba36Yuri Skorokhodov5 years ago | 1 | // Copyright (c) Microsoft Corporation. All rights reserved. |
| 2 | // Licensed under the MIT license. See LICENSE file in the project root for details. | |
| 3 | | |
1c2424f4RedMickey5 years ago | 4 | import * as path from "path"; |
09f6024fHeniker4 years ago | 5 | import * as semver from "semver"; |
4cd25962JiglioNero4 years ago | 6 | import { GeneralPlatform, MobilePlatformDeps, TargetType } from "../generalPlatform"; |
341dba36Yuri Skorokhodov5 years ago | 7 | import { ImacOSRunOptions, PlatformType } from "../launchArgs"; |
| 8 | import { OutputVerifier, PatternToFailure } from "../../common/outputVerifier"; | |
| 9 | import { TelemetryHelper } from "../../common/telemetryHelper"; | |
| 10 | import { CommandExecutor } from "../../common/commandExecutor"; | |
| 11 | import { InternalErrorCode } from "../../common/error/internalErrorCode"; | |
0d77292aJiglioNero4 years ago | 12 | import { PlistBuddy } from "../ios/plistBuddy"; |
1c2424f4RedMickey5 years ago | 13 | import { ChildProcess } from "../../common/node/childProcess"; |
47927908RedMickey4 years ago | 14 | import { ProjectVersionHelper } from "../../common/projectVersionHelper"; |
09f6024fHeniker4 years ago | 15 | import { MacOSDebugModeManager } from "./macOSDebugModeManager"; |
341dba36Yuri Skorokhodov5 years ago | 16 | |
| 17 | /** | |
| 18 | * macOS specific platform implementation for debugging RN applications. | |
| 19 | */ | |
4cd25962JiglioNero4 years ago | 20 | export class MacOSPlatform extends GeneralPlatform { |
34472878RedMickey5 years ago | 21 | private static SUCCESS_PATTERNS = ["Launching app"]; |
341dba36Yuri Skorokhodov5 years ago | 22 | private static FAILURE_PATTERNS: PatternToFailure[] = [ |
| 23 | { | |
| 24 | pattern: "Unrecognized command 'run-macos'", | |
| 25 | errorCode: InternalErrorCode.ReactNativemacOSIsNotInstalled, | |
| 26 | }, | |
| 27 | ]; | |
| 28 | | |
1c2424f4RedMickey5 years ago | 29 | public static DEFAULT_MACOS_PROJECT_RELATIVE_PATH = "macos"; |
| 30 | | |
| 31 | private macosProjectRoot: string; | |
| 32 | private plistBuddy: PlistBuddy; | |
| 33 | private macOSDebugModeManager: MacOSDebugModeManager; | |
| 34 | | |
341dba36Yuri Skorokhodov5 years ago | 35 | constructor(protected runOptions: ImacOSRunOptions, platformDeps: MobilePlatformDeps = {}) { |
| 36 | super(runOptions, platformDeps); | |
1c2424f4RedMickey5 years ago | 37 | |
| 38 | const macosProjectFolderPath = MacOSPlatform.getOptFromRunArgs( | |
| 39 | this.runArguments, | |
| 40 | "--project-path", | |
| 41 | false, | |
| 42 | ); | |
| 43 | this.macosProjectRoot = path.join( | |
| 44 | this.projectPath, | |
| 45 | macosProjectFolderPath || MacOSPlatform.DEFAULT_MACOS_PROJECT_RELATIVE_PATH, | |
| 46 | ); | |
| 47 | this.plistBuddy = new PlistBuddy(); | |
| 48 | | |
| 49 | const schemeFromArgs = MacOSPlatform.getOptFromRunArgs( | |
| 50 | this.runArguments, | |
| 51 | "--scheme", | |
| 52 | false, | |
| 53 | ); | |
| 54 | this.macOSDebugModeManager = new MacOSDebugModeManager( | |
| 55 | this.macosProjectRoot, | |
| 56 | this.projectPath, | |
| 57 | schemeFromArgs ? schemeFromArgs : this.runOptions.scheme, | |
| 58 | ); | |
341dba36Yuri Skorokhodov5 years ago | 59 | } |
| 60 | | |
0d77292aJiglioNero4 years ago | 61 | public async runApp(): Promise<void> { |
008d88e5RedMickey4 years ago | 62 | let extProps: any = { |
341dba36Yuri Skorokhodov5 years ago | 63 | platform: { |
| 64 | value: PlatformType.macOS, | |
| 65 | isPii: false, | |
| 66 | }, | |
| 67 | }; | |
| 68 | | |
e7a2c40dRedMickey4 years ago | 69 | this.projectObserver?.updateRNMacosProjectState(true); |
008d88e5RedMickey4 years ago | 70 | if (this.runOptions.isDirect) { |
| 71 | extProps.isDirect = { | |
| 72 | value: true, | |
| 73 | isPii: false, | |
| 74 | }; | |
e7a2c40dRedMickey4 years ago | 75 | this.projectObserver?.updateRNMacosHermesProjectState(true); |
008d88e5RedMickey4 years ago | 76 | } |
| 77 | | |
34472878RedMickey5 years ago | 78 | extProps = TelemetryHelper.addPlatformPropertiesToTelemetryProperties( |
| 79 | this.runOptions, | |
| 80 | this.runOptions.reactNativeVersions, | |
| 81 | extProps, | |
| 82 | ); | |
341dba36Yuri Skorokhodov5 years ago | 83 | |
0d77292aJiglioNero4 years ago | 84 | await TelemetryHelper.generate("MacOSPlatform.runApp", extProps, async () => { |
4cd25962JiglioNero4 years ago | 85 | const env = GeneralPlatform.getEnvArgument( |
34472878RedMickey5 years ago | 86 | process.env, |
| 87 | this.runOptions.env, | |
| 88 | this.runOptions.envFile, | |
| 89 | ); | |
341dba36Yuri Skorokhodov5 years ago | 90 | |
34472878RedMickey5 years ago | 91 | if ( |
| 92 | !semver.valid( | |
| 93 | this.runOptions.reactNativeVersions.reactNativeVersion, | |
09f6024fHeniker4 years ago | 94 | ) /* Custom RN implementations should support this flag*/ || |
34472878RedMickey5 years ago | 95 | semver.gte( |
| 96 | this.runOptions.reactNativeVersions.reactNativeVersion, | |
| 97 | MacOSPlatform.NO_PACKAGER_VERSION, | |
47927908RedMickey4 years ago | 98 | ) || |
| 99 | ProjectVersionHelper.isCanaryVersion( | |
| 100 | this.runOptions.reactNativeVersions.reactNativeVersion, | |
34472878RedMickey5 years ago | 101 | ) |
| 102 | ) { | |
341dba36Yuri Skorokhodov5 years ago | 103 | this.runArguments.push("--no-packager"); |
| 104 | } | |
| 105 | | |
34472878RedMickey5 years ago | 106 | const runmacOSSpawn = new CommandExecutor( |
4dfb1c4cetatanova5 years ago | 107 | this.runOptions.nodeModulesRoot, |
34472878RedMickey5 years ago | 108 | this.projectPath, |
| 109 | this.logger, | |
| 110 | ).spawnReactCommand(`run-${this.platformName}`, this.runArguments, { env }); | |
0d77292aJiglioNero4 years ago | 111 | await new OutputVerifier( |
34472878RedMickey5 years ago | 112 | () => Promise.resolve(MacOSPlatform.SUCCESS_PATTERNS), |
| 113 | () => Promise.resolve(MacOSPlatform.FAILURE_PATTERNS), | |
| 114 | this.platformName, | |
| 115 | ).process(runmacOSSpawn); | |
341dba36Yuri Skorokhodov5 years ago | 116 | }); |
| 117 | } | |
| 118 | | |
0d77292aJiglioNero4 years ago | 119 | public async prewarmBundleCache(): Promise<void> { |
341dba36Yuri Skorokhodov5 years ago | 120 | return this.packager.prewarmBundleCache(PlatformType.macOS); |
| 121 | } | |
| 122 | | |
| 123 | public getRunArguments(): string[] { | |
09f6024fHeniker4 years ago | 124 | const runArguments: string[] = []; |
341dba36Yuri Skorokhodov5 years ago | 125 | |
| 126 | if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) { | |
| 127 | runArguments.push(...this.runOptions.runArguments); | |
| 128 | } else { | |
09f6024fHeniker4 years ago | 129 | const target = |
4cd25962JiglioNero4 years ago | 130 | this.runOptions.target === TargetType.Simulator ? "" : this.runOptions.target; |
341dba36Yuri Skorokhodov5 years ago | 131 | if (target) { |
| 132 | runArguments.push(`--${target}`); | |
| 133 | } | |
| 134 | } | |
| 135 | | |
| 136 | return runArguments; | |
| 137 | } | |
1c2424f4RedMickey5 years ago | 138 | |
0d77292aJiglioNero4 years ago | 139 | public async enableJSDebuggingMode(): Promise<void> { |
1c2424f4RedMickey5 years ago | 140 | // Configure the app for debugging |
| 141 | // Wait until the configuration file exists, and check to see if debugging is enabled | |
0d77292aJiglioNero4 years ago | 142 | const [debugModeEnabled, appName] = await Promise.all<boolean | string>([ |
1c2424f4RedMickey5 years ago | 143 | this.macOSDebugModeManager.getAppRemoteDebuggingSetting( |
| 144 | this.runOptions.configuration, | |
| 145 | this.runOptions.productName, | |
| 146 | ), | |
| 147 | this.getApplicationName(), | |
0d77292aJiglioNero4 years ago | 148 | ]); |
| 149 | if (debugModeEnabled) { | |
| 150 | return; | |
| 151 | } | |
1c2424f4RedMickey5 years ago | 152 | |
0d77292aJiglioNero4 years ago | 153 | // Debugging must still be enabled |
| 154 | // We enable debugging by writing to a plist file that backs a NSUserDefaults object, | |
| 155 | // but that file is written to by the app on occasion. To avoid races, we shut the app | |
| 156 | // down before writing to the file. | |
| 157 | await this.terminateMacOSapp(<string>appName); | |
| 158 | // Write to the settings file while the app is not running to avoid races | |
| 159 | await this.macOSDebugModeManager.setAppRemoteDebuggingSetting( | |
09f6024fHeniker4 years ago | 160 | /* enable=*/ true, |
0d77292aJiglioNero4 years ago | 161 | this.runOptions.configuration, |
| 162 | this.runOptions.productName, | |
| 163 | ); | |
| 164 | // Relaunch the app | |
| 165 | await this.runApp(); | |
1c2424f4RedMickey5 years ago | 166 | } |
| 167 | | |
| 168 | public disableJSDebuggingMode(): Promise<void> { | |
| 169 | return this.macOSDebugModeManager.setAppRemoteDebuggingSetting( | |
09f6024fHeniker4 years ago | 170 | /* enable=*/ false, |
1c2424f4RedMickey5 years ago | 171 | this.runOptions.configuration, |
| 172 | this.runOptions.productName, | |
| 173 | ); | |
| 174 | } | |
| 175 | | |
0d77292aJiglioNero4 years ago | 176 | private async getApplicationName(): Promise<string> { |
| 177 | const iOSBuildLocationData = await this.plistBuddy.getExecutableAndConfigurationFolder( | |
| 178 | this.macosProjectRoot, | |
| 179 | this.projectPath, | |
| 180 | PlatformType.macOS, | |
| 181 | false, | |
| 182 | this.runOptions.configuration, | |
| 183 | this.runOptions.productName, | |
| 184 | this.getSchemeFromDebuggingParameters(), | |
| 185 | ); | |
| 186 | return iOSBuildLocationData.executable; | |
1c2424f4RedMickey5 years ago | 187 | } |
| 188 | | |
| 189 | private getSchemeFromDebuggingParameters(): string | undefined { | |
| 190 | let scheme = this.runOptions.scheme; | |
| 191 | if (!scheme) { | |
| 192 | const schemeFromArgs = MacOSPlatform.getOptFromRunArgs( | |
| 193 | this.runArguments, | |
| 194 | "--scheme", | |
| 195 | false, | |
| 196 | ); | |
| 197 | if (schemeFromArgs) { | |
| 198 | scheme = schemeFromArgs; | |
| 199 | } | |
| 200 | } | |
| 201 | return scheme; | |
| 202 | } | |
| 203 | | |
0d77292aJiglioNero4 years ago | 204 | private async terminateMacOSapp(appName: string): Promise<void> { |
09f6024fHeniker4 years ago | 205 | const childProcess = new ChildProcess(); |
0d77292aJiglioNero4 years ago | 206 | // An example of the output from the command above: |
| 207 | // 40943 ?? 4:13.97 node /Users/user/Documents/rn_for_mac_proj/node_modules/.bin/react-native start --port 8081 | |
| 208 | // 40959 ?? 0:10.36 /Users/user/.nvm/versions/node/v10.19.0/bin/node /Users/user/Documents/rn_for_mac_proj/node_modules/metro/node_modules/jest-worker/build/workers/processChild.js | |
| 209 | // 41004 ?? 0:21.34 /Users/user/Library/Developer/Xcode/DerivedData/rn_for_mac_proj-ghuavabiztosiqfqkrityjoxqfmv/Build/Products/Debug/rn_for_mac_proj.app/Contents/MacOS/rn_for_mac_proj | |
| 210 | // 75514 ttys007 0:00.00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn rn_for_mac_proj | |
| 211 | const searchResults = await childProcess.execToString(`ps -ax | grep ${appName}`); | |
| 212 | if (searchResults) { | |
| 213 | const processIdRgx = /(^\d*)\s\?\?/g; | |
| 214 | // We are looking for a process whose path contains the "appName.app" part | |
| 215 | const processData = searchResults.split("\n").find(str => str.includes(appName)); | |
| 216 | | |
| 217 | if (processData) { | |
| 218 | const match = processIdRgx.exec(processData.trim()); | |
| 219 | if (match && match[1]) { | |
| 220 | await childProcess.execToString(`kill ${match[1]}`); | |
| 221 | } | |
| 222 | } | |
| 223 | } | |
1c2424f4RedMickey5 years ago | 224 | } |
341dba36Yuri Skorokhodov5 years ago | 225 | } |