Skip to content

Resolve settings variables in Swift settings #1439

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 1 commit into from
Mar 18, 2025
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
92 changes: 76 additions & 16 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import * as os from "os";
import * as path from "path";

export type DebugAdapters = "auto" | "lldb-dap" | "CodeLLDB";
export type SetupCodeLLDBOptions =
Expand Down Expand Up @@ -106,14 +108,17 @@ const configuration = {
get lsp(): LSPConfiguration {
return {
get serverPath(): string {
return vscode.workspace
.getConfiguration("swift.sourcekit-lsp")
.get<string>("serverPath", "");
return substituteVariablesInString(
vscode.workspace
.getConfiguration("swift.sourcekit-lsp")
.get<string>("serverPath", "")
);
},
get serverArguments(): string[] {
return vscode.workspace
.getConfiguration("swift.sourcekit-lsp")
.get<string[]>("serverArguments", []);
.get<string[]>("serverArguments", [])
.map(substituteVariablesInString);
},
get inlayHintsEnabled(): boolean {
return vscode.workspace
Expand Down Expand Up @@ -192,7 +197,8 @@ const configuration = {
get additionalTestArguments(): string[] {
return vscode.workspace
.getConfiguration("swift", workspaceFolder)
.get<string[]>("additionalTestArguments", []);
.get<string[]>("additionalTestArguments", [])
.map(substituteVariablesInString);
},
/** auto-generate launch.json configurations */
get autoGenerateLaunchConfigurations(): boolean {
Expand All @@ -213,9 +219,11 @@ const configuration = {
.get<boolean>("searchSubfoldersForPackages", false);
},
get attachmentsPath(): string {
return vscode.workspace
.getConfiguration("swift", workspaceFolder)
.get<string>("attachmentsPath", "./.build/attachments");
return substituteVariablesInString(
vscode.workspace
.getConfiguration("swift", workspaceFolder)
.get<string>("attachmentsPath", "./.build/attachments")
);
},
pluginPermissions(pluginId?: string): PluginPermissionConfiguration {
return pluginSetting("pluginPermissions", pluginId, false) ?? {};
Expand Down Expand Up @@ -256,7 +264,9 @@ const configuration = {
}
},
get customDebugAdapterPath(): string {
return vscode.workspace.getConfiguration("swift.debugger").get<string>("path", "");
return substituteVariablesInString(
vscode.workspace.getConfiguration("swift.debugger").get<string>("path", "")
);
},
get disable(): boolean {
return vscode.workspace
Expand All @@ -274,7 +284,8 @@ const configuration = {
get excludeFromCodeCoverage(): string[] {
return vscode.workspace
.getConfiguration("swift")
.get<string[]>("excludeFromCodeCoverage", []);
.get<string[]>("excludeFromCodeCoverage", [])
.map(substituteVariablesInString);
},
/** Files and directories to exclude from the Package Dependencies view. */
get excludePathsFromPackageDependencies(): string[] {
Expand All @@ -284,15 +295,21 @@ const configuration = {
},
/** Path to folder that include swift executable */
get path(): string {
return vscode.workspace.getConfiguration("swift").get<string>("path", "");
return substituteVariablesInString(
vscode.workspace.getConfiguration("swift").get<string>("path", "")
);
},
/** Path to folder that include swift runtime */
get runtimePath(): string {
return vscode.workspace.getConfiguration("swift").get<string>("runtimePath", "");
return substituteVariablesInString(
vscode.workspace.getConfiguration("swift").get<string>("runtimePath", "")
);
},
/** Path to custom --sdk */
get sdk(): string {
return vscode.workspace.getConfiguration("swift").get<string>("SDK", "");
return substituteVariablesInString(
vscode.workspace.getConfiguration("swift").get<string>("SDK", "")
);
},
set sdk(value: string | undefined) {
vscode.workspace.getConfiguration("swift").update("SDK", value);
Expand All @@ -306,18 +323,26 @@ const configuration = {
},
/** swift build arguments */
get buildArguments(): string[] {
return vscode.workspace.getConfiguration("swift").get<string[]>("buildArguments", []);
return vscode.workspace
.getConfiguration("swift")
.get<string[]>("buildArguments", [])
.map(substituteVariablesInString);
},
/** swift package arguments */
get packageArguments(): string[] {
return vscode.workspace.getConfiguration("swift").get<string[]>("packageArguments", []);
return vscode.workspace
.getConfiguration("swift")
.get<string[]>("packageArguments", [])
.map(substituteVariablesInString);
},
/** thread/address sanitizer */
get sanitizer(): string {
return vscode.workspace.getConfiguration("swift").get<string>("sanitizer", "off");
},
get buildPath(): string {
return vscode.workspace.getConfiguration("swift").get<string>("buildPath", "");
return substituteVariablesInString(
vscode.workspace.getConfiguration("swift").get<string>("buildPath", "")
);
},
get disableSwiftPMIntegration(): boolean {
return vscode.workspace
Expand Down Expand Up @@ -437,4 +462,39 @@ const configuration = {
},
};

const vsCodeVariableRegex = new RegExp(/\$\{(.+?)\}/g);
function substituteVariablesInString(val: string): string {
return val.replace(vsCodeVariableRegex, (substring: string, varName: string) =>
typeof varName === "string" ? computeVscodeVar(varName) || substring : substring
);
}

function computeVscodeVar(varName: string): string | null {
const workspaceFolder = () => {
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) {
const documentUri = activeEditor.document.uri;
const folder = vscode.workspace.getWorkspaceFolder(documentUri);
if (folder) {
return folder.uri.fsPath;
}
}

// If there is no active editor then return the first workspace folder
return vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath ?? "";
};

// https://code.visualstudio.com/docs/editor/variables-reference
// Variables to be substituted should be added here.
const supportedVariables: { [k: string]: () => string } = {
workspaceFolder,
workspaceFolderBasename: () => path.basename(workspaceFolder()),
cwd: () => process.cwd(),
userHome: () => os.homedir(),
pathSeparator: () => path.sep,
};

return varName in supportedVariables ? supportedVariables[varName]() : null;
}

export default configuration;
64 changes: 64 additions & 0 deletions test/integration-tests/configuration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the VS Code Swift open source project
//
// Copyright (c) 2025 the VS Code Swift project authors
// Licensed under Apache License v2.0
//
// See LICENSE.txt for license information
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
//
// SPDX-License-Identifier: Apache-2.0
//
//===----------------------------------------------------------------------===//

import * as vscode from "vscode";
import * as path from "path";
import { activateExtensionForSuite, updateSettings } from "./utilities/testutilities";
import { expect } from "chai";
import { afterEach } from "mocha";
import configuration from "../../src/configuration";
import { createBuildAllTask } from "../../src/tasks/SwiftTaskProvider";
import { WorkspaceContext } from "../../src/WorkspaceContext";

suite("Configuration Test Suite", function () {
let workspaceContext: WorkspaceContext;

activateExtensionForSuite({
async setup(ctx) {
workspaceContext = ctx;
},
});

let resetSettings: (() => Promise<void>) | undefined;
afterEach(async () => {
if (resetSettings) {
await resetSettings();
}
});

test("Should substitute variables in build task", async function () {
resetSettings = await updateSettings({
"swift.buildPath": "${workspaceFolder}/somepath",
});

const task = createBuildAllTask(workspaceContext.folders[0], false);
expect(task).to.not.be.undefined;
expect(task.definition.args).to.not.be.undefined;
const index = task.definition.args.indexOf("--scratch-path");
expect(task.definition.args[index + 1]).to.equal(
vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath + "/somepath"
);
});

test("Should substitute variables in configuration", async function () {
resetSettings = await updateSettings({
"swift.buildPath": "${workspaceFolder}${pathSeparator}${workspaceFolderBasename}",
});

const basePath = vscode.workspace.workspaceFolders?.at(0)?.uri.fsPath;
const baseName = path.basename(basePath ?? "");
const sep = path.sep;
expect(configuration.buildPath).to.equal(`${basePath}${sep}${baseName}`);
});
});
Loading