Skip to content

Prompt for CodeLLDB settings only when launching a debug session #1381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 1 addition & 73 deletions src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ import * as path from "path";
import { FolderContext } from "./FolderContext";
import { StatusItem } from "./ui/StatusItem";
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
import { swiftLibraryPathKey, getErrorDescription } from "./utilities/utilities";
import { swiftLibraryPathKey } from "./utilities/utilities";
import { pathExists, isPathInsidePath } from "./utilities/filesystem";
import { getLLDBLibPath } from "./debugger/lldb";
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
import { TemporaryFolder } from "./utilities/tempFolder";
import { TaskManager } from "./tasks/TaskManager";
Expand All @@ -29,7 +28,6 @@ import configuration from "./configuration";
import contextKeys from "./contextKeys";
import { setSnippetContextKey } from "./SwiftSnippets";
import { CommentCompletionProviders } from "./editor/CommentCompletion";
import { DebugAdapter, LaunchConfigType } from "./debugger/debugAdapter";
import { SwiftBuildStatus } from "./ui/SwiftBuildStatus";
import { SwiftToolchain } from "./toolchain/toolchain";
import { DiagnosticsManager } from "./DiagnosticsManager";
Expand Down Expand Up @@ -444,76 +442,6 @@ export class WorkspaceContext implements vscode.Disposable {
return { dispose: () => this.swiftFileObservers.delete(listener) };
}

/** find LLDB version and setup path in CodeLLDB */
async setLLDBVersion() {
// check we are using CodeLLDB
if (DebugAdapter.getLaunchConfigType(this.swiftVersion) !== LaunchConfigType.CODE_LLDB) {
return;
}
const libPathResult = await getLLDBLibPath(this.toolchain);
if (!libPathResult.success) {
// if failure message is undefined then fail silently
if (!libPathResult.failure) {
return;
}
const errorMessage = `Error: ${getErrorDescription(libPathResult.failure)}`;
vscode.window.showErrorMessage(
`Failed to setup CodeLLDB for debugging of Swift code. Debugging may produce unexpected results. ${errorMessage}`
);
this.outputChannel.log(`Failed to setup CodeLLDB: ${errorMessage}`);
return;
}

const libPath = libPathResult.success;
const lldbConfig = vscode.workspace.getConfiguration("lldb");
const configLLDBPath = lldbConfig.get<string>("library");
const expressions = lldbConfig.get<string>("launch.expressions");
if (configLLDBPath === libPath && expressions === "native") {
return;
}

// show dialog for setting up LLDB
vscode.window
.showInformationMessage(
"The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or the workspace settings?",
"Global",
"Workspace",
"Cancel"
)
.then(result => {
switch (result) {
case "Global":
lldbConfig.update("library", libPath, vscode.ConfigurationTarget.Global);
lldbConfig.update(
"launch.expressions",
"native",
vscode.ConfigurationTarget.Global
);
// clear workspace setting
lldbConfig.update(
"library",
undefined,
vscode.ConfigurationTarget.Workspace
);
// clear workspace setting
lldbConfig.update(
"launch.expressions",
undefined,
vscode.ConfigurationTarget.Workspace
);
break;
case "Workspace":
lldbConfig.update("library", libPath, vscode.ConfigurationTarget.Workspace);
lldbConfig.update(
"launch.expressions",
"native",
vscode.ConfigurationTarget.Workspace
);
break;
}
});
}

