Skip to content

Toolchain selection #817

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

Merged
merged 27 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7551f68
allow empty SwiftToolchain in WorkspaceContext
matthewbastien Apr 25, 2024
2fada2d
add toolchain selection command
matthewbastien May 21, 2024
2544f08
add documentation to public functions
matthewbastien May 21, 2024
516dd67
clean up in use toolchain message
matthewbastien May 22, 2024
e8759ae
fix swiftly toolchain selection on Linux
matthewbastien May 22, 2024
c15e907
fix maxOS toolchain selection and clean up some code
matthewbastien May 22, 2024
aef544f
fallback to xcrun if lldb executable cannot be found
matthewbastien May 22, 2024
448de08
address Paul's comments
matthewbastien May 23, 2024
d12ada6
only create a WorkspaceContext if a valid toolchain is available
matthewbastien May 23, 2024
7879d1a
fix build errors
matthewbastien May 23, 2024
cac44b8
set xctest directory correctly if using an Xcode toolchain
matthewbastien May 24, 2024
7c521f3
capitalize Swift in UI messages
matthewbastien May 24, 2024
0daad57
address some review comments
matthewbastien May 27, 2024
32ecfe8
revert checks for toolchain being undefined
matthewbastien May 28, 2024
b07b1ee
Merge branch 'swift-server:main' into toolchain-selection
matthewbastien May 28, 2024
a2a6ced
fix compile error
matthewbastien May 28, 2024
2c170c4
revert changing null to undefined
matthewbastien May 28, 2024
f1cfb1c
reword the warning notification when the user selects Download Toolchain
matthewbastien May 28, 2024
3bfc9d2
Add Swiftly install action for linux
matthewbastien May 28, 2024
7b24bd5
remove swift.selectXcodeDeveloperDir command
matthewbastien May 28, 2024
4a425eb
reduce the complexity of command registration on startup
matthewbastien May 28, 2024
6acc27b
create new project should be unavailable without a toolchain
matthewbastien May 28, 2024
6905f21
add warning that workspace settings take precedence over user settings
matthewbastien May 29, 2024
9247d97
rename toolchain selection function for clarity
matthewbastien May 29, 2024
12bc1bd
minor rename for clarity
matthewbastien May 29, 2024
4d518a0
remove unused scope variable in setToolchainPath()
matthewbastien May 29, 2024
12dd707
Merge branch 'main' into toolchain-selection
matthewbastien May 30, 2024
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
28 changes: 20 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@
"category": "Swift"
},
{
"command": "swift.selectXcodeDeveloperDir",
"title": "Select Xcode Developer Dir...",
"command": "swift.selectToolchain",
"title": "Select Toolchain...",
"category": "Swift"
},
{
Expand Down Expand Up @@ -523,12 +523,28 @@
},
{
"command": "swift.switchPlatform",
"when": "isMac"
"when": "swift.isActivated && isMac"
},
{
"command": "swift.insertFunctionComment",
"when": "swift.isActivated"
},
{
"command": "swift.resetPackage",
"when": "swift.hasPackage"
},
{
"command": "swift.restartLSPServer",
"when": "swift.isActivated"
},
{
"command": "swift.showTestCoverageReport",
"when": "swift.isActivated"
},
{
"command": "swift.toggleTestCoverage",
"when": "swift.isActivated"
},
{
"command": "swift.openPackage",
"when": "swift.hasPackage"
Expand All @@ -553,10 +569,6 @@
"command": "swift.openExternal",
"when": "false"
},
{
"command": "swift.selectXcodeDeveloperDir",
"when": "isMac"
},
{
"command": "swift.run",
"when": "editorLangId == swift && swift.currentTargetType == 'executable'"
Expand Down Expand Up @@ -1148,4 +1160,4 @@
"vscode-languageclient": "^9.0.1",
"xml2js": "^0.6.2"
}
}
}
49 changes: 8 additions & 41 deletions src/WorkspaceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
import { getLLDBLibPath } from "./debugger/lldb";
import { LanguageClientManager } from "./sourcekit-lsp/LanguageClientManager";
import { TemporaryFolder } from "./utilities/tempFolder";
import { SwiftToolchain } from "./toolchain/toolchain";
import { TaskManager } from "./tasks/TaskManager";
import { BackgroundCompilation } from "./BackgroundCompilation";
import { makeDebugConfigurations } from "./debugger/launch";
Expand All @@ -35,8 +34,8 @@ import contextKeys from "./contextKeys";
import { setSnippetContextKey } from "./SwiftSnippets";
import { CommentCompletionProviders } from "./editor/CommentCompletion";
import { DebugAdapter } from "./debugger/debugAdapter";
import { Version } from "./utilities/version";
import { SwiftBuildStatus } from "./ui/SwiftBuildStatus";
import { SwiftToolchain } from "./toolchain/toolchain";

