com.atlassian.maven.plugin.clover.internal.AbstractCloverMojo Maven / Gradle / Ivy
package com.atlassian.maven.plugin.clover.internal;
/*
* 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 org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Taskdef;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceLoader;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import com.atlassian.clover.CloverNames;
import com.atlassian.maven.plugin.clover.MvnLogBuildListener;
/**
* Common code for all Clover plugin build Mojos.
*/
public abstract class AbstractCloverMojo extends AbstractMojo implements CloverConfiguration {
/**
* The directory where the Clover plugin will put all the files it generates during the build process. For
* example the Clover plugin will put instrumented sources somewhere inside this directory.
*
* @parameter default-value="${project.build.directory}/clover"
* @required
*/
protected String cloverOutputDirectory;
/**
* The location of the Clover database.
*
* @parameter expression="${maven.clover.cloverDatabase}"
*/
private String cloverDatabase;
/**
* The location to store the clover snapshot file. This file needs to persist between builds to enable Clover's
* build optimization feature. If not specified, the snapshot will be stored next to the cloverDatabase.
*
* @parameter expression="${maven.clover.snapshot}"
*/
protected File snapshot;
/**
* If true, then a single cloverDatabase will be used for the entire project.
* This flag will be ignored if a custom cloverDatabase location is specified.
*
* @parameter expression="${maven.clover.singleCloverDatabase}" default-value="false"
*/
private 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;
/**
* A Clover license file to be used by the plugin. The plugin tries to resolve this parameter first as a resource,
* then as a URL, and then as a file location on the filesystem. If not provided, Clover will use a bundled
* license key.
*
* @parameter expression="${maven.clover.licenseLocation}"
* @see #license
*/
protected String licenseLocation;
/**
* The full Clover license String to use. If supplied, this certificate will be used over {@link #licenseLocation}.
* NB. newline chars must be preserved. If not provided, Clover will use a bundled license key.
*
* @parameter expression="${maven.clover.license}"
* @see #licenseLocation
*/
protected String license;
/**
* 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;
/**
* 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
*/
private MavenProject project;
/**
* Resource manager used to locate any Clover license file provided by the user.
*
* @component
*/
private ResourceManager resourceManager;
/**
* A flag to indicate not to run clover for this execution. If set to true, Clover will not be run.
*
* @parameter expression="${maven.clover.skip}" default-value="false"
*/
protected boolean skip;
/**
* If you wish to enable debug level logging in just the Clover plugin, set this to true. This is useful for
* integrating Clover into the build.
*
* @parameter expression="${maven.clover.debug}" default-value="false"
*/
protected boolean debug;
/**
* 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;
/**
* {@inheritDoc}
*
* @see org.apache.maven.plugin.AbstractMojo#execute()
*/
public void execute() throws MojoExecutionException {
registerLicenseFile();
}
public void setResourceManager(final ResourceManager resourceManager) {
this.resourceManager = resourceManager;
}
public ResourceManager getResourceManager() {
return this.resourceManager;
}
protected void registerLicenseFile() throws MojoExecutionException {
registerLicenseFile(
this.project,
getResourceManager(),
this.licenseLocation,
getLog(),
this.getClass().getClassLoader(),
this.license);
}
/**
* Registers the license file for Clover runtime by setting the clover.license.path
system property.
* If the user has configured the licenseLocation
parameter the plugin tries to resolve it first as a
* resource, then as a URL, and then as a file location on the filesystem.
*
* Note: We're defining this method as static because it is also required in the report mojo and reporting mojos
* and main mojos cannot share anything right now. See http://jira.codehaus.org/browse/MNG-1886.
*
* @param project maven project
* @param resourceManager resource manager
* @param licenseLocation path to license file
* @param logger logger
* @param classloader class loader
* @param licenseCert license key
* @throws MojoExecutionException when the license file cannot be found
*/
public static void registerLicenseFile(final MavenProject project,
final ResourceManager resourceManager,
final String licenseLocation,
final Log logger,
final ClassLoader classloader,
final String licenseCert) throws MojoExecutionException {
if (licenseCert != null) {
logger.debug("Full license supplied. Length: '" + licenseCert.length() +
"'. License location: '" + licenseLocation + "' will be ignored.");
System.setProperty(CloverNames.PROP_LICENSE_CERT, licenseCert);
return;
}
if (licenseLocation != null) {
logger.debug("Using licenseLocation '" + licenseLocation + "'");
final File licenseFile = getResourceAsFile(project, resourceManager, licenseLocation, logger, classloader);
logger.debug("Using license file [" + licenseFile.getPath() + "]");
System.setProperty(CloverNames.PROP_LICENSE_PATH, licenseFile.getPath());
} else {
logger.debug("No 'maven.clover.licenseLocation' configured. Using default license.");
}
}
public static File getResourceAsFile(final MavenProject project,
final ResourceManager resourceManager,
final String resourceLocation,
final Log logger,
final ClassLoader classloader) throws MojoExecutionException {
logger.debug("Getting resource: '" + resourceLocation + "'");
resourceManager.addSearchPath("url", "");
resourceManager.addSearchPath(FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
final ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(classloader);
try {
logger.debug("Attempting to load resource from [" + resourceLocation + "] ...");
final File outputFile = File.createTempFile("mvn", "resource");
outputFile.deleteOnExit();
return resourceManager.getResourceAsFile(resourceLocation, outputFile.getPath());
} catch (Exception e) {
throw new MojoExecutionException("Failed to load resource as file [" + resourceLocation + "]", e);
}
} finally {
Thread.currentThread().setContextClassLoader(origLoader);
}
}
/**
* Register the Clover Ant tasks against a fake Ant {{@link Project}} object so that we can the tasks later on.
* This is the Java equivalent of the taskdef
call that you would need in your Ant
* build.xml
file if you wanted to use the Clover Ant tasks from Ant.
*
* Note: We're defining this method as static because it is also required in the report mojo and reporting mojos
* and main mojos cannot share anything right now. See http://jira.codehaus.org/browse/MNG-1886.
*
* @param antProject project
* @param log logger
*/
public static void registerCloverAntTasks(final Project antProject, final Log log) {
antProject.addBuildListener(new MvnLogBuildListener(log));
final Taskdef taskdef = (Taskdef) antProject.createTask("taskdef");
taskdef.init();
taskdef.setResource("cloverlib.xml");
taskdef.execute();
}
/**
* Wait 2*'flush interval' milliseconds to ensure that the coverage data have been flushed to the Clover database.
*
* TODO: This method should not be static but we need it static here because we cannot share code
* between non report mojos and main build mojos. See http://jira.codehaus.org/browse/MNG-1886
*
* @param waitForFlush whether to pause until flush occurs
* @param flushInterval current interval
*/
public static void waitForFlush(final boolean waitForFlush, final int flushInterval) {
if (waitForFlush) {
try {
Thread.sleep(2 * flushInterval);
} catch (InterruptedException e) {
// Nothing to do... Just go on and try to check for coverage.
}
}
}
/**
* Check if a Clover database exists (either a single module Clover database or an aggregated one).
*
* @return true if a Clover database exists.
*/
protected boolean areCloverDatabasesAvailable() {
boolean shouldRun = false;
final File singleModuleCloverDatabase = new File(resolveCloverDatabase());
final File mergedCloverDatabase = new File(this.cloverMergeDatabase);
if (singleModuleCloverDatabase.exists() || mergedCloverDatabase.exists()) {
shouldRun = true;
}
return shouldRun;
}
public MavenProject getProject() {
return this.project;
}
public boolean getWaitForFlush() {
return this.waitForFlush;
}
public String getCloverDatabase() {
return cloverDatabase;
}
public String resolveCloverDatabase() {
return new ConfigUtil(this).resolveCloverDatabase();
}
protected String getCloverMergeDatabase() {
return this.cloverMergeDatabase;
}
public int getFlushInterval() {
return this.flushInterval;
}
public void setProject(final MavenProject project) {
this.project = project;
}
public void setLicenseLocation(final String licenseLocation) {
this.licenseLocation = licenseLocation;
}
public void setLicense(final String license) {
this.license = license;
}
public List getReactorProjects() {
return reactorProjects;
}
public boolean isSingleCloverDatabase() {
return this.singleCloverDatabase;
}
protected boolean isLastProjectInReactor() {
final MavenProject lastProject = getReactorProjects().get(getReactorProjects().size() - 1);
final MavenProject thisProject = getProject();
return thisProject.equals(lastProject);
}
/**
* Returns true if the supplied potentialModule project is a module
* of the specified parentProject.
*
* @param parentProject the parent project.
* @param potentialModule the potential moduleproject.
* @return true if the potentialModule is indeed a module of the specified
* parent project.
*/
protected boolean isModuleOfProject(final MavenProject parentProject, final MavenProject potentialModule) {
boolean result = false;
final List modules = parentProject.getModules();
if (modules != null) {
final File parentBaseDir = parentProject.getBasedir();
for (final String module : modules) {
File moduleBaseDir = new File(parentBaseDir, module);
try {
// need these to be canonical paths so we can perform a true equality
// operation and remember is a path and for flat multimodule project
// structures they will be like this: ../a-project
final String lhs = potentialModule.getBasedir().getCanonicalPath();
final String rhs = moduleBaseDir.getCanonicalPath();
if (lhs.equals(rhs)) {
getLog().debug("isModuleOfProject: lhs=" + lhs + " rhs=" + rhs + " MATCH FOUND");
result = true;
break;
} else {
getLog().debug("isModuleOfProject: lhs=" + lhs + " rhs=" + rhs);
}
} catch (IOException e) {
// suppress the exception (?)
getLog().error("error encountered trying to resolve canonical module paths");
}
}
}
return result;
}
/**
* Returns all the projects that are modules, or modules of modules, of the
* specified project found within the reactor.
*
* The searchLevel parameter controls how many descendent levels of modules
* are returned. With a searchLevels equals to 1, only the immediate modules
* of the specified project are returned.
*
* A searchLevel equals to 2 returns those module's modules as well.
*
* A searchLevel equals to -1 returns the entire module hierarchy beneath the
* specified project. Note that this is simply the equivalent to the entire reactor
* if the specified project is the root execution project.
*
* @param project the project to search under
* @param levels the number of descendent levels to return (List<MavenProject>)
* @return the list of module projects.
*/
protected List getModuleProjects(final MavenProject project, final int levels) {
final List projects = new ArrayList();
final boolean infinite = (levels == -1);
getLog().debug("getModuleProjects: project=" + project.getId()
+ " getReactorProjects is " + (getReactorProjects() == null ? "null" : "not null")
+ " infinite=" + infinite + " levels=" + levels);
if ((getReactorProjects() != null) && (infinite || levels > 0)) {
for (final MavenProject reactorProject : getReactorProjects()) {
getLog().debug("getModuleProjects: checking " + reactorProject.getId() + " against " + project.getId());
if (isModuleOfProject(project, reactorProject)) {
getLog().debug("getModuleProjects: reactor project " + reactorProject.getId() + " is a module of " + project.getId());
projects.add(reactorProject);
if (project == reactorProject) {
projects.add(project); //CLMVN-78 don't recurse if project is the same as reactorProject.
} else {
projects.addAll(getModuleProjects(reactorProject,
infinite ? levels : levels - 1));
}
} else {
getLog().debug("getModuleProjects: reactor project " + reactorProject.getId() + " is not a module of " + project.getId());
}
}
}
return projects;
}
/**
* returns all the projects that are in the reactor build as
* direct or indirect modules of the specified project.
*
* @param project the project to search beneath
* @return the list of modules that are direct or indirect module descendants (List<MavenProject>)
* of the specified project
*/
protected List getDescendantModuleProjects(final MavenProject project) {
getLog().debug("Getting descendant module projects for " + project);
return getModuleProjects(project, -1);
}
}