Skip to content

Commit 2a98336

Browse files
committed
Fix Swiftly toolchain management and selection support
1 parent 506762c commit 2a98336

File tree

3 files changed

+83
-24
lines changed

3 files changed

+83
-24
lines changed

src/toolchain/swiftly.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ const ListResult = z.object({
2727
isDefault: z.boolean(),
2828
version: z.discriminatedUnion("type", [
2929
z.object({
30-
major: z.number(),
31-
minor: z.number(),
30+
major: z.number().optional(),
31+
minor: z.number().optional(),
3232
patch: z.number().optional(),
3333
name: z.string(),
3434
type: z.literal("stable"),
3535
}),
3636
z.object({
37-
major: z.number(),
38-
minor: z.number(),
37+
major: z.number().optional(),
38+
minor: z.number().optional(),
3939
branch: z.string(),
4040
date: z.string(),
4141
name: z.string(),
@@ -134,7 +134,7 @@ export class Swiftly {
134134
return process.platform === "linux" || process.platform === "darwin";
135135
}
136136

137-
public static async inUseLocation(swiftlyPath: string, cwd?: vscode.Uri) {
137+
public static async inUseLocation(swiftlyPath: string = "swiftly", cwd?: vscode.Uri) {
138138
const { stdout: inUse } = await execFile(swiftlyPath, ["use", "--print-location"], {
139139
cwd: cwd?.fsPath,
140140
});
@@ -182,6 +182,36 @@ export class Swiftly {
182182
return undefined;
183183
}
184184

185+
/**
186+
* Returns the home directory for Swiftly.
187+
*
188+
* @returns The path to the Swiftly home directory.
189+
*/
190+
static getHomeDir(): string | undefined {
191+
return process.env["SWIFTLY_HOME_DIR"];
192+
}
193+
194+
/**
195+
* Returns the directory where Swift binaries managed by Swiftly are installed.
196+
* This is a placeholder method and should be implemented based on your environment.
197+
*
198+
* @returns The path to the Swiftly binaries directory.
199+
*/
200+
static getBinDir(): string {
201+
const overriddenBinDir = process.env["SWIFTLY_BIN_DIR"];
202+
if (overriddenBinDir) {
203+
return overriddenBinDir;
204+
}
205+
206+
// If SWIFTLY_BIN_DIR is not set, use the default location based on SWIFTLY_HOME_DIR
207+
// This assumes that the binaries are located in the "bin" subdirectory of SWIFTLY_HOME_DIR
208+
const swiftlyHomeDir = Swiftly.getHomeDir();
209+
if (!swiftlyHomeDir) {
210+
throw new Error("Swiftly is not installed or SWIFTLY_HOME_DIR is not set.");
211+
}
212+
return path.join(swiftlyHomeDir, "bin");
213+
}
214+
185215
/**
186216
* Reads the Swiftly configuration file, if it exists.
187217
*

src/toolchain/toolchain.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ export class SwiftToolchain {
112112
public customSDK?: string,
113113
public xcTestPath?: string,
114114
public swiftTestingPath?: string,
115-
public swiftPMTestingHelperPath?: string
115+
public swiftPMTestingHelperPath?: string,
116+
public isSwiftlyManaged: boolean = false // true if this toolchain is managed by Swiftly
116117
) {
117118
this.swiftVersionString = targetInfo.compilerVersion;
118119
}
@@ -121,7 +122,10 @@ export class SwiftToolchain {
121122
folder?: vscode.Uri,
122123
outputChannel?: vscode.OutputChannel
123124
): Promise<SwiftToolchain> {
124-
const swiftFolderPath = await this.getSwiftFolderPath(folder, outputChannel);
125+
const { path: swiftFolderPath, isSwiftlyManaged } = await this.getSwiftFolderPath(
126+
folder,
127+
outputChannel
128+
);
125129
const toolchainPath = await this.getToolchainPath(swiftFolderPath, folder, outputChannel);
126130
const targetInfo = await this.getSwiftTargetInfo(
127131
this._getToolchainExecutable(toolchainPath, "swift")
@@ -159,7 +163,8 @@ export class SwiftToolchain {
159163
customSDK,
160164
xcTestPath,
161165
swiftTestingPath,
162-
swiftPMTestingHelperPath
166+
swiftPMTestingHelperPath,
167+
isSwiftlyManaged
163168
);
164169
}
165170

@@ -518,7 +523,7 @@ export class SwiftToolchain {
518523
private static async getSwiftFolderPath(
519524
cwd?: vscode.Uri,
520525
outputChannel?: vscode.OutputChannel
521-
): Promise<string> {
526+
): Promise<{ path: string; isSwiftlyManaged: boolean }> {
522527
try {
523528
let swift: string;
524529
if (configuration.path !== "") {
@@ -564,18 +569,24 @@ export class SwiftToolchain {
564569
}
565570
// swift may be a symbolic link
566571
let realSwift = await fs.realpath(swift);
572+
let isSwiftlyManaged = false;
573+
567574
if (path.basename(realSwift) === "swiftly") {
568575
try {
569576
const inUse = await Swiftly.inUseLocation(realSwift, cwd);
570577
if (inUse) {
571578
realSwift = path.join(inUse, "usr", "bin", "swift");
579+
isSwiftlyManaged = true;
572580
}
573581
} catch {
574582
// Ignore, will fall back to original path
575583
}
576584
}
577585
const swiftPath = expandFilePathTilde(path.dirname(realSwift));
578-
return await this.getSwiftEnvPath(swiftPath);
586+
return {
587+
path: await this.getSwiftEnvPath(swiftPath),
588+
isSwiftlyManaged,
589+
};
579590
} catch (error) {
580591
outputChannel?.appendLine(`Failed to find swift executable: ${error}`);
581592
throw Error("Failed to find swift executable");

src/ui/ToolchainSelection.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,25 +98,27 @@ export async function selectToolchain() {
9898
}
9999

100100
/** A {@link vscode.QuickPickItem} that contains the path to an installed Swift toolchain */
101-
type SwiftToolchainItem = PublicSwiftToolchainItem | XcodeToolchainItem;
101+
type SwiftToolchainItem = PublicSwiftToolchainItem | XcodeToolchainItem | SwiftlyToolchainItem;
102102

103103
/** Common properties for a {@link vscode.QuickPickItem} that represents a Swift toolchain */
104104
interface BaseSwiftToolchainItem extends vscode.QuickPickItem {
105105
type: "toolchain";
106-
toolchainPath: string;
107-
swiftFolderPath: string;
108106
onDidSelect?(): Promise<void>;
109107
}
110108

111109
/** A {@link vscode.QuickPickItem} for a Swift toolchain that has been installed manually */
112110
interface PublicSwiftToolchainItem extends BaseSwiftToolchainItem {
113-
category: "public" | "swiftly";
111+
category: "public";
112+
toolchainPath: string;
113+
swiftFolderPath: string;
114114
}
115115

116116
/** A {@link vscode.QuickPickItem} for a Swift toolchain provided by an installed Xcode application */
117117
interface XcodeToolchainItem extends BaseSwiftToolchainItem {
118118
category: "xcode";
119119
xcodePath: string;
120+
toolchainPath: string;
121+
swiftFolderPath: string;
120122
}
121123

122124
/** A {@link vscode.QuickPickItem} that performs an action for the user */
@@ -125,6 +127,11 @@ interface ActionItem extends vscode.QuickPickItem {
125127
run(): Promise<void>;
126128
}
127129

130+
interface SwiftlyToolchainItem extends BaseSwiftToolchainItem {
131+
category: "swiftly";
132+
version: string;
133+
}
134+
128135
/** A {@link vscode.QuickPickItem} that separates items in the UI */
129136
class SeparatorItem implements vscode.QuickPickItem {
130137
readonly type = "separator";
@@ -178,7 +185,6 @@ async function getQuickPickItems(
178185
type: "toolchain",
179186
category: "public",
180187
label: path.basename(toolchainPath, ".xctoolchain"),
181-
detail: toolchainPath,
182188
toolchainPath: path.join(toolchainPath, "usr"),
183189
swiftFolderPath: path.join(toolchainPath, "usr", "bin"),
184190
};
@@ -195,18 +201,28 @@ async function getQuickPickItems(
195201
// Find any Swift toolchains installed via Swiftly
196202
const swiftlyToolchains = (await Swiftly.listAvailableToolchains())
197203
.reverse()
198-
.map<SwiftToolchainItem>(toolchainPath => ({
204+
.map<SwiftlyToolchainItem>(toolchainPath => ({
199205
type: "toolchain",
200-
category: "swiftly",
201206
label: path.basename(toolchainPath),
202-
detail: toolchainPath,
203-
toolchainPath: path.join(toolchainPath, "usr"),
204-
swiftFolderPath: path.join(toolchainPath, "usr", "bin"),
207+
category: "swiftly",
208+
version: path.basename(toolchainPath),
205209
}));
206210
// Mark which toolchain is being actively used
207211
if (activeToolchain) {
208212
const toolchainInUse = [...xcodes, ...toolchains, ...swiftlyToolchains].find(toolchain => {
209-
return toolchain.toolchainPath === activeToolchain.toolchainPath;
213+
if (activeToolchain.isSwiftlyManaged) {
214+
if (toolchain.category !== "swiftly") {
215+
return false;
216+
}
217+
218+
// For Swiftly toolchains, check if the label matches the active toolchain version
219+
return toolchain.label === activeToolchain.swiftVersion.toString();
220+
}
221+
// For non-Swiftly toolchains, check if the toolchain path matches
222+
return (
223+
(toolchain as PublicSwiftToolchainItem | XcodeToolchainItem).toolchainPath ===
224+
activeToolchain.toolchainPath
225+
);
210226
});
211227
if (toolchainInUse) {
212228
toolchainInUse.description = "$(check) in use";
@@ -304,19 +320,21 @@ export async function showToolchainSelectionQuickPick(activeToolchain: SwiftTool
304320
}
305321
}
306322
// Update the toolchain path
307-
let swiftPath = selected.swiftFolderPath;
323+
let swiftPath: string;
308324

309325
// Handle Swiftly toolchains specially
310326
if (selected.category === "swiftly") {
311327
try {
312328
// Run swiftly use <version> and get the path to the toolchain
313329
await Swiftly.use(selected.label);
314-
const inUseLocation = await Swiftly.inUseLocation("swiftly");
315-
swiftPath = path.join(inUseLocation, "usr", "bin");
330+
swiftPath = Swiftly.getBinDir();
316331
} catch (error) {
317332
void vscode.window.showErrorMessage(`Failed to switch Swiftly toolchain: ${error}`);
318333
return;
319334
}
335+
} else {
336+
// For non-Swiftly toolchains, use the swiftFolderPath
337+
swiftPath = (selected as PublicSwiftToolchainItem | XcodeToolchainItem).swiftFolderPath;
320338
}
321339

322340
const isUpdated = await setToolchainPath(swiftPath, developerDir);

0 commit comments

Comments
 (0)