Skip to content

Commit

Permalink
Configurable Command Plugin Build Configuration
Browse files Browse the repository at this point in the history
Packages can define their own plugins either directly or through
their dependencies. These plugins define commands, and the extension
exposes a list of these when you use `> Swift: Run Command Plugin`.

It may be desirable to build and run these plugins with specific
arguments. This patch introduces a new setting that can be specified
globally or on a per workspace folder basis that allows users to
configure arguments to pass to plugin command invocations.

The setting is defined under `swift.pluginArguments`, and is specified
as an object in the following form:

```json
{
	"PluginCommandName:intent-name": ["--some", "--args"]
}
```

- The top level string key is the command id in the form
  `PluginCommandName:intent-name`. For instance, swift-format's
  format-source-code command would be specified as
  `swift-format:format-source-code`
- Specifying `PluginCommandName` will apply the arguments to all intents
  in the command plugin
- Specifying `*` will apply the arguments to all commands.

This patch also adds this wildcard functionality to the
`swift.pluginPermissions` setting.

Issue: swiftlang#1365
  • Loading branch information
plemarquand committed Feb 26, 2025
1 parent 9314392 commit 85e5062
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 59 deletions.
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 an 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

0 comments on commit 85e5062

Please sign in to comment.