brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.4 KiB · d35460a Raw
251 lines · typescript
1import * as vscode from "vscode";2import * as child_process from "child_process";3import * as util from "util";4import { LLDBDapServer } from "./lldb-dap-server";5import { createDebugAdapterExecutable } from "./debug-adapter-factory";6import { ConfigureButton, showErrorMessage } from "./ui/show-error-message";7import { ErrorWithNotification } from "./ui/error-with-notification";8import { LogFilePathProvider } from "./logging";9 10const exec = util.promisify(child_process.execFile);11 12/**13 * Determines whether or not the given lldb-dap executable supports executing14 * in server mode.15 *16 * @param exe the path to the lldb-dap executable17 * @returns a boolean indicating whether or not lldb-dap supports server mode18 */19async function isServerModeSupported(exe: string): Promise<boolean> {20  const { stdout } = await exec(exe, ["--help"]);21  return /--connection/.test(stdout);22}23 24interface BoolConfig {25  type: "boolean";26  default: boolean;27}28interface StringConfig {29  type: "string";30  default: string;31}32interface NumberConfig {33  type: "number";34  default: number;35}36interface StringArrayConfig {37  type: "stringArray";38  default: string[];39}40type DefaultConfig =41  | BoolConfig42  | NumberConfig43  | StringConfig44  | StringArrayConfig;45 46const configurations: Record<string, DefaultConfig> = {47  // Keys for debugger configurations.48  commandEscapePrefix: { type: "string", default: "`" },49  customFrameFormat: { type: "string", default: "" },50  customThreadFormat: { type: "string", default: "" },51  detachOnError: { type: "boolean", default: false },52  disableASLR: { type: "boolean", default: true },53  disableSTDIO: { type: "boolean", default: false },54  displayExtendedBacktrace: { type: "boolean", default: false },55  enableAutoVariableSummaries: { type: "boolean", default: false },56  enableSyntheticChildDebugging: { type: "boolean", default: false },57  timeout: { type: "number", default: 30 },58 59  // Keys for platform / target configuration.60  platformName: { type: "string", default: "" },61  targetTriple: { type: "string", default: "" },62 63  // Keys for debugger command hooks.64  initCommands: { type: "stringArray", default: [] },65  preRunCommands: { type: "stringArray", default: [] },66  postRunCommands: { type: "stringArray", default: [] },67  stopCommands: { type: "stringArray", default: [] },68  exitCommands: { type: "stringArray", default: [] },69  terminateCommands: { type: "stringArray", default: [] },70};71 72export function getDefaultConfigKey(key: string): string | number | boolean | string[] | undefined {73  return configurations[key]?.default;74}75 76export class LLDBDapConfigurationProvider77  implements vscode.DebugConfigurationProvider78{79  constructor(80    private readonly server: LLDBDapServer,81    private readonly logger: vscode.LogOutputChannel,82    private readonly logFilePath: LogFilePathProvider,83  ) {84    vscode.commands.registerCommand(85      "lldb-dap.resolveDebugConfiguration",86      (87        folder: vscode.WorkspaceFolder | undefined,88        debugConfiguration: vscode.DebugConfiguration,89        token?: vscode.CancellationToken,90      ) => this.resolveDebugConfiguration(folder, debugConfiguration, token),91    );92    vscode.commands.registerCommand(93      "lldb-dap.resolveDebugConfigurationWithSubstitutedVariables",94      (95        folder: vscode.WorkspaceFolder | undefined,96        debugConfiguration: vscode.DebugConfiguration,97        token?: vscode.CancellationToken,98      ) =>99        this.resolveDebugConfigurationWithSubstitutedVariables(100          folder,101          debugConfiguration,102          token,103        ),104    );105  }106 107  async resolveDebugConfiguration(108    folder: vscode.WorkspaceFolder | undefined,109    debugConfiguration: vscode.DebugConfiguration,110    token?: vscode.CancellationToken,111  ): Promise<vscode.DebugConfiguration> {112    this.logger.info(113      `Resolving debug configuration for "${debugConfiguration.name}"`,114    );115    this.logger.debug(116      "Initial debug configuration:\n" +117        JSON.stringify(debugConfiguration, undefined, 2),118    );119    let config = vscode.workspace.getConfiguration("lldb-dap");120    for (const [key, cfg] of Object.entries(configurations)) {121      if (Reflect.has(debugConfiguration, key)) {122        continue;123      }124      const value = config.get(key);125      if (value === undefined || value === cfg.default) {126        continue;127      }128      switch (cfg.type) {129        case "string":130          if (typeof value !== "string") {131            throw new Error(`Expected ${key} to be a string, got ${value}`);132          }133          break;134        case "number":135          if (typeof value !== "number") {136            throw new Error(`Expected ${key} to be a number, got ${value}`);137          }138          break;139        case "boolean":140          if (typeof value !== "boolean") {141            throw new Error(`Expected ${key} to be a boolean, got ${value}`);142          }143          break;144        case "stringArray":145          if (typeof value !== "object" && Array.isArray(value)) {146            throw new Error(147              `Expected ${key} to be a array of strings, got ${value}`,148            );149          }150          if ((value as string[]).length === 0) {151            continue;152          }153          break;154      }155 156      debugConfiguration[key] = value;157    }158 159    return debugConfiguration;160  }161 162  async resolveDebugConfigurationWithSubstitutedVariables(163    folder: vscode.WorkspaceFolder | undefined,164    debugConfiguration: vscode.DebugConfiguration,165    _token?: vscode.CancellationToken,166  ): Promise<vscode.DebugConfiguration | null | undefined> {167    try {168      if (169        "debugAdapterHostname" in debugConfiguration &&170        !("debugAdapterPort" in debugConfiguration)171      ) {172        throw new ErrorWithNotification(173          "A debugAdapterPort must be provided when debugAdapterHostname is set. Please update your launch configuration.",174          new ConfigureButton(),175        );176      }177 178      // Check if we're going to launch a debug session or use an existing process179      if ("debugAdapterPort" in debugConfiguration) {180        if (181          "debugAdapterExecutable" in debugConfiguration ||182          "debugAdapterArgs" in debugConfiguration183        ) {184          throw new ErrorWithNotification(185            "The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",186            new ConfigureButton(),187          );188        }189      } else {190        // Always try to create the debug adapter executable as this will show the user errors191        // if there are any.192        const executable = await createDebugAdapterExecutable(193          this.logger,194          this.logFilePath,195          folder,196          debugConfiguration,197        );198        if (!executable) {199          return undefined;200        }201 202        // Server mode needs to be handled here since DebugAdapterDescriptorFactory203        // will show an unhelpful error if it returns undefined. We'd rather show a204        // nicer error message here and allow stopping the debug session gracefully.205        const config = vscode.workspace.getConfiguration("lldb-dap", folder);206        if (207          config.get<boolean>("serverMode", false) &&208          (await isServerModeSupported(executable.command))209        ) {210          const connectionTimeoutSeconds = config.get<number | undefined>(211            "connectionTimeout",212            undefined,213          );214          const serverInfo = await this.server.start(215            executable.command,216            executable.args,217            executable.options,218            connectionTimeoutSeconds,219          );220          if (!serverInfo) {221            return undefined;222          }223          // Use a debug adapter host and port combination rather than an executable224          // and list of arguments.225          delete debugConfiguration.debugAdapterExecutable;226          delete debugConfiguration.debugAdapterArgs;227          debugConfiguration.debugAdapterHostname = serverInfo.host;228          debugConfiguration.debugAdapterPort = serverInfo.port;229        }230      }231 232      this.logger.info(233        "Resolved debug configuration:\n" +234          JSON.stringify(debugConfiguration, undefined, 2),235      );236 237      return debugConfiguration;238    } catch (error) {239      this.logger.error(error as Error);240      // Show a better error message to the user if possible241      if (!(error instanceof ErrorWithNotification)) {242        throw error;243      }244      return await error.showNotification({245        modal: true,246        showConfigureButton: true,247      });248    }249  }250}251