]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5604 Design plugin should no more rely on Maven APIs
authorJulien HENRY <julien.henry@sonarsource.com>
Mon, 8 Sep 2014 20:12:13 +0000 (22:12 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Wed, 10 Sep 2014 07:05:35 +0000 (09:05 +0200)
plugins/sonar-design-plugin/pom.xml
plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java
plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java [new file with mode: 0644]
sonar-maven-plugin/pom.xml
sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java

index d7aef6c022363ce45e992e8b5f4bcfa35f8a3611..f04d435231fbd5fcaf3c7118ee6552e1520162bd 100644 (file)
       <artifactId>maven-dependency-tree</artifactId>
       <scope>provided</scope>
     </dependency>
-    
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
+
     <!-- unit tests -->
     <dependency>
       <groupId>org.codehaus.sonar</groupId>
       <artifactId>sonar-testing-harness</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
index 088b3d182b7b730487dc4e329ba8f853e008f107..9afa425a0e44a4eb808835e2b11f55d894dc95d9 100644 (file)
  */
 package org.sonar.plugins.design.batch;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.reflect.TypeToken;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.factory.ArtifactFactory;
 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
@@ -34,28 +43,42 @@ import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeV
 import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
 import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
 import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
-import org.sonar.api.batch.SupportedEnvironment;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.sonar.api.batch.Sensor;
 import org.sonar.api.batch.SensorContext;
 import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.batch.SupportedEnvironment;
+import org.sonar.api.config.Settings;
 import org.sonar.api.design.Dependency;
 import org.sonar.api.resources.Library;
 import org.sonar.api.resources.Project;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.utils.SonarException;
 
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 @SupportedEnvironment("maven")
 public class MavenDependenciesSensor implements Sensor {
 
+  private static final String SONAR_MAVEN_PROJECT_DEPENDENCY = "sonar.maven.projectDependencies";
+
+  private static final Logger LOG = LoggerFactory.getLogger(MavenDependenciesSensor.class);
+
   private ArtifactRepository localRepository;
   private ArtifactFactory artifactFactory;
   private ArtifactMetadataSource artifactMetadataSource;
   private ArtifactCollector artifactCollector;
   private DependencyTreeBuilder treeBuilder;
   private SonarIndex index;
+  private Settings settings;
 
-  public MavenDependenciesSensor(ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource artifactMetadataSource,
-                                 ArtifactCollector artifactCollector, DependencyTreeBuilder treeBuilder, SonarIndex index) {
+  public MavenDependenciesSensor(Settings settings, ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource artifactMetadataSource,
+    ArtifactCollector artifactCollector, DependencyTreeBuilder treeBuilder, SonarIndex index) {
+    this.settings = settings;
     this.localRepository = localRepository;
     this.artifactFactory = artifactFactory;
     this.artifactMetadataSource = artifactMetadataSource;
@@ -64,42 +87,153 @@ public class MavenDependenciesSensor implements Sensor {
     this.treeBuilder = treeBuilder;
   }
 
+  /**
+   * Used with SQ Maven plugin 2.5+
+   */
+  public MavenDependenciesSensor(Settings settings, SonarIndex index) {
+    this.settings = settings;
+    this.index = index;
+  }
+
   public boolean shouldExecuteOnProject(Project project) {
     return true;
   }
 
-  public void analyse(final Project project, final SensorContext context) {
-    try {
-      DependencyNode root = treeBuilder.buildDependencyTree(project.getPom(), localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector);
+  private static class InputDependency {
+
+    private final String key;
+
+    private final String version;
+
+    private String scope;
+
+    List<InputDependency> dependencies = new ArrayList<InputDependency>();
+
+    public InputDependency(String key, String version) {
+      this.key = key;
+      this.version = version;
+    }
+
+    public String key() {
+      return key;
+    }
+
+    public String version() {
+      return version;
+    }
+
+    public String scope() {
+      return scope;
+    }
+
+    public InputDependency setScope(String scope) {
+      this.scope = scope;
+      return this;
+    }
+
+    public List<InputDependency> dependencies() {
+      return dependencies;
+    }
+  }
+
+  private static class DependencyDeserializer implements JsonDeserializer<InputDependency> {
 
-      DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() {
-        public boolean visit(DependencyNode node) {
-          return true;
+    @Override
+    public InputDependency deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+
+      JsonObject dep = json.getAsJsonObject();
+      String key = dep.get("k").getAsString();
+      String version = dep.get("v").getAsString();
+      InputDependency result = new InputDependency(key, version);
+      result.setScope(dep.get("s").getAsString());
+      JsonElement subDeps = dep.get("d");
+      if (subDeps != null) {
+        JsonArray arrayOfSubDeps = subDeps.getAsJsonArray();
+        for (JsonElement e : arrayOfSubDeps) {
+          result.dependencies().add(deserialize(e, typeOfT, context));
         }
+      }
+      return result;
+    }
+
+  }
+
+  public void analyse(final Project project, final SensorContext context) {
+    if (settings.hasKey(SONAR_MAVEN_PROJECT_DEPENDENCY)) {
+      LOG.debug("Using dependency provided by property " + SONAR_MAVEN_PROJECT_DEPENDENCY);
+      String depsAsJson = settings.getString(SONAR_MAVEN_PROJECT_DEPENDENCY);
+      Collection<InputDependency> deps;
+      try {
+        GsonBuilder gsonBuilder = new GsonBuilder();
+        gsonBuilder.registerTypeAdapter(InputDependency.class, new DependencyDeserializer());
+        Gson gson = gsonBuilder.create();
 
-        public boolean endVisit(DependencyNode node) {
-          if (node.getParent() != null && node.getParent() != node) {
-            saveDependency(node, context);
+        Type collectionType = new TypeToken<Collection<InputDependency>>() {
+        }.getType();
+        deps = gson.fromJson(depsAsJson, collectionType);
+        saveDependencies(project, deps, context);
+      } catch (Exception e) {
+        throw new IllegalStateException("Unable to deserialize dependency information: " + depsAsJson, e);
+      }
+    }
+    else if (treeBuilder != null) {
+      LOG.warn("Computation of Maven dependencies by SonarQube is deprecated. Please update the version of SonarQube Maven plugin to 2.5+");
+      try {
+        DependencyNode root = treeBuilder.buildDependencyTree(project.getPom(), localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector);
+
+        DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() {
+          public boolean visit(DependencyNode node) {
+            return true;
           }
-          return true;
-        }
-      });
 
-      // mode verbose OFF : do not show the same lib many times
-      DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED;
+          public boolean endVisit(DependencyNode node) {
+            if (node.getParent() != null && node.getParent() != node) {
+              saveDependency(node, context);
+            }
+            return true;
+          }
+        });
+
+        // mode verbose OFF : do not show the same lib many times
+        DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED;
+
+        CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
+        DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter);
+        root.accept(firstPassVisitor);
 
-      CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
-      DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter);
-      root.accept(firstPassVisitor);
+        DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
+        visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
 
-      DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
-      visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
+        root.accept(visitor);
+
+      } catch (DependencyTreeBuilderException e) {
+        throw new SonarException("Can not load the graph of dependencies of the project " + project.getKey(), e);
+      }
+    }
+  }
 
-      root.accept(visitor);
+  private void saveDependencies(Resource from, Collection<InputDependency> deps, SensorContext context) {
+    for (InputDependency inputDep : deps) {
+      Resource to = toResource(inputDep, context);
+      Dependency dependency = new Dependency(from, to);
+      dependency.setUsage(inputDep.scope());
+      dependency.setWeight(1);
+      context.saveDependency(dependency);
+      if (!inputDep.dependencies().isEmpty()) {
+        saveDependencies(to, inputDep.dependencies(), context);
+      }
+    }
+  }
 
-    } catch (DependencyTreeBuilderException e) {
-      throw new SonarException("Can not load the graph of dependencies of the project " + project.getKey(), e);
+  private Resource toResource(InputDependency dependency, SensorContext context) {
+    Project project = new Project(dependency.key());
+    Resource result = context.getResource(project);
+    if (result == null || !((Project) result).getAnalysisVersion().equals(dependency.version())) {
+      Library lib = new Library(project.getKey(), dependency.version());
+      context.saveResource(lib);
+      result = context.getResource(lib);
     }
+    return result;
   }
 
   protected void saveDependency(DependencyNode node, SensorContext context) {
diff --git a/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java b/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java
new file mode 100644 (file)
index 0000000..a0adb1a
--- /dev/null
@@ -0,0 +1,74 @@
+package org.sonar.plugins.design.batch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.config.Settings;
+import org.sonar.api.design.Dependency;
+import org.sonar.api.resources.Library;
+import org.sonar.api.resources.Project;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MavenDependenciesSensorTest {
+
+  private MavenDependenciesSensor sensor;
+  private Settings settings;
+  private SonarIndex sonarIndex;
+  private SensorContext sensorContext;
+
+  @Before
+  public void prepare() {
+    settings = new Settings();
+    sonarIndex = mock(SonarIndex.class);
+    sensor = new MavenDependenciesSensor(settings, sonarIndex);
+    sensorContext = mock(SensorContext.class);
+    when(sensorContext.getResource(any(Library.class))).thenAnswer(new Answer<Library>() {
+      public Library answer(InvocationOnMock invocation) throws Throwable {
+        return (invocation.getArguments()[0] instanceof Library) ? (Library) invocation.getArguments()[0] : null;
+      }
+    });
+
+  }
+
+  @Test
+  public void testDependenciesProvidedAsProperties() {
+    settings
+      .setProperty(
+        "sonar.maven.projectDependencies",
+        "[{\"k\":\"antlr:antlr\",\"v\":\"2.7.2\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"commons-beanutils:commons-beanutils\",\"v\":\"1.7.0\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"commons-chain:commons-chain\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"commons-digester:commons-digester\",\"v\":\"1.8\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"commons-fileupload:commons-fileupload\",\"v\":\"1.1.1\",\"s\":\"compile\",\"d\":[{\"k\":\"commons-io:commons-io\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]}]},"
+          + "{\"k\":\"commons-logging:commons-logging\",\"v\":\"1.0.4\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"commons-validator:commons-validator\",\"v\":\"1.3.1\",\"s\":\"compile\",\"d\":[]},"
+          + "{\"k\":\"javax.servlet:servlet-api\",\"v\":\"2.3\",\"s\":\"provided\",\"d\":[]},"
+          + "{\"k\":\"junit:junit\",\"v\":\"3.8.1\",\"s\":\"test\",\"d\":[]},"
+          + "{\"k\":\"oro:oro\",\"v\":\"2.0.8\",\"s\":\"compile\",\"d\":[]}]");
+
+    Project project = new Project("foo");
+    sensor.analyse(project, sensorContext);
+
+    Library antlr = new Library("antlr:antlr", "2.7.2");
+    verify(sensorContext).saveResource(eq(antlr));
+    Library commonsFU = new Library("commons-fileupload:commons-fileupload", "1.1.1");
+    verify(sensorContext).saveResource(eq(commonsFU));
+    Library commonsIo = new Library("commons-io:commons-io", "1.1");
+    verify(sensorContext).saveResource(eq(commonsIo));
+    Library junit = new Library("junit:junit", "3.8.1");
+    verify(sensorContext).saveResource(eq(junit));
+
+    verify(sensorContext).saveDependency(new Dependency(project, antlr).setUsage("compile").setWeight(1));
+    verify(sensorContext).saveDependency(new Dependency(commonsFU, commonsIo).setUsage("compile").setWeight(1));
+    verify(sensorContext).saveDependency(new Dependency(project, junit).setUsage("test").setWeight(1));
+  }
+
+}
index d59510fd17bc1b583818237839891682b0e213b8..5a93cf227ade875e987093921be63d6050248e9e 100644 (file)
       <groupId>org.codehaus.sonar.runner</groupId>
       <artifactId>sonar-runner-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-plugin-api</artifactId>
index dfc313844a2146521233a4ad152b379468cfc58e..b3bbc9bb70b339733e9f624577449c62fc69d226 100644 (file)
@@ -31,19 +31,34 @@ import org.apache.maven.plugin.AbstractMojo;
 import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.maven.project.MavenProject;
 import org.apache.maven.project.MavenProjectBuilder;
+import org.apache.maven.shared.dependency.tree.DependencyNode;
 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
+import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
+import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter;
+import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter;
+import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter;
+import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
+import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor;
+import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor;
+import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor;
 import org.sonar.runner.api.EmbeddedRunner;
 import org.sonar.runner.api.RunnerProperties;
 import org.sonar.runner.api.ScanProperties;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Stack;
 
 /**
  * @goal sonar
  * @aggregator
  * @requiresDependencyResolution test
+ * @deprecated Only kept for backward compatibility with old version of SQ Maven plugin
  */
+@Deprecated
 public final class SonarMojo extends AbstractMojo {
 
   /**
@@ -151,7 +166,8 @@ public final class SonarMojo extends AbstractMojo {
         .setProperty(ScanProperties.PROJECT_VERSION, toString(project.getVersion()))
         .setProperty(ScanProperties.PROJECT_NAME, toString(project.getName()))
         .setProperty(ScanProperties.PROJECT_DESCRIPTION, toString(project.getDescription()))
-        .setProperty(ScanProperties.PROJECT_SOURCE_DIRS, ".");
+        .setProperty(ScanProperties.PROJECT_SOURCE_DIRS, ".")
+        .setProperty("sonar.maven.projectDependencies", dependenciesToJson(collectProjectDependencies()));
       // Exclude log implementation to not conflict with Maven 3.1 logging impl
       runner.mask("org.slf4j.LoggerFactory")
         // Include slf4j Logger that is exposed by some Sonar components
@@ -176,6 +192,126 @@ public final class SonarMojo extends AbstractMojo {
     }
   }
 
+  private static class Dependency {
+
+    private final String key;
+    private final String version;
+    private String scope;
+    List<Dependency> dependencies = new ArrayList<SonarMojo.Dependency>();
+
+    public Dependency(String key, String version) {
+      this.key = key;
+      this.version = version;
+    }
+
+    public String key() {
+      return key;
+    }
+
+    public String version() {
+      return version;
+    }
+
+    public String scope() {
+      return scope;
+    }
+
+    public Dependency setScope(String scope) {
+      this.scope = scope;
+      return this;
+    }
+
+    public List<Dependency> dependencies() {
+      return dependencies;
+    }
+  }
+
+  private List<Dependency> collectProjectDependencies() {
+    final List<Dependency> result = new ArrayList<SonarMojo.Dependency>();
+    try {
+      DependencyNode root = dependencyTreeBuilder.buildDependencyTree(project, localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector);
+
+      DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() {
+
+        private Stack<Dependency> stack = new Stack<SonarMojo.Dependency>();
+
+        public boolean visit(DependencyNode node) {
+          if (node.getParent() != null && node.getParent() != node) {
+            Dependency dependency = toDependency(node);
+            if (stack.isEmpty()) {
+              result.add(dependency);
+            }
+            else {
+              stack.peek().dependencies().add(dependency);
+            }
+            stack.push(dependency);
+          }
+          return true;
+        }
+
+        public boolean endVisit(DependencyNode node) {
+          if (!stack.isEmpty()) {
+            stack.pop();
+          }
+          return true;
+        }
+      });
+
+      // mode verbose OFF : do not show the same lib many times
+      DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED;
+
+      CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor();
+      DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter);
+      root.accept(firstPassVisitor);
+
+      DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes());
+      visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter);
+
+      root.accept(visitor);
+
+    } catch (DependencyTreeBuilderException e) {
+      throw new IllegalStateException("Can not load the graph of dependencies of the project " + getSonarKey(project), e);
+    }
+    return result;
+  }
+
+  private Dependency toDependency(DependencyNode node) {
+    String key = String.format("%s:%s", node.getArtifact().getGroupId(), node.getArtifact().getArtifactId());
+    String version = node.getArtifact().getBaseVersion();
+    return new Dependency(key, version).setScope(node.getArtifact().getScope());
+  }
+
+  private String dependenciesToJson(List<Dependency> deps) {
+    StringBuilder json = new StringBuilder();
+    json.append('[');
+    serializeDeps(json, deps);
+    json.append(']');
+    return json.toString();
+  }
+
+  private void serializeDeps(StringBuilder json, List<Dependency> deps) {
+    for (Iterator<Dependency> dependencyIt = deps.iterator(); dependencyIt.hasNext();) {
+      serializeDep(json, dependencyIt.next());
+      if (dependencyIt.hasNext()) {
+        json.append(',');
+      }
+    }
+  }
+
+  private void serializeDep(StringBuilder json, Dependency dependency) {
+    json.append("{");
+    json.append("\"k\":");
+    json.append(dependency.key());
+    json.append(",\"v\":\"");
+    json.append(dependency.version());
+    json.append("\",\"s\":\"");
+    json.append(dependency.scope());
+    json.append("\",\"d\":[");
+    serializeDeps(json, dependency.dependencies());
+    json.append("]");
+    json.append("}");
+  }
+
   private ArtifactVersion getMavenVersion() {
     return runtimeInformation.getApplicationVersion();
   }