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;
50 * Build a Sonar Plugin from the current project.
52 * @author Evgeny Mandrikov
56 * @requiresDependencyResolution runtime
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[] { "**/**" };
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.
70 private String[] includes;
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.
78 private String[] excludes;
83 * @component role="org.codehaus.plexus.archiver.Archiver" role-hint="jar"
85 protected JarArchiver jarArchiver;
88 * The archive configuration to use.
89 * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
93 private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
100 private DependencyTreeBuilder dependencyTreeBuilder;
103 * The artifact repository to use.
105 * @parameter expression="${localRepository}"
109 private ArtifactRepository localRepository;
112 * The artifact factory to use.
118 private ArtifactFactory artifactFactory;
121 * The artifact metadata source to use.
127 private ArtifactMetadataSource artifactMetadataSource;
130 * The artifact collector to use.
136 private ArtifactCollector artifactCollector;
139 * @parameter expression="${sonar.addMavenDescriptor}"
141 private boolean addMavenDescriptor = true;
143 public void execute() throws MojoExecutionException, MojoFailureException {
147 File jarFile = createArchive();
148 String classifier = getClassifier();
149 if (classifier != null) {
150 projectHelper.attachArtifact(getProject(), "jar", classifier, jarFile);
152 getProject().getArtifact().setFile(jarFile);
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);
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("-------------------------------------------------------");
182 if (isUseChildFirstClassLoader()) {
183 archive.addManifestEntry(PluginManifest.USE_CHILD_FIRST_CLASSLOADER, "true");
186 if (StringUtils.isNotBlank(getExtendPlugin())) {
187 archive.addManifestEntry(PluginManifest.EXTEND_PLUGIN, getExtendPlugin());
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;