microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/macos/macOSPlatform.ts

233lines · modepreview

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import * as semver from "semver";
import * as path from "path";
import { GeneralMobilePlatform, MobilePlatformDeps } from "../generalMobilePlatform";
import { MacOSDebugModeManager } from "./macOSDebugModeManager";
import { ImacOSRunOptions, PlatformType } from "../launchArgs";
import { OutputVerifier, PatternToFailure } from "../../common/outputVerifier";
import { TelemetryHelper } from "../../common/telemetryHelper";
import { CommandExecutor } from "../../common/commandExecutor";
import { InternalErrorCode } from "../../common/error/internalErrorCode";
import { PlistBuddy, IOSBuildLocationData } from "../ios/plistBuddy";
import { ChildProcess } from "../../common/node/childProcess";

/**
 * macOS specific platform implementation for debugging RN applications.
 */
export class MacOSPlatform extends GeneralMobilePlatform {
    private static SUCCESS_PATTERNS = ["Launching app"];
    private static FAILURE_PATTERNS: PatternToFailure[] = [
        {
            pattern: "Unrecognized command 'run-macos'",
            errorCode: InternalErrorCode.ReactNativemacOSIsNotInstalled,
        },
    ];

    public static DEFAULT_MACOS_PROJECT_RELATIVE_PATH = "macos";

    private macosProjectRoot: string;
    private plistBuddy: PlistBuddy;
    private macOSDebugModeManager: MacOSDebugModeManager;

    constructor(protected runOptions: ImacOSRunOptions, platformDeps: MobilePlatformDeps = {}) {
        super(runOptions, platformDeps);

        const macosProjectFolderPath = MacOSPlatform.getOptFromRunArgs(
            this.runArguments,
            "--project-path",
            false,
        );
        this.macosProjectRoot = path.join(
            this.projectPath,
            macosProjectFolderPath || MacOSPlatform.DEFAULT_MACOS_PROJECT_RELATIVE_PATH,
        );
        this.plistBuddy = new PlistBuddy();

        const schemeFromArgs = MacOSPlatform.getOptFromRunArgs(
            this.runArguments,
            "--scheme",
            false,
        );
        this.macOSDebugModeManager = new MacOSDebugModeManager(
            this.macosProjectRoot,
            this.projectPath,
            schemeFromArgs ? schemeFromArgs : this.runOptions.scheme,
        );
    }

    public runApp(): Promise<void> {
        let extProps = {
            platform: {
                value: PlatformType.macOS,
                isPii: false,
            },
        };

        extProps = TelemetryHelper.addPlatformPropertiesToTelemetryProperties(
            this.runOptions,
            this.runOptions.reactNativeVersions,
            extProps,
        );

        return TelemetryHelper.generate("MacOSPlatform.runApp", extProps, () => {
            const env = GeneralMobilePlatform.getEnvArgument(
                process.env,
                this.runOptions.env,
                this.runOptions.envFile,
            );

            if (
                !semver.valid(
                    this.runOptions.reactNativeVersions.reactNativeVersion,
                ) /*Custom RN implementations should support this flag*/ ||
                semver.gte(
                    this.runOptions.reactNativeVersions.reactNativeVersion,
                    MacOSPlatform.NO_PACKAGER_VERSION,
                )
            ) {
                this.runArguments.push("--no-packager");
            }

            const runmacOSSpawn = new CommandExecutor(
                this.runOptions.nodeModulesRoot,
                this.projectPath,
                this.logger,
            ).spawnReactCommand(`run-${this.platformName}`, this.runArguments, { env });
            return new OutputVerifier(
                () => Promise.resolve(MacOSPlatform.SUCCESS_PATTERNS),
                () => Promise.resolve(MacOSPlatform.FAILURE_PATTERNS),
                this.platformName,
            ).process(runmacOSSpawn);
        });
    }

    public prewarmBundleCache(): Promise<void> {
        return this.packager.prewarmBundleCache(PlatformType.macOS);
    }

    public getRunArguments(): string[] {
        let runArguments: string[] = [];

        if (this.runOptions.runArguments && this.runOptions.runArguments.length > 0) {
            runArguments.push(...this.runOptions.runArguments);
        } else {
            let target =
                this.runOptions.target === MacOSPlatform.simulatorString
                    ? ""
                    : this.runOptions.target;
            if (target) {
                runArguments.push(`--${target}`);
            }
        }

        return runArguments;
    }

    public enableJSDebuggingMode(): Promise<void> {
        // Configure the app for debugging
        // Wait until the configuration file exists, and check to see if debugging is enabled
        return Promise.all<boolean | string>([
            this.macOSDebugModeManager.getAppRemoteDebuggingSetting(
                this.runOptions.configuration,
                this.runOptions.productName,
            ),
            this.getApplicationName(),
        ]).then(([debugModeEnabled, appName]) => {
            if (debugModeEnabled) {
                return Promise.resolve();
            }

            // Debugging must still be enabled
            // We enable debugging by writing to a plist file that backs a NSUserDefaults object,
            // but that file is written to by the app on occasion. To avoid races, we shut the app
            // down before writing to the file.
            return (
                this.terminateMacOSapp(<string>appName)
                    // Write to the settings file while the app is not running to avoid races
                    .then(() =>
                        this.macOSDebugModeManager.setAppRemoteDebuggingSetting(
                            /*enable=*/ true,
                            this.runOptions.configuration,
                            this.runOptions.productName,
                        ),
                    )
                    .then(() => {
                        // Relaunch the app
                        return this.runApp();
                    })
            );
        });
    }

    public disableJSDebuggingMode(): Promise<void> {
        return this.macOSDebugModeManager.setAppRemoteDebuggingSetting(
            /*enable=*/ false,
            this.runOptions.configuration,
            this.runOptions.productName,
        );
    }

    private getApplicationName(): Promise<string> {
        return this.plistBuddy
            .getExecutableAndConfigurationFolder(
                this.macosProjectRoot,
                this.projectPath,
                PlatformType.macOS,
                false,
                this.runOptions.configuration,
                this.runOptions.productName,
                this.getSchemeFromDebuggingParameters(),
            )
            .then((iOSBuildLocationData: IOSBuildLocationData) => {
                return iOSBuildLocationData.executable;
            });
    }

    private getSchemeFromDebuggingParameters(): string | undefined {
        let scheme = this.runOptions.scheme;
        if (!scheme) {
            const schemeFromArgs = MacOSPlatform.getOptFromRunArgs(
                this.runArguments,
                "--scheme",
                false,
            );
            if (schemeFromArgs) {
                scheme = schemeFromArgs;
            }
        }
        return scheme;
    }

    private terminateMacOSapp(appName: string): Promise<void> {
        let childProcess = new ChildProcess();
        return (
            childProcess
                .execToString(`ps -ax | grep ${appName}`)
                // An example of the output from the command above:
                // 40943 ??         4:13.97 node /Users/user/Documents/rn_for_mac_proj/node_modules/.bin/react-native start --port 8081
                // 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
                // 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
                // 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
                .then(searchResults => {
                    if (searchResults) {
                        const processIdRgx = /(^\d*)\s\?\?/g;
                        //  We are looking for a process whose path contains the "appName.app" part
                        const processData = searchResults
                            .split("\n")
                            .find(str => str.includes(appName));

                        if (processData) {
                            const match = processIdRgx.exec(processData.trim());
                            if (match && match[1]) {
                                // eslint-disable-next-line @typescript-eslint/no-empty-function
                                return childProcess.execToString(`kill ${match[1]}`).then(() => {});
                            }
                        }
                    }
                    return void 0;
                })
        );
    }
}