aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--plugins/sonar-design-plugin/pom.xml10
-rw-r--r--plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java184
-rw-r--r--plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java74
-rw-r--r--sonar-maven-plugin/pom.xml4
-rw-r--r--sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java138
5 files changed, 383 insertions, 27 deletions
diff --git a/plugins/sonar-design-plugin/pom.xml b/plugins/sonar-design-plugin/pom.xml
index d7aef6c0223..f04d435231f 100644
--- a/plugins/sonar-design-plugin/pom.xml
+++ b/plugins/sonar-design-plugin/pom.xml
@@ -28,13 +28,21 @@
<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>
diff --git a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java
index 088b3d182b7..9afa425a0e4 100644
--- a/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java
+++ b/plugins/sonar-design-plugin/src/main/java/org/sonar/plugins/design/batch/MavenDependenciesSensor.java
@@ -19,6 +19,15 @@
*/
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
index 00000000000..a0adb1a03fb
--- /dev/null
+++ b/plugins/sonar-design-plugin/src/test/java/org/sonar/plugins/design/batch/MavenDependenciesSensorTest.java
@@ -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));
+ }
+
+}
diff --git a/sonar-maven-plugin/pom.xml b/sonar-maven-plugin/pom.xml
index d59510fd17b..5a93cf227ad 100644
--- a/sonar-maven-plugin/pom.xml
+++ b/sonar-maven-plugin/pom.xml
@@ -20,6 +20,10 @@
<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>
<scope>provided</scope>
diff --git a/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java b/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java
index dfc313844a2..b3bbc9bb70b 100644
--- a/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java
+++ b/sonar-maven-plugin/src/main/java/org/sonar/maven/SonarMojo.java
@@ -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();
}