]> source.dussan.org Git - sonarqube.git/blob
494260d139c608e862bf611284f0da014af65d16
[sonarqube.git] /
1 /*
2  * Sonar, open source software quality management tool.
3  * Copyright (C) 2009 SonarSource SA
4  * mailto:contact AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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
19  */
20 package org.sonar.updatecenter.mavenplugin;
21
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;
44
45 import java.io.File;
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;
51 import java.util.Set;
52
53 /**
54  * Build a Sonar Plugin from the current project.
55  * 
56  * @author Evgeny Mandrikov
57  * @goal sonar-plugin
58  * @phase package
59  * @requiresProject
60  * @requiresDependencyResolution runtime
61  * @threadSafe
62  */
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[] { "**/**" };
67
68   /**
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.
71    * 
72    * @parameter
73    */
74   private String[] includes;
75
76   /**
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.
79    * 
80    * @parameter
81    */
82   private String[] excludes;
83
84   /**
85    * The Jar archiver.
86    * 
87    * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="jar"
88    */
89   protected JarArchiver jarArchiver;
90
91   /**
92    * The archive configuration to use.
93    * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
94    * 
95    * @parameter
96    */
97   private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
98
99   /**
100    * @component
101    * @required
102    * @readonly
103    */
104   private DependencyTreeBuilder dependencyTreeBuilder;
105
106   /**
107    * The artifact repository to use.
108    * 
109    * @parameter expression="${localRepository}"
110    * @required
111    * @readonly
112    */
113   private ArtifactRepository localRepository;
114
115   /**
116    * The artifact factory to use.
117    * 
118    * @component
119    * @required
120    * @readonly
121    */
122   private ArtifactFactory artifactFactory;
123
124   /**
125    * The artifact metadata source to use.
126    * 
127    * @component
128    * @required
129    * @readonly
130    */
131   private ArtifactMetadataSource artifactMetadataSource;
132
133   /**
134    * The artifact collector to use.
135    * 
136    * @component
137    * @required
138    * @readonly
139    */
140   private ArtifactCollector artifactCollector;
141
142   /**
143    * @parameter expression="${sonar.addMavenDescriptor}"
144    */
145   private boolean addMavenDescriptor = true;
146
147   public void execute() throws MojoExecutionException, MojoFailureException {
148     checkPluginKey();
149     checkPluginClass();
150
151     File jarFile = createArchive();
152     String classifier = getClassifier();
153     if (classifier != null) {
154       projectHelper.attachArtifact(getProject(), "jar", classifier, jarFile);
155     } else {
156       getProject().getArtifact().setFile(jarFile);
157     }
158   }
159
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);
165
166     try {
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("-------------------------------------------------------");
185
186       if (isUseChildFirstClassLoader()) {
187         archive.addManifestEntry(PluginManifest.USE_CHILD_FIRST_CLASSLOADER, "true");
188       }
189
190       if (isSkipDependenciesPackaging()) {
191         getLog().info("Skip packaging of dependencies");
192
193       } else {
194         List<String> libs = copyDependencies();
195         if ( !libs.isEmpty()) {
196           archiver.getArchiver().addDirectory(getAppDirectory(), getIncludes(), getExcludes());
197           archive.addManifestEntry(PluginManifest.DEPENDENCIES, StringUtils.join(libs, " "));
198         }
199       }
200
201       archiver.createArchive(getProject(), archive);
202       return jarFile;
203
204     } catch (Exception e) {
205       throw new MojoExecutionException("Error assembling Sonar-plugin: " + e.getMessage(), e);
206     }
207   }
208
209   private void addManifestProperty(String label, String key, String value) {
210     getLog().info("    " + label + ": " + StringUtils.defaultString(value));
211     archive.addManifestEntry(key, value);
212   }
213
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());
221         }
222       }
223     }
224     return StringUtils.join(licenses, " ");
225   }
226
227   private String getPluginOrganization() {
228     if (getProject().getOrganization() != null) {
229       return getProject().getOrganization().getName();
230     }
231     return null;
232   }
233
234   private String getPluginOrganizationUrl() {
235     if (getProject().getOrganization() != null) {
236       return getProject().getOrganization().getUrl();
237     }
238     return null;
239   }
240
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());
244     }
245   }
246
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");
250     }
251   }
252
253   private String getPluginKey() {
254     if ( StringUtils.isNotBlank(getExplicitPluginKey())) {
255       return getExplicitPluginKey();
256     }
257     return PluginKeyUtils.sanitize(getProject().getArtifactId());
258   }
259
260   protected static File getJarFile(File basedir, String finalName, String classifier) {
261     if (classifier == null) {
262       classifier = "";
263     } else if (classifier.trim().length() > 0 && !classifier.startsWith("-")) {
264       classifier = "-" + classifier;
265     }
266     return new File(basedir, finalName + classifier + ".jar");
267   }
268
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());
279     }
280
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")
286           .toString()
287           );
288     }
289     return libs;
290   }
291
292   private String getDefaultFinalName(Artifact artifact) {
293     return artifact.getArtifactId() + "-" + artifact.getVersion() + "." + artifact.getArtifactHandler().getExtension();
294   }
295
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())) {
301         continue;
302       }
303       if (Artifact.SCOPE_PROVIDED.equals(artifact.getScope()) || Artifact.SCOPE_TEST.equals(artifact.getScope())) {
304         continue;
305       }
306       if (containsArtifact(providedArtifacts, artifact)) {
307         continue;
308       }
309       result.add(artifact);
310     }
311     return result;
312   }
313
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())) {
318         return true;
319       }
320     }
321     return false;
322   }
323
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);
331     return result;
332   }
333
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()));
339       }
340
341       if (isProvidedBySonar) {
342         sonarArtifacts.add(dependency.getArtifact());
343       }
344
345       if ( !Artifact.SCOPE_TEST.equals(dependency.getArtifact().getScope())) {
346         for (Object childDep : dependency.getChildren()) {
347           searchForSonarProvidedArtifacts((DependencyNode) childDep, sonarArtifacts, isProvidedBySonar);
348         }
349       }
350     }
351   }
352
353   private String[] getIncludes() {
354     if (includes != null && includes.length > 0) {
355       return includes;
356     }
357     return DEFAULT_INCLUDES;
358   }
359
360   private String[] getExcludes() {
361     if (excludes != null && excludes.length > 0) {
362       return excludes;
363     }
364     return DEFAULT_EXCLUDES;
365   }
366
367 }