diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 7c28a1e05..ba63b1ba8 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -9,6 +9,7 @@ Release with new features and bugfixes: * https://github.com/devonfw/IDEasy/issues/2176[#2176]: Support 7z archive extraction * https://github.com/devonfw/IDEasy/issues/2100[#2100]: Fix Python not available for Mac x64 * https://github.com/devonfw/IDEasy/issues/2134[#2134]: Add auto-completion for icd command +* https://github.com/devonfw/IDEasy/issues/2180[#2180]: Add *_PLUGINS_EXTRA variable to install extra IDE plugins per user The full list of changes for this release can be found in https://github.com/devonfw/IDEasy/milestone/48?closed=1[milestone 2026.08.001]. diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java index 135c4e3c4..871570507 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/PluginBasedCommandlet.java @@ -12,6 +12,8 @@ import com.devonfw.tools.ide.cli.CliException; import com.devonfw.tools.ide.common.Tag; import com.devonfw.tools.ide.context.IdeContext; +import com.devonfw.tools.ide.environment.EnvironmentVariables; +import com.devonfw.tools.ide.environment.VariableLine; import com.devonfw.tools.ide.io.FileAccess; import com.devonfw.tools.ide.process.ProcessContext; import com.devonfw.tools.ide.process.ProcessErrorHandling; @@ -28,6 +30,9 @@ public abstract class PluginBasedCommandlet extends LocalToolCommandlet { private static final Logger LOG = LoggerFactory.getLogger(PluginBasedCommandlet.class); + /** Suffix of the tool-specific variable listing additional plugins to activate (e.g. {@code VSCODE_PLUGINS_EXTRA}). */ + public static final String VARIABLE_SUFFIX_PLUGINS_EXTRA = "_PLUGINS_EXTRA"; + private ToolPlugins plugins; /** {@link FlagProperty} to force the reset and reinstallation of plugins as configured in the project settings. */ @@ -68,6 +73,8 @@ public ToolPlugins getPlugins() { Path userPluginsPath = getUserHomePluginsConfigPath(); loadPluginsFromDirectory(toolPlugins, userPluginsPath); + activateExtraPlugins(toolPlugins); + this.plugins = toolPlugins; } @@ -316,4 +323,33 @@ protected void handleInstallForInactivePlugin(ToolPluginDescriptor plugin) { LOG.debug("Omitting installation of inactive plugin {} ({}).", plugin.name(), plugin.id()); } + + /** + * Activates the plugins configured in the tool-specific {@code «TOOL»}{@value #VARIABLE_SUFFIX_PLUGINS_EXTRA} variable (e.g. + * {@code VSCODE_PLUGINS_EXTRA=copilot,docker}). This allows a user to permanently opt-in to plugins that are not {@link ToolPluginDescriptor#active() active} + * in the project settings, without modifying the shared settings and without losing them when plugins are purged and reinstalled on IDE upgrade. Values refer + * to the {@link ToolPluginDescriptor#name() name} of the plugin (the filename of its {@code .properties} file) and not to the + * {@link ToolPluginDescriptor#id() id}. Names that do not resolve to a configured plugin are logged as a warning and skipped so that a single stale entry + * cannot break the entire installation. + * + * @param toolPlugins the {@link ToolPlugins} to modify. + */ + private void activateExtraPlugins(ToolPlugins toolPlugins) { + + String variable = EnvironmentVariables.getToolVariablePrefix(this.tool) + VARIABLE_SUFFIX_PLUGINS_EXTRA; + String value = this.context.getVariables().get(variable); + if ((value == null) || value.isBlank()) { + return; + } + for (String entry : VariableLine.parseArray(value)) { + String name = entry; + if (name.endsWith(IdeContext.EXT_PROPERTIES)) { + name = name.substring(0, name.length() - IdeContext.EXT_PROPERTIES.length()); + } + if (toolPlugins.activate(name) == null) { + LOG.warn("Undefined plugin '{}' configured in variable {} - no file {}{} found in {} or {}.", name, variable, name, IdeContext.EXT_PROPERTIES, + getPluginsConfigPath(), getUserHomePluginsConfigPath()); + } + } + } } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java index ef2f487dc..3ebf6f604 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPluginDescriptor.java @@ -81,4 +81,17 @@ private static String getString(Properties properties, String key, String legacy return value != null ? value.trim() : null; } + /** + * @param newActive the new value of {@link #active()}. + * @return this {@link ToolPluginDescriptor} if {@link #active()} already has the given value, otherwise a copy of this descriptor with all other properties + * unchanged and {@link #active()} set to the given value. + */ + public ToolPluginDescriptor withActive(boolean newActive) { + + if (newActive == this.active) { + return this; + } + return new ToolPluginDescriptor(this.id, this.name, this.url, this.version, newActive, this.tags); + } + } diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPlugins.java b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPlugins.java index ca626ad6b..7fa3a46ef 100644 --- a/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPlugins.java +++ b/cli/src/main/java/com/devonfw/tools/ide/tool/plugin/ToolPlugins.java @@ -72,4 +72,27 @@ private void put(String key, ToolPluginDescriptor descriptor, Map plugins = pluginBasedCommandlet.getPlugins().getPlugins(); + + assertThat(plugins).filteredOn(plugin -> "anyedit".equals(plugin.name())) + .singleElement().extracting(ToolPluginDescriptor::active).isEqualTo(Boolean.TRUE); + } + + @Test + void testUnlistedPluginKeepsConfiguredState() { + + IdeTestContext localContext = newContext(PROJECT_PLUGIN_EXTRA, null, false); + ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(localContext, TOOL, tags); + + ToolPlugins toolPlugins = pluginBasedCommandlet.getPlugins(); + + // configured as inactive and not listed in ECLIPSE_PLUGINS_EXTRA - has to stay inactive + assertThat(toolPlugins.getByName("checkstyle").active()).isFalse(); + // configured as active and not listed in ECLIPSE_PLUGINS_EXTRA - has to stay active + assertThat(toolPlugins.getByName("spotbugs").active()).isTrue(); + } + + @Test + void testUnknownExtraPluginLogsWarning() throws IOException { + + IdeTestContext localContext = newContext(PROJECT_PLUGIN_EXTRA, null, true); + Files.writeString(localContext.getIdeHome().resolve(IdeContext.FOLDER_CONF).resolve(EnvironmentVariables.DEFAULT_PROPERTIES), + "ECLIPSE_PLUGINS_EXTRA=anyedit,doesnotexist\n"); + localContext.reload(); + ExamplePluginBasedCommandlet pluginBasedCommandlet = new ExamplePluginBasedCommandlet(localContext, TOOL, tags); + + ToolPlugins toolPlugins = pluginBasedCommandlet.getPlugins(); + + assertThat(toolPlugins.getByName("anyedit").active()).isTrue(); + assertThat(localContext).logAtWarning().hasMessageContaining("doesnotexist"); + } } diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/conf/ide.properties b/cli/src/test/resources/ide-projects/plugin-extra/project/conf/ide.properties new file mode 100644 index 000000000..c667ec0ae --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/conf/ide.properties @@ -0,0 +1 @@ +ECLIPSE_PLUGINS_EXTRA=anyedit diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/anyedit.properties b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/anyedit.properties new file mode 100644 index 000000000..e6cea1566 --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/anyedit.properties @@ -0,0 +1,3 @@ +url=https://raw.githubusercontent.com/iloveeclipse/plugins/latest/ +id=AnyEditTools.feature.group +active=false diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/checkstyle.properties b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/checkstyle.properties new file mode 100644 index 000000000..054b42651 --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/checkstyle.properties @@ -0,0 +1,3 @@ +url=https://checkstyle.org/eclipse-cs-update-site/ +id=net.sf.eclipsecs.feature.group +active=false diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/spotbugs.properties b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/spotbugs.properties new file mode 100644 index 000000000..80dae0658 --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/eclipse/plugins/spotbugs.properties @@ -0,0 +1,3 @@ +url=https://spotbugs.github.io/eclipse/ +id=com.github.spotbugs.plugin.eclipse.feature.group +active=true diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/settings/ide.properties b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/ide.properties new file mode 100644 index 000000000..da8cf725a --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/settings/ide.properties @@ -0,0 +1 @@ +ECLIPSE_VERSION=2023-03 diff --git a/cli/src/test/resources/ide-projects/plugin-extra/project/workspaces/main/readme b/cli/src/test/resources/ide-projects/plugin-extra/project/workspaces/main/readme new file mode 100644 index 000000000..894d9672e --- /dev/null +++ b/cli/src/test/resources/ide-projects/plugin-extra/project/workspaces/main/readme @@ -0,0 +1 @@ +this is the main workspace of plugin-extra diff --git a/documentation/plugin.adoc b/documentation/plugin.adoc index 79bedfe61..f834fbb9f 100644 --- a/documentation/plugin.adoc +++ b/documentation/plugin.adoc @@ -33,6 +33,28 @@ For `VisualStudio Code`, this is the extension ID you can directly get from the |=== +== Additional plugins per user + +The plugins configured in the project link:settings.adoc[settings] are a shared baseline for the entire team. +If you want additional plugins only for yourself, you do not need to modify the shared settings. +Instead set the variable `«TOOL»_PLUGINS_EXTRA` in your user-specific link:conf.adoc[configuration] (`$IDE_HOME/conf/ide.properties` or `~/.ide/ide.properties`): + +``` +VSCODE_PLUGINS_EXTRA=copilot,cpp-tools,docker +``` + +Each entry refers to the filename of the plugin properties file without the `.properties` extension. +Therefore `copilot` refers to `copilot.properties` and not to the `id` configured inside that file. +The according plugin still has to be configured as properties file (either in the project settings or in `~/.ide/settings/«ide»/plugins`). +Entries that cannot be resolved are skipped with a warning so a single stale entry will never break the installation of your other plugins. + +This works for every IDE, e.g. `ECLIPSE_PLUGINS_EXTRA`, `INTELLIJ_PLUGINS_EXTRA` or `ANDROID_STUDIO_PLUGINS_EXTRA`. + +The benefit over installing a plugin manually inside your IDE is that plugins configured this way are reinstalled automatically whenever IDEasy purges and reinstalls the plugins (e.g. when the IDE is upgraded), whereas manually installed plugins would be lost. + +Please note that removing an entry from `«TOOL»_PLUGINS_EXTRA` will not uninstall the according plugin. +See the next section for how to reset your plugins. + == Resetting installed plugins When first installing an IDE using `ide install «ide»`, IDEasy will automatically install all plugins that are configured in the project settings. diff --git a/documentation/variables.adoc b/documentation/variables.adoc index dd0b99741..cc03acc5e 100644 --- a/documentation/variables.adoc +++ b/documentation/variables.adoc @@ -25,6 +25,7 @@ See also link:https://github.com/devonfw/IDEasy/blob/main/cli/src/main/java/com/ |`WORKSPACE_PATH`|`$IDE_HOME/workspaces/$WORKSPACE`|Absolute path to current link:workspaces.adoc[workspace]. Never set this variable in any `ide.properties` file. |`«TOOL»_VERSION`|`*`|The version of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_VERSION` or `MVN_VERSION`). |`«TOOL»_EDITION`|`«tool»`|The edition of the tool `«TOOL»` to install and use (e.g. `ECLIPSE_EDITION`, `INTELLIJ_EDITION` or `DOCKER_EDITION`). Default of `DOCKER_EDITION` is `rancher`. +|`«TOOL»_PLUGINS_EXTRA`|e.g. `copilot,docker`|Additional link:plugin.adoc[plugins] to install for the IDE `«TOOL»` (e.g. `VSCODE_PLUGINS_EXTRA`, `INTELLIJ_PLUGINS_EXTRA` or `ECLIPSE_PLUGINS_EXTRA`). Each entry refers to the filename of the plugin properties file without the `.properties` extension (e.g. `copilot` for `copilot.properties`) and not to the `id` configured inside that file. This option should only be set for individual users in `$IDE_HOME/conf/ide.properties` or `~/.ide/ide.properties` (and not in shared `settings`). |*`«TOOL»_HOME`*|`$IDE_HOME/software/«tool»`|Path to installation of «tool» (e.g. MVN_HOME for maven) |`«TOOL»_BUILD_OPTS`|`clean install`|The arguments provided to the build-tool `«TOOL»` in order to run a build. E.g.`clean install` |`«TOOL»_RELEASE_OPTS`|e.g. `clean deploy -Dchangelist= -Pdeploy`|The arguments provided to the build-tool `«TOOL»` in order to perform a release build. E.g.`clean deploy -Dchangelist= -Pdeploy`