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
5 changes: 3 additions & 2 deletions src/debugger/debugAdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +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 { getTargetBinaryPath, swiftPrelaunchBuildTaskArguments } from "./launch";
import { getLLDBLibPath, updateLaunchConfigForCI } from "./lldb";
import { registerLoggingDebugAdapterTracker } from "./logTracker";

Expand Down Expand Up @@ -133,7 +133,8 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
launchConfig.program = await getTargetBinaryPath(
targetName,
buildConfiguration,
folderContext
folderContext,
await swiftPrelaunchBuildTaskArguments(launchConfig, folderContext.workspaceFolder)
);
delete launchConfig.target;
}
Expand Down
66 changes: 63 additions & 3 deletions src/debugger/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,17 @@ export async function makeDebugConfigurations(
export async function getTargetBinaryPath(
targetName: string,
buildConfiguration: "debug" | "release",
folderCtx: FolderContext
folderCtx: FolderContext,
extraArgs: string[] = []
): Promise<string> {
try {
// Use dynamic path resolution with --show-bin-path
const binPath = await folderCtx.toolchain.buildFlags.getBuildBinaryPath(
folderCtx.folder.fsPath,
buildConfiguration,
folderCtx.workspaceContext.logger
folderCtx.workspaceContext.logger,
"",
extraArgs
);
return path.join(binPath, targetName);
} catch (error) {
Expand Down Expand Up @@ -256,7 +259,8 @@ export async function createSnippetConfiguration(
const binPath = await ctx.toolchain.buildFlags.getBuildBinaryPath(
ctx.folder.fsPath,
"debug",
ctx.workspaceContext.logger
ctx.workspaceContext.logger,
"snippet"
);

return {
Expand Down Expand Up @@ -328,3 +332,59 @@ function updateConfigWithNewKeys(
oldConfig[key] = newConfig[key];
}
}

/**
* Get the arguments for a launch configuration's preLaunchTask if it's a Swift build task
* @param launchConfig The launch configuration to check
* @param workspaceFolder The workspace folder context (optional)
* @returns Promise<string[] | undefined> the task arguments if it's a Swift build task, undefined otherwise
*/
export async function swiftPrelaunchBuildTaskArguments(
launchConfig: vscode.DebugConfiguration,
workspaceFolder?: vscode.WorkspaceFolder
): Promise<string[] | undefined> {
const preLaunchTask = launchConfig.preLaunchTask;

if (!preLaunchTask || typeof preLaunchTask !== "string") {
return undefined;
}

try {
// Fetch all available tasks
const allTasks = await vscode.tasks.fetchTasks();

// Find the task by name
const task = allTasks.find(t => {
// Check if task name matches (with or without "swift: " prefix)
const taskName = t.name;
const matches =
taskName === preLaunchTask ||
taskName === `swift: ${preLaunchTask}` ||
`swift: ${taskName}` === preLaunchTask;

// If workspace folder is specified, also check scope
if (workspaceFolder && matches) {
return t.scope === workspaceFolder || t.scope === vscode.TaskScope.Workspace;
}

return matches;
});

if (!task) {
return undefined;
}

// Check if task type is "swift"
if (task.definition.type !== "swift") {
return undefined;
}

// Check if args contain "build"
const args = (task.definition.args as string[]) || [];
const hasBuild = args.includes("build");
return hasBuild ? args : undefined;
} catch (error) {
// Log error but don't throw - return undefined for safety
return undefined;
}
}
8 changes: 5 additions & 3 deletions src/toolchain/BuildFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,14 @@ export class BuildFlags {
async getBuildBinaryPath(
workspacePath: string,
buildConfiguration: "debug" | "release" = "debug",
logger: SwiftLogger
logger: SwiftLogger,
idSuffix: string = "",
extraArgs: string[] = []
): Promise<string> {
// Checking the bin path requires a swift process execution, so we maintain a cache.
// The cache key is based on workspace, configuration, and build arguments.
const buildArgsHash = JSON.stringify(configuration.buildArguments);
const cacheKey = `${workspacePath}:${buildConfiguration}:${buildArgsHash}`;
const cacheKey = `${workspacePath}:${buildConfiguration}:${buildArgsHash}${idSuffix}`;

if (BuildFlags.buildPathCache.has(cacheKey)) {
return BuildFlags.buildPathCache.get(cacheKey)!;
Expand All @@ -257,7 +259,7 @@ export class BuildFlags {
const baseArgs = ["build", "--show-bin-path", "--configuration", buildConfiguration];
const fullArgs = [
...this.withAdditionalFlags(baseArgs),
...binPathAffectingArgs(configuration.buildArguments),
...binPathAffectingArgs([...configuration.buildArguments, ...extraArgs]),
];

try {
Expand Down
150 changes: 149 additions & 1 deletion test/unit-tests/debugger/launch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { FolderContext } from "@src/FolderContext";
import { Product, SwiftPackage } from "@src/SwiftPackage";
import configuration, { FolderConfiguration } from "@src/configuration";
import { SWIFT_LAUNCH_CONFIG_TYPE } from "@src/debugger/debugAdapter";
import { makeDebugConfigurations } from "@src/debugger/launch";
import { makeDebugConfigurations, swiftPrelaunchBuildTaskArguments } from "@src/debugger/launch";

import {
MockedObject,
Expand Down Expand Up @@ -301,3 +301,151 @@ suite("Launch Configurations Test", () => {
expect(mockLaunchWSConfig.update).to.not.have.been.called;
});
});

suite("Swift PreLaunch Build Task Arguments Test", () => {
const mockTasks = mockGlobalObject(vscode, "tasks");

setup(() => {
// Reset mocks before each test
mockTasks.fetchTasks.reset();
});

test("swiftPrelaunchBuildTaskArguments returns task args for Swift build task", async () => {
const expectedArgs = ["build", "--product", "executable", "--build-system"];
const mockTask = mockObject<vscode.Task>({
name: "swift: Build Debug executable",
definition: {
type: "swift",
args: expectedArgs,
},
scope: vscode.TaskScope.Workspace,
source: "swift",
isBackground: false,
presentationOptions: {},
problemMatchers: [],
runOptions: {},
});

mockTasks.fetchTasks.resolves([instance(mockTask)]);

const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
preLaunchTask: "swift: Build Debug executable",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.deep.equal(expectedArgs);
});

test("swiftPrelaunchBuildTaskArguments returns undefined for non-Swift task", async () => {
const mockTask = mockObject<vscode.Task>({
name: "npm: build",
definition: {
type: "npm",
args: ["run", "build"],
},
scope: vscode.TaskScope.Workspace,
source: "npm",
isBackground: false,
presentationOptions: {},
problemMatchers: [],
runOptions: {},
});

mockTasks.fetchTasks.resolves([instance(mockTask)]);

const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
preLaunchTask: "npm: build",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.be.undefined;
});

test("swiftPrelaunchBuildTaskArguments returns undefined for Swift task without build arg", async () => {
const mockTask = mockObject<vscode.Task>({
name: "swift: Test",
definition: {
type: "swift",
args: ["test", "--build-system"],
},
scope: vscode.TaskScope.Workspace,
source: "swift",
isBackground: false,
presentationOptions: {},
problemMatchers: [],
runOptions: {},
});

mockTasks.fetchTasks.resolves([instance(mockTask)]);

const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
preLaunchTask: "swift: Test",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.be.undefined;
});

test("swiftPrelaunchBuildTaskArguments returns undefined for launch config without preLaunchTask", async () => {
const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.be.undefined;
});

test("swiftPrelaunchBuildTaskArguments handles errors gracefully", async () => {
mockTasks.fetchTasks.rejects(new Error("Failed to fetch tasks"));

const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
preLaunchTask: "swift: Build Debug executable",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.be.undefined;
});

test("swiftPrelaunchBuildTaskArguments handles task name variations", async () => {
const expectedArgs = ["build", "--product", "executable", "--build-system"];
const mockTask = mockObject<vscode.Task>({
name: "Build Debug executable",
definition: {
type: "swift",
args: expectedArgs,
},
scope: vscode.TaskScope.Workspace,
source: "swift",
isBackground: false,
presentationOptions: {},
problemMatchers: [],
runOptions: {},
});

mockTasks.fetchTasks.resolves([instance(mockTask)]);

const launchConfig: vscode.DebugConfiguration = {
type: "swift",
request: "launch",
name: "Debug executable",
preLaunchTask: "swift: Build Debug executable",
};

const result = await swiftPrelaunchBuildTaskArguments(launchConfig);
expect(result).to.deep.equal(expectedArgs);
});
});