Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
72 changes: 69 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,65 @@ 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" and "--build-system"
const args = (task.definition.args as string[]) || [];
const hasBuild = args.includes("build");
const hasBuildSystem = args.includes("--build-system");

if (hasBuild && hasBuildSystem) {
return args;
}

return 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
178 changes: 177 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,179 @@ 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 Swift task without --build-system arg", async () => {
const mockTask = mockObject<vscode.Task>({
name: "swift: Build Debug executable",
definition: {
type: "swift",
args: ["build", "--product", "executable"],
},
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.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);
});
});