Skip to content

Commit b6b258c

Browse files
authored
Fix Several Windows Debugging Issues (#1083)
Fixes up some issues with Windows debugging found in the latest 6.0 toolchain: - Add Testing.dll to the PATH same as XCTest.dll - Don't append .exe when debugging testing binaries - Don't follow incorrectly created symlink at `.\build\debug`, point to real folder at the build triple path
1 parent 2dad6d7 commit b6b258c

File tree

4 files changed

+153
-80
lines changed

4 files changed

+153
-80
lines changed

src/debugger/buildConfig.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -191,14 +191,19 @@ export class TestingConfigurationFactory {
191191
...swiftRuntimeEnv(),
192192
...configuration.folder(this.ctx.workspaceFolder).testEnvironmentVariables,
193193
};
194-
// On Windows, add XCTest.dll to the Path
194+
// On Windows, add XCTest.dll/Testing.dll to the Path
195195
// and run the .xctest executable from the .build directory.
196196
const runtimePath = this.ctx.workspaceContext.toolchain.runtimePath;
197197
const xcTestPath = this.ctx.workspaceContext.toolchain.xcTestPath;
198198
if (xcTestPath && xcTestPath !== runtimePath) {
199199
testEnv.Path = `${xcTestPath};${testEnv.Path ?? process.env.Path}`;
200200
}
201201

202+
const swiftTestingPath = this.ctx.workspaceContext.toolchain.swiftTestingPath;
203+
if (swiftTestingPath && swiftTestingPath !== runtimePath) {
204+
testEnv.Path = `${swiftTestingPath};${testEnv.Path ?? process.env.Path}`;
205+
}
206+
202207
return {
203208
...this.baseConfig,
204209
program: await this.testExecutableOutputPath(),
@@ -513,7 +518,9 @@ export class TestingConfigurationFactory {
513518
}
514519

515520
private get artifactFolderForTestKind(): string {
516-
return isRelease(this.testKind) ? "release" : "debug";
521+
const mode = isRelease(this.testKind) ? "release" : "debug";
522+
const triple = this.ctx.workspaceContext.toolchain.unversionedTriple;
523+
return triple ? path.join(triple, mode) : mode;
517524
}
518525

519526
private xcTestOutputPath(): string {

src/debugger/debugAdapterFactory.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,14 @@ export class LLDBDebugConfigurationProvider implements vscode.DebugConfiguration
7777
): Promise<vscode.DebugConfiguration> {
7878
launchConfig.env = this.convertEnvironmentVariables(launchConfig.env);
7979
// Fix the program path on Windows to include the ".exe" extension
80-
if (this.platform === "win32" && path.extname(launchConfig.program) !== ".exe") {
80+
if (
81+
this.platform === "win32" &&
82+
launchConfig.testType === undefined &&
83+
path.extname(launchConfig.program) !== ".exe"
84+
) {
8185
launchConfig.program += ".exe";
8286
}
87+
8388
// Delegate to CodeLLDB if that's the debug adapter we have selected
8489
if (DebugAdapter.getDebugAdapterType(this.swiftVersion) === "lldb-vscode") {
8590
launchConfig.type = "lldb";

src/toolchain/toolchain.ts

+132-76
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { Sanitizer } from "./Sanitizer";
3131
interface InfoPlist {
3232
DefaultProperties: {
3333
XCTEST_VERSION: string | undefined;
34+
SWIFT_TESTING_VERSION: string | undefined;
3435
};
3536
}
3637

@@ -50,6 +51,7 @@ interface SwiftTargetInfo {
5051
compilerVersion: string;
5152
target?: {
5253
triple: string;
54+
unversionedTriple: string;
5355
[name: string]: string | string[];
5456
};
5557
paths: {
@@ -97,18 +99,22 @@ export function getDarwinTargetTriple(target: DarwinCompatibleTarget): string |
9799
}
98100

99101
export class SwiftToolchain {
102+
public swiftVersionString: string;
103+
100104
constructor(
101105
public swiftFolderPath: string, // folder swift executable in $PATH was found in
102106
public toolchainPath: string, // toolchain folder. One folder up from swift bin folder. This is to support toolchains without usr folder
103-
public swiftVersionString: string, // Swift version as a string, including description
107+
private targetInfo: SwiftTargetInfo,
104108
public swiftVersion: Version, // Swift version as semVar variable
105109
public runtimePath?: string, // runtime library included in output from `swift -print-target-info`
106-
private defaultTarget?: string,
107110
public defaultSDK?: string,
108111
public customSDK?: string,
109112
public xcTestPath?: string,
113+
public swiftTestingPath?: string,
110114
public swiftPMTestingHelperPath?: string
111-
) {}
115+
) {
116+
this.swiftVersionString = targetInfo.compilerVersion;
117+
}
112118

113119
static async create(): Promise<SwiftToolchain> {
114120
const swiftFolderPath = await this.getSwiftFolderPath();
@@ -125,22 +131,32 @@ export class SwiftToolchain {
125131
runtimePath,
126132
customSDK ?? defaultSDK
127133
);
134+
const swiftTestingPath = await this.getSwiftTestingPath(
135+
targetInfo,
136+
swiftVersion,
137+
runtimePath,
138+
customSDK ?? defaultSDK
139+
);
128140
const swiftPMTestingHelperPath = await this.getSwiftPMTestingHelperPath(toolchainPath);
129141

130142
return new SwiftToolchain(
131143
swiftFolderPath,
132144
toolchainPath,
133-
targetInfo.compilerVersion,
145+
targetInfo,
134146
swiftVersion,
135147
runtimePath,
136-
targetInfo.target?.triple,
137148
defaultSDK,
138149
customSDK,
139150
xcTestPath,
151+
swiftTestingPath,
140152
swiftPMTestingHelperPath
141153
);
142154
}
143155

156+
public get unversionedTriple(): string | undefined {
157+
return this.targetInfo.target?.unversionedTriple;
158+
}
159+
144160
/** build flags */
145161
public get buildFlags(): BuildFlags {
146162
return new BuildFlags(this);
@@ -445,8 +461,8 @@ export class SwiftToolchain {
445461
if (this.runtimePath) {
446462
str += `\nRuntime Library Path: ${this.runtimePath}`;
447463
}
448-
if (this.defaultTarget) {
449-
str += `\nDefault Target: ${this.defaultTarget}`;
464+
if (this.targetInfo.target?.triple) {
465+
str += `\nDefault Target: ${this.targetInfo.target?.triple}`;
450466
}
451467
if (this.defaultSDK) {
452468
str += `\nDefault SDK: ${this.defaultSDK}`;
@@ -638,6 +654,31 @@ export class SwiftToolchain {
638654
return undefined;
639655
}
640656

657+
/**
658+
* @param targetInfo swift target info
659+
* @param swiftVersion parsed swift version
660+
* @param runtimePath path to Swift runtime
661+
* @param sdkroot path to swift SDK
662+
* @returns path to folder where xctest can be found
663+
*/
664+
private static async getSwiftTestingPath(
665+
targetInfo: SwiftTargetInfo,
666+
swiftVersion: Version,
667+
runtimePath: string | undefined,
668+
sdkroot: string | undefined
669+
): Promise<string | undefined> {
670+
if (process.platform !== "win32") {
671+
return undefined;
672+
}
673+
return this.getWindowsPlatformDLLPath(
674+
"Testing",
675+
targetInfo,
676+
swiftVersion,
677+
runtimePath,
678+
sdkroot
679+
);
680+
}
681+
641682
/**
642683
* @param targetInfo swift target info
643684
* @param swiftVersion parsed swift version
@@ -663,80 +704,95 @@ export class SwiftToolchain {
663704
return path.join(developerDir, "usr", "bin");
664705
}
665706
case "win32": {
666-
// look up runtime library directory for XCTest alternatively
667-
const fallbackPath =
668-
runtimePath !== undefined &&
669-
(await pathExists(path.join(runtimePath, "XCTest.dll")))
670-
? runtimePath
671-
: undefined;
672-
if (!sdkroot) {
673-
return fallbackPath;
674-
}
675-
const platformPath = path.dirname(path.dirname(path.dirname(sdkroot)));
676-
const platformManifest = path.join(platformPath, "Info.plist");
677-
if ((await pathExists(platformManifest)) !== true) {
678-
if (fallbackPath) {
679-
return fallbackPath;
680-
}
681-
vscode.window.showWarningMessage(
682-
"XCTest not found due to non-standardized library layout. Tests explorer won't work as expected."
683-
);
684-
return undefined;
685-
}
686-
const data = await fs.readFile(platformManifest, "utf8");
687-
let infoPlist;
688-
try {
689-
infoPlist = plist.parse(data) as unknown as InfoPlist;
690-
} catch (error) {
691-
vscode.window.showWarningMessage(
692-
`Unable to parse ${platformManifest}: ${error}`
693-
);
694-
return undefined;
695-
}
696-
const version = infoPlist.DefaultProperties.XCTEST_VERSION;
697-
if (!version) {
698-
throw Error("Info.plist is missing the XCTEST_VERSION key.");
699-
}
700-
701-
if (swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0))) {
702-
let bindir: string;
703-
const arch = targetInfo.target?.triple.split("-", 1)[0];
704-
switch (arch) {
705-
case "x86_64":
706-
bindir = "bin64";
707-
break;
708-
case "i686":
709-
bindir = "bin32";
710-
break;
711-
case "aarch64":
712-
bindir = "bin64a";
713-
break;
714-
default:
715-
throw Error(`unsupported architecture ${arch}`);
716-
}
717-
return path.join(
718-
platformPath,
719-
"Developer",
720-
"Library",
721-
`XCTest-${version}`,
722-
"usr",
723-
bindir
724-
);
725-
} else {
726-
return path.join(
727-
platformPath,
728-
"Developer",
729-
"Library",
730-
`XCTest-${version}`,
731-
"usr",
732-
"bin"
733-
);
734-
}
707+
return await this.getWindowsPlatformDLLPath(
708+
"XCTest",
709+
targetInfo,
710+
swiftVersion,
711+
runtimePath,
712+
sdkroot
713+
);
735714
}
736715
}
737716
return undefined;
738717
}
739718

719+
private static async getWindowsPlatformDLLPath(
720+
type: "XCTest" | "Testing",
721+
targetInfo: SwiftTargetInfo,
722+
swiftVersion: Version,
723+
runtimePath: string | undefined,
724+
sdkroot: string | undefined
725+
): Promise<string | undefined> {
726+
// look up runtime library directory for XCTest/Testing alternatively
727+
const fallbackPath =
728+
runtimePath !== undefined && (await pathExists(path.join(runtimePath, `${type}.dll`)))
729+
? runtimePath
730+
: undefined;
731+
if (!sdkroot) {
732+
return fallbackPath;
733+
}
734+
735+
const platformPath = path.dirname(path.dirname(path.dirname(sdkroot)));
736+
const platformManifest = path.join(platformPath, "Info.plist");
737+
if ((await pathExists(platformManifest)) !== true) {
738+
if (fallbackPath) {
739+
return fallbackPath;
740+
}
741+
vscode.window.showWarningMessage(
742+
`${type} not found due to non-standardized library layout. Tests explorer won't work as expected.`
743+
);
744+
return undefined;
745+
}
746+
const data = await fs.readFile(platformManifest, "utf8");
747+
let infoPlist;
748+
try {
749+
infoPlist = plist.parse(data) as unknown as InfoPlist;
750+
} catch (error) {
751+
vscode.window.showWarningMessage(`Unable to parse ${platformManifest}: ${error}`);
752+
return undefined;
753+
}
754+
const plistKey = type === "XCTest" ? "XCTEST_VERSION" : "SWIFT_TESTING_VERSION";
755+
const version = infoPlist.DefaultProperties[plistKey];
756+
if (!version) {
757+
throw Error(`Info.plist is missing the ${plistKey} key.`);
758+
}
759+
760+
if (swiftVersion.isGreaterThanOrEqual(new Version(5, 7, 0))) {
761+
let bindir: string;
762+
const arch = targetInfo.target?.triple.split("-", 1)[0];
763+
switch (arch) {
764+
case "x86_64":
765+
bindir = "bin64";
766+
break;
767+
case "i686":
768+
bindir = "bin32";
769+
break;
770+
case "aarch64":
771+
bindir = "bin64a";
772+
break;
773+
default:
774+
throw Error(`unsupported architecture ${arch}`);
775+
}
776+
return path.join(
777+
platformPath,
778+
"Developer",
779+
"Library",
780+
`${type}-${version}`,
781+
"usr",
782+
bindir
783+
);
784+
} else {
785+
return path.join(
786+
platformPath,
787+
"Developer",
788+
"Library",
789+
`${type}-${version}`,
790+
"usr",
791+
"bin"
792+
);
793+
}
794+
}
795+
740796
/** @returns swift target info */
741797
private static async getSwiftTargetInfo(): Promise<SwiftTargetInfo> {
742798
try {

test/suite/tasks/SwiftTaskProvider.test.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ suite("SwiftTaskProvider Test Suite", () => {
6868
new SwiftToolchain(
6969
"/invalid/swift/path",
7070
"/invalid/toolchain/path",
71-
"1.2.3",
71+
{
72+
compilerVersion: "1.2.3",
73+
paths: {
74+
runtimeLibraryPaths: [],
75+
},
76+
},
7277
new Version(1, 2, 3)
7378
)
7479
);

0 commit comments

Comments
 (0)