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 return false; 167 } 168 169 if (!(enhancedPluginXmlFile != null && enhancedPluginXmlFile.isFile() && enhancedPluginXmlFile.canRead())) { 170 return false; 171 } 172 173 return true; 174 } 175 176 /** 177 * {@inheritDoc} 178 */ 179 @Override 180 protected void executeReport(Locale locale) throws MavenReportException { 181 PluginDescriptor pluginDescriptor = extractPluginDescriptor(); 182 183 // Generate the mojos' documentation 184 generateMojosDocumentation(pluginDescriptor, locale); 185 186 if (requirementsHistories.isEmpty()) { 187 // detect requirements history 188 String v = null; 189 try { 190 List<Version> versions = discoverVersions(requirementsHistoryDetectionRange); 191 if (versions.isEmpty()) { 192 getLog().info("No plugin history found for range " + requirementsHistoryDetectionRange); 193 } else { 194 getLog().info("Detecting plugin requirements history for range " 195 + requirementsHistoryDetectionRange + ": " 196 + versions.size() + " releases, from " + versions.get(0) + " to " 197 + versions.get(versions.size() - 1)); 198 } 199 200 Collections.reverse(versions); 201 for (Version version : versions) { 202 v = version.toString(); 203 MavenProject versionProject = buildMavenProject(v); 204 RequirementsHistory requirements = RequirementsHistory.discoverRequirements(versionProject); 205 requirementsHistories.add(requirements); 206 getLog().debug(" - " + requirements); 207 } 208 } catch (VersionRangeResolutionException vrre) { 209 throw new MavenReportException( 210 "Cannot resolve past versions " + requirementsHistoryDetectionRange, vrre); 211 } catch (ProjectBuildingException pbe) { 212 throw new MavenReportException("Cannot resolve MavenProject for version " + v, pbe); 213 } 214 } 215 216 // Write the overview 217 PluginOverviewRenderer r = new PluginOverviewRenderer( 218 getSink(), i18n, locale, getProject(), requirementsHistories, pluginDescriptor, hasExtensionsToLoad); 219 r.render(); 220 } 221 222 private PluginDescriptor extractPluginDescriptor() throws MavenReportException { 223 PluginDescriptorBuilder builder = new EnhancedPluginDescriptorBuilder(rtInfo); 224 225 try (Reader input = new XmlStreamReader(Files.newInputStream(enhancedPluginXmlFile.toPath()))) { 226 return builder.build(input); 227 } catch (IOException | PlexusConfigurationException e) { 228 throw new MavenReportException("Error extracting plugin descriptor from " + enhancedPluginXmlFile, e); 229 } 230 } 231 232 /** 233 * @param locale The locale 234 * @param key The key to search for 235 * @return The text appropriate for the locale. 236 */ 237 private String getI18nString(Locale locale, String key) { 238 return i18n.getString("plugin-report", locale, "report.plugin." + key); 239 } 240 241 /** 242 * {@inheritDoc} 243 */ 244 @Override 245 public String getName(Locale locale) { 246 return getI18nString(locale, "name"); 247 } 248 249 /** 250 * {@inheritDoc} 251 */ 252 @Override 253 public String getDescription(Locale locale) { 254 return getI18nString(locale, "description"); 255 } 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override 261 public String getOutputName() { 262 return "plugin-info"; 263 } 264 265 /** 266 * Generate the mojos' documentation with the {@link #getSinkFactory()} 267 * 268 * @param pluginDescriptor not null 269 * @param locale not null 270 * @throws MavenReportException if any 271 * @throws IOException 272 */ 273 private void generateMojosDocumentation(PluginDescriptor pluginDescriptor, Locale locale) 274 throws MavenReportException { 275 if (pluginDescriptor.getMojos() != null) { 276 for (MojoDescriptor descriptor : pluginDescriptor.getMojos()) { 277 GoalRenderer renderer; 278 try { 279 String filename = descriptor.getGoal() + "-mojo.html"; 280 Sink sink = getSinkFactory().createSink(getReportOutputDirectory(), filename); 281 renderer = new GoalRenderer( 282 sink, 283 i18n, 284 locale, 285 project, 286 descriptor, 287 getReportOutputDirectory(), 288 disableInternalJavadocLinkValidation, 289 getLog()); 290 } catch (IOException e) { 291 throw new MavenReportException("Cannot generate sink for mojo " + descriptor.getGoal(), e); 292 } 293 renderer.render(); 294 } 295 } 296 } 297 298 private List<Version> discoverVersions(String range) throws VersionRangeResolutionException { 299 MavenProject currentProject = mavenSession.getCurrentProject(); 300 VersionRangeRequest rangeRequest = new VersionRangeRequest(); 301 rangeRequest.setArtifact( 302 new DefaultArtifact(currentProject.getGroupId() + ":" + currentProject.getArtifactId() + ":" + range)); 303 rangeRequest.setRepositories( 304 RepositoryUtils.toRepos(mavenSession.getCurrentProject().getRemoteArtifactRepositories())); 305 VersionRangeResult rangeResult = 306 repositorySystem.resolveVersionRange(mavenSession.getRepositorySession(), rangeRequest); 307 return rangeResult.getVersions().stream() 308 .filter(version -> !ArtifactUtils.isSnapshot(version.toString())) 309 .collect(Collectors.toList()); 310 } 311 312 private MavenProject buildMavenProject(String version) throws ProjectBuildingException { 313 MavenProject currentProject = mavenSession.getCurrentProject(); 314 ProjectBuildingRequest buildRequest = new DefaultProjectBuildingRequest(); 315 buildRequest.setLocalRepository(mavenSession.getLocalRepository()); 316 buildRequest.setRemoteRepositories(mavenSession.getCurrentProject().getRemoteArtifactRepositories()); 317 buildRequest.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL); 318 buildRequest.setProcessPlugins(false); 319 buildRequest.setRepositoryMerging(ProjectBuildingRequest.RepositoryMerging.REQUEST_DOMINANT); 320 buildRequest.setSystemProperties(mavenSession.getSystemProperties()); 321 buildRequest.setUserProperties(mavenSession.getUserProperties()); 322 buildRequest.setRepositorySession(mavenSession.getRepositorySession()); 323 return projectBuilder 324 .build( 325 RepositoryUtils.toArtifact(new DefaultArtifact(currentProject.getGroupId() + ":" 326 + currentProject.getArtifactId() + ":pom:" + version)), 327 buildRequest) 328 .getProject(); 329 } 330}