]> source.dussan.org Git - sonarqube.git/blob
274c0d74b023534658e7879a2a3f6350ea48d4a7
[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.*;
48
49 /**
50  * Build a Sonar Plugin from the current project.
51  * 
52  * @author Evgeny Mandrikov
53  * @goal sonar-plugin
54  * @phase package
55  * @requiresProject
56  * @requiresDependencyResolution runtime
57  * @threadSafe
58  */
59 public class SonarPluginMojo extends AbstractSonarPluginMojo {
60   private static final String LIB_DIR = "META-INF/lib/";
61   private static final String[] DEFAULT_EXCLUDES = new String[] { "**/package.html" };
62   private static final String[] DEFAULT_INCLUDES = new String[] { "**/**" };
63
64   /**
65    * List of files to include. Specified as fileset patterns which are relative to the input directory whose contents
66    * is being packaged into the JAR.
67    * 
68    * @parameter
69    */
70   private String[] includes;
71
72   /**
73    * List of files to exclude. Specified as fileset patterns which are relative to the input directory whose contents
74    * is being packaged into the JAR.
75    * 
76    * @parameter
77    */
78   private String[] excludes;
79
80   /**
81    * The Jar archiver.
82    * 
83    * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="jar"
84    */
85   protected JarArchiver jarArchiver;
86
87   /**
88    * The archive configuration to use.
89    * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
90    * 
91    * @parameter
92    */
93   private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
94
95   /**
96    * @component
97    * @required
98    * @readonly
99    */
100   private DependencyTreeBuilder dependencyTreeBuilder;
101
102   /**
103    * The artifact repository to use.
104    * 
105    * @parameter expression="${localRepository}"
106    * @required
107    * @readonly
108    */
109   private ArtifactRepository localRepository;
110
111   /**
112    * The artifact factory to use.
113    * 
114    * @component
115    * @required
116    * @readonly
117    */
118   private ArtifactFactory artifactFactory;
119
120   /**
121    * The artifact metadata source to use.
122    * 
123    * @component
124    * @required
125    * @readonly
126    */
127   private ArtifactMetadataSource artifactMetadataSource;
128
129   /**
130    * The artifact collector to use.
131    * 
132    * @component
133    * @required
134    * @readonly
135    */
136   private ArtifactCollector artifactCollector;
137
138   /**
139    * @parameter expression="${sonar.addMavenDescriptor}"
140    */
141   private boolean addMavenDescriptor = true;
142
143   public void execute() throws MojoExecutionException, MojoFailureException {
144     checkPluginKey();
145     checkPluginClass();
146
147     File jarFile = createArchive();
148     String classifier = getClassifier();
149     if (classifier != null) {
150       projectHelper.attachArtifact(getProject(), "jar", classifier, jarFile);
151     } else {
152       getProject().getArtifact().setFile(jarFile);
153     }
154   }
155
156   public File createArchive() throws MojoExecutionException {
157     File jarFile = getJarFile(getOutputDirectory(), getFinalName(), getClassifier());
158     MavenArchiver archiver = new MavenArchiver();
159     archiver.setArchiver(jarArchiver);
160     archiver.setOutputFile(jarFile);
161
162     try {
163       archiver.getArchiver().addDirectory(getClassesDirectory(), getIncludes(), getExcludes());
164       archive.setAddMavenDescriptor(addMavenDescriptor);
165       getLog().info("-------------------------------------------------------");
166       getLog().info("Plugin definition in update center");
167       addManifestProperty("Key", PluginManifest.KEY, getPluginKey());
168       addManifestProperty("Name", PluginManifest.NAME, getPluginName());
169       addManifestProperty("Description", PluginManifest.DESCRIPTION, getPluginDescription());
170       addManifestProperty("Version", PluginManifest.VERSION, getProject().getVersion());
171       addManifestProperty("Main class", PluginManifest.MAIN_CLASS, getPluginClass());
172       addManifestProperty("Homepage", PluginManifest.HOMEPAGE, getPluginUrl());
173       addManifestProperty("Sonar version", PluginManifest.SONAR_VERSION, getSonarPluginApiArtifact().getVersion());
174       addManifestProperty("License", PluginManifest.LICENSE, getPluginLicense());
175       addManifestProperty("Organization", PluginManifest.ORGANIZATION, getPluginOrganization());
176       addManifestProperty("Organization URL", PluginManifest.ORGANIZATION_URL, getPluginOrganizationUrl());
177       addManifestProperty("Terms & Conditions URL", PluginManifest.TERMS_CONDITIONS_URL, getPluginTermsConditionsUrl());
178       addManifestProperty("Issue Tracker URL", PluginManifest.ISSUE_TRACKER_URL, getPluginIssueTrackerUrl());
179       addManifestProperty("Build date", PluginManifest.BUILD_DATE, FormatUtils.toString(new Date(), true));
180       getLog().info("-------------------------------------------------------");
181
182       if (isUseChildFirstClassLoader()) {
183         archive.addManifestEntry(PluginManifest.USE_CHILD_FIRST_CLASSLOADER, "true");
184       }
185
186       if (StringUtils.isNotBlank(getExtendPlugin())) {
187         archive.addManifestEntry(PluginManifest.EXTEND_PLUGIN, getExtendPlugin());
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 }