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