]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9679 Add plugins and their updated_at date to the scanner report
authorJulien HENRY <julien.henry@sonarsource.com>
Tue, 8 Aug 2017 13:23:15 +0000 (15:23 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 7 Sep 2017 06:33:31 +0000 (08:33 +0200)
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PluginInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPlugin.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/FakePluginInstaller.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto

index 8465d10093c38129380fdd7808bad4536ed3fdf8..aeba67788c2608301f457dabba000c1e0922a595 100644 (file)
  */
 package org.sonar.scanner.bootstrap;
 
+import java.util.List;
 import java.util.Map;
-import org.sonar.api.Plugin;
 import org.sonar.api.batch.ScannerSide;
-import org.sonar.core.platform.PluginInfo;
 
 @ScannerSide
 public interface PluginInstaller {
@@ -32,11 +31,11 @@ public interface PluginInstaller {
    * already in local cache.
    * @return information about all installed plugins, grouped by key
    */
-  Map<String, PluginInfo> installRemotes();
+  Map<String, ScannerPlugin> installRemotes();
 
   /**
-   * Used only by tests.
+   * Used only by medium tests.
    * @see org.sonar.scanner.mediumtest.ScannerMediumTester
    */
-  Map<String, Plugin> installLocals();
+  List<Object[]> installLocals();
 }
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPlugin.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPlugin.java
new file mode 100644 (file)
index 0000000..86f7672
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.scanner.bootstrap;
+
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.updatecenter.common.Version;
+
+public class ScannerPlugin {
+
+  private final String key;
+  private final long updatedAt;
+  private final PluginInfo info;
+
+  public ScannerPlugin(String key, long updatedAt, PluginInfo info) {
+    this.key = key;
+    this.updatedAt = updatedAt;
+    this.info = info;
+  }
+
+  public PluginInfo getInfo() {
+    return info;
+  }
+
+  public String getName() {
+    return info.getName();
+  }
+
+  public Version getVersion() {
+    return info.getVersion();
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public long getUpdatedAt() {
+    return updatedAt;
+  }
+
+}
index a8f40fdb6e1fe44011fb05acf272b4f08ca02f0d..a37a98d3a79b1f0bd35c28e8e9661234e3705885 100644 (file)
@@ -27,9 +27,9 @@ import java.io.InputStream;
 import java.io.Reader;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.apache.commons.io.FileUtils;
-import org.sonar.api.Plugin;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
@@ -60,12 +60,12 @@ public class ScannerPluginInstaller implements PluginInstaller {
   }
 
   @Override
-  public Map<String, PluginInfo> installRemotes() {
+  public Map<String, ScannerPlugin> installRemotes() {
     return loadPlugins(listInstalledPlugins());
   }
 
-  private Map<String, PluginInfo> loadPlugins(InstalledPlugin[] remotePlugins) {
-    Map<String, PluginInfo> infosByKey = new HashMap<>(remotePlugins.length);
+  private Map<String, ScannerPlugin> loadPlugins(InstalledPlugin[] remotePlugins) {
+    Map<String, ScannerPlugin> infosByKey = new HashMap<>(remotePlugins.length);
 
     Profiler profiler = Profiler.create(LOG).startDebug("Load plugins");
 
@@ -73,7 +73,7 @@ public class ScannerPluginInstaller implements PluginInstaller {
       if (pluginPredicate.apply(installedPlugin.key)) {
         File jarFile = download(installedPlugin);
         PluginInfo info = PluginInfo.create(jarFile);
-        infosByKey.put(info.getKey(), info);
+        infosByKey.put(info.getKey(), new ScannerPlugin(installedPlugin.key, installedPlugin.updatedAt, info));
       }
     }
 
@@ -82,12 +82,12 @@ public class ScannerPluginInstaller implements PluginInstaller {
   }
 
   /**
-   * Returns empty on purpose. This method is used only by tests.
+   * Returns empty on purpose. This method is used only by medium tests.
    * @see org.sonar.scanner.mediumtest.ScannerMediumTester
    */
   @Override
-  public Map<String, Plugin> installLocals() {
-    return Collections.emptyMap();
+  public List<Object[]> installLocals() {
+    return Collections.emptyList();
   }
 
   @VisibleForTesting
@@ -125,6 +125,7 @@ public class ScannerPluginInstaller implements PluginInstaller {
     String key;
     String hash;
     String filename;
+    long updatedAt;
   }
 
   private class FileDownloader implements FileCache.Downloader {
index d8d9c6a95369751f1e08eff132a032e84a1132f6..2db16dc26ef41cdfba842ae1ab3f25d0fc798af7 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.base.Preconditions;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Function;
 import javax.annotation.CheckForNull;
 import org.picocontainer.Startable;
 import org.sonar.api.Plugin;
@@ -32,6 +33,9 @@ import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginLoader;
 import org.sonar.core.platform.PluginRepository;
 
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
 /**
  * Orchestrates the installation and loading of plugins
  */
@@ -42,7 +46,7 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
   private final PluginLoader loader;
 
   private Map<String, Plugin> pluginInstancesByKeys;
-  private Map<String, PluginInfo> infosByKeys;
+  private Map<String, ScannerPlugin> pluginsByKeys;
   private Map<ClassLoader, String> keysByClassLoader;
 
   public ScannerPluginRepository(PluginInstaller installer, PluginLoader loader) {
@@ -52,15 +56,18 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
 
   @Override
   public void start() {
-    infosByKeys = new HashMap<>(installer.installRemotes());
-    pluginInstancesByKeys = new HashMap<>(loader.load(infosByKeys));
-
-    // this part is only used by tests
-    for (Map.Entry<String, Plugin> entry : installer.installLocals().entrySet()) {
-      String pluginKey = entry.getKey();
+    pluginsByKeys = new HashMap<>(installer.installRemotes());
+    pluginInstancesByKeys = new HashMap<>(
+      loader.load(pluginsByKeys.values().stream()
+        .map(ScannerPlugin::getInfo)
+        .collect(toMap(PluginInfo::getKey, Function.identity()))));
+
+    // this part is only used by medium tests
+    for (Object[] localPlugin : installer.installLocals()) {
+      String pluginKey = (String) localPlugin[0];
       PluginInfo pluginInfo = new PluginInfo(pluginKey);
-      infosByKeys.put(pluginKey, pluginInfo);
-      pluginInstancesByKeys.put(pluginKey, entry.getValue());
+      pluginsByKeys.put(pluginKey, new ScannerPlugin(pluginInfo.getKey(), (long) localPlugin[2], pluginInfo));
+      pluginInstancesByKeys.put(pluginKey, (Plugin) localPlugin[1]);
     }
 
     keysByClassLoader = new HashMap<>();
@@ -77,11 +84,11 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
   }
 
   private void logPlugins() {
-    if (infosByKeys.isEmpty()) {
+    if (pluginsByKeys.isEmpty()) {
       LOG.debug("No plugins loaded");
     } else {
       LOG.debug("Plugins:");
-      for (PluginInfo p : infosByKeys.values()) {
+      for (ScannerPlugin p : pluginsByKeys.values()) {
         LOG.debug("  * {} {} ({})", p.getName(), p.getVersion(), p.getKey());
       }
     }
@@ -93,20 +100,24 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
     loader.unload(pluginInstancesByKeys.values());
 
     pluginInstancesByKeys.clear();
-    infosByKeys.clear();
+    pluginsByKeys.clear();
     keysByClassLoader.clear();
   }
 
+  public Map<String, ScannerPlugin> getPluginsByKey() {
+    return pluginsByKeys;
+  }
+
   @Override
   public Collection<PluginInfo> getPluginInfos() {
-    return infosByKeys.values();
+    return pluginsByKeys.values().stream().map(ScannerPlugin::getInfo).collect(toList());
   }
 
   @Override
   public PluginInfo getPluginInfo(String key) {
-    PluginInfo info = infosByKeys.get(key);
+    ScannerPlugin info = pluginsByKeys.get(key);
     Preconditions.checkState(info != null, "Plugin [%s] does not exist", key);
-    return info;
+    return info.getInfo();
   }
 
   @Override
@@ -118,6 +129,6 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
 
   @Override
   public boolean hasPlugin(String key) {
-    return infosByKeys.containsKey(key);
+    return pluginsByKeys.containsKey(key);
   }
 }
index 123dfe19ec0ea6bc9f903547477411fc6f5cff05..8ba0187aedbcfb01baf40b6d25e5a2bcfabaf686 100644 (file)
 package org.sonar.scanner.mediumtest;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import org.sonar.api.Plugin;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.scanner.bootstrap.PluginInstaller;
+import org.sonar.scanner.bootstrap.ScannerPlugin;
 
 public class FakePluginInstaller implements PluginInstaller {
 
-  private final Map<String, PluginInfo> infosByKeys = new HashMap<>();
-  private final Map<String, Plugin> instancesByKeys = new HashMap<>();
+  private final Map<String, ScannerPlugin> pluginsByKeys = new HashMap<>();
+  private final List<Object[]> mediumTestPlugins = new ArrayList<>();
 
-  public FakePluginInstaller add(String pluginKey, File jarFile) {
-    infosByKeys.put(pluginKey, PluginInfo.create(jarFile));
+  public FakePluginInstaller add(String pluginKey, File jarFile, long lastUpdatedAt) {
+    pluginsByKeys.put(pluginKey, new ScannerPlugin(pluginKey, lastUpdatedAt, PluginInfo.create(jarFile)));
     return this;
   }
 
-  public FakePluginInstaller add(String pluginKey, Plugin instance) {
-    instancesByKeys.put(pluginKey, instance);
+  public FakePluginInstaller add(String pluginKey, Plugin instance, long lastUpdatedAt) {
+    mediumTestPlugins.add(new Object[] {pluginKey, instance, lastUpdatedAt});
     return this;
   }
 
   @Override
-  public Map<String, PluginInfo> installRemotes() {
-    return infosByKeys;
+  public Map<String, ScannerPlugin> installRemotes() {
+    return pluginsByKeys;
   }
 
   @Override
-  public Map<String, Plugin> installLocals() {
-    return instancesByKeys;
+  public List<Object[]> installLocals() {
+    return mediumTestPlugins;
   }
 }
index df162b78f24e2715ef0e235e3deb628da717ebe0..15f0a0cfcf8f777669372bbd843dfb83455715a7 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.scanner.report;
 
+import java.util.Map.Entry;
 import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.AnalysisMode;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
@@ -26,6 +27,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.config.Configuration;
 import org.sonar.scanner.ProjectAnalysisInfo;
+import org.sonar.scanner.bootstrap.ScannerPlugin;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
 import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportWriter;
@@ -40,15 +43,17 @@ public class MetadataPublisher implements ReportPublisherStep {
   private final InputModuleHierarchy moduleHierarchy;
   private final CpdSettings cpdSettings;
   private final AnalysisMode mode;
+  private final ScannerPluginRepository pluginRepository;
 
   public MetadataPublisher(ProjectAnalysisInfo projectAnalysisInfo, InputModuleHierarchy moduleHierarchy, Configuration settings,
-    ModuleQProfiles qProfiles, CpdSettings cpdSettings, AnalysisMode mode) {
+    ModuleQProfiles qProfiles, CpdSettings cpdSettings, AnalysisMode mode, ScannerPluginRepository pluginRepository) {
     this.projectAnalysisInfo = projectAnalysisInfo;
     this.moduleHierarchy = moduleHierarchy;
     this.settings = settings;
     this.qProfiles = qProfiles;
     this.cpdSettings = cpdSettings;
     this.mode = mode;
+    this.pluginRepository = pluginRepository;
   }
 
   @Override
@@ -76,6 +81,11 @@ public class MetadataPublisher implements ReportPublisherStep {
         .setName(qp.getName())
         .setRulesUpdatedAt(qp.getRulesUpdatedAt().getTime()).build());
     }
+    for (Entry<String, ScannerPlugin> pluginEntry : pluginRepository.getPluginsByKey().entrySet()) {
+      builder.getMutablePluginsByKey().put(pluginEntry.getKey(), ScannerReport.Metadata.Plugin.newBuilder()
+        .setKey(pluginEntry.getKey())
+        .setUpdatedAt(pluginEntry.getValue().getUpdatedAt()).build());
+    }
     writer.writeMetadata(builder.build());
   }
 }
index 967c5c8f351ee0e791c77d661fc02ff151ca1a3c..59bc8fc8c532a8b7763e81c82488772d021d42cd 100644 (file)
@@ -28,6 +28,7 @@ import org.sonar.core.platform.PluginLoader;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyCollectionOf;
+import static org.mockito.Matchers.anyMapOf;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -41,14 +42,15 @@ public class ScannerPluginRepositoryTest {
   @Test
   public void install_and_load_plugins() {
     PluginInfo info = new PluginInfo("squid");
-    ImmutableMap<String, PluginInfo> infos = ImmutableMap.of("squid", info);
+    ImmutableMap<String, ScannerPlugin> plugins = ImmutableMap.of("squid", new ScannerPlugin("squid", 1L, info));
     Plugin instance = mock(Plugin.class);
-    when(loader.load(infos)).thenReturn(ImmutableMap.of("squid", instance));
-    when(installer.installRemotes()).thenReturn(infos);
+    when(loader.load(anyMapOf(String.class, PluginInfo.class))).thenReturn(ImmutableMap.of("squid", instance));
+    when(installer.installRemotes()).thenReturn(plugins);
 
     underTest.start();
 
     assertThat(underTest.getPluginInfos()).containsOnly(info);
+    assertThat(underTest.getPluginsByKey()).isEqualTo(plugins);
     assertThat(underTest.getPluginInfo("squid")).isSameAs(info);
     assertThat(underTest.getPluginInstance("squid")).isSameAs(instance);
 
index 92d0cd0598f913bd6d40f3de9d7ee55fcb5530c1..e572f184aa2f702e0a868b25b8663d2681cd53fe 100644 (file)
@@ -106,12 +106,20 @@ public class ScannerMediumTester extends ExternalResource {
   }
 
   public ScannerMediumTester registerPlugin(String pluginKey, File location) {
-    pluginInstaller.add(pluginKey, location);
+    return registerPlugin(pluginKey, location, 1L);
+  }
+
+  public ScannerMediumTester registerPlugin(String pluginKey, File location, long lastUpdatedAt) {
+    pluginInstaller.add(pluginKey, location, lastUpdatedAt);
     return this;
   }
 
   public ScannerMediumTester registerPlugin(String pluginKey, Plugin instance) {
-    pluginInstaller.add(pluginKey, instance);
+    return registerPlugin(pluginKey, instance, 1L);
+  }
+
+  public ScannerMediumTester registerPlugin(String pluginKey, Plugin instance, long lastUpdatedAt) {
+    pluginInstaller.add(pluginKey, instance, lastUpdatedAt);
     return this;
   }
 
index fbe8a44cf43515118133588afb571fa9ab082e6b..a949364d429ba4ad4631ba03c17875210b9817c1 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.scanner.report;
 
+import com.google.common.collect.ImmutableMap;
 import java.io.File;
 import java.io.IOException;
 import java.util.Date;
@@ -34,6 +35,8 @@ import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.scanner.ProjectAnalysisInfo;
+import org.sonar.scanner.bootstrap.ScannerPlugin;
+import org.sonar.scanner.bootstrap.ScannerPluginRepository;
 import org.sonar.scanner.cpd.CpdSettings;
 import org.sonar.scanner.protocol.output.ScannerReport;
 import org.sonar.scanner.protocol.output.ScannerReportReader;
@@ -42,6 +45,7 @@ import org.sonar.scanner.rule.ModuleQProfiles;
 import org.sonar.scanner.rule.QProfile;
 
 import static java.util.Arrays.asList;
+import static java.util.Collections.emptyMap;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.mockito.Mockito.mock;
@@ -60,6 +64,7 @@ public class MetadataPublisherTest {
   private CpdSettings cpdSettings;
   private InputModuleHierarchy inputModuleHierarchy;
   private AnalysisMode analysisMode;
+  private ScannerPluginRepository pluginRepository;
 
   @Before
   public void prepare() throws IOException {
@@ -68,7 +73,9 @@ public class MetadataPublisherTest {
     when(projectAnalysisInfo.analysisDate()).thenReturn(new Date(1234567L));
     settings = new MapSettings();
     qProfiles = mock(ModuleQProfiles.class);
+    pluginRepository = mock(ScannerPluginRepository.class);
     createPublisher(ProjectDefinition.create().setKey("foo"));
+    when(pluginRepository.getPluginsByKey()).thenReturn(emptyMap());
   }
 
   private void createPublisher(ProjectDefinition def) throws IOException {
@@ -76,7 +83,7 @@ public class MetadataPublisherTest {
     inputModuleHierarchy = mock(InputModuleHierarchy.class);
     when(inputModuleHierarchy.root()).thenReturn(rootModule);
     analysisMode = mock(AnalysisMode.class);
-    underTest = new MetadataPublisher(projectAnalysisInfo, inputModuleHierarchy, settings.asConfig(), qProfiles, cpdSettings, analysisMode);
+    underTest = new MetadataPublisher(projectAnalysisInfo, inputModuleHierarchy, settings.asConfig(), qProfiles, cpdSettings, analysisMode, pluginRepository);
   }
 
   @Test
@@ -84,6 +91,9 @@ public class MetadataPublisherTest {
     settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
     Date date = new Date();
     when(qProfiles.findAll()).thenReturn(asList(new QProfile("q1", "Q1", "java", date)));
+    when(pluginRepository.getPluginsByKey()).thenReturn(ImmutableMap.of(
+      "java", new ScannerPlugin("java", 12345L, null),
+      "php", new ScannerPlugin("php", 45678L, null)));
     File outputDir = temp.newFolder();
     ScannerReportWriter writer = new ScannerReportWriter(outputDir);
 
@@ -101,6 +111,14 @@ public class MetadataPublisherTest {
       .setLanguage("java")
       .setRulesUpdatedAt(date.getTime())
       .build()));
+    assertThat(metadata.getPluginsByKey()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin.newBuilder()
+      .setKey("java")
+      .setUpdatedAt(12345)
+      .build()),
+      entry("php", org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin.newBuilder()
+        .setKey("php")
+        .setUpdatedAt(45678)
+        .build()));
   }
 
   @Test
index 75a93b65e121205c3565ec8112e95816c0bb712a..4cbecc332410af815f70184c0019527a565fc661 100644 (file)
@@ -37,7 +37,8 @@ message Metadata {
   int32 root_component_ref = 5;
   bool cross_project_duplication_activated = 6;
   map<string, QProfile> qprofiles_per_language = 7;
-  bool incremental = 8;
+  map<string, Plugin> plugins_by_key = 8;
+  bool incremental = 9;
 
   message QProfile {
     string key = 1;
@@ -45,6 +46,11 @@ message Metadata {
     string language = 3;
     int64 rulesUpdatedAt = 4;
   }
+  
+  message Plugin {
+    string key = 1;
+    int64 updatedAt = 2;
+  }
 }
 
 message ContextProperty {