com.atlassian.maven.plugin.clover.CloverReportMojo Maven / Gradle / Ivy
package com.atlassian.maven.plugin.clover;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.ArtifactResolver;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
import org.apache.tools.ant.PropertyHelper;
import org.codehaus.plexus.resource.ResourceManager;
import com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo;
import com.atlassian.maven.plugin.clover.internal.AntPropertyHelper;
import com.atlassian.maven.plugin.clover.internal.CloverConfiguration;
import com.atlassian.maven.plugin.clover.internal.ConfigUtil;
import com.atlassian.clover.cfg.Interval;
/**
* Generate a Clover report from existing Clover databases. The generated report
* is an external report generated by Clover itself. If the project generating the report is a top level project and
* if the aggregate
configuration element is set to true then an aggregated report will also be created.
*
* Note: This report mojo should be an @aggregator and the clover:aggregate
mojo shouldn't exist. This
* is a limitation of the site plugin which doesn't support @aggregator reports...
*
* @goal clover
*/
public class CloverReportMojo extends AbstractMavenReport implements CloverConfiguration {
// TODO: Need some way to share config elements and code between report mojos and main build mojos.
// See http://jira.codehaus.org/browse/MNG-1886
/**
* Use a custom report descriptor for generating your Clover Reports.
* The format for the configuration file is identical to an Ant build file which uses the <clover-report/>
* task. For a complete reference, please consult the:
* Creating custom reports and
* clover-report documentation
*
* @parameter expression="${maven.clover.reportDescriptor}"
*/
private File reportDescriptor;
/**
* If set to true, the clover-report configuration file will be resolved as a versioned artifact by looking for it
* in your configured maven repositories - both remote and local.
*
* @parameter expression="${maven.clover.resolveReportDescriptor}" default-value="false"
*/
private boolean resolveReportDescriptor;
/**
* The component that is used to resolve additional artifacts required.
*
* @component
*/
protected ArtifactResolver artifactResolver;
/**
* Remote repositories used for the project.
*
* @parameter expression="${project.remoteArtifactRepositories}"
*/
protected List repositories;
/**
* The component used for creating artifact instances.
*
* @component
*/
protected ArtifactFactory artifactFactory;
/**
* The local repository.
*
* @parameter expression="${localRepository}"
*/
protected ArtifactRepository localRepository;
/**
* The location of the Clover database.
*
* @parameter expression="${maven.clover.cloverDatabase}"
*/
private String cloverDatabase;
/**
* If true, then a single database will be saved for the entire project, in the target directory of the execution
* root.
* If a custom location for the cloverDatabase is specified, this flag is ignored.
* @parameter expression="${maven.clover.singleCloverDatabase}" default-value="false"
*/
protected boolean singleCloverDatabase;
/**
* The location of the merged clover database to create when running a report in a multimodule build.
*
* @parameter expression="${maven.clover.cloverMergeDatabase}" default-value="${project.build.directory}/clover/cloverMerge.db"
* @required
*/
private String cloverMergeDatabase;
/**
* The directory where the Clover report will be generated.
*
* @parameter expression="${maven.clover.outputDirectory}" default-value="${project.reporting.outputDirectory}/clover"
* @required
*/
private File outputDirectory;
/**
* The location where historical Clover data will be saved.
* Note: It's recommended to modify the location of this directory so that it points to a more permanent
* location as the ${project.build.directory}
directory is erased when the project is cleaned.
*
* @parameter expression="${maven.clover.historyDir}" default-value="${project.build.directory}/clover/history"
* @required
*/
private String historyDir;
/**
* When the Clover Flush Policy is set to "interval" or threaded this value is the minimum
* period between flush operations (in milliseconds).
*
* @parameter expression="${maven.clover.flushInterval}" default-value="500"
*/
private int flushInterval;
/**
* If true we'll wait 2*flushInterval to ensure coverage data is flushed to the Clover database before running
* any query on it.
*
* Note: The only use case where you would want to turn this off is if you're running your tests in a separate
* JVM. In that case the coverage data will be flushed by default upon the JVM shutdown and there would be no need
* to wait for the data to be flushed. As we can't control whether users want to fork their tests or not, we're
* offering this parameter to them.
*
* @parameter expression="${maven.clover.waitForFlush}" default-value="true"
*/
private boolean waitForFlush;
/**
* Decide whether to generate an HTML report or not.
*
* @parameter default-value="true" expression="${maven.clover.generateHtml}"
*/
private boolean generateHtml;
/**
* Decide whether to generate a PDF report or not.
*
* @parameter default-value="false" expression="${maven.clover.generatePdf}"
*/
private boolean generatePdf;
/**
* Decide whether to generate a XML report or not.
*
* @parameter default-value="true" expression="${maven.clover.generateXml}"
*/
private boolean generateXml;
/**
* Decide whether to generate a JSON report or not.
*
* @parameter default-value="false" expression="${maven.clover.generateJson}"
*/
private boolean generateJson;
/**
* Decide whether to generate a Clover historical report or not.
*
* @parameter default-value="false" expression="${maven.clover.generateHistorical}"
*/
private boolean generateHistorical;
/**
* How to order coverage tables.
*
* @parameter default-value="PcCoveredAsc" expression="${maven.clover.orderBy}"
*/
private String orderBy;
/**
* Comma or space separated list of Clover somesrcexcluded (block, statement or method filers) to exclude when
* generating coverage reports.
*
* @parameter expression="${maven.clover.contextFilters}" default-value=""
*/
private String contextFilters;
/**
* Style of the HTML report: ADG (default) or CLASSIC (deprecated).
*
* @deprecated this parameter will be removed in next major release
* @parameter expression="${maven.clover.reportStyle}" default-value="ADG"
*/
private String reportStyle;
/**
* Whether to show inner functions, i.e. functions declared inside methods in the report. This applies to Java8
* lambda functions for instance. If set to false
then they are hidden on the list of methods, but
* code metrics still include them.
*
* Note: if you will use showLambdaFunctions=true and showInnerFunctions=false then only lambda functions declared
* as a class field will be listed.
*
* @parameter expression="${maven.clover.showInnerFunctions}" default-value="false"
* @since 3.2.1
*/
private boolean showInnerFunctions;
/**
* Whether to show lambda functions in the report. Lambda functions can be either declared inside method body
* or as a class field. If set to false
then they are hidden on the list of methods, but code
* metrics still include them.
*
* Note: if you will use showLambdaFunctions=true and showInnerFunctions=false then only lambda functions declared
* as a class field will be listed.
*
* @parameter expression="${maven.clover.showLambdaFunctions}" default-value="false"
* @since 3.2.1
*/
private boolean showLambdaFunctions;
/**
* Title of the report
*
* @parameter expression="${maven.clover.title}" default-value="${project.name} ${project.version}"
*/
private String title;
/**
* Title anchor of the report
*
* @parameter expression="${maven.clover.titleAnchor}" default-value="${project.url}"
*/
private String titleAnchor;
/**
* The charset to use in the html reports.
*
* @parameter expression="${maven.clover.charset}" default-value="UTF-8"
*/
private String charset;
/**
* Note: This is passed by Maven and must not be configured by the user.
*
* @component
*/
private Renderer siteRenderer;
/**
* The Maven project instance for the executing project.
* Note: This is passed by Maven and must not be configured by the user.
*
* @parameter expression="${project}"
* @required
* @readonly
*/
private MavenProject project;
/**
* The projects in the reactor for aggregation report.
* Note: This is passed by Maven and must not be configured by the user.
*
* @parameter expression="${reactorProjects}"
* @readonly
*/
private List reactorProjects;
/**
* @parameter expression="${maven.clover.licenseLocation}"
* @see com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo#licenseLocation
*/
private String licenseLocation;
/**
* @parameter expression="${maven.clover.license}"
* @see com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo#license
*/
private String license;
/**
* Resource manager used to locate any Clover license file provided by the user.
*
* @component
*/
private ResourceManager resourceManager;
/**
* A span specifies the age of the coverage data that should be used when creating a report.
*
* @parameter expression="${maven.clover.span}"
*/
private String span = Interval.DEFAULT_SPAN.toString();
/**
* If set to true, a report will be generated even in the absence of coverage data.
*
* @parameter expression="${maven.clover.alwaysReport}" default-value="true"
*/
private boolean alwaysReport = true;
/**
* @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale)
*/
public void executeReport(final Locale locale) throws MavenReportException {
if (!canGenerateReport()) {
getLog().info("No report being generated for this module.");
}
// only run the report once, on the very last project.
final MavenProject lastProject = getReactorProjects().get(getReactorProjects().size() - 1);
final MavenProject thisProject = getProject();
if (isSingleCloverDatabase() && !thisProject.equals(lastProject)) {
getLog().info("Skipping report generation until the final project in the reactor.");
return;
}
// Register the Clover license
try {
AbstractCloverMojo.registerLicenseFile(this.project, this.resourceManager, this.licenseLocation, getLog(),
this.getClass().getClassLoader(), this.license);
}
catch (MojoExecutionException e) {
throw new MavenReportException("Failed to locate Clover license", e);
}
// Ensure the output directory exists
this.outputDirectory.mkdirs();
if (reportDescriptor == null) {
reportDescriptor = resolveCloverDescriptor();
} else if (!reportDescriptor.exists()){ // try finding this as a resource
try {
reportDescriptor = AbstractCloverMojo.getResourceAsFile(
project, resourceManager, reportDescriptor.getPath(), getLog(), this.getClass().getClassLoader());
} catch (MojoExecutionException e) {
throw new MavenReportException("Could not resolve report descriptor: " + reportDescriptor.getPath(), e);
}
}
getLog().info("Using Clover report descriptor: " + reportDescriptor.getAbsolutePath());
if(title != null && title.startsWith("Unnamed")) { // no project.name on the project
title = project.getArtifactId() + " " + project.getVersion();
}
File singleModuleCloverDatabase = new File(resolveCloverDatabase());
if (singleModuleCloverDatabase.exists()) {
createAllReportTypes(resolveCloverDatabase(), title);
}
File mergedCloverDatabase = new File(this.cloverMergeDatabase);
if (mergedCloverDatabase.exists()) {
createAllReportTypes(this.cloverMergeDatabase, title + " (Aggregated)");
}
}
/**
* Example of title prefixes: "Maven Clover", "Maven Aggregated Clover"
*/
private void createAllReportTypes(final String database, final String titlePrefix) throws MavenReportException {
final String outpath = outputDirectory.getAbsolutePath();
if (this.generateHtml) {
createReport(database, "html", titlePrefix, outpath, outpath, false);
}
if (this.generatePdf) {
createReport(database, "pdf", titlePrefix, outpath + "/clover.pdf", outpath + "/historical.pdf", true);
}
if (this.generateXml) {
createReport(database, "xml", titlePrefix, outpath + "/clover.xml", null, false);
}
if (this.generateJson) {
createReport(database, "json", titlePrefix, outpath, null, false);
}
}
/**
* Note: We use Clover's clover-report
Ant task instead of the Clover CLI APIs because the CLI
* APIs are limited and do not support historical reports.
*/
private void createReport(final String database, final String format, final String title,
final String output, final String historyOut, final boolean summary) {
final Project antProject = new Project();
antProject.init();
PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper( antProject );
propertyHelper.setNext( new AntPropertyHelper( project, getLog() ) );
antProject.setUserProperty("ant.file", reportDescriptor.getAbsolutePath());
antProject.setCoreLoader(getClass().getClassLoader());
addMavenProperties(antProject);
antProject.setProperty("cloverdb", database);
antProject.setProperty("output", output);
antProject.setProperty("history", historyDir);
antProject.setProperty("title", title == null ? "" : title); // empty string will have it be ignore by clover
antProject.setProperty("titleAnchor", titleAnchor == null ? "" : titleAnchor);
final String projectDir = project.getBasedir().getPath();
antProject.setProperty("projectDir", projectDir);
antProject.setProperty("testPattern", "**/src/test/**");
antProject.setProperty("filter", contextFilters != null ? contextFilters : "");
antProject.setProperty("orderBy", orderBy);
antProject.setProperty("charset", charset);
antProject.setProperty("reportStyle", reportStyle);
antProject.setProperty("type", format);
antProject.setProperty("span", span);
antProject.setProperty("alwaysReport", "" + alwaysReport);
antProject.setProperty("summary", String.valueOf(summary));
antProject.setProperty("showInnerFunctions", String.valueOf(showInnerFunctions));
antProject.setProperty("showLambdaFunctions", String.valueOf(showLambdaFunctions));
if (historyOut != null) {
antProject.setProperty("historyout", historyOut);
}
AbstractCloverMojo.registerCloverAntTasks(antProject, getLog());
ProjectHelper.configureProject(antProject, reportDescriptor);
antProject.setBaseDir(project.getBasedir());
String target = isHistoricalDirectoryValid(output) && (historyOut != null) ? "historical" : "current";
antProject.executeTarget(target);
}
private void addMavenProperties(final Project antProject) {
final Map