/**
* Context for whole workspace. Holds array of contexts for each workspace folder
Expand All @@ -46,62 +45,28 @@ export class WorkspaceContext implements vscode.Disposable {
public folders: FolderContext[] = [];
public currentFolder: FolderContext | null | undefined;
public currentDocument: vscode.Uri | null;
public outputChannel: SwiftOutputChannel;
public statusItem: StatusItem;
public buildStatus: SwiftBuildStatus;
public languageClientManager: LanguageClientManager;
public tasks: TaskManager;
public subscriptions: { dispose(): unknown }[];
public subscriptions: vscode.Disposable[];
public commentCompletionProvider: CommentCompletionProviders;
private lastFocusUri: vscode.Uri | undefined;
private initialisationFinished = false;

private constructor(
public tempFolder: TemporaryFolder,
public outputChannel: SwiftOutputChannel,
public toolchain: SwiftToolchain
) {
this.outputChannel = new SwiftOutputChannel();
this.statusItem = new StatusItem();
this.buildStatus = new SwiftBuildStatus(this.statusItem);
this.languageClientManager = new LanguageClientManager(this);
this.outputChannel.log(this.toolchain.swiftVersionString);
this.toolchain.logDiagnostics(this.outputChannel);
this.tasks = new TaskManager(this);
this.currentDocument = null;
this.commentCompletionProvider = new CommentCompletionProviders();
contextKeys.createNewProjectAvailable = toolchain.swiftVersion.isGreaterThanOrEqual(
new Version(5, 8, 0)
);

const onChangeConfig = vscode.workspace.onDidChangeConfiguration(async event => {
// on toolchain config change, reload window
if (event.affectsConfiguration("swift.path")) {
vscode.window
.showInformationMessage(
"Changing the Swift path requires the project be reloaded.",
"Ok"
)
.then(selected => {
if (selected === "Ok") {
vscode.commands.executeCommand("workbench.action.reloadWindow");
}
});
}
// on sdk config change, restart sourcekit-lsp
if (event.affectsConfiguration("swift.SDK")) {
// FIXME: There is a bug stopping us from restarting SourceKit-LSP directly.
// As long as it's fixed we won't need to reload on newer versions.
vscode.window
.showInformationMessage(
"Changing the Swift SDK path requires the project be reloaded.",
"Ok"
)
.then(selected => {
if (selected === "Ok") {
vscode.commands.executeCommand("workbench.action.reloadWindow");
}
});
}
// on runtime path config change, regenerate launch.json
if (event.affectsConfiguration("swift.runtimePath")) {
if (!this.needToAutoGenerateLaunchConfig()) {
Expand Down Expand Up @@ -228,10 +193,12 @@ export class WorkspaceContext implements vscode.Disposable {
}

/** Get swift version and create WorkspaceContext */
static async create(): Promise<WorkspaceContext> {
static async create(
outputChannel: SwiftOutputChannel,
toolchain: SwiftToolchain
): Promise<WorkspaceContext> {
const tempFolder = await TemporaryFolder.create();
const toolchain = await SwiftToolchain.create();
return new WorkspaceContext(tempFolder, toolchain);
return new WorkspaceContext(tempFolder, outputChannel, toolchain);
}

/**
Expand Down
106 changes: 37 additions & 69 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { debugLaunchConfig, getLaunchConfiguration } from "./debugger/launch";
import { execFile } from "./utilities/utilities";
import { SwiftExecOperation, TaskOperation } from "./tasks/TaskQueue";
import { SwiftProjectTemplate } from "./toolchain/toolchain";
import { showToolchainSelectionQuickPick, showToolchainError } from "./ui/ToolchainSelection";

/**
* References:
Expand All @@ -40,6 +41,8 @@ import { SwiftProjectTemplate } from "./toolchain/toolchain";
* https://code.visualstudio.com/api/extension-guides/command
*/

export type WorkspaceContextWithToolchain = WorkspaceContext & { toolchain: SwiftToolchain };

/**
* Executes a {@link vscode.Task task} to resolve this package's dependencies.
*/
Expand Down Expand Up @@ -97,19 +100,27 @@ export async function updateDependencies(ctx: WorkspaceContext) {
* Prompts the user to input project details and then executes `swift package init`
* to create the project.
*/
export async function createNewProject(ctx: WorkspaceContext): Promise<void> {
export async function createNewProject(toolchain: SwiftToolchain | undefined): Promise<void> {
// It is possible for this command to be run without a valid toolchain because it can be
// run before the Swift extension is activated. Show the toolchain error notification in
// this case.
if (!toolchain) {
showToolchainError();
return;
}

// The context key `swift.createNewProjectAvailable` only works if the extension has been
// activated. As such, we also have to allow this command to run when no workspace is
// active. Show an error to the user if the command is unavailable.
if (!ctx.toolchain.swiftVersion.isGreaterThanOrEqual(new Version(5, 8, 0))) {
if (!toolchain.swiftVersion.isGreaterThanOrEqual(new Version(5, 8, 0))) {
vscode.window.showErrorMessage(
"Creating a new swift project is only available starting in swift version 5.8.0."
);
return;
}

// Prompt the user for the type of project they would like to create
const availableProjectTemplates = await ctx.toolchain.getProjectTemplates();
const availableProjectTemplates = await toolchain.getProjectTemplates();
const selectedProjectTemplate = await vscode.window.showQuickPick<
vscode.QuickPickItem & { type: SwiftProjectTemplate }
>(
Expand Down Expand Up @@ -186,7 +197,7 @@ export async function createNewProject(ctx: WorkspaceContext): Promise<void> {
async () => {
await execSwift(
["package", "init", "--type", projectType, "--name", projectName],
ctx.toolchain,
toolchain,
{
cwd: projectUri.fsPath,
}
Expand Down Expand Up @@ -630,7 +641,7 @@ async function openPackage(workspaceContext: WorkspaceContext) {
}
}

function insertFunctionComment(workspaceContext: WorkspaceContext) {
async function insertFunctionComment(workspaceContext: WorkspaceContext): Promise<void> {
const activeEditor = vscode.window.activeTextEditor;
if (!activeEditor) {
return;
Expand All @@ -640,8 +651,8 @@ function insertFunctionComment(workspaceContext: WorkspaceContext) {
}

/** Restart the SourceKit-LSP server */
function restartLSPServer(workspaceContext: WorkspaceContext) {
workspaceContext.languageClientManager.restart();
function restartLSPServer(workspaceContext: WorkspaceContext): Promise<void> {
return workspaceContext.languageClientManager.restart();
}

/** Execute task and show UI while running */
Expand Down Expand Up @@ -726,61 +737,9 @@ async function switchPlatform() {
);
}

/**
* Choose DEVELOPER_DIR
* @param workspaceContext
*/
async function selectXcodeDeveloperDir() {
const defaultXcode = await SwiftToolchain.getXcodeDeveloperDir();
const selectedXcode = configuration.swiftEnvironmentVariables.DEVELOPER_DIR;
const xcodes = await SwiftToolchain.getXcodeInstalls();
await withQuickPick(
selectedXcode ?? defaultXcode,
xcodes.map(xcode => {
const developerDir = `${xcode}/Contents/Developer`;
return {
label: developerDir === defaultXcode ? `${xcode} (default)` : xcode,
folder: developerDir === defaultXcode ? undefined : developerDir,
};
}),
async selected => {
let swiftEnv = configuration.swiftEnvironmentVariables;
const previousDeveloperDir = swiftEnv.DEVELOPER_DIR ?? defaultXcode;
if (selected.folder) {
swiftEnv.DEVELOPER_DIR = selected.folder;
} else if (swiftEnv.DEVELOPER_DIR) {
// if DEVELOPER_DIR was set and the new folder is the default then
// delete variable
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { DEVELOPER_DIR, ...rest } = swiftEnv;
swiftEnv = rest;
}
configuration.swiftEnvironmentVariables = swiftEnv;
// if SDK is inside previous DEVELOPER_DIR then move to new DEVELOPER_DIR
if (
configuration.sdk.length > 0 &&
configuration.sdk.startsWith(previousDeveloperDir)
) {
configuration.sdk = configuration.sdk.replace(
previousDeveloperDir,
selected.folder ?? defaultXcode
);
}
vscode.window
.showInformationMessage(
"Changing the Xcode Developer Directory requires the project be reloaded.",
"Ok"
)
.then(() => {
vscode.commands.executeCommand("workbench.action.reloadWindow");
});
}
);
}

async function attachDebugger(workspaceContext: WorkspaceContext) {
async function attachDebugger(ctx: WorkspaceContext) {
// use LLDB to get list of processes
const lldb = workspaceContext.toolchain.getLLDB();
const lldb = await ctx.toolchain.getLLDB();
try {
const { stdout } = await execFile(lldb, [
"--batch",
Expand Down Expand Up @@ -823,12 +782,24 @@ function updateAfterError(result: boolean, folderContext: FolderContext) {
}
}

export function registerToolchainCommands(
toolchain: SwiftToolchain | undefined
): vscode.Disposable[] {
return [
vscode.commands.registerCommand("swift.createNewProject", () =>
createNewProject(toolchain)
),
vscode.commands.registerCommand("swift.selectToolchain", () =>
showToolchainSelectionQuickPick(toolchain)
),
];
}

/**
* Registers this extension's commands in the given {@link vscode.ExtensionContext context}.
*/
export function register(ctx: WorkspaceContext) {
ctx.subscriptions.push(
vscode.commands.registerCommand("swift.createNewProject", () => createNewProject(ctx)),
export function register(ctx: WorkspaceContext): vscode.Disposable[] {
return [
vscode.commands.registerCommand("swift.resolveDependencies", () =>
resolveDependencies(ctx)
),
Expand Down Expand Up @@ -873,9 +844,6 @@ export function register(ctx: WorkspaceContext) {
openInExternalEditor(item);
}
}),
vscode.commands.registerCommand("swift.selectXcodeDeveloperDir", () =>
selectXcodeDeveloperDir()
),
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx))
);
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)),
];
}
7 changes: 6 additions & 1 deletion src/debugger/lldb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ import { SwiftToolchain } from "../toolchain/toolchain";
* @returns Library path for LLDB
*/
export async function getLLDBLibPath(toolchain: SwiftToolchain): Promise<Result<string>> {
const executable = toolchain.getLLDB();
let executable: string;
try {
executable = await toolchain.getLLDB();
} catch (error) {
return Result.makeFailure(error);
}
let pathHint = path.dirname(toolchain.swiftFolderPath);
try {
const statement = `print('<!' + lldb.SBHostOS.GetLLDBPath(lldb.ePathTypeLLDBShlibDir).fullpath + '!>')`;
Expand Down
Loading