001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.maven.plugin.plugin.report; 020 021import javax.inject.Inject; 022 023import java.io.File; 024import java.io.IOException; 025import java.io.Reader; 026import java.nio.file.Files; 027import java.util.ArrayList; 028import java.util.Collections; 029import java.util.List; 030import java.util.Locale; 031import java.util.stream.Collectors; 032 033import org.apache.maven.RepositoryUtils; 034import org.apache.maven.artifact.ArtifactUtils; 035import org.apache.maven.doxia.sink.Sink; 036import org.apache.maven.execution.MavenSession; 037import org.apache.maven.model.building.ModelBuildingRequest; 038import org.apache.maven.plugin.descriptor.MojoDescriptor; 039import org.apache.maven.plugin.descriptor.PluginDescriptor; 040import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder; 041import org.apache.maven.plugins.annotations.Execute; 042import org.apache.maven.plugins.annotations.LifecyclePhase; 043import org.apache.maven.plugins.annotations.Mojo; 044import org.apache.maven.plugins.annotations.Parameter; 045import org.apache.maven.plugins.plugin.descriptor.EnhancedPluginDescriptorBuilder; 046import org.apache.maven.project.DefaultProjectBuildingRequest; 047import org.apache.maven.project.MavenProject; 048import org.apache.maven.project.ProjectBuilder; 049import org.apache.maven.project.ProjectBuildingException; 050import org.apache.maven.project.ProjectBuildingRequest; 051import org.apache.maven.reporting.AbstractMavenReport; 052import org.apache.maven.reporting.MavenReportException; 053import org.apache.maven.rtinfo.RuntimeInformation; 054import org.codehaus.plexus.configuration.PlexusConfigurationException; 055import org.codehaus.plexus.i18n.I18N; 056import org.codehaus.plexus.util.xml.XmlStreamReader; 057import org.eclipse.aether.RepositorySystem; 058import org.eclipse.aether.artifact.DefaultArtifact; 059import org.eclipse.aether.resolution.VersionRangeRequest; 060import org.eclipse.aether.resolution.VersionRangeResolutionException; 061import org.eclipse.aether.resolution.VersionRangeResult; 062import org.eclipse.aether.version.Version; 063 064/** 065 * Generates the plugin's report: the plugin details page at <code>plugin-info.html</code> 066 * and one <code><i>goal</i>-mojo.html</code> per goal. 067 * Relies on one output file from <a href="../maven-plugin-plugin/descriptor-mojo.html">plugin:descriptor</a>. 068 * 069 * @author <a href="snicoll@apache.org">Stephane Nicoll</a> 070 * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> 071 * @since 3.7.0 072 */ 073@Mojo(name = "report", threadSafe = true) 074@Execute(phase = LifecyclePhase.PROCESS_CLASSES) 075public class PluginReport extends AbstractMavenReport { 076 077 /** 078 * Set this to "true" to skip generating the report. 079 * 080 * @since 3.7.0 081 */ 082 @Parameter(defaultValue = "false", property = "maven.plugin.report.skip") 083 private boolean skip; 084 085 /** 086 * Set this to "true" to generate the usage section for "plugin-info.html" with 087 * {@code <extensions>true</extensions>}. 088 * 089 * @since 3.7.0 090 */ 091 @Parameter(defaultValue = "false", property = "maven.plugin.report.hasExtensionsToLoad") 092 private boolean hasExtensionsToLoad; 093 094 /** 095 * The Plugin requirements history list. 096 * <p> 097 * Can be specified as list of <code>requirementsHistory</code>: 098 * 099 * <pre> 100 * <requirementsHistories> 101 * <requirementsHistory> 102 * <version>plugin version</version> 103 * <maven>maven version</maven> 104 * <jdk>jdk version</jdk> 105 * </requirementsHistory> 106 * </requirementsHistories> 107 * </pre> 108 * 109 * @since 3.7.0 110 */ 111 @Parameter 112 private List<RequirementsHistory> requirementsHistories = new ArrayList<>(); 113 114 /** 115 * Plugin's version range for automatic detection of requirements history. 116 * 117 * @since 3.12.0 118 */ 119 @Parameter(defaultValue = "[0,)") 120 private String requirementsHistoryDetectionRange; 121 122 private final RuntimeInformation rtInfo; 123 124 /** 125 * Internationalization component. 126 */ 127 private final I18N i18n; 128 129 /** 130 * Path to enhanced plugin descriptor to generate the report from (must contain some XHTML values) 131 * 132 * @since 3.7.0 133 */ 134 @Parameter(defaultValue = "${project.build.directory}/plugin-enhanced.xml", required = true, readonly = true) 135 private File enhancedPluginXmlFile; 136 137 /** 138 * In case the internal javadoc site has not been generated when running this report goal 139 * (e.g. when using an aggregator javadoc report) link validation needs to be disabled by setting 140 * this value to {@code true}. 141 * This might have the drawback that some links being generated in the report might be broken 142 * in case not all parameter types and javadoc link references are resolvable through the sites being given to 143 * goal {@code plugin:descriptor}. 144 * 145 * @since 3.7.0 146 */ 147 @Parameter(property = "maven.plugin.report.disableInternalJavadocLinkValidation") 148 private boolean disableInternalJavadocLinkValidation; 149 150 private final MavenSession mavenSession; 151 152 private final RepositorySystem repositorySystem; 153 154 private final ProjectBuilder projectBuilder; 155 156 @Inject 157 public PluginReport( 158 RuntimeInformation rtInfo, 159 I18N i18n, 160 MavenSession mavenSession, 161 RepositorySystem repositorySystem, 162 ProjectBuilder projectBuilder) { 163 this.rtInfo = rtInfo; 164 this.i18n = i18n; 165 this.mavenSession = mavenSession; 166 this.repositorySystem = repositorySystem; 167 this.projectBuilder = projectBuilder; 168 } 169 170 /** 171 * {@inheritDoc} 172 */ 173 @Override 174 public boolean canGenerateReport() { 175 if (skip) { 176 return false; 177 } 178 179 if (!(enhancedPluginXmlFile != null && enhancedPluginXmlFile.isFile() && enhancedPluginXmlFile.canRead())) { 180 return false; 181 } 182 183 return true; 184 } 185 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 protected void executeReport(Locale locale) throws MavenReportException { 191 PluginDescriptor pluginDescriptor = extractPluginDescriptor(); 192 193 // Generate the mojos' documentation 194 generateMojosDocumentation(pluginDescriptor, locale); 195 196 if (requirementsHistories.isEmpty()) { 197 // detect requirements history 198 String v = null; 199 try { 200 List<Version> versions = discoverVersions(requirementsHistoryDetectionRange); 201 if (versions.isEmpty()) { 202 getLog().info("No plugin history found for range " + requirementsHistoryDetectionRange); 203 } else { 204 getLog().info("Detecting plugin requirements history for range " 205 + requirementsHistoryDetectionRange + ": " 206 + versions.size() + " releases, from " + versions.get(0) + " to " 207 + versions.get(versions.size() - 1)); 208 } 209 210 Collections.reverse(versions); 211 for (Version version : versions) { 212 v = version.toString(); 213 MavenProject versionProject = buildMavenProject(v); 214 RequirementsHistory requirements = RequirementsHistory.discoverRequirements(versionProject); 215 requirementsHistories.add(requirements); 216 getLog().debug(" - " + requirements); 217 } 218 } catch (VersionRangeResolutionException vrre) { 219 throw new MavenReportException( 220 "Cannot resolve past versions " + requirementsHistoryDetectionRange, vrre); 221 } catch (ProjectBuildingException pbe) { 222 throw new MavenReportException("Cannot resolve MavenProject for version " + v, pbe); 223 } 224 } 225 226 // Write the overview 227 PluginOverviewRenderer r = new PluginOverviewRenderer( 228 getSink(), i18n, locale, getProject(), requirementsHistories, pluginDescriptor, hasExtensionsToLoad); 229 r.render(); 230 } 231 232 private PluginDescriptor extractPluginDescriptor() throws MavenReportException { 233 PluginDescriptorBuilder builder = new EnhancedPluginDescriptorBuilder(rtInfo); 234 235 try (Reader input = new XmlStreamReader(Files.newInputStream(enhancedPluginXmlFile.toPath()))) { 236 return builder.build(input); 237 } catch (IOException | PlexusConfigurationException e) { 238 throw new MavenReportException("Error extracting plugin descriptor from " + enhancedPluginXmlFile, e); 239 } 240 } 241 242 /** 243 * @param locale The locale 244 * @param key The key to search for 245 * @return The text appropriate for the locale. 246 */ 247 private String getI18nString(Locale locale, String key) { 248 return i18n.getString("plugin-report", locale, "report.plugin." + key); 249 } 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public String getName(Locale locale) { 256 return getI18nString(locale, "name"); 257 } 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override 263 public String getDescription(Locale locale) { 264 return getI18nString(locale, "description"); 265 } 266 267 /** 268 * {@inheritDoc} 269 */ 270 @Override 271 public String getOutputName() { 272 return "plugin-info"; 273 } 274 275 /** 276 * Generate the mojos' documentation with the {@link #getSinkFactory()} 277 * 278 * @param pluginDescriptor not null 279 * @param locale not null 280 * @throws MavenReportException if any 281 * @throws IOException 282 */ 283 private void generateMojosDocumentation(PluginDescriptor pluginDescriptor, Locale locale) 284 throws MavenReportException { 285 if (pluginDescriptor.getMojos() != null) { 286 for (MojoDescriptor descriptor : pluginDescriptor.getMojos()) { 287 GoalRenderer renderer; 288 try { 289 String filename = descriptor.getGoal() + "-mojo.html"; 290 Sink sink = getSinkFactory().createSink(getReportOutputDirectory(), filename); 291 renderer = new GoalRenderer( 292 sink, 293 i18n, 294 locale, 295 project, 296 descriptor, 297 getReportOutputDirectory(), 298 disableInternalJavadocLinkValidation, 299 getLog()); 300 } catch (IOException e) { 301 throw new MavenReportException("Cannot generate sink for mojo " + descriptor.getGoal(), e); 302 } 303 renderer.render(); 304 } 305 } 306 } 307 308 private List<Version> discoverVersions(String range) throws VersionRangeResolutionException { 309 MavenProject currentProject = mavenSession.getCurrentProject(); 310 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 311 rangeRequest.setArtifact( 312 new DefaultArtifact(currentProject.getGroupId() + ":" + currentProject.getArtifactId() + ":" + range)); 313 rangeRequest.setRepositories( 314 RepositoryUtils.toRepos(mavenSession.getCurrentProject().getRemoteArtifactRepositories())); 315 VersionRangeResult rangeResult = 316 repositorySystem.resolveVersionRange(mavenSession.getRepositorySession(), rangeRequest); 317 return rangeResult.getVersions().stream() 318 .filter(version -> !ArtifactUtils.isSnapshot(version.toString())) 319 .collect(Collectors.toList()); 320 } 321 322 private MavenProject buildMavenProject(String version) throws ProjectBuildingException { 323 MavenProject currentProject = mavenSession.getCurrentProject(); 324 ProjectBuildingRequest buildRequest = new DefaultProjectBuildingRequest(); 325 buildRequest.setLocalRepository(mavenSession.getLocalRepository()); 326 buildRequest.setRemoteRepositories(mavenSession.getCurrentProject().getRemoteArtifactRepositories()); 327 buildRequest.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); 328 buildRequest.setProcessPlugins(false); 329 buildRequest.setRepositoryMerging(ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT); 330 buildRequest.setSystemProperties(mavenSession.getSystemProperties()); 331 buildRequest.setUserProperties(mavenSession.getUserProperties()); 332 buildRequest.setRepositorySession(mavenSession.getRepositorySession()); 333 return projectBuilder 334 .build( 335 RepositoryUtils.toArtifact(new DefaultArtifact(currentProject.getGroupId() + ":" 336 + currentProject.getArtifactId() + ":pom:" + version)), 337 buildRequest) 338 .getProject(); 339 } 340}