2 * Sonar, open source software quality management tool.
3 * Copyright (C) 2009 SonarSource SA
4 * mailto:contact AT sonarsource DOT com
6 * Sonar is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * Sonar is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with Sonar; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
20 package org.sonar.updatecenter.mavenplugin;
22 import org.apache.commons.lang.StringUtils;
23 import org.apache.maven.archiver.MavenArchiveConfiguration;
24 import org.apache.maven.archiver.MavenArchiver;
25 import org.apache.maven.artifact.Artifact;
26 import org.apache.maven.artifact.factory.ArtifactFactory;
27 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
28 import org.apache.maven.artifact.repository.ArtifactRepository;
29 import org.apache.maven.artifact.resolver.ArtifactCollector;
30 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
31 import org.apache.maven.artifact.resolver.filter.ScopeArtifactFilter;
32 import org.apache.maven.model.License;
33 import org.apache.maven.plugin.MojoExecutionException;
34 import org.apache.maven.plugin.MojoFailureException;
35 import org.apache.maven.shared.dependency.tree.DependencyNode;
36 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder;
37 import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException;
38 import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor;
39 import org.codehaus.plexus.archiver.jar.JarArchiver;
40 import org.codehaus.plexus.util.FileUtils;
41 import org.sonar.updatecenter.common.FormatUtils;
42 import org.sonar.updatecenter.common.PluginKeyUtils;
43 import org.sonar.updatecenter.common.PluginManifest;
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.Date;
49 import java.util.HashSet;
50 import java.util.List;
54 * Build a Sonar Plugin from the current project.
56 * @author Evgeny Mandrikov
60 * @requiresDependencyResolution runtime
63 public class SonarPluginMojo extends AbstractSonarPluginMojo {
64 private static final String LIB_DIR = "META-INF/lib/";
65 private static final String[] DEFAULT_EXCLUDES = new String[] { "**/package.html" };
66 private static final String[] DEFAULT_INCLUDES = new String[] { "**/**" };
69 * List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
70 * is being packaged into the JAR.
74 private String[] includes;
77 * List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents
78 * is being packaged into the JAR.
82 private String[] excludes;
87 * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="jar"
89 protected JarArchiver jarArchiver;
92 * The archive configuration to use.
93 * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
97 private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
104 private DependencyTreeBuilder dependencyTreeBuilder;
107 * The artifact repository to use.
109 * @parameter expression="${localRepository}"
113 private ArtifactRepository localRepository;
116 * The artifact factory to use.
122 private ArtifactFactory artifactFactory;
125 * The artifact metadata source to use.
131 private ArtifactMetadataSource artifactMetadataSource;
134 * The artifact collector to use.
140 private ArtifactCollector artifactCollector;
143 * @parameter expression="${sonar.addMavenDescriptor}"
145 private boolean addMavenDescriptor = true;
147 public void execute() throws MojoExecutionException, MojoFailureException {
151 File jarFile = createArchive();
152 String classifier = getClassifier();
153 if (classifier != null) {
154 projectHelper.attachArtifact(getProject(), "jar", classifier, jarFile);
156 getProject().getArtifact().setFile(jarFile);
160 public File createArchive() throws MojoExecutionException {
161 File jarFile = getJarFile(getOutputDirectory(), getFinalName(), getClassifier());
162 MavenArchiver archiver = new MavenArchiver();
163 archiver.setArchiver(jarArchiver);
164 archiver.setOutputFile(jarFile);
167 archiver.getArchiver().addDirectory(getClassesDirectory(), getIncludes(), getExcludes());
168 archive.setAddMavenDescriptor(addMavenDescriptor);
169 getLog().info("-------------------------------------------------------");
170 getLog().info("Plugin definition in update center");
171 addManifestProperty("Key", PluginManifest.KEY, getPluginKey());
172 addManifestProperty("Name", PluginManifest.NAME, getPluginName());
173 addManifestProperty("Description", PluginManifest.DESCRIPTION, getPluginDescription());
174 addManifestProperty("Version", PluginManifest.VERSION, getProject().getVersion());
175 addManifestProperty("Main class", PluginManifest.MAIN_CLASS, getPluginClass());
176 addManifestProperty("Homepage", PluginManifest.HOMEPAGE, getPluginUrl());
177 addManifestProperty("Sonar version", PluginManifest.SONAR_VERSION, getSonarPluginApiArtifact().getVersion());
178 addManifestProperty("License", PluginManifest.LICENSE, getPluginLicense());
179 addManifestProperty("Organization", PluginManifest.ORGANIZATION, getPluginOrganization());
180 addManifestProperty("Organization URL", PluginManifest.ORGANIZATION_URL, getPluginOrganizationUrl());
181 addManifestProperty("Terms & Conditions URL", PluginManifest.TERMS_CONDITIONS_URL, getPluginTermsConditionsUrl());
182 addManifestProperty("Issue Tracker URL", PluginManifest.ISSUE_TRACKER_URL, getPluginIssueTrackerUrl());
183 addManifestProperty("Build date", PluginManifest.BUILD_DATE, FormatUtils.toString(new Date(), true));
184 getLog().info("-------------------------------------------------------");
186 if (isUseChildFirstClassLoader()) {
187 archive.addManifestEntry(PluginManifest.USE_CHILD_FIRST_CLASSLOADER, "true");
190 if (isSkipDependenciesPackaging()) {
191 getLog().info("Skip packaging of dependencies");
194 List<String> libs = copyDependencies();
195 if ( !libs.isEmpty()) {
196 archiver.getArchiver().addDirectory(getAppDirectory(), getIncludes(), getExcludes());
197 archive.addManifestEntry(PluginManifest.DEPENDENCIES, StringUtils.join(libs, " "));
201 archiver.createArchive(getProject(), archive);
204 } catch (Exception e) {
205 throw new MojoExecutionException("Error assembling Sonar-plugin: " + e.getMessage(), e);
209 private void addManifestProperty(String label, String key, String value) {
210 getLog().info(" " + label + ": " + StringUtils.defaultString(value));
211 archive.addManifestEntry(key, value);
214 private String getPluginLicense() {
215 List<String> licenses = new ArrayList<String>();
216 if (getProject().getLicenses() != null) {
217 for (Object license : getProject().getLicenses()) {
218 License l = (License) license;
219 if (l.getName() != null) {
220 licenses.add(l.getName());
224 return StringUtils.join(licenses, " ");
227 private String getPluginOrganization() {
228 if (getProject().getOrganization() != null) {
229 return getProject().getOrganization().getName();
234 private String getPluginOrganizationUrl() {
235 if (getProject().getOrganization() != null) {
236 return getProject().getOrganization().getUrl();
241 private void checkPluginKey() throws MojoExecutionException {
242 if ( StringUtils.isNotBlank(getExplicitPluginKey()) && !PluginKeyUtils.isValid(getExplicitPluginKey())) {
243 throw new MojoExecutionException("Plugin key is badly formatted. Please use ascii letters and digits only. Value: " + getExplicitPluginKey());
247 private void checkPluginClass() throws MojoExecutionException {
248 if ( !new File(getClassesDirectory(), getPluginClass().replace('.', '/') + ".class").exists()) {
249 throw new MojoExecutionException("Error assembling Sonar-plugin: Plugin-Class '" + getPluginClass() + "' not found");
253 private String getPluginKey() {
254 if ( StringUtils.isNotBlank(getExplicitPluginKey())) {
255 return getExplicitPluginKey();
257 return PluginKeyUtils.sanitize(getProject().getArtifactId());
260 protected static File getJarFile(File basedir, String finalName, String classifier) {
261 if (classifier == null) {
263 } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) {
264 classifier = "-" + classifier;
266 return new File(basedir, finalName + classifier + ".jar");
269 private List<String> copyDependencies() throws IOException, DependencyTreeBuilderException {
270 List<String> ids = new ArrayList<String>();
271 List<String> libs = new ArrayList<String>();
272 File libDirectory = new File(getAppDirectory(), LIB_DIR);
273 Set<Artifact> artifacts = getNotProvidedDependencies();
274 for (Artifact artifact : artifacts) {
275 String targetFileName = getDefaultFinalName(artifact);
276 FileUtils.copyFileIfModified(artifact.getFile(), new File(libDirectory, targetFileName));
277 libs.add(LIB_DIR + targetFileName);
278 ids.add(artifact.getDependencyConflictId());
281 if ( !ids.isEmpty()) {
282 getLog().info(getMessage("Following dependencies are packaged in the plugin:", ids));
283 getLog().info(new StringBuilder()
284 .append("See following page for more details about plugin dependencies:\n")
285 .append("\n\thttp://docs.codehaus.org/display/SONAR/Coding+a+plugin\n")
292 private String getDefaultFinalName(Artifact artifact) {
293 return artifact.getArtifactId() + "-" + artifact.getVersion() + "." + artifact.getArtifactHandler().getExtension();
296 private Set<Artifact> getNotProvidedDependencies() throws DependencyTreeBuilderException {
297 Set<Artifact> result = new HashSet<Artifact>();
298 Set<Artifact> providedArtifacts = getSonarProvidedArtifacts();
299 for (Artifact artifact : getIncludedArtifacts()) {
300 if ("sonar-plugin".equals(artifact.getType())) {
303 if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) || Artifact.SCOPE_TEST.equals(artifact.getScope())) {
306 if (containsArtifact(providedArtifacts, artifact)) {
309 result.add(artifact);
314 private boolean containsArtifact(Set<Artifact> artifacts, Artifact artifact) {
315 for (Artifact a : artifacts) {
316 if (StringUtils.equals(a.getGroupId(), artifact.getGroupId()) &&
317 StringUtils.equals(a.getArtifactId(), artifact.getArtifactId())) {
324 private Set<Artifact> getSonarProvidedArtifacts() throws DependencyTreeBuilderException {
325 Set<Artifact> result = new HashSet<Artifact>();
326 ArtifactFilter artifactFilter = new ScopeArtifactFilter(Artifact.SCOPE_RUNTIME);
327 DependencyNode rootNode = dependencyTreeBuilder.buildDependencyTree(getProject(), localRepository, artifactFactory,
328 artifactMetadataSource, artifactFilter, artifactCollector);
329 rootNode.accept(new BuildingDependencyNodeVisitor());
330 searchForSonarProvidedArtifacts(rootNode, result, false);
334 private void searchForSonarProvidedArtifacts(DependencyNode dependency, Set<Artifact> sonarArtifacts, boolean isProvidedBySonar) {
335 if (dependency != null) {
336 // skip check on root node - see SONAR-1815
337 if (dependency.getParent() != null) {
338 isProvidedBySonar = isProvidedBySonar || ("org.codehaus.sonar".equals(dependency.getArtifact().getGroupId()) && !Artifact.SCOPE_TEST.equals(dependency.getArtifact().getScope()));
341 if (isProvidedBySonar) {
342 sonarArtifacts.add(dependency.getArtifact());
345 if ( !Artifact.SCOPE_TEST.equals(dependency.getArtifact().getScope())) {
346 for (Object childDep : dependency.getChildren()) {
347 searchForSonarProvidedArtifacts((DependencyNode) childDep, sonarArtifacts, isProvidedBySonar);
353 private String[] getIncludes() {
354 if (includes != null && includes.length > 0) {
357 return DEFAULT_INCLUDES;
360 private String[] getExcludes() {
361 if (excludes != null && excludes.length > 0) {
364 return DEFAULT_EXCLUDES;