brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.9 KiB · 433d48f Raw
351 lines · typescript
1import * as path from "path";2import * as util from "util";3import * as vscode from "vscode";4import * as child_process from "child_process";5import * as fs from "node:fs/promises";6import { ConfigureButton, OpenSettingsButton } from "./ui/show-error-message";7import { ErrorWithNotification } from "./ui/error-with-notification";8import { LogFilePathProvider, LogType } from "./logging";9import { expandUser } from "./utils";10 11const exec = util.promisify(child_process.execFile);12 13async function isExecutable(path: string): Promise<Boolean> {14  try {15    await fs.access(path, fs.constants.X_OK);16  } catch {17    return false;18  }19  return true;20}21 22async function findWithXcrun(executable: string): Promise<string | undefined> {23  if (process.platform === "darwin") {24    try {25      let { stdout, stderr } = await exec("/usr/bin/xcrun", [26        "-find",27        executable,28      ]);29      if (stdout) {30        return stdout.toString().trimEnd();31      }32    } catch (error) {}33  }34  return undefined;35}36 37async function findInPath(executable: string): Promise<string | undefined> {38  const env_path =39    process.platform === "win32" ? process.env["Path"] : process.env["PATH"];40  if (!env_path) {41    return undefined;42  }43 44  const paths = env_path.split(path.delimiter);45  for (const p of paths) {46    const exe_path = path.join(p, executable);47    if (await isExecutable(exe_path)) {48      return exe_path;49    }50  }51  return undefined;52}53 54async function findDAPExecutable(): Promise<string | undefined> {55  const executable = process.platform === "win32" ? "lldb-dap.exe" : "lldb-dap";56 57  // Prefer lldb-dap from Xcode on Darwin.58  const xcrun_dap = await findWithXcrun(executable);59  if (xcrun_dap) {60    return xcrun_dap;61  }62 63  // Find lldb-dap in the user's path.64  const path_dap = await findInPath(executable);65  if (path_dap) {66    return path_dap;67  }68 69  return undefined;70}71 72/**73 * Validates the DAP environment provided in the debug configuration.74 * It must be a dictionary of string keys and values OR an array of string values.75 *76 * @param debugConfigEnv The supposed DAP environment that will be validated77 * @returns Whether or not the DAP environment is valid78 */79function validateDAPEnv(debugConfigEnv: any): boolean {80  // If the env is an object, it should have string values.81  // The keys are guaranteed to be strings.82  if (83    typeof debugConfigEnv === "object" &&84    Object.values(debugConfigEnv).findIndex(85      (entry) => typeof entry !== "string",86    ) !== -187  ) {88    return false;89  }90 91  // If the env is an array, it should have string values which match the regex.92  if (93    Array.isArray(debugConfigEnv) &&94    debugConfigEnv.findIndex(95      (entry) =>96        typeof entry !== "string" || !/^\w+(=.*)?$/.test(entry),97    ) !== -198  ) {99    return false;100  }101 102  // The env is valid.103  return true;104}105 106/**107 * Retrieves the lldb-dap executable path either from settings or the provided108 * {@link vscode.DebugConfiguration}.109 *110 * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within111 * @param configuration The {@link vscode.DebugConfiguration} that will be launched112 * @throws An {@link ErrorWithNotification} if something went wrong113 * @returns The path to the lldb-dap executable114 */115async function getDAPExecutable(116  workspaceFolder: vscode.WorkspaceFolder | undefined,117  configuration: vscode.DebugConfiguration,118): Promise<string> {119  // Check if the executable was provided in the launch configuration.120  let launchConfigPath = configuration["debugAdapterExecutable"];121  if (typeof launchConfigPath === "string" && launchConfigPath.length !== 0) {122    launchConfigPath = expandUser(launchConfigPath);123    if (!(await isExecutable(launchConfigPath))) {124      throw new ErrorWithNotification(125        `Debug adapter path "${launchConfigPath}" is not a valid file. The path comes from your launch configuration.`,126        new ConfigureButton(),127      );128    }129    return launchConfigPath;130  }131 132  // Check if the executable was provided in the extension's configuration.133  const config = vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);134  const configPath = expandUser(config.get<string>("executable-path") ?? "");135  if (configPath && configPath.length !== 0) {136    if (!(await isExecutable(configPath))) {137      throw new ErrorWithNotification(138        `Debug adapter path "${configPath}" is not a valid file. The path comes from your settings.`,139        new OpenSettingsButton("lldb-dap.executable-path"),140      );141    }142    return configPath;143  }144 145  // Try finding the lldb-dap binary.146  const foundPath = await findDAPExecutable();147  if (foundPath) {148    if (!(await isExecutable(foundPath))) {149      throw new ErrorWithNotification(150        `Found a potential debug adapter on your system at "${configPath}", but it is not a valid file.`,151        new OpenSettingsButton("lldb-dap.executable-path"),152      );153    }154    return foundPath;155  }156 157  throw new ErrorWithNotification(158    "Unable to find the path to the LLDB debug adapter executable.",159    new OpenSettingsButton("lldb-dap.executable-path"),160  );161}162 163/**164 * Retrieves the arguments that will be provided to lldb-dap either from settings or the provided165 * {@link vscode.DebugConfiguration}.166 *167 * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within168 * @param configuration The {@link vscode.DebugConfiguration} that will be launched169 * @throws An {@link ErrorWithNotification} if something went wrong170 * @returns The arguments that will be provided to lldb-dap171 */172async function getDAPArguments(173  workspaceFolder: vscode.WorkspaceFolder | undefined,174  configuration: vscode.DebugConfiguration,175): Promise<string[]> {176  // Check the debug configuration for arguments first.177  const debugConfigArgs = configuration.debugAdapterArgs;178  if (debugConfigArgs) {179    if (180      !Array.isArray(debugConfigArgs) ||181      debugConfigArgs.findIndex((entry) => typeof entry !== "string") !== -1182    ) {183      throw new ErrorWithNotification(184        "The debugAdapterArgs property must be an array of string values. Please update your launch configuration",185        new ConfigureButton(),186      );187    }188    return debugConfigArgs;189  }190  // Fall back on the workspace configuration.191  return vscode.workspace192    .getConfiguration("lldb-dap", workspaceFolder)193    .get<string[]>("arguments", []);194}195 196/**197 * Retrieves the environment that will be provided to lldb-dap either from settings or the provided198 * {@link vscode.DebugConfiguration}.199 *200 * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within201 * @param configuration The {@link vscode.DebugConfiguration} that will be launched202 * @throws An {@link ErrorWithNotification} if something went wrong203 * @returns The environment that will be provided to lldb-dap204 */205async function getDAPEnvironment(206  workspaceFolder: vscode.WorkspaceFolder | undefined,207  configuration: vscode.DebugConfiguration,208): Promise<{ [key: string]: string }> {209  const debugConfigEnv = configuration.debugAdapterEnv;210  if (debugConfigEnv) {211    if (validateDAPEnv(debugConfigEnv) === false) {212      throw new ErrorWithNotification(213        "The debugAdapterEnv property must be a dictionary of string keys and values OR an array of string values. Please update your launch configuration",214        new ConfigureButton(),215      );216    }217 218    // Transform, so that the returned value is always a dictionary.219    if (Array.isArray(debugConfigEnv)) {220      const ret: { [key: string]: string } = {};221      for (const envVar of debugConfigEnv as string[]) {222        const equalSignPos = envVar.search("=");223        if (equalSignPos >= 0) {224          ret[envVar.substr(0, equalSignPos)] = envVar.substr(equalSignPos + 1);225        } else {226          ret[envVar] = "";227        }228      }229      return ret;230    } else {231      return debugConfigEnv;232    }233  }234 235  const config = vscode.workspace.workspaceFile236    ? vscode.workspace.getConfiguration("lldb-dap")237    : vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);238  return config.get<{ [key: string]: string }>("environment") || {};239}240 241/**242 * Creates a new {@link vscode.DebugAdapterExecutable} based on the provided workspace folder and243 * debug configuration. Assumes that the given debug configuration is for a local launch of lldb-dap.244 *245 * @param logger The {@link vscode.LogOutputChannel} to log setup diagnostics246 * @param logFilePath The {@link LogFilePathProvider} for determining where to put session logs247 * @param workspaceFolder The {@link vscode.WorkspaceFolder} that the debug session will be launched within248 * @param configuration The {@link vscode.DebugConfiguration} that will be launched249 * @throws An {@link ErrorWithNotification} if something went wrong250 * @returns The {@link vscode.DebugAdapterExecutable} that can be used to launch lldb-dap251 */252export async function createDebugAdapterExecutable(253  logger: vscode.LogOutputChannel,254  logFilePath: LogFilePathProvider,255  workspaceFolder: vscode.WorkspaceFolder | undefined,256  configuration: vscode.DebugConfiguration,257): Promise<vscode.DebugAdapterExecutable> {258  const config = vscode.workspace.workspaceFile259    ? vscode.workspace.getConfiguration("lldb-dap")260    : vscode.workspace.getConfiguration("lldb-dap", workspaceFolder);261  const log_path = config.get<string>("log-path");262  let env: { [key: string]: string } = {};263  if (log_path) {264    env["LLDBDAP_LOG"] = log_path;265  } else if (266    vscode.workspace267      .getConfiguration("lldb-dap")268      .get("captureSessionLogs", false)269  ) {270    env["LLDBDAP_LOG"] = logFilePath.get(LogType.DEBUG_SESSION);271  }272  const configEnvironment = await getDAPEnvironment(273    workspaceFolder,274    configuration,275  );276  const dapPath = await getDAPExecutable(workspaceFolder, configuration);277 278  const dbgOptions = {279    env: {280      ...configEnvironment,281      ...env,282    },283    cwd: configuration.cwd ?? workspaceFolder?.uri.fsPath,284  };285  const dbgArgs = await getDAPArguments(workspaceFolder, configuration);286 287  logger.info(`lldb-dap path: ${dapPath}`);288  logger.info(`lldb-dap args: ${dbgArgs}`);289  logger.info(`cwd: ${dbgOptions.cwd}`);290  logger.info(`env: ${JSON.stringify(dbgOptions.env)}`);291 292  return new vscode.DebugAdapterExecutable(dapPath, dbgArgs, dbgOptions);293}294 295/**296 * This class defines a factory used to find the lldb-dap binary to use297 * depending on the session configuration.298 */299export class LLDBDapDescriptorFactory300  implements vscode.DebugAdapterDescriptorFactory301{302  constructor(303    private readonly logger: vscode.LogOutputChannel,304    private logFilePath: LogFilePathProvider,305  ) {306    vscode.commands.registerCommand(307      "lldb-dap.createDebugAdapterDescriptor",308      (309        session: vscode.DebugSession,310        executable: vscode.DebugAdapterExecutable | undefined,311      ) => this.createDebugAdapterDescriptor(session, executable),312    );313  }314 315  async createDebugAdapterDescriptor(316    session: vscode.DebugSession,317    executable: vscode.DebugAdapterExecutable | undefined,318  ): Promise<vscode.DebugAdapterDescriptor | undefined> {319    this.logger.info(`Creating debug adapter for session "${session.name}"`);320    this.logger.info(321      `Session "${session.name}" debug configuration:\n` +322        JSON.stringify(session.configuration, undefined, 2),323    );324    if (executable) {325      const error = new Error(326        "Setting the debug adapter executable in the package.json is not supported.",327      );328      this.logger.error(error);329      throw error;330    }331 332    // Use a server connection if the debugAdapterPort is provided333    if (session.configuration.debugAdapterPort) {334      this.logger.info(335        `Spawning debug adapter server on port ${session.configuration.debugAdapterPort}`,336      );337      return new vscode.DebugAdapterServer(338        session.configuration.debugAdapterPort,339        session.configuration.debugAdapterHostname,340      );341    }342 343    return createDebugAdapterExecutable(344      this.logger,345      this.logFilePath,346      session.workspaceFolder,347      session.configuration,348    );349  }350}351