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; 020 021import java.io.File; 022import java.net.URI; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.LinkedHashSet; 026import java.util.List; 027import java.util.Set; 028 029import org.apache.maven.artifact.Artifact; 030import org.apache.maven.artifact.resolver.filter.ArtifactFilter; 031import org.apache.maven.artifact.resolver.filter.IncludesArtifactFilter; 032import org.apache.maven.execution.MavenSession; 033import org.apache.maven.plugin.MojoExecutionException; 034import org.apache.maven.plugin.descriptor.InvalidPluginDescriptorException; 035import org.apache.maven.plugin.descriptor.PluginDescriptor; 036import org.apache.maven.plugins.annotations.Component; 037import org.apache.maven.plugins.annotations.LifecyclePhase; 038import org.apache.maven.plugins.annotations.Mojo; 039import org.apache.maven.plugins.annotations.Parameter; 040import org.apache.maven.plugins.annotations.ResolutionScope; 041import org.apache.maven.settings.Settings; 042import org.apache.maven.tools.plugin.DefaultPluginToolsRequest; 043import org.apache.maven.tools.plugin.ExtendedPluginDescriptor; 044import org.apache.maven.tools.plugin.PluginToolsRequest; 045import org.apache.maven.tools.plugin.extractor.ExtractionException; 046import org.apache.maven.tools.plugin.generator.GeneratorException; 047import org.apache.maven.tools.plugin.generator.GeneratorUtils; 048import org.apache.maven.tools.plugin.generator.PluginDescriptorFilesGenerator; 049import org.apache.maven.tools.plugin.scanner.MojoScanner; 050import org.codehaus.plexus.component.repository.ComponentDependency; 051import org.codehaus.plexus.util.ReaderFactory; 052import org.sonatype.plexus.build.incremental.BuildContext; 053 054/** 055 * <p> 056 * Generate a plugin descriptor. 057 * </p> 058 * <p> 059 * <b>Note:</b> Since 3.0, for Java plugin annotations support, 060 * default <a href="http://maven.apache.org/ref/current/maven-core/lifecycles.html">phase</a> 061 * defined by this goal is after the "compilation" of any scripts. This doesn't override 062 * <a href="/ref/current/maven-core/default-bindings.html#Bindings_for_maven-plugin_packaging">the default binding coded 063 * at generate-resources phase</a> in Maven core. 064 * </p> 065 * @author <a href="mailto:jason@maven.org">Jason van Zyl</a> 066 * @since 2.0 067 */ 068@Mojo( 069 name = "descriptor", 070 defaultPhase = LifecyclePhase.PROCESS_CLASSES, 071 requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME, 072 threadSafe = true) 073public class DescriptorGeneratorMojo extends AbstractGeneratorMojo { 074 private static final String VALUE_AUTO = "auto"; 075 076 /** 077 * The directory where the generated <code>plugin.xml</code> file will be put. 078 */ 079 @Parameter(defaultValue = "${project.build.outputDirectory}/META-INF/maven", readonly = true) 080 private File outputDirectory; 081 082 /** 083 * The file encoding of the source files. 084 * 085 * @since 2.5 086 */ 087 @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}") 088 private String encoding; 089 090 /** 091 * A flag to disable generation of the <code>plugin.xml</code> in favor of a hand authored plugin descriptor. 092 * 093 * @since 2.6 094 */ 095 @Parameter(defaultValue = "false") 096 private boolean skipDescriptor; 097 098 /** 099 * <p> 100 * The role names of mojo extractors to use. 101 * </p> 102 * <p> 103 * If not set, all mojo extractors will be used. If set to an empty extractor name, no mojo extractors 104 * will be used. 105 * </p> 106 * Example: 107 * <pre> 108 * <!-- Use all mojo extractors --> 109 * <extractors/> 110 * 111 * <!-- Use no mojo extractors --> 112 * <extractors> 113 * <extractor/> 114 * </extractors> 115 * 116 * <!-- Use only bsh mojo extractor --> 117 * <extractors> 118 * <extractor>bsh</extractor> 119 * </extractors> 120 * </pre> 121 * The extractors with the following names ship with {@code maven-plugin-tools}: 122 * <ol> 123 * <li>{@code java-annotations}</li> 124 * <li>{@code java-javadoc}, deprecated</li> 125 * <li>{@code ant}, deprecated</li> 126 * <li>{@code bsh}, deprecated</li> 127 * </ol> 128 */ 129 @Parameter 130 private Set<String> extractors; 131 132 /** 133 * By default, an exception is throw if no mojo descriptor is found. As the maven-plugin is defined in core, the 134 * descriptor generator mojo is bound to generate-resources phase. 135 * But for annotations, the compiled classes are needed, so skip error 136 * 137 * @since 3.0 138 */ 139 @Parameter(property = "maven.plugin.skipErrorNoDescriptorsFound", defaultValue = "false") 140 private boolean skipErrorNoDescriptorsFound; 141 142 /** 143 * Flag controlling is "expected dependencies in provided scope" check to be performed or not. Default value: 144 * {@code true}. 145 * 146 * @since 3.6.3 147 */ 148 @Parameter(defaultValue = "true", property = "maven.plugin.checkExpectedProvidedScope") 149 private boolean checkExpectedProvidedScope = true; 150 151 /** 152 * List of {@code groupId} strings of artifact coordinates that are expected to be in "provided" scope. Default 153 * value: {@code ["org.apache.maven"]}. 154 * 155 * @since 3.6.3 156 */ 157 @Parameter 158 private List<String> expectedProvidedScopeGroupIds = Collections.singletonList("org.apache.maven"); 159 160 /** 161 * List of {@code groupId:artifactId} strings of artifact coordinates that are to be excluded from "expected 162 * provided scope" check. Default value: 163 * {@code ["org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils"]}. 164 * 165 * @since 3.6.3 166 */ 167 @Parameter 168 private List<String> expectedProvidedScopeExclusions = Arrays.asList( 169 "org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils"); 170 171 /** 172 * Specify the dependencies as {@code groupId:artifactId} containing (abstract) Mojos, to filter 173 * dependencies scanned at runtime and focus on dependencies that are really useful to Mojo analysis. 174 * By default, the value is {@code null} and all dependencies are scanned (as before this parameter was added). 175 * If specified in the configuration with no children, no dependencies are scanned. 176 * 177 * @since 3.5 178 */ 179 @Parameter 180 private List<String> mojoDependencies = null; 181 182 /** 183 * Creates links to existing external javadoc-generated documentation. 184 * <br> 185 * <b>Notes</b>: 186 * all given links should have a fetchable {@code /package-list} or {@code /element-list} file. 187 * For instance: 188 * <pre> 189 * <externalJavadocBaseUrls> 190 * <externalJavadocBaseUrl>https://docs.oracle.com/javase/8/docs/api/</externalJavadocBaseUrl> 191 * </externalJavadocBaseUrls> 192 * </pre> 193 * is valid because <code>https://docs.oracle.com/javase/8/docs/api/package-list</code> exists. 194 * See <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options"> 195 * link option of the javadoc tool</a>. 196 * Using this parameter requires connectivity to the given URLs during the goal execution. 197 * @since 3.7.0 198 */ 199 @Parameter(property = "externalJavadocBaseUrls", alias = "links") 200 protected List<URI> externalJavadocBaseUrls; 201 202 /** 203 * The base URL for the Javadoc site containing the current project's API documentation. 204 * This may be relative to the root of the generated Maven site. 205 * It does not need to exist yet at the time when this goal is executed. 206 * Must end with a slash. 207 * <b>In case this is set the javadoc reporting goal should be executed prior to 208 * <a href="../maven-plugin-report-plugin/index.html">Plugin Report</a>.</b> 209 * @since 3.7.0 210 */ 211 @Parameter(property = "internalJavadocBaseUrl") 212 protected URI internalJavadocBaseUrl; 213 214 /** 215 * The version of the javadoc tool (equal to the container JDK version) used to generate the internal javadoc 216 * Only relevant if {@link #internalJavadocBaseUrl} is set. 217 * The default value needs to be overwritten in case toolchains are being used for generating Javadoc. 218 * 219 * @since 3.7.0 220 */ 221 @Parameter(property = "internalJavadocVersion", defaultValue = "${java.version}") 222 protected String internalJavadocVersion; 223 224 /** 225 * The Maven Settings, for evaluating proxy settings used to access {@link #javadocLinks} 226 * 227 * @since 3.7.0 228 */ 229 @Component 230 private Settings settings; 231 232 @Component 233 private MavenSession mavenSession; 234 235 /** 236 * The required Java version to set in the plugin descriptor. This is evaluated by Maven 4 and ignored by earlier 237 * Maven versions. Can be either one of the following formats: 238 * 239 * <ul> 240 * <li>A version range which specifies the supported Java versions. It can either use the usual mathematical 241 * syntax like {@code "[1.7,9),[11,)"} or use a single version like {@code "1.8"}. The latter is a short 242 * form for {@code "[1.8,)"}, i.e. denotes the minimum version required.</li> 243 * <li>{@code "auto"} to determine the minimum Java version from the binary class version being generated during 244 * compilation (determined by the extractor).</li> 245 * </ul> 246 * 247 * @since 3.8.0 248 */ 249 @Parameter(defaultValue = VALUE_AUTO) 250 String requiredJavaVersion; 251 252 /** 253 * The required Maven version to set in the plugin descriptor. This is evaluated by Maven 4 and ignored by earlier 254 * Maven versions. Can be either one of the following formats: 255 * 256 * <ul> 257 * <li>A version range which specifies the supported Maven versions. It can either use the usual mathematical 258 * syntax like {@code "[2.0.10,2.1.0),[3.0,)"} or use a single version like {@code "2.2.1"}. The latter is a short 259 * form for {@code "[2.2.1,)"}, i.e. denotes the minimum version required.</li> 260 * <li>{@code "auto"} to determine the minimum Maven version from the POM's Maven prerequisite, or if not set the 261 * referenced Maven Plugin API version.</li> 262 * </ul> 263 * This value takes precedence over the 264 * <a href="https://maven.apache.org/pom.html#Prerequisites">POM's Maven prerequisite</a> in Maven 4. 265 * 266 * @since 3.8.0 267 */ 268 @Parameter(defaultValue = VALUE_AUTO) 269 String requiredMavenVersion; 270 271 /** 272 * The component used for scanning the source tree for mojos. 273 */ 274 @Component 275 private MojoScanner mojoScanner; 276 277 @Component 278 protected BuildContext buildContext; 279 280 public void generate() throws MojoExecutionException { 281 282 if (!"maven-plugin".equalsIgnoreCase(project.getArtifactId()) 283 && project.getArtifactId().toLowerCase().startsWith("maven-") 284 && project.getArtifactId().toLowerCase().endsWith("-plugin") 285 && !"org.apache.maven.plugins".equals(project.getGroupId())) { 286 getLog().warn(LS + LS + "Artifact Ids of the format maven-___-plugin are reserved for" + LS 287 + "plugins in the Group Id org.apache.maven.plugins" + LS 288 + "Please change your artifactId to the format ___-maven-plugin" + LS 289 + "In the future this error will break the build." + LS + LS); 290 } 291 292 if (skipDescriptor) { 293 getLog().warn("Execution skipped"); 294 return; 295 } 296 297 if (checkExpectedProvidedScope) { 298 Set<Artifact> wrongScopedArtifacts = dependenciesNotInProvidedScope(); 299 if (!wrongScopedArtifacts.isEmpty()) { 300 StringBuilder message = new StringBuilder( 301 LS + LS + "Some dependencies of Maven Plugins are expected to be in provided scope." + LS 302 + "Please make sure that dependencies listed below declared in POM" + LS 303 + "have set '<scope>provided</scope>' as well." + LS + LS 304 + "The following dependencies are in wrong scope:" + LS); 305 for (Artifact artifact : wrongScopedArtifacts) { 306 message.append(" * ").append(artifact).append(LS); 307 } 308 message.append(LS).append(LS); 309 310 getLog().warn(message.toString()); 311 } 312 } 313 314 mojoScanner.setActiveExtractors(extractors); 315 316 // TODO: could use this more, eg in the writing of the plugin descriptor! 317 PluginDescriptor pluginDescriptor = new PluginDescriptor(); 318 319 pluginDescriptor.setGroupId(project.getGroupId()); 320 321 pluginDescriptor.setArtifactId(project.getArtifactId()); 322 323 pluginDescriptor.setVersion(project.getVersion()); 324 325 pluginDescriptor.setGoalPrefix(goalPrefix); 326 327 pluginDescriptor.setName(project.getName()); 328 329 pluginDescriptor.setDescription(project.getDescription()); 330 331 if (encoding == null || encoding.length() < 1) { 332 getLog().warn("Using platform encoding (" + ReaderFactory.FILE_ENCODING 333 + " actually) to read mojo source files, i.e. build is platform dependent!"); 334 } else { 335 getLog().info("Using '" + encoding + "' encoding to read mojo source files."); 336 } 337 338 if (internalJavadocBaseUrl != null && !internalJavadocBaseUrl.getPath().endsWith("/")) { 339 throw new MojoExecutionException("Given parameter 'internalJavadocBaseUrl' must end with a slash but is '" 340 + internalJavadocBaseUrl + "'"); 341 } 342 try { 343 List<ComponentDependency> deps = GeneratorUtils.toComponentDependencies(project.getArtifacts()); 344 pluginDescriptor.setDependencies(deps); 345 346 PluginToolsRequest request = new DefaultPluginToolsRequest(project, pluginDescriptor); 347 request.setEncoding(encoding); 348 request.setSkipErrorNoDescriptorsFound(skipErrorNoDescriptorsFound); 349 request.setDependencies(filterMojoDependencies()); 350 request.setRepoSession(mavenSession.getRepositorySession()); 351 request.setInternalJavadocBaseUrl(internalJavadocBaseUrl); 352 request.setInternalJavadocVersion(internalJavadocVersion); 353 request.setExternalJavadocBaseUrls(externalJavadocBaseUrls); 354 request.setSettings(settings); 355 356 mojoScanner.populatePluginDescriptor(request); 357 request.setPluginDescriptor(extendPluginDescriptor(request)); 358 359 outputDirectory.mkdirs(); 360 361 PluginDescriptorFilesGenerator pluginDescriptorGenerator = new PluginDescriptorFilesGenerator(); 362 pluginDescriptorGenerator.execute(outputDirectory, request); 363 364 buildContext.refresh(outputDirectory); 365 } catch (GeneratorException e) { 366 throw new MojoExecutionException("Error writing plugin descriptor", e); 367 } catch (InvalidPluginDescriptorException | ExtractionException e) { 368 throw new MojoExecutionException( 369 "Error extracting plugin descriptor: '" + e.getLocalizedMessage() + "'", e); 370 } catch (LinkageError e) { 371 throw new MojoExecutionException( 372 "The API of the mojo scanner is not compatible with this plugin version." 373 + " Please check the plugin dependencies configured" 374 + " in the POM and ensure the versions match.", 375 e); 376 } 377 } 378 379 private PluginDescriptor extendPluginDescriptor(PluginToolsRequest request) { 380 ExtendedPluginDescriptor extendedPluginDescriptor = new ExtendedPluginDescriptor(request.getPluginDescriptor()); 381 extendedPluginDescriptor.setRequiredJavaVersion(getRequiredJavaVersion(request)); 382 extendedPluginDescriptor.setRequiredMavenVersion(getRequiredMavenVersion(request)); 383 return extendedPluginDescriptor; 384 } 385 386 private String getRequiredMavenVersion(PluginToolsRequest request) { 387 if (!VALUE_AUTO.equals(requiredMavenVersion)) { 388 return requiredMavenVersion; 389 } 390 getLog().debug("Trying to derive Maven version automatically from project prerequisites..."); 391 String requiredMavenVersion = 392 project.getPrerequisites() != null ? project.getPrerequisites().getMaven() : null; 393 if (requiredMavenVersion == null) { 394 getLog().debug("Trying to derive Maven version automatically from referenced Maven Plugin API artifact " 395 + "version..."); 396 requiredMavenVersion = request.getUsedMavenApiVersion(); 397 } 398 if (requiredMavenVersion == null) { 399 getLog().warn("Cannot determine the required Maven version automatically, it is recommended to " 400 + "configure some explicit value manually."); 401 } 402 return requiredMavenVersion; 403 } 404 405 private String getRequiredJavaVersion(PluginToolsRequest request) { 406 if (!VALUE_AUTO.equals(requiredJavaVersion)) { 407 return requiredJavaVersion; 408 } 409 String minRequiredJavaVersion = request.getRequiredJavaVersion(); 410 if (minRequiredJavaVersion == null) { 411 getLog().warn("Cannot determine the minimally required Java version automatically, it is recommended to " 412 + "configure some explicit value manually."); 413 return null; 414 } 415 416 return minRequiredJavaVersion; 417 } 418 419 /** 420 * Collects all dependencies expected to be in "provided" scope but are NOT in "provided" scope. 421 */ 422 private Set<Artifact> dependenciesNotInProvidedScope() { 423 LinkedHashSet<Artifact> wrongScopedDependencies = new LinkedHashSet<>(); 424 425 for (Artifact dependency : project.getArtifacts()) { 426 String ga = dependency.getGroupId() + ":" + dependency.getArtifactId(); 427 if (expectedProvidedScopeGroupIds.contains(dependency.getGroupId()) 428 && !expectedProvidedScopeExclusions.contains(ga) 429 && !Artifact.SCOPE_PROVIDED.equals(dependency.getScope())) { 430 wrongScopedDependencies.add(dependency); 431 } 432 } 433 434 return wrongScopedDependencies; 435 } 436 437 /** 438 * Get dependencies filtered with mojoDependencies configuration. 439 * 440 * @return eventually filtered dependencies, or even <code>null</code> if configured with empty mojoDependencies 441 * list 442 * @see #mojoDependencies 443 */ 444 private Set<Artifact> filterMojoDependencies() { 445 Set<Artifact> filteredArtifacts; 446 if (mojoDependencies == null) { 447 filteredArtifacts = new LinkedHashSet<>(project.getArtifacts()); 448 } else if (mojoDependencies.isEmpty()) { 449 filteredArtifacts = null; 450 } else { 451 filteredArtifacts = new LinkedHashSet<>(); 452 453 ArtifactFilter filter = new IncludesArtifactFilter(mojoDependencies); 454 455 for (Artifact artifact : project.getArtifacts()) { 456 if (filter.include(artifact)) { 457 filteredArtifacts.add(artifact); 458 } 459 } 460 } 461 462 return filteredArtifacts; 463 } 464}