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
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,14 @@
"command": "swift.previewDocumentation",
"when": "swift.supportsDocumentationLivePreview"
},
{
"command": "swift.installSwiftlyToolchain",
"when": "swift.supportsSwiftlyInstall"
},
{
"command": "swift.installSwiftlySnapshotToolchain",
"when": "swift.supportsSwiftlyInstall"
},
{
"command": "swift.createNewProject",
"when": "!swift.isActivated || swift.createNewProjectAvailable"
Expand Down
4 changes: 2 additions & 2 deletions src/commands/installSwiftlyToolchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export async function installSwiftlyToolchain(ctx: WorkspaceContext): Promise<vo
return;
}

const availableToolchains = await Swiftly.listAvailable(ctx.logger);
const availableToolchains = await Swiftly.listAvailable(undefined, ctx.logger);

if (availableToolchains.length === 0) {
ctx.logger?.debug("No toolchains available for installation via Swiftly.");
Expand Down Expand Up @@ -193,7 +193,7 @@ export async function installSwiftlySnapshotToolchain(ctx: WorkspaceContext): Pr
return; // User cancelled input
}

const availableToolchains = await Swiftly.listAvailable(ctx.logger, branch);
const availableToolchains = await Swiftly.listAvailable(branch, ctx.logger);

if (availableToolchains.length === 0) {
ctx.logger?.debug("No toolchains available for installation via Swiftly.");
Expand Down
19 changes: 19 additions & 0 deletions src/contextKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ export interface ContextKeys {
*/
supportsDocumentationLivePreview: boolean;

/**
* Whether the installed version of Swiftly can be used to install toolchains from within VS Code.
*/
supportsSwiftlyInstall: boolean;

/**
* Whether the swift.switchPlatform command is available.
*/
Expand All @@ -109,6 +114,7 @@ export function createContextKeys(): ContextKeys {
let createNewProjectAvailable: boolean = false;
let supportsReindexing: boolean = false;
let supportsDocumentationLivePreview: boolean = false;
let supportsSwiftlyInstall: boolean = false;
let switchPlatformAvailable: boolean = false;

return {
Expand Down Expand Up @@ -278,6 +284,19 @@ export function createContextKeys(): ContextKeys {
});
},

get supportsSwiftlyInstall() {
return supportsSwiftlyInstall;
},

set supportsSwiftlyInstall(value: boolean) {
supportsSwiftlyInstall = value;
void vscode.commands
.executeCommand("setContext", "swift.supportsSwiftlyInstall", value)
.then(() => {
/* Put in worker queue */
});
},

get switchPlatformAvailable() {
return switchPlatformAvailable;
},
Expand Down
2 changes: 2 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { SwiftLogger } from "./logging/SwiftLogger";
import { SwiftLoggerFactory } from "./logging/SwiftLoggerFactory";
import { SwiftEnvironmentVariablesManager, SwiftTerminalProfileProvider } from "./terminal";
import { SelectedXcodeWatcher } from "./toolchain/SelectedXcodeWatcher";
import { checkForSwiftlyInstallation } from "./toolchain/swiftly";
import { SwiftToolchain } from "./toolchain/toolchain";
import { LanguageStatusItems } from "./ui/LanguageStatusItems";
import { ProjectPanelProvider } from "./ui/ProjectPanelProvider";
Expand Down Expand Up @@ -69,6 +70,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<Api> {

const contextKeys = createContextKeys();
const toolchain = await createActiveToolchain(contextKeys, logger);
checkForSwiftlyInstallation(contextKeys, logger);

// If we don't have a toolchain, show an error and stop initializing the extension.
// This can happen if the user has not installed Swift or if the toolchain is not
Expand Down
88 changes: 71 additions & 17 deletions src/toolchain/swiftly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { z } from "zod/v4/mini";

// Import the reusable installation function
import { installSwiftlyToolchainVersion } from "../commands/installSwiftlyToolchain";
import { ContextKeys } from "../contextKeys";
import { SwiftLogger } from "../logging/SwiftLogger";
import { showMissingToolchainDialog } from "../ui/ToolchainSelection";
import { findBinaryPath } from "../utilities/shell";
Expand Down Expand Up @@ -220,11 +221,11 @@ export class Swiftly {
}

/**
* Finds the list of toolchains managed by Swiftly.
* Finds the list of toolchains installed via Swiftly.
*
* @returns an array of toolchain paths
* @returns an array of toolchain version names.
*/
public static async listAvailableToolchains(logger?: SwiftLogger): Promise<string[]> {
public static async list(logger?: SwiftLogger): Promise<string[]> {
if (!this.isSupported()) {
return [];
}
Expand All @@ -235,13 +236,13 @@ export class Swiftly {
}

if (!(await Swiftly.supportsJsonOutput(logger))) {
return await Swiftly.getToolchainInstallLegacy(logger);
return await Swiftly.listFromSwiftlyConfig(logger);
}

return await Swiftly.getListAvailableToolchains(logger);
return await Swiftly.listUsingJSONFormat(logger);
}

private static async getListAvailableToolchains(logger?: SwiftLogger): Promise<string[]> {
private static async listUsingJSONFormat(logger?: SwiftLogger): Promise<string[]> {
try {
const { stdout } = await execFile("swiftly", ["list", "--format=json"]);
const response = ListResult.parse(JSON.parse(stdout));
Expand All @@ -254,7 +255,7 @@ export class Swiftly {
}
}

private static async getToolchainInstallLegacy(logger?: SwiftLogger) {
private static async listFromSwiftlyConfig(logger?: SwiftLogger) {
try {
const swiftlyHomeDir: string | undefined = process.env["SWIFTLY_HOME_DIR"];
if (!swiftlyHomeDir) {
Expand All @@ -279,17 +280,32 @@ export class Swiftly {
}
}

/**
* Checks whether or not the current operating system supports Swiftly.
*/
public static isSupported() {
return process.platform === "linux" || process.platform === "darwin";
}

/**
* Retrieves the location of the toolchain that is currently in use by Swiftly.
*
* @param swiftlyPath Optional path to the Swiftly binary.
* @param cwd Optional current working directory to check within.
*/
public static async inUseLocation(swiftlyPath: string = "swiftly", cwd?: vscode.Uri) {
const { stdout: inUse } = await execFile(swiftlyPath, ["use", "--print-location"], {
cwd: cwd?.fsPath,
});
return inUse.trimEnd();
}

/**
* Retrieves the version name of the toolchain that is currently in use by Swiftly.
*
* @param swiftlyPath Optional path to the Swiftly binary.
* @param cwd Optional current working directory to check within.
*/
public static async inUseVersion(
swiftlyPath: string = "swiftly",
cwd?: vscode.Uri
Expand All @@ -309,6 +325,11 @@ export class Swiftly {
return result.version;
}

/**
* Instructs Swiftly to use a specific version of the Swift toolchain.
*
* @param version The version name to use. Obtainable via {@link Swiftly.list}.
*/
public static async use(version: string): Promise<void> {
if (!this.isSupported()) {
throw new Error("Swiftly is not supported on this platform");
Expand All @@ -319,6 +340,7 @@ export class Swiftly {
/**
* Determine if Swiftly is being used to manage the active toolchain and if so, return
* the path to the active toolchain.
*
* @returns The location of the active toolchain if swiftly is being used to manage it.
*/
public static async toolchain(
Expand Down Expand Up @@ -383,15 +405,15 @@ export class Swiftly {
}

/**
* Lists all toolchains available for installation from swiftly
* Lists all toolchains available for installation from swiftly.
*
* @param branch Optional branch to filter available toolchains (e.g., "main" for snapshots)
* @param logger Optional logger for error reporting
* @returns Array of available toolchains
* @param branch Optional branch to filter available toolchains (e.g., "main" for snapshots).
* @param logger Optional logger for error reporting.
* @returns Array of available toolchains.
*/
public static async listAvailable(
logger?: SwiftLogger,
branch?: string
branch?: string,
logger?: SwiftLogger
): Promise<SwiftlyToolchain[]> {
if (!this.isSupported()) {
return [];
Expand Down Expand Up @@ -425,11 +447,11 @@ export class Swiftly {
}

/**
* Installs a toolchain via swiftly with optional progress tracking
* Installs a toolchain via swiftly with optional progress tracking.
*
* @param version The toolchain version to install
* @param progressCallback Optional callback that receives progress data as JSON objects
* @param logger Optional logger for error reporting
* @param version The toolchain version to install.
* @param progressCallback Optional callback that receives progress data as JSON objects.
* @param logger Optional logger for error reporting.
*/
public static async installToolchain(
version: string,
Expand Down Expand Up @@ -742,6 +764,9 @@ export class Swiftly {
return JSON.parse(swiftlyConfigRaw);
}

/**
* Checks whether or not Swiftly is installed on the current system.
*/
public static async isInstalled(): Promise<boolean> {
if (!this.isSupported()) {
return false;
Expand All @@ -754,3 +779,32 @@ export class Swiftly {
}
}
}

/**
* Checks whether or not Swiftly is installed and updates context keys appropriately.
*/
export function checkForSwiftlyInstallation(contextKeys: ContextKeys, logger: SwiftLogger): void {
contextKeys.supportsSwiftlyInstall = false;
if (!Swiftly.isSupported()) {
logger.debug(`Swiftly is not available on ${process.platform}`);
return;
}
// Don't block while checking the Swiftly insallation.
void Swiftly.isInstalled().then(async isInstalled => {
if (!isInstalled) {
logger.debug("Swiftly is not installed on this system.");
return;
}
const version = await Swiftly.version(logger);
if (!version) {
logger.warn("Unable to determine Swiftly version.");
return;
}
logger.debug(`Detected Swiftly version ${version}.`);
contextKeys.supportsSwiftlyInstall = version.isGreaterThanOrEqual({
major: 1,
minor: 1,
patch: 0,
});
});
}
Loading
Loading