/** set focus based on the file a TextEditor is editing */
async focusTextEditor(editor?: vscode.TextEditor) {
await this.focusUri(editor?.document.uri);
Expand Down
56 changes: 2 additions & 54 deletions src/debugger/debugAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@
//
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import configuration from "../configuration";
import contextKeys from "../contextKeys";
import { fileExists } from "../utilities/filesystem";
import { Version } from "../utilities/version";
import { SwiftToolchain } from "../toolchain/toolchain";
import { SwiftOutputChannel } from "../ui/SwiftOutputChannel";

/**
* The launch configuration type added by the Swift extension that will delegate to the appropriate
Expand Down Expand Up @@ -61,59 +57,11 @@ export class DebugAdapter {
* @param toolchain The Swift toolchain to use
* @returns A path to the debug adapter for the user's toolchain and configuration
**/
public static async debugAdapterPath(toolchain: SwiftToolchain): Promise<string> {
public static async getLLDBDebugAdapterPath(toolchain: SwiftToolchain): Promise<string> {
const customDebugAdapterPath = configuration.debugger.customDebugAdapterPath;
if (customDebugAdapterPath.length > 0) {
return customDebugAdapterPath;
}

const debugAdapter = this.getLaunchConfigType(toolchain.swiftVersion);
switch (debugAdapter) {
case LaunchConfigType.LLDB_DAP:
return toolchain.getLLDBDebugAdapter();
case LaunchConfigType.CODE_LLDB:
return toolchain.getLLDB();
}
}

/**
* Verify that the toolchain debug adapter exists and display an error message to the user
* if it doesn't.
*
* Has the side effect of setting the `swift.lldbVSCodeAvailable` context key depending
* on the result.
*
* @param workspace WorkspaceContext
* @param quiet Whether or not the dialog should be displayed if the adapter does not exist
* @returns Whether or not the debug adapter exists
*/
public static async verifyDebugAdapterExists(
toolchain: SwiftToolchain,
outputChannel: SwiftOutputChannel,
quiet = false
): Promise<boolean> {
const lldbDebugAdapterPath = await this.debugAdapterPath(toolchain).catch(error => {
outputChannel.log(error);
return undefined;
});

if (!lldbDebugAdapterPath || !(await fileExists(lldbDebugAdapterPath))) {
if (!quiet) {
const debugAdapterName = this.getLaunchConfigType(toolchain.swiftVersion);
vscode.window.showErrorMessage(
configuration.debugger.customDebugAdapterPath.length > 0
? `Cannot find ${debugAdapterName} debug adapter specified in setting Swift.Debugger.Path.`
: `Cannot find ${debugAdapterName} debug adapter in your Swift toolchain.`
);
}
if (lldbDebugAdapterPath) {
outputChannel.log(`Failed to find ${lldbDebugAdapterPath}`);
}
contextKeys.lldbVSCodeAvailable = false;
return false;
}

contextKeys.lldbVSCodeAvailable = true;
return true;
return toolchain.getLLDBDebugAdapter();
}
}
99 changes: 71 additions & 28 deletions src/debugger/debugAdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debu
import { registerLoggingDebugAdapterTracker } from "./logTracker";
import { SwiftToolchain } from "../toolchain/toolchain";
import { SwiftOutputChannel } from "../ui/SwiftOutputChannel";
import { fileExists } from "../utilities/filesystem";
import { getLLDBLibPath } from "./lldb";
import { getErrorDescription } from "../utilities/utilities";

/**
* Registers the active debugger with the extension, and reregisters it
Expand All @@ -27,35 +30,11 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel";
* @returns A disposable to be disposed when the extension is deactivated
*/
export function registerDebugger(workspaceContext: WorkspaceContext): vscode.Disposable {
async function updateDebugAdapter() {
await workspaceContext.setLLDBVersion();

// Verify that the adapter exists, but only after registration. This async method
// is basically an unstructured task so we don't want to run it before the adapter
// registration above as it could cause code executing immediately after register()
// to use the incorrect adapter.
DebugAdapter.verifyDebugAdapterExists(
workspaceContext.toolchain,
workspaceContext.outputChannel,
true
).catch(error => {
workspaceContext.outputChannel.log(error);
});
}

const subscriptions: vscode.Disposable[] = [
registerLoggingDebugAdapterTracker(),
registerLLDBDebugAdapter(workspaceContext.toolchain, workspaceContext.outputChannel),
vscode.workspace.onDidChangeConfiguration(event => {
if (event.affectsConfiguration("swift.debugger.useDebugAdapterFromToolchain")) {
updateDebugAdapter();
}
}),
];

// Perform the initial registration, then reregister every time the settings change.
updateDebugAdapter();

return {
dispose: () => {
subscriptions.map(sub => sub.dispose());
Expand All @@ -79,7 +58,7 @@ function registerLLDBDebugAdapter(

const debugConfigProvider = vscode.debug.registerDebugConfigurationProvider(
SWIFT_LAUNCH_CONFIG_TYPE,
new LLDBDebugConfigurationProvider(process.platform, toolchain)
new LLDBDebugConfigurationProvider(process.platform, toolchain, outputChannel)
);

return {
Expand Down Expand Up @@ -117,8 +96,7 @@ export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDes
}

async createDebugAdapterDescriptor(): Promise<vscode.DebugAdapterDescriptor> {
const path = await DebugAdapter.debugAdapterPath(this.toolchain);
await DebugAdapter.verifyDebugAdapterExists(this.toolchain, this.outputChannel);
const path = await DebugAdapter.getLLDBDebugAdapterPath(this.toolchain);
return new vscode.DebugAdapterExecutable(path, [], {});
}
}
Expand All @@ -135,7 +113,8 @@ export class LLDBDebugAdapterExecutableFactory implements vscode.DebugAdapterDes
export class LLDBDebugConfigurationProvider implements vscode.DebugConfigurationProvider {
constructor(
private platform: NodeJS.Platform,
private toolchain: SwiftToolchain
private toolchain: SwiftToolchain,
private outputChannel: SwiftOutputChannel
) {}

async resolveDebugConfiguration(
Expand All @@ -155,15 +134,79 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
launchConfig.type = DebugAdapter.getLaunchConfigType(this.toolchain.swiftVersion);
if (launchConfig.type === LaunchConfigType.CODE_LLDB) {
launchConfig.sourceLanguages = ["swift"];
// Prompt the user to update CodeLLDB settings if necessary
await this.promptForCodeLldbSettings();
} else if (launchConfig.type === LaunchConfigType.LLDB_DAP) {
if (launchConfig.env) {
launchConfig.env = this.convertEnvironmentVariables(launchConfig.env);
}
const lldbDapPath = await DebugAdapter.getLLDBDebugAdapterPath(this.toolchain);
// Verify that the debug adapter exists or bail otherwise
if (!(await fileExists(lldbDapPath))) {
vscode.window.showErrorMessage(
`Cannot find the LLDB debug adapter in your Swift toolchain: No such file or directory "${lldbDapPath}"`
);
return undefined;
}
}

return launchConfig;
}

private async promptForCodeLldbSettings(): Promise<void> {
const libLldbPathResult = await getLLDBLibPath(this.toolchain);
if (!libLldbPathResult.success) {
const errorMessage = `Error: ${getErrorDescription(libLldbPathResult.failure)}`;
vscode.window.showWarningMessage(
`Failed to setup CodeLLDB for debugging of Swift code. Debugging may produce unexpected results. ${errorMessage}`
);
this.outputChannel.log(`Failed to setup CodeLLDB: ${errorMessage}`);
return;
}
const libLldbPath = libLldbPathResult.success;
const lldbConfig = vscode.workspace.getConfiguration("lldb");
if (
lldbConfig.get<string>("library") === libLldbPath &&
lldbConfig.get<string>("launch.expressions") === "native"
) {
return;
}
const userSelection = await vscode.window.showInformationMessage(
"The Swift extension needs to update some CodeLLDB settings to enable debugging features. Do you want to set this up in your global settings or workspace settings?",
{ modal: true },
"Global",
"Workspace",
"Run Anyway"
);
switch (userSelection) {
case "Global":
lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Global);
lldbConfig.update(
"launch.expressions",
"native",
vscode.ConfigurationTarget.Global
);
// clear workspace setting
lldbConfig.update("library", undefined, vscode.ConfigurationTarget.Workspace);
// clear workspace setting
lldbConfig.update(
"launch.expressions",
undefined,
vscode.ConfigurationTarget.Workspace
);
break;
case "Workspace":
lldbConfig.update("library", libLldbPath, vscode.ConfigurationTarget.Workspace);
lldbConfig.update(
"launch.expressions",
"native",
vscode.ConfigurationTarget.Workspace
);
break;
}
return;
}

private convertEnvironmentVariables(map: { [key: string]: string }): string[] {
return Object.entries(map).map(([key, value]) => `${key}=${value}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/debugger/lldb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ export const CI_DISABLE_ASLR =
: {};

/**
* Get LLDB library for given LLDB executable
* @param executable LLDB executable
* Get the path to the LLDB library.
*
* @returns Library path for LLDB
*/
export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise<Result<string>> {
Expand Down
Loading
Loading