Skip to content

Configurable Command Plugin Build Configuration #1409

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 2 commits into from
Mar 11, 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
22 changes: 20 additions & 2 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This document outlines useful configuration options not covered by the settings

## Command Plugins

Swift packages can define [command plugins](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) that can perform arbitrary tasks. For example, the [swift-format](https://github.com/swiftlang/swift-format) package exposes a `format-source-code` command which will use swift-format to format source code in a folder. These plugin commands can be invoked from VS Code using `> Swift: Run Command Plugin`.
Swift packages can define [command plugins](https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/Plugins.md) that can perform arbitrary tasks. For example, the [swift-format](https://github.com/swiftlang/swift-format) package exposes a `format-source-code` command which will use swift-format to format source code in a folder. These plugin commands can be invoked from VS Code using `> Swift: Run Command Plugin`.

A plugin may require permissions to perform tasks like writing to the file system or using the network. If a plugin command requires one of these permissions, you will be prompted in the integrated terminal to accept them. If you trust the command and wish to apply permissions on every command execution, you can configure a setting in your `settings.json`.

Expand All @@ -23,7 +23,7 @@ A plugin may require permissions to perform tasks like writing to the file syste
}
```

A key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin.
A key of `PluginName:command` will set permissions for a specific command. A key of `PluginName` will set permissions for all commands in the plugin. If you'd like the same permissions to be applied to all plugins use `*` as the plugin name. Precedence order is determined by specificity, where more specific names take priority. The name `*` is the least specific and `PluginName:command` is the most specific.

Alternatively, you can define a task in your tasks.json and define permissions directly on the task. This will create a new entry in the list shown by `> Swift: Run Command Plugin`.

Expand All @@ -43,6 +43,24 @@ Alternatively, you can define a task in your tasks.json and define permissions d
}
```

If you'd like to provide specific arguments to your plugin command invocation you can use the `swift.pluginArguments` setting. Defining an array for this setting applies the same arguments to all plugin command invocations.

```json
{
"swift.pluginArguments": ["-c", "release"]
}
```

Alternatively you can specfiy which specific command the arguments should apply to using `PluginName:command`. A key of `PluginName` will use the arguments for all commands in the plugin. If you'd like the same arguments to be used for all plugins use `*` as the plugin name.

```json
{
"swift.pluginArguments": {
"PluginName:command": ["-c", "release"]
}
}
```

## SourceKit-LSP

[SourceKit-LSP](https://github.com/apple/sourcekit-lsp) is the language server used by the the Swift extension to provide symbol completion, jump to definition etc. It is developed by Apple to provide Swift and C language support for any editor that supports the Language Server Protocol.
Expand Down
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,30 @@
"default": true,
"markdownDescription": "Controls whether or not the extension will contribute environment variables defined in `Swift: Environment Variables` to the integrated terminal. If this is set to `true` and a custom `Swift: Path` is also set then the swift path is appended to the terminal's `PATH`."
},
"swift.pluginArguments": {
"default": [],
"markdownDescription": "Configure a list of arguments to pass to command invocations. This can either be an array of arguments, which will apply to all command invocations, or an object with command names as the key where the value is an array of arguments.",
"scope": "machine-overridable",
"oneOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "object",
"patternProperties": {
"^([a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+)?)$": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
]
},
"swift.pluginPermissions": {
"type": "object",
"default": {},
Expand Down
51 changes: 44 additions & 7 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ export interface FolderConfiguration {
/** location to save swift-testing attachments */
readonly attachmentsPath: string;
/** look up saved permissions for the supplied plugin */
pluginPermissions(pluginId: string): PluginPermissionConfiguration;
pluginPermissions(pluginId?: string): PluginPermissionConfiguration;
/** look up saved arguments for the supplied plugin, or global plugin arguments if no plugin id is provided */
pluginArguments(pluginId?: string): string[];
}

export interface PluginPermissionConfiguration {
Expand Down Expand Up @@ -143,6 +145,42 @@ const configuration = {
},

folder(workspaceFolder: vscode.WorkspaceFolder): FolderConfiguration {
function pluginSetting<T>(
setting: string,
pluginId?: string,
resultIsArray: boolean = false
): T | undefined {
if (!pluginId) {
// Check for * as a wildcard plugin ID for configurations that want both
// global arguments as well as specific additional arguments for a plugin.
const wildcardSetting = pluginSetting(setting, "*", resultIsArray) as T | undefined;
if (wildcardSetting) {
return wildcardSetting;
}

// Check if there is a global setting like `"swift.pluginArguments": ["-c", "release"]`
// that should apply to all plugins.
const args = vscode.workspace
.getConfiguration("swift", workspaceFolder)
.get<T>(setting);

if (resultIsArray && Array.isArray(args)) {
return args;
} else if (
!resultIsArray &&
args !== null &&
typeof args === "object" &&
Object.keys(args).length !== 0
) {
return args;
}
return undefined;
}

return vscode.workspace.getConfiguration("swift", workspaceFolder).get<{
[key: string]: T;
}>(setting, {})[pluginId];
}
return {
/** Environment variables to set when running tests */
get testEnvironmentVariables(): { [key: string]: string } {
Expand Down Expand Up @@ -179,12 +217,11 @@ const configuration = {
.getConfiguration("swift", workspaceFolder)
.get<string>("attachmentsPath", "./.build/attachments");
},
pluginPermissions(pluginId: string): PluginPermissionConfiguration {
return (
vscode.workspace.getConfiguration("swift", workspaceFolder).get<{
[key: string]: PluginPermissionConfiguration;
}>("pluginPermissions", {})[pluginId] ?? {}
);
pluginPermissions(pluginId?: string): PluginPermissionConfiguration {
return pluginSetting("pluginPermissions", pluginId, false) ?? {};
},
pluginArguments(pluginId?: string): string[] {
return pluginSetting("pluginArguments", pluginId, true) ?? [];
},
};
},
Expand Down
23 changes: 17 additions & 6 deletions src/tasks/SwiftPluginTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
* are keyed by either plugin command name (package), or in the form `name:command`.
* User-configured permissions take precedence over the hardcoded permissions, and the more
* specific form of `name:command` takes precedence over the more general form of `name`.
* @param folderContext The folder context to search for the `swift.pluginPermissions` key.
* @param folderContext The folder context to search for the `swift.pluginPermissions` and `swift.pluginArguments` keys.
* @param taskDefinition The task definition to search for the `disableSandbox` and `allowWritingToPackageDirectory` keys.
* @param plugin The plugin to generate arguments for.
* @returns A list of permission related arguments to pass when invoking the plugin.
Expand All @@ -213,9 +213,14 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
plugin: PackagePlugin
): string[] {
const config = configuration.folder(folderContext);
const globalPackageConfig = config.pluginPermissions();
const packageConfig = config.pluginPermissions(plugin.package);
const commandConfig = config.pluginPermissions(`${plugin.package}:${plugin.command}`);

const globalPackageArgs = config.pluginArguments();
const packageArgs = config.pluginArguments(plugin.package);
const commandArgs = config.pluginArguments(`${plugin.package}:${plugin.command}`);

const taskDefinitionConfiguration: PluginPermissionConfiguration = {};
if (taskDefinition.disableSandbox) {
taskDefinitionConfiguration.disableSandbox = true;
Expand All @@ -232,11 +237,17 @@ export class SwiftPluginTaskProvider implements vscode.TaskProvider {
taskDefinition.allowNetworkConnections;
}

return this.pluginArguments({
...packageConfig,
...commandConfig,
...taskDefinitionConfiguration,
});
return [
...globalPackageArgs,
...packageArgs,
...commandArgs,
...this.pluginArguments({
...globalPackageConfig,
...packageConfig,
...commandConfig,
...taskDefinitionConfiguration,
}),
];
}

private pluginArguments(config: PluginPermissionConfiguration): string[] {
Expand Down
Loading
Loading