1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * John Oliver, Marc R. Hoffmann, Jan Wloka - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.maven; 13 14 import java.io.File; 15 import java.io.IOException; 16 import java.util.ArrayList; 17 import java.util.Arrays; 18 import java.util.List; 19 import java.util.Locale; 20 21 import org.apache.maven.artifact.Artifact; 22 import org.apache.maven.model.Dependency; 23 import org.apache.maven.plugins.annotations.Mojo; 24 import org.apache.maven.plugins.annotations.Parameter; 25 import org.apache.maven.project.MavenProject; 26 import org.jacoco.report.IReportGroupVisitor; 27 28 /** 29 * <p> 30 * Creates a structured code coverage report (HTML, XML, and CSV) from multiple 31 * projects within reactor. The report is created from all modules this project 32 * depends on. From those projects class and source files as well as JaCoCo 33 * execution data files will be collected. In addition execution data is 34 * collected from the project itself. This also allows to create coverage 35 * reports when tests are in separate projects than the code under test, for 36 * example in case of integration tests. 37 * </p> 38 * 39 * <p> 40 * Using the dependency scope allows to distinguish projects which contribute 41 * execution data but should not become part of the report: 42 * </p> 43 * 44 * <ul> 45 * <li><code>compile</code>, <code>runtime</code>, <code>provided</code>: 46 * Project source and execution data is included in the report.</li> 47 * <li><code>test</code>: Only execution data is considered for the report.</li> 48 * </ul> 49 * 50 * @since 0.7.7 51 */ 52 @Mojo(name = "report-aggregate", threadSafe = true) 53 public class ReportAggregateMojo extends AbstractReportMojo { 54 55 /** 56 * A list of execution data files to include in the report from each 57 * project. May use wildcard characters (* and ?). When not specified all 58 * *.exec files from the target folder will be included. 59 */ 60 @Parameter 61 List<String> dataFileIncludes; 62 63 /** 64 * A list of execution data files to exclude from the report. May use 65 * wildcard characters (* and ?). When not specified nothing will be 66 * excluded. 67 */ 68 @Parameter 69 List<String> dataFileExcludes; 70 71 /** 72 * Output directory for the reports. Note that this parameter is only 73 * relevant if the goal is run from the command line or from the default 74 * build lifecycle. If the goal is run indirectly as part of a site 75 * generation, the output directory configured in the Maven Site Plugin is 76 * used instead. 77 */ 78 @Parameter(defaultValue = "${project.reporting.outputDirectory}/jacoco-aggregate") 79 private File outputDirectory; 80 81 /** 82 * The projects in the reactor. 83 */ 84 @Parameter(property = "reactorProjects", readonly = true) 85 private List<MavenProject> reactorProjects; 86 87 @Override 88 boolean canGenerateReportRegardingDataFiles() { 89 return true; 90 } 91 92 @Override 93 boolean canGenerateReportRegardingClassesDirectory() { 94 return true; 95 } 96 97 @Override 98 void loadExecutionData(final ReportSupport support) throws IOException { 99 // https://issues.apache.org/jira/browse/MNG-5440 100 if (dataFileIncludes == null) { 101 dataFileIncludes = Arrays.asList("target/*.exec"); 102 } 103 104 final FileFilter filter = new FileFilter(dataFileIncludes, 105 dataFileExcludes); 106 loadExecutionData(support, filter, getProject().getBasedir()); 107 for (final MavenProject dependency : findDependencies( 108 Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME, 109 Artifact.SCOPE_PROVIDED, Artifact.SCOPE_TEST)) { 110 loadExecutionData(support, filter, dependency.getBasedir()); 111 } 112 } 113 114 private void loadExecutionData(final ReportSupport support, 115 final FileFilter filter, final File basedir) throws IOException { 116 for (final File execFile : filter.getFiles(basedir)) { 117 support.loadExecutionData(execFile); 118 } 119 } 120 121 @Override 122 void addFormatters(final ReportSupport support, final Locale locale) 123 throws IOException { 124 support.addAllFormatters(outputDirectory, outputEncoding, footer, 125 locale); 126 } 127 128 @Override 129 void createReport(final IReportGroupVisitor visitor, 130 final ReportSupport support) throws IOException { 131 final IReportGroupVisitor group = visitor.visitGroup(title); 132 for (final MavenProject dependency : findDependencies( 133 Artifact.SCOPE_COMPILE, Artifact.SCOPE_RUNTIME, 134 Artifact.SCOPE_PROVIDED)) { 135 support.processProject(group, dependency.getArtifactId(), 136 dependency, getIncludes(), getExcludes(), sourceEncoding); 137 } 138 } 139 140 @Override 141 protected String getOutputDirectory() { 142 return outputDirectory.getAbsolutePath(); 143 } 144 145 @Override 146 public void setReportOutputDirectory(final File reportOutputDirectory) { 147 if (reportOutputDirectory != null 148 && !reportOutputDirectory.getAbsolutePath().endsWith( 149 "jacoco-aggregate")) { 150 outputDirectory = new File(reportOutputDirectory, 151 "jacoco-aggregate"); 152 } else { 153 outputDirectory = reportOutputDirectory; 154 } 155 } 156 157 public String getOutputName() { 158 return "jacoco-aggregate/index"; 159 } 160 161 public String getName(final Locale locale) { 162 return "JaCoCo Aggregate"; 163 } 164 165 private List<MavenProject> findDependencies(final String... scopes) { 166 final List<MavenProject> result = new ArrayList<MavenProject>(); 167 final List<String> scopeList = Arrays.asList(scopes); 168 for (final Object dependencyObject : getProject().getDependencies()) { 169 final Dependency dependency = (Dependency) dependencyObject; 170 if (scopeList.contains(dependency.getScope())) { 171 final MavenProject project = findProjectFromReactor(dependency); 172 if (project != null) { 173 result.add(project); 174 } 175 } 176 } 177 return result; 178 } 179 180 private MavenProject findProjectFromReactor(final Dependency d) { 181 for (final MavenProject p : reactorProjects) { 182 if (p.getGroupId().equals(d.getGroupId()) 183 && p.getArtifactId().equals(d.getArtifactId()) 184 && p.getVersion().equals(d.getVersion())) { 185 return p; 186 } 187 } 188 return null; 189 } 190 191 } 192