Skip to content

Support different toolchains per folder #1478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
18 changes: 14 additions & 4 deletions src/FolderContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { BackgroundCompilation } from "./BackgroundCompilation";
import { TaskQueue } from "./tasks/TaskQueue";
import { isPathInsidePath } from "./utilities/filesystem";
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
import { SwiftToolchain } from "./toolchain/toolchain";

export class FolderContext implements vscode.Disposable {
private packageWatcher: PackageWatcher;
Expand All @@ -39,13 +40,13 @@ export class FolderContext implements vscode.Disposable {
*/
private constructor(
public folder: vscode.Uri,
public toolchain: SwiftToolchain,
public linuxMain: LinuxMain,
public swiftPackage: SwiftPackage,
public workspaceFolder: vscode.WorkspaceFolder,
public workspaceContext: WorkspaceContext
) {
this.packageWatcher = new PackageWatcher(this, workspaceContext);
this.packageWatcher.install();
this.backgroundCompilation = new BackgroundCompilation(this);
this.taskQueue = new TaskQueue(this);
}
Expand All @@ -72,17 +73,19 @@ export class FolderContext implements vscode.Disposable {
const statusItemText = `Loading Package (${FolderContext.uriName(folder)})`;
workspaceContext.statusItem.start(statusItemText);

const toolchain = await SwiftToolchain.create(folder);
const { linuxMain, swiftPackage } =
await workspaceContext.statusItem.showStatusWhileRunning(statusItemText, async () => {
const linuxMain = await LinuxMain.create(folder);
const swiftPackage = await SwiftPackage.create(folder, workspaceContext.toolchain);
const swiftPackage = await SwiftPackage.create(folder, toolchain);
return { linuxMain, swiftPackage };
});

workspaceContext.statusItem.end(statusItemText);

const folderContext = new FolderContext(
folder,
toolchain,
linuxMain,
swiftPackage,
workspaceFolder,
Expand All @@ -100,6 +103,9 @@ export class FolderContext implements vscode.Disposable {
);
}

// Start watching for changes to Package.swift, Package.resolved and .swift-version
await folderContext.packageWatcher.install();

return folderContext;
}

Expand All @@ -120,9 +126,13 @@ export class FolderContext implements vscode.Disposable {
return this.workspaceFolder.uri === this.folder;
}

get swiftVersion() {
return this.toolchain.swiftVersion;
}

/** reload swift package for this folder */
async reload() {
await this.swiftPackage.reload(this.workspaceContext.toolchain);
await this.swiftPackage.reload(this.toolchain);
}

/** reload Package.resolved for this folder */
Expand All @@ -137,7 +147,7 @@ export class FolderContext implements vscode.Disposable {

/** Load Swift Plugins and store in Package */
async loadSwiftPlugins(outputChannel: SwiftOutputChannel) {
await this.swiftPackage.loadSwiftPlugins(this.workspaceContext.toolchain, outputChannel);
await this.swiftPackage.loadSwiftPlugins(this.toolchain, outputChannel);
}

/**
Expand Down
46 changes: 45 additions & 1 deletion src/PackageWatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
//
//===----------------------------------------------------------------------===//

import * as path from "path";
import * as fs from "fs/promises";
import * as vscode from "vscode";
import { FolderContext } from "./FolderContext";
import { FolderOperation, WorkspaceContext } from "./WorkspaceContext";
import { BuildFlags } from "./toolchain/BuildFlags";
import { Version } from "./utilities/version";

/**
* Watches for changes to **Package.swift** and **Package.resolved**.
Expand All @@ -28,6 +31,8 @@ export class PackageWatcher {
private resolvedFileWatcher?: vscode.FileSystemWatcher;
private workspaceStateFileWatcher?: vscode.FileSystemWatcher;
private snippetWatcher?: vscode.FileSystemWatcher;
private swiftVersionFileWatcher?: vscode.FileSystemWatcher;
private currentVersion?: Version;

constructor(
private folderContext: FolderContext,
Expand All @@ -38,11 +43,12 @@ export class PackageWatcher {
* Creates and installs {@link vscode.FileSystemWatcher file system watchers} for
* **Package.swift** and **Package.resolved**.
*/
install() {
async install() {
this.packageFileWatcher = this.createPackageFileWatcher();
this.resolvedFileWatcher = this.createResolvedFileWatcher();
this.workspaceStateFileWatcher = this.createWorkspaceStateFileWatcher();
this.snippetWatcher = this.createSnippetFileWatcher();
this.swiftVersionFileWatcher = await this.createSwiftVersionFileWatcher();
}

/**
Expand All @@ -54,6 +60,7 @@ export class PackageWatcher {
this.resolvedFileWatcher?.dispose();
this.workspaceStateFileWatcher?.dispose();
this.snippetWatcher?.dispose();
this.swiftVersionFileWatcher?.dispose();
}

private createPackageFileWatcher(): vscode.FileSystemWatcher {
Expand Down Expand Up @@ -99,6 +106,43 @@ export class PackageWatcher {
return watcher;
}

private async createSwiftVersionFileWatcher(): Promise<vscode.FileSystemWatcher> {
const watcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this.folderContext.folder, ".swift-version")
);
watcher.onDidCreate(async () => await this.handleSwiftVersionFileChange());
watcher.onDidChange(async () => await this.handleSwiftVersionFileChange());
watcher.onDidDelete(async () => await this.handleSwiftVersionFileChange());
this.currentVersion =
(await this.readSwiftVersionFile()) ?? this.folderContext.toolchain.swiftVersion;
return watcher;
}

async handleSwiftVersionFileChange() {
try {
const version = await this.readSwiftVersionFile();
if (version && version.toString() !== this.currentVersion?.toString()) {
this.workspaceContext.fireEvent(
this.folderContext,
FolderOperation.swiftVersionUpdated
);
}
this.currentVersion = version ?? this.folderContext.toolchain.swiftVersion;
} catch {
// do nothing
}
}

private async readSwiftVersionFile() {
const versionFile = path.join(this.folderContext.folder.fsPath, ".swift-version");
try {
const contents = await fs.readFile(versionFile);
return Version.fromString(contents.toString().trim());
} catch {
return undefined;
}
}

/**
* Handles a create or change event for **Package.swift**.
*
Expand Down
6 changes: 3 additions & 3 deletions src/SwiftSnippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import { TaskOperation } from "./tasks/TaskQueue";
*/
export function setSnippetContextKey(ctx: WorkspaceContext) {
if (
ctx.swiftVersion.isLessThan({ major: 5, minor: 7, patch: 0 }) ||
!ctx.currentFolder ||
!ctx.currentDocument
!ctx.currentDocument ||
ctx.currentFolder.swiftVersion.isLessThan({ major: 5, minor: 7, patch: 0 })
) {
contextKeys.fileIsSnippet = false;
return;
Expand Down Expand Up @@ -97,7 +97,7 @@ export async function debugSnippetWithOptions(
reveal: vscode.TaskRevealKind.Always,
},
},
ctx.toolchain
folderContext.toolchain
);
const snippetDebugConfig = createSnippetConfiguration(snippetName, folderContext);
try {
Expand Down
16 changes: 8 additions & 8 deletions src/TestExplorer/TestExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ export class TestExplorer {
this.onDidCreateTestRunEmitter
);

this.lspTestDiscovery = new LSPTestDiscovery(
folderContext.workspaceContext.languageClientManager
);
const workspaceContext = folderContext.workspaceContext;
const languageClientManager = workspaceContext.languageClientManager.get(folderContext);
this.lspTestDiscovery = new LSPTestDiscovery(languageClientManager);

// add end of task handler to be called whenever a build task has finished. If
// it is the build task for this folder then update the tests
Expand Down Expand Up @@ -169,10 +169,10 @@ export class TestExplorer {
break;
case FolderOperation.focus:
if (folder) {
workspace.languageClientManager.documentSymbolWatcher = (
document,
symbols
) => TestExplorer.onDocumentSymbols(folder, document, symbols);
const languageClientManager =
workspace.languageClientManager.get(folder);
languageClientManager.documentSymbolWatcher = (document, symbols) =>
TestExplorer.onDocumentSymbols(folder, document, symbols);
}
}
}
Expand Down Expand Up @@ -289,7 +289,7 @@ export class TestExplorer {
}
});
}
const toolchain = explorer.folderContext.workspaceContext.toolchain;
const toolchain = explorer.folderContext.toolchain;
// get build options before build is run so we can be sure they aren't changed
// mid-build
const testBuildOptions = buildOptions(toolchain);
Expand Down
6 changes: 3 additions & 3 deletions src/TestExplorer/TestRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ export class TestRunner {
this.xcTestOutputParser =
testKind === TestKind.parallel
? new ParallelXCTestOutputParser(
this.folderContext.workspaceContext.toolchain.hasMultiLineParallelTestOutput
this.folderContext.toolchain.hasMultiLineParallelTestOutput
)
: new XCTestOutputParser();
this.swiftTestOutputParser = new SwiftTestingOutputParser(
Expand Down Expand Up @@ -757,7 +757,7 @@ export class TestRunner {
prefix: this.folderContext.name,
presentationOptions: { reveal: vscode.TaskRevealKind.Never },
},
this.folderContext.workspaceContext.toolchain,
this.folderContext.toolchain,
{ ...process.env, ...testBuildConfig.env },
{ readOnlyTerminal: process.platform !== "win32" }
);
Expand Down Expand Up @@ -842,7 +842,7 @@ export class TestRunner {

const buffer = await asyncfs.readFile(filename, "utf8");
const xUnitParser = new TestXUnitParser(
this.folderContext.workspaceContext.toolchain.hasMultiLineParallelTestOutput
this.folderContext.toolchain.hasMultiLineParallelTestOutput
);
const results = await xUnitParser.parse(
buffer,
Expand Down
42 changes: 23 additions & 19 deletions src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { StatusItem } from "./ui/StatusItem";
import { SwiftOutputChannel } from "./ui/SwiftOutputChannel";
import { swiftLibraryPathKey } from "./utilities/utilities";
import { isPathInsidePath } from "./utilities/filesystem";
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
import { LanguageClientToolchainCoordinator } from "./sourcekit-lsp/LanguageClientToolchainCoordinator";
import { TemporaryFolder } from "./utilities/tempFolder";
import { TaskManager } from "./tasks/TaskManager";
import { makeDebugConfigurations } from "./debugger/launch";
Expand All @@ -45,7 +45,7 @@ export class WorkspaceContext implements vscode.Disposable {
public currentDocument: vscode.Uri | null;
public statusItem: StatusItem;
public buildStatus: SwiftBuildStatus;
public languageClientManager: LanguageClientManager;
public languageClientManager: LanguageClientToolchainCoordinator;
public tasks: TaskManager;
public diagnostics: DiagnosticsManager;
public subscriptions: vscode.Disposable[];
Expand All @@ -69,11 +69,11 @@ export class WorkspaceContext implements vscode.Disposable {
extensionContext: vscode.ExtensionContext,
public tempFolder: TemporaryFolder,
public outputChannel: SwiftOutputChannel,
public toolchain: SwiftToolchain
public globalToolchain: SwiftToolchain
) {
this.statusItem = new StatusItem();
this.buildStatus = new SwiftBuildStatus(this.statusItem);
this.languageClientManager = new LanguageClientManager(this);
this.languageClientManager = new LanguageClientToolchainCoordinator(this);
this.tasks = new TaskManager(this);
this.diagnostics = new DiagnosticsManager(this);
this.documentation = new DocumentationManager(extensionContext, this);
Expand Down Expand Up @@ -200,8 +200,8 @@ export class WorkspaceContext implements vscode.Disposable {
this.subscriptions.length = 0;
}

get swiftVersion() {
return this.toolchain.swiftVersion;
get globalToolchainSwiftVersion() {
return this.globalToolchain.swiftVersion;
}

/** Get swift version and create WorkspaceContext */
Expand Down Expand Up @@ -239,19 +239,21 @@ export class WorkspaceContext implements vscode.Disposable {
contextKeys.currentTargetType = undefined;
}

// Set context keys that depend on features from SourceKit-LSP
this.languageClientManager.useLanguageClient(async client => {
const experimentalCaps = client.initializeResult?.capabilities.experimental;
if (!experimentalCaps) {
contextKeys.supportsReindexing = false;
contextKeys.supportsDocumentationLivePreview = false;
return;
}
contextKeys.supportsReindexing =
experimentalCaps[ReIndexProjectRequest.method] !== undefined;
contextKeys.supportsDocumentationLivePreview =
experimentalCaps[DocCDocumentationRequest.method] !== undefined;
});
if (this.currentFolder) {
const languageClient = this.languageClientManager.get(this.currentFolder);
languageClient.useLanguageClient(async client => {
const experimentalCaps = client.initializeResult?.capabilities.experimental;
if (!experimentalCaps) {
contextKeys.supportsReindexing = false;
contextKeys.supportsDocumentationLivePreview = false;
return;
}
contextKeys.supportsReindexing =
experimentalCaps[ReIndexProjectRequest.method] !== undefined;
contextKeys.supportsDocumentationLivePreview =
experimentalCaps[DocCDocumentationRequest.method] !== undefined;
});
}

setSnippetContextKey(this);
}
Expand Down Expand Up @@ -637,6 +639,8 @@ export enum FolderOperation {
packageViewUpdated = "packageViewUpdated",
// Package plugins list has been updated
pluginsUpdated = "pluginsUpdated",
// The folder's swift toolchain version has been updated
swiftVersionUpdated = "swiftVersionUpdated",
}

/** Workspace Folder Event */
Expand Down
12 changes: 8 additions & 4 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
vscode.commands.registerCommand("swift.runScript", () => runSwiftScript(ctx)),
vscode.commands.registerCommand("swift.openPackage", () => {
if (ctx.currentFolder) {
return openPackage(ctx.toolchain.swiftVersion, ctx.currentFolder.folder);
return openPackage(ctx.currentFolder.swiftVersion, ctx.currentFolder.folder);
}
}),
vscode.commands.registerCommand(Commands.RUN_SNIPPET, target =>
Expand All @@ -146,9 +146,13 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
),
vscode.commands.registerCommand(Commands.RUN_PLUGIN_TASK, () => runPluginTask()),
vscode.commands.registerCommand(Commands.RUN_TASK, name => runTask(ctx, name)),
vscode.commands.registerCommand("swift.restartLSPServer", () =>
ctx.languageClientManager.restart()
),
vscode.commands.registerCommand("swift.restartLSPServer", async () => {
if (!ctx.currentFolder) {
return;
}
const languageClientManager = ctx.languageClientManager.get(ctx.currentFolder);
await languageClientManager.restart();
}),
vscode.commands.registerCommand("swift.reindexProject", () => reindexProject(ctx)),
vscode.commands.registerCommand("swift.insertFunctionComment", () =>
insertFunctionComment(ctx)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export async function folderCleanBuild(folderContext: FolderContext) {
presentationOptions: { reveal: vscode.TaskRevealKind.Silent },
group: vscode.TaskGroup.Clean,
},
folderContext.workspaceContext.toolchain
folderContext.toolchain
);

return await executeTaskWithUI(task, "Clean Build", folderContext);
Expand Down
Loading