Skip to content

Commit

Permalink
SLCORE-408 Clean synchronized plugins that are not used anymore
Browse files Browse the repository at this point in the history
  • Loading branch information
damien-urruty-sonarsource committed Nov 5, 2024
1 parent 5d3d9a3 commit ca93ef7
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ public void onConfigurationScopeRemoved(ConfigurationScopeRemovedEvent event) {

@EventListener
public void onBindingChanged(BindingConfigChangedEvent event) {
if (!fullSynchronizationEnabled) {
return;
}
var configScopeId = event.getConfigScopeId();
scopeSynchronizationTimestampRepository.clearLastSynchronizationTimestamp(configScopeId);
if (event.getPreviousConfig().isBound()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.sonarsource.sonarlint.core.commons.Version;
import org.sonarsource.sonarlint.core.commons.api.SonarLanguage;
Expand Down Expand Up @@ -56,15 +58,24 @@ public PluginSynchronizationSummary synchronize(ServerApi serverApi, boolean sup

var storedPluginsByKey = storage.plugins().getStoredPluginsByKey();
var serverPlugins = serverApi.plugins().getInstalled(cancelMonitor);
var pluginsToDownload = serverPlugins.stream()
.filter(p -> shouldDownload(p, storedPluginsByKey))
var downloadSkipReasonByServerPlugin = serverPlugins.stream()
.collect(Collectors.toMap(Function.identity(), plugin -> determineIfShouldSkipDownload(plugin, storedPluginsByKey)));

var pluginsToDownload = downloadSkipReasonByServerPlugin.entrySet().stream()
.filter(entry -> entry.getValue().isEmpty())
.map(Map.Entry::getKey)
.collect(Collectors.toList());

if (pluginsToDownload.isEmpty()) {
storage.plugins().storeNoPlugins();
return new PluginSynchronizationSummary(false);
}
downloadAll(serverApi, pluginsToDownload, cancelMonitor);
var serverPluginsExpectedInStorage = downloadSkipReasonByServerPlugin.entrySet().stream()
.filter(entry -> entry.getValue().isEmpty() || entry.getValue().get().equals(DownloadSkipReason.UP_TO_DATE))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
storage.plugins().cleanUpUnknownPlugins(serverPluginsExpectedInStorage);
return new PluginSynchronizationSummary(true);
}

Expand All @@ -79,24 +90,24 @@ private void downloadPlugin(ServerApi serverApi, ServerPlugin plugin, SonarLintC
serverApi.plugins().getPlugin(plugin.getKey(), pluginBinary -> storage.plugins().store(plugin, pluginBinary), cancelMonitor);
}

private boolean shouldDownload(ServerPlugin serverPlugin, Map<String, StoredPlugin> storedPluginsByKey) {
private Optional<DownloadSkipReason> determineIfShouldSkipDownload(ServerPlugin serverPlugin, Map<String, StoredPlugin> storedPluginsByKey) {
if (embeddedPluginKeys.contains(serverPlugin.getKey())) {
LOG.debug("[SYNC] Code analyzer '{}' is embedded in SonarLint. Skip downloading it.", serverPlugin.getKey());
return false;
return Optional.of(DownloadSkipReason.EMBEDDED);
}
if (upToDate(serverPlugin, storedPluginsByKey)) {
LOG.debug("[SYNC] Code analyzer '{}' is up-to-date. Skip downloading it.", serverPlugin.getKey());
return false;
return Optional.of(DownloadSkipReason.UP_TO_DATE);
}
if (!serverPlugin.isSonarLintSupported()) {
LOG.debug("[SYNC] Code analyzer '{}' does not support SonarLint. Skip downloading it.", serverPlugin.getKey());
return false;
return Optional.of(DownloadSkipReason.NOT_SONARLINT_SUPPORTED);
}
if (sonarSourceDisabledPluginKeys.contains(serverPlugin.getKey())) {
LOG.debug("[SYNC] Code analyzer '{}' is disabled in SonarLint (language not enabled). Skip downloading it.", serverPlugin.getKey());
return false;
return Optional.of(DownloadSkipReason.LANGUAGE_NOT_ENABLED);
}
return true;
return Optional.empty();
}

private static boolean upToDate(ServerPlugin serverPlugin, Map<String, StoredPlugin> storedPluginsByKey) {
Expand All @@ -118,4 +129,8 @@ private static Set<String> getSonarSourceDisabledPluginKeys(Set<SonarLanguage> e
}
return disabledPluginKeys;
}

private enum DownloadSkipReason {
EMBEDDED, UP_TO_DATE, NOT_SONARLINT_SUPPORTED, LANGUAGE_NOT_ENABLED
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public ServerConnection(StorageFacade storageFacade, String connectionId, boolea
this.storage = storageFacade.connection(connectionId);
serverInfoSynchronizer = new ServerInfoSynchronizer(storage);
this.storageSynchronizer = new LocalStorageSynchronizer(enabledLanguagesToSync, embeddedPluginKeys, serverInfoSynchronizer, storage);
storage.plugins().cleanUp();
}

public PluginSynchronizationSummary sync(ServerApi serverApi, SonarLintCancelMonitor cancelMonitor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down Expand Up @@ -115,20 +116,23 @@ private StoredPlugin adapt(Sonarlint.PluginReferences.PluginReference plugin) {
rootPath.resolve(plugin.getFilename()));
}

public void cleanUp() {
rwLock.write(() -> {
var unknownFiles = getUnknownFiles();
LOG.debug("Cleanup of the plugin storage in folder {}, {} unknown files", rootPath, unknownFiles.size());
public void cleanUpUnknownPlugins(List<ServerPlugin> serverPluginsExpectedInStorage) {
var expectedPluginPaths = serverPluginsExpectedInStorage.stream().map(plugin -> rootPath.resolve(plugin.getFilename())).collect(Collectors.toSet());
rwLock.write(() -> deleteFiles(getUnknownFiles(expectedPluginPaths)));
}

private void deleteFiles(List<File> unknownFiles) {
if (!unknownFiles.isEmpty()) {
LOG.debug("Cleaning up the plugins storage {}, removing {} unknown files:", rootPath, unknownFiles.size());
unknownFiles.forEach(f -> LOG.debug(f.getAbsolutePath()));
unknownFiles.forEach(FileUtils::deleteQuietly);
});
}
}

private List<File> getUnknownFiles() {
private List<File> getUnknownFiles(Set<Path> knownPluginsPaths) {
if (!Files.exists(rootPath)) {
return Collections.emptyList();
}
var knownPluginsPaths = getStoredPlugins().stream().map(StoredPlugin::getJarPath).collect(Collectors.toSet());
LOG.debug("Known plugin paths: {}", knownPluginsPaths);
try (Stream<Path> pathsInDir = Files.list(rootPath)) {
var paths = pathsInDir.collect(Collectors.toList());
Expand All @@ -154,6 +158,7 @@ public void storeNoPlugins() {
ProtobufFileUtil.writeToFile(references, pluginReferencesFilePath);
});
}
deleteFiles(getUnknownFiles(Set.of()));
}

private void createPluginDirectory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,28 @@ void it_should_not_pull_the_old_typescript_plugin_if_language_not_enabled() {
});
}

@Test
void it_should_clean_up_plugins_that_are_no_longer_relevant() {
var server = newSonarQubeServer("10.3")
.withPlugin(TestPlugin.PHP)
.withProject("projectKey", project -> project.withBranch("main"))
.start();
var client = newFakeClient().build();
backend = newBackend()
.withSonarQubeConnection("connectionId", server, storage -> storage.withPlugin(TestPlugin.JAVA))
.withBoundConfigScope("configScopeId", "connectionId", "projectKey")
.withEnabledLanguageInStandaloneMode(Language.PHP)
.withFullSynchronization()
.build(client);

waitAtMost(3, SECONDS).untilAsserted(() -> {
assertThat(getPluginsStorageFolder().toFile().listFiles())
.extracting(File::getName)
.containsOnly(PluginsStorage.PLUGIN_REFERENCES_PB, TestPlugin.PHP.getPath().getFileName().toString());
assertThat(client.getLogMessages()).contains("Cleaning up the plugins storage " + getPluginsStorageFolder() + ", removing 1 unknown files:");
});
}

@NotNull
private Map<String, PluginReference> readPluginReferences(Path filePath) {
return ProtobufFileUtil.readFile(filePath, Sonarlint.PluginReferences.parser()).getPluginsByKeyMap();
Expand Down

0 comments on commit ca93ef7

Please sign in to comment.