/******************************************************************************* * Copyright (c) 2009, 2020 Mountainminds GmbH & Co. KG and Contributors * This program and the accompanying materials are made available under * the terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * John Oliver, Marc R. Hoffmann, Jan Wloka - initial API and implementation * *******************************************************************************/ package org.jacoco.maven; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.model.Dependency; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.jacoco.report.IReportGroupVisitor; /** *

* Creates a structured code coverage report (HTML, XML, and CSV) from multiple * projects within reactor. The report is created from all modules this project * depends on. From those projects class and source files as well as JaCoCo * execution data files will be collected. In addition execution data is * collected from the project itself. This also allows to create coverage * reports when tests are in separate projects than the code under test, for * example in case of integration tests. *

* *

* Using the dependency scope allows to distinguish projects which contribute * execution data but should not become part of the report: *

* * * * @since 0.7.7 */ @Mojo(name = "report-aggregate", threadSafe = true) public class ReportAggregateMojo extends AbstractReportMojo { /** * A list of execution data files to include in the report from each * project. May use wildcard characters (* and ?). When not specified all * *.exec files from the target folder will be included. */ @Parameter List dataFileIncludes; /** * A list of execution data files to exclude from the report. May use * wildcard characters (* and ?). When not specified nothing will be * excluded. */ @Parameter List dataFileExcludes; /** * Output directory for the reports. Note that this parameter is only * relevant if the goal is run from the command line or from the default * build lifecycle. If the goal is run indirectly as part of a site * generation, the output directory configured in the Maven Site Plugin is * used instead. */ @Parameter(defaultValue = "${project.reporting.outputDirectory}/jacoco-aggregate") private File outputDirectory; /** * The projects in the reactor. */ @Parameter(property = "reactorProjects", readonly = true) private List reactorProjects; @Override boolean canGenerateReportRegardingDataFiles() { return true; } @Override boolean canGenerateReportRegardingClassesDirectory() { return true; } @Override void loadExecutionData(final ReportSupport support) throws IOException { // https://issues.apache.org/jira/browse/MNG-5440 if (dataFileIncludes == null) { dataFileIncludes = Arrays.asList("target/*.exec"); } final FileFilter filter = new FileFilter(dataFileIncludes, dataFileExcludes); loadExecutionData(support, filter, project.getBasedir()); for (final MavenProject dependency : findDependencies( Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_TEST)) { loadExecutionData(support, filter, dependency.getBasedir()); } } private void loadExecutionData(final ReportSupport support, final FileFilter filter, final File basedir) throws IOException { for (final File execFile : filter.getFiles(basedir)) { support.loadExecutionData(execFile); } } @Override void addFormatters(final ReportSupport support, final Locale locale) throws IOException { support.addAllFormatters(outputDirectory, outputEncoding, footer, locale); } @Override void createReport(final IReportGroupVisitor visitor, final ReportSupport support) throws IOException { final IReportGroupVisitor group = visitor.visitGroup(title); for (final MavenProject dependency : findDependencies( Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_PROVIDED)) { support.processProject(group, dependency.getArtifactId(), dependency, getIncludes(), getExcludes(), sourceEncoding); } } public File getReportOutputDirectory() { return outputDirectory; } public void setReportOutputDirectory(final File reportOutputDirectory) { if (reportOutputDirectory != null && !reportOutputDirectory .getAbsolutePath().endsWith("jacoco-aggregate")) { outputDirectory = new File(reportOutputDirectory, "jacoco-aggregate"); } else { outputDirectory = reportOutputDirectory; } } public String getOutputName() { return "jacoco-aggregate/index"; } public String getName(final Locale locale) { return "JaCoCo Aggregate"; } private List findDependencies(final String... scopes) { final List result = new ArrayList(); final List scopeList = Arrays.asList(scopes); for (final Object dependencyObject : project.getDependencies()) { final Dependency dependency = (Dependency) dependencyObject; if (scopeList.contains(dependency.getScope())) { final MavenProject project = findProjectFromReactor(dependency); if (project != null) { result.add(project); } } } return result; } /** * Note that if dependency specified using version range and reactor * contains multiple modules with same artifactId and groupId but of * different versions, then first dependency which matches range will be * selected. For example in case of range [0,2] if version 1 is * before version 2 in reactor, then version 1 will be selected. */ private MavenProject findProjectFromReactor(final Dependency d) { final VersionRange depVersionAsRange; try { depVersionAsRange = VersionRange .createFromVersionSpec(d.getVersion()); } catch (final InvalidVersionSpecificationException e) { throw new AssertionError(e); } for (final MavenProject p : reactorProjects) { final DefaultArtifactVersion pv = new DefaultArtifactVersion( p.getVersion()); if (p.getGroupId().equals(d.getGroupId()) && p.getArtifactId().equals(d.getArtifactId()) && depVersionAsRange.containsVersion(pv)) { return p; } } return null; } }