Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Prompt to cancel and replace the active test run if one is in flight ([#1774](https://github.com/swiftlang/vscode-swift/pull/1774))
- A walkthrough for first time extension users ([#1560](https://github.com/swiftlang/vscode-swift/issues/1560))
- Allow `swift.backgroundCompilation` setting to accept an object where enabling the `useDefaultTask` property will run the default build task, and the `release` property will run the `release` variant of the Build All task ([#1857](https://github.com/swiftlang/vscode-swift/pull/1857))
- Added new `target` and `configuration` properties to `swift` launch configurations that can be used instead of `program` for SwiftPM based projects ([#1890](https://github.com/swiftlang/vscode-swift/pull/1890))

### Fixed

Expand Down
5 changes: 4 additions & 1 deletion assets/test/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "swift",
"request": "launch",
"name": "Debug PackageExe (defaultPackage)",
// Explicitly use "program" to test searching for launch configs by program.
"program": "${workspaceFolder:test}/defaultPackage/.build/debug/PackageExe",
"args": [],
"cwd": "${workspaceFolder:test}/defaultPackage",
Expand All @@ -15,7 +16,9 @@
"type": "swift",
"request": "launch",
"name": "Release PackageExe (defaultPackage)",
"program": "${workspaceFolder:test}/defaultPackage/.build/release/PackageExe",
// Explicitly use "target" and "configuration" to test searching for launch configs by target.
"target": "PackageExe",
"configuration": "release",
"args": [],
"cwd": "${workspaceFolder:test}/defaultPackage",
"preLaunchTask": "swift: Build Release PackageExe (defaultPackage)",
Expand Down
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1725,14 +1725,23 @@
},
"configurationAttributes": {
"launch": {
"required": [
"program"
],
"properties": {
"program": {
"type": "string",
"description": "Path to the program to debug."
},
"target": {
"type": "string",
"description": "The name of the SwiftPM target to debug."
},
"configuration": {
"type": "string",
"enum": [
"debug",
"release"
],
"description": "The configuration of the SwiftPM target to use."
},
"args": {
"type": [
"array",
Expand Down
4 changes: 2 additions & 2 deletions src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function folderCleanBuild(folderContext: FolderContext) {
export async function debugBuildWithOptions(
ctx: WorkspaceContext,
options: vscode.DebugSessionOptions,
targetName?: string
targetName: string | undefined
) {
const current = ctx.currentFolder;
if (!current) {
Expand Down Expand Up @@ -107,7 +107,7 @@ export async function debugBuildWithOptions(
return;
}

const launchConfig = await getLaunchConfiguration(target.name, current);
const launchConfig = await getLaunchConfiguration(target.name, "debug", current);
if (launchConfig) {
ctx.buildStarted(target.name, launchConfig, options);
const result = await debugLaunchConfig(
Expand Down
43 changes: 41 additions & 2 deletions src/debugger/debugAdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { SwiftToolchain } from "../toolchain/toolchain";
import { fileExists } from "../utilities/filesystem";
import { getErrorDescription, swiftRuntimeEnv } from "../utilities/utilities";
import { DebugAdapter, LaunchConfigType, SWIFT_LAUNCH_CONFIG_TYPE } from "./debugAdapter";
import { getTargetBinaryPath } from "./launch";
import { getLLDBLibPath, updateLaunchConfigForCI } from "./lldb";
import { registerLoggingDebugAdapterTracker } from "./logTracker";

Expand Down Expand Up @@ -94,10 +95,48 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
folder: vscode.WorkspaceFolder | undefined,
launchConfig: vscode.DebugConfiguration
): Promise<vscode.DebugConfiguration | undefined | null> {
const workspaceFolder = this.workspaceContext.folders.find(
const folderContext = this.workspaceContext.folders.find(
f => f.workspaceFolder.uri.fsPath === folder?.uri.fsPath
);
const toolchain = workspaceFolder?.toolchain ?? this.workspaceContext.globalToolchain;
const toolchain = folderContext?.toolchain ?? this.workspaceContext.globalToolchain;

// "launch" requests must have either a "target" or "program" property
if (
launchConfig.request === "launch" &&
!("program" in launchConfig) &&
!("target" in launchConfig)
) {
throw new Error(
"You must specify either a 'program' or a 'target' when 'request' is set to 'launch' in a Swift debug configuration. Please update your debug configuration."
);
}

// Convert the "target" and "configuration" properties to a "program"
if (typeof launchConfig.target === "string") {
if ("program" in launchConfig) {
throw new Error(
`Unable to set both "target" and "program" on the same Swift debug configuration. Please remove one of them from your debug configuration.`
);
}
const targetName = launchConfig.target;
if (!folderContext) {
throw new Error(
`Unable to resolve target "${targetName}". No Swift package is available to search within.`
);
}
const buildConfiguration = launchConfig.configuration ?? "debug";
if (!["debug", "release"].includes(buildConfiguration)) {
throw new Error(
`Unknown configuration property "${buildConfiguration}" in Swift debug configuration. Valid options are "debug" or "release. Please update your debug configuration.`
);
}
launchConfig.program = await getTargetBinaryPath(
targetName,
buildConfiguration,
folderContext
);
delete launchConfig.target;
}

// Fix the program path on Windows to include the ".exe" extension
if (
Expand Down
198 changes: 93 additions & 105 deletions src/debugger/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//
import { realpathSync } from "fs";
import * as path from "path";
import { isDeepStrictEqual } from "util";
import * as vscode from "vscode";
Expand Down Expand Up @@ -70,6 +69,8 @@ export async function makeDebugConfigurations(
const config = structuredClone(launchConfigs[index]);
updateConfigWithNewKeys(config, generatedConfig, [
"program",
"target",
"configuration",
"cwd",
"preLaunchTask",
"type",
Expand Down Expand Up @@ -121,55 +122,85 @@ export async function makeDebugConfigurations(
return true;
}

// Return debug launch configuration for an executable in the given folder
export async function getLaunchConfiguration(
target: string,
export async function getTargetBinaryPath(
targetName: string,
buildConfiguration: "debug" | "release",
folderCtx: FolderContext
): Promise<vscode.DebugConfiguration | undefined> {
const wsLaunchSection = vscode.workspace.workspaceFile
? vscode.workspace.getConfiguration("launch")
: vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder);
const launchConfigs = wsLaunchSection.get<vscode.DebugConfiguration[]>("configurations") || [];
const { folder } = getFolderAndNameSuffix(folderCtx);
): Promise<string> {
try {
// Use dynamic path resolution with --show-bin-path
const binPath = await folderCtx.toolchain.buildFlags.getBuildBinaryPath(
folderCtx.folder.fsPath,
folder,
"debug",
buildConfiguration,
folderCtx.workspaceContext.logger
);
const targetPath = path.join(binPath, target);

const expandPath = (p: string) =>
p.replace(
`$\{workspaceFolder:${folderCtx.workspaceFolder.name}}`,
folderCtx.folder.fsPath
);

// Users could be on different platforms with different path annotations,
// so normalize before we compare.
const launchConfig = launchConfigs.find(
config =>
// Old launch configs had program paths that looked like ${workspaceFolder:test}/defaultPackage/.build/debug,
// where `debug` was a symlink to the host-triple-folder/debug. Because targetPath is determined by `--show-bin-path`
// in `getBuildBinaryPath` we need to follow this symlink to get the real path if we want to compare them.
path.normalize(realpathSync(expandPath(config.program))) ===
path.normalize(targetPath)
);
return launchConfig;
return path.join(binPath, targetName);
} catch (error) {
// Fallback to traditional path construction if dynamic resolution fails
const targetPath = path.join(
BuildFlags.buildDirectoryFromWorkspacePath(folder, true),
"debug",
target
return getLegacyTargetBinaryPath(targetName, buildConfiguration, folderCtx);
}
}

export function getLegacyTargetBinaryPath(
targetName: string,
buildConfiguration: "debug" | "release",
folderCtx: FolderContext
): string {
return path.join(
BuildFlags.buildDirectoryFromWorkspacePath(folderCtx.folder.fsPath, true),
buildConfiguration,
targetName
);
}

/** Expands VS Code variables such as ${workspaceFolder} in the given string. */
function expandVariables(str: string): string {
let expandedStr = str;
const availableWorkspaceFolders = vscode.workspace.workspaceFolders ?? [];
// Expand the top level VS Code workspace folder.
if (availableWorkspaceFolders.length > 0) {
expandedStr = expandedStr.replaceAll(
"${workspaceFolder}",
availableWorkspaceFolders[0].uri.fsPath
);
const launchConfig = launchConfigs.find(
config => path.normalize(config.program) === path.normalize(targetPath)
}
// Expand each available VS Code workspace folder.
for (const workspaceFolder of availableWorkspaceFolders) {
expandedStr = expandedStr.replaceAll(
`$\{workspaceFolder:${workspaceFolder.name}}`,
workspaceFolder.uri.fsPath
);
return launchConfig;
}
return expandedStr;
}

// Return debug launch configuration for an executable in the given folder
export async function getLaunchConfiguration(
target: string,
buildConfiguration: "debug" | "release",
folderCtx: FolderContext
): Promise<vscode.DebugConfiguration | undefined> {
const wsLaunchSection = vscode.workspace.workspaceFile
? vscode.workspace.getConfiguration("launch")
: vscode.workspace.getConfiguration("launch", folderCtx.workspaceFolder);
const launchConfigs = wsLaunchSection.get<vscode.DebugConfiguration[]>("configurations") || [];
const targetPath = await getTargetBinaryPath(target, buildConfiguration, folderCtx);
const legacyTargetPath = getLegacyTargetBinaryPath(target, buildConfiguration, folderCtx);
return launchConfigs.find(config => {
// Newer launch configs use "target" and "configuration" properties which are easier to query.
if (config.target) {
const configBuildConfiguration = config.configuration ?? "debug";
return config.target === target && configBuildConfiguration === buildConfiguration;
}
// Users could be on different platforms with different path annotations, so normalize before we compare.
const normalizedConfigPath = path.normalize(expandVariables(config.program));
const normalizedTargetPath = path.normalize(targetPath);
const normalizedLegacyTargetPath = path.normalize(legacyTargetPath);
// Old launch configs had program paths that looked like "${workspaceFolder:test}/defaultPackage/.build/debug",
// where `debug` was a symlink to the <host-triple-folder>/debug. We want to support both old and new, so we're
// comparing against both to find a match.
return [normalizedTargetPath, normalizedLegacyTargetPath].includes(normalizedConfigPath);
});
}

// Return array of DebugConfigurations for executables based on what is in Package.swift
Expand All @@ -182,72 +213,30 @@ async function createExecutableConfigurations(
// to make it easier for users switching between platforms.
const { folder, nameSuffix } = getFolderAndNameSuffix(ctx, undefined, "posix");

try {
// Get dynamic build paths for both debug and release configurations
const [debugBinPath, releaseBinPath] = await Promise.all([
ctx.toolchain.buildFlags.getBuildBinaryPath(
ctx.folder.fsPath,
folder,
"debug",
ctx.workspaceContext.logger
),
ctx.toolchain.buildFlags.getBuildBinaryPath(
ctx.folder.fsPath,
folder,
"release",
ctx.workspaceContext.logger
),
]);

return executableProducts.flatMap(product => {
const baseConfig = {
type: SWIFT_LAUNCH_CONFIG_TYPE,
request: "launch",
args: [],
cwd: folder,
};
return [
{
...baseConfig,
name: `Debug ${product.name}${nameSuffix}`,
program: path.posix.join(debugBinPath, product.name),
preLaunchTask: `swift: Build Debug ${product.name}${nameSuffix}`,
},
{
...baseConfig,
name: `Release ${product.name}${nameSuffix}`,
program: path.posix.join(releaseBinPath, product.name),
preLaunchTask: `swift: Build Release ${product.name}${nameSuffix}`,
},
];
});
} catch (error) {
// Fallback to traditional path construction if dynamic resolution fails
const buildDirectory = BuildFlags.buildDirectoryFromWorkspacePath(folder, true, "posix");

return executableProducts.flatMap(product => {
const baseConfig = {
type: SWIFT_LAUNCH_CONFIG_TYPE,
request: "launch",
args: [],
cwd: folder,
};
return [
{
...baseConfig,
name: `Debug ${product.name}${nameSuffix}`,
program: path.posix.join(buildDirectory, "debug", product.name),
preLaunchTask: `swift: Build Debug ${product.name}${nameSuffix}`,
},
{
...baseConfig,
name: `Release ${product.name}${nameSuffix}`,
program: path.posix.join(buildDirectory, "release", product.name),
preLaunchTask: `swift: Build Release ${product.name}${nameSuffix}`,
},
];
});
}
return executableProducts.flatMap(product => {
const baseConfig = {
type: SWIFT_LAUNCH_CONFIG_TYPE,
request: "launch",
args: [],
cwd: folder,
};
return [
{
...baseConfig,
name: `Debug ${product.name}${nameSuffix}`,
target: product.name,
configuration: "debug",
preLaunchTask: `swift: Build Debug ${product.name}${nameSuffix}`,
},
{
...baseConfig,
name: `Release ${product.name}${nameSuffix}`,
target: product.name,
configuration: "release",
preLaunchTask: `swift: Build Release ${product.name}${nameSuffix}`,
},
];
});
}

/**
Expand All @@ -266,7 +255,6 @@ export async function createSnippetConfiguration(
// Use dynamic path resolution with --show-bin-path
const binPath = await ctx.toolchain.buildFlags.getBuildBinaryPath(
ctx.folder.fsPath,
folder,
"debug",
ctx.workspaceContext.logger
);
Expand Down
5 changes: 3 additions & 2 deletions src/toolchain/BuildFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,6 @@ export class BuildFlags {
* @returns Promise resolving to the build binary path
*/
async getBuildBinaryPath(
cwd: string,
workspacePath: string,
buildConfiguration: "debug" | "release" = "debug",
logger: SwiftLogger
Expand Down Expand Up @@ -263,7 +262,9 @@ export class BuildFlags {

try {
// Execute swift build --show-bin-path
const result = await execSwift(fullArgs, this.toolchain, { cwd });
const result = await execSwift(fullArgs, this.toolchain, {
cwd: workspacePath,
});
const binPath = result.stdout.trim();

// Cache the result
Expand Down
Loading