View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.javadoc;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.util.List;
26  
27  import org.apache.maven.archiver.MavenArchiveConfiguration;
28  import org.apache.maven.archiver.MavenArchiver;
29  import org.apache.maven.artifact.DependencyResolutionRequiredException;
30  import org.apache.maven.artifact.handler.ArtifactHandler;
31  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
32  import org.apache.maven.doxia.tools.SiteTool;
33  import org.apache.maven.model.Resource;
34  import org.apache.maven.plugin.MojoExecutionException;
35  import org.apache.maven.plugins.annotations.LifecyclePhase;
36  import org.apache.maven.plugins.annotations.Mojo;
37  import org.apache.maven.plugins.annotations.Parameter;
38  import org.apache.maven.plugins.annotations.ResolutionScope;
39  import org.apache.maven.plugins.javadoc.resolver.ResourceResolver;
40  import org.apache.maven.project.MavenProjectHelper;
41  import org.apache.maven.project.ProjectBuilder;
42  import org.apache.maven.reporting.MavenReportException;
43  import org.apache.maven.toolchain.ToolchainManager;
44  import org.codehaus.plexus.archiver.ArchiverException;
45  import org.codehaus.plexus.archiver.jar.JarArchiver;
46  import org.codehaus.plexus.archiver.jar.ManifestException;
47  import org.codehaus.plexus.archiver.manager.ArchiverManager;
48  import org.eclipse.aether.RepositorySystem;
49  
50  /**
51   * Bundles the Javadoc documentation for <code>main Java code</code> in an <b>NON aggregator</b> project into
52   * a jar using the standard <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html">
53   * Javadoc Tool</a>.
54   *
55   * @since 2.0
56   */
57  @Mojo(
58          name = "jar",
59          defaultPhase = LifecyclePhase.PACKAGE,
60          requiresDependencyResolution = ResolutionScope.COMPILE,
61          threadSafe = true)
62  public class JavadocJarMojo extends AbstractJavadocMojo {
63      /**
64       * Includes all generated Javadoc files
65       */
66      private static final String[] DEFAULT_INCLUDES = new String[] {"**/**"};
67  
68      /**
69       * Excludes all processing files.
70       *
71       * @see AbstractJavadocMojo#DEBUG_JAVADOC_SCRIPT_NAME
72       * @see AbstractJavadocMojo#OPTIONS_FILE_NAME
73       * @see AbstractJavadocMojo#PACKAGES_FILE_NAME
74       * @see AbstractJavadocMojo#ARGFILE_FILE_NAME
75       * @see AbstractJavadocMojo#FILES_FILE_NAME
76       */
77      private static final String[] DEFAULT_EXCLUDES = new String[] {
78          DEBUG_JAVADOC_SCRIPT_NAME, OPTIONS_FILE_NAME, PACKAGES_FILE_NAME, ARGFILE_FILE_NAME, FILES_FILE_NAME
79      };
80  
81      // ----------------------------------------------------------------------
82      // Mojo components
83      // ----------------------------------------------------------------------
84  
85      /**
86       * Used for attaching the artifact in the project.
87       */
88      private MavenProjectHelper projectHelper;
89  
90      /**
91       * The Jar archiver.
92       *
93       * @since 2.5
94       */
95      private JarArchiver jarArchiver;
96  
97      // CHECKSTYLE_OFF: ParameterNumber
98      @Inject
99      public JavadocJarMojo(
100             MavenProjectHelper projectHelper,
101             JarArchiver jarArchiver,
102             SiteTool siteTool,
103             ArchiverManager archiverManager,
104             ResourceResolver resourceResolver,
105             RepositorySystem repoSystem,
106             ArtifactHandlerManager artifactHandlerManager,
107             ProjectBuilder mavenProjectBuilder,
108             ToolchainManager toolchainManager) {
109         super(
110                 siteTool,
111                 archiverManager,
112                 resourceResolver,
113                 repoSystem,
114                 artifactHandlerManager,
115                 mavenProjectBuilder,
116                 toolchainManager);
117         this.projectHelper = projectHelper;
118         this.jarArchiver = jarArchiver;
119     }
120     // CHECKSTYLE_ON: ParameterNumber
121 
122     // ----------------------------------------------------------------------
123     // Mojo Parameters
124     // ----------------------------------------------------------------------
125 
126     /**
127      * Specifies the directory where the generated jar file will be put.
128      */
129     @Parameter(property = "project.build.directory")
130     private String jarOutputDirectory;
131 
132     /**
133      * Specifies the filename that will be used for the generated jar file. Please note that <code>-javadoc</code>
134      * or <code>-test-javadoc</code> will be appended to the file name.
135      */
136     @Parameter(property = "project.build.finalName")
137     private String finalName;
138 
139     /**
140      * Specifies whether to attach the generated artifact to the project helper.
141      * <br/>
142      */
143     @Parameter(property = "attach", defaultValue = "true")
144     private boolean attach;
145 
146     /**
147      * The archive configuration to use.
148      * See <a href="http://maven.apache.org/shared/maven-archiver/index.html">Maven Archiver Reference</a>.
149      *
150      * @since 2.5
151      */
152     @Parameter
153     private MavenArchiveConfiguration archive = new JavadocArchiveConfiguration();
154 
155     /**
156      * Path to the default MANIFEST file to use. It will be used if
157      * <code>useDefaultManifestFile</code> is set to <code>true</code>.
158      *
159      * @since 2.5
160      */
161     @Parameter(defaultValue = "${project.build.outputDirectory}/META-INF/MANIFEST.MF", required = true, readonly = true)
162     private File defaultManifestFile;
163 
164     /**
165      * Set this to <code>true</code> to enable the use of the <code>defaultManifestFile</code>.
166      * <br/>
167      *
168      * @since 2.5
169      */
170     @Parameter(defaultValue = "false")
171     private boolean useDefaultManifestFile;
172 
173     /**
174      * @since 2.10
175      */
176     @Parameter(property = "maven.javadoc.classifier", defaultValue = "javadoc", required = true)
177     private String classifier;
178 
179     /**
180      * Detect the links for all modules defined in the project.
181      * <br/>
182      * If {@code reactorProjects} is defined in a non-aggregator way, it generates default offline links
183      * between modules based on the defined project's URLs. For instance, if a parent project has two projects
184      * <code>module1</code> and <code>module2</code>, the <code>-linkoffline</code> will be:
185      * <br/>
186      * The added Javadoc <code>-linkoffline</code> parameter for <b>module1</b> will be
187      * <code>/absolute/path/to/</code><b>module2</b><code>/target/site/apidocs</code>
188      * <br/>
189      * The added Javadoc <code>-linkoffline</code> parameter for <b>module2</b> will be
190      * <code>/absolute/path/to/</code><b>module1</b><code>/target/site/apidocs</code>
191      *
192      * @see #offlineLinks
193      * @since 2.6
194      */
195     @Parameter(property = "detectOfflineLinks", defaultValue = "false")
196     private boolean detectOfflineLinks;
197 
198     /** {@inheritDoc} */
199     @Override
200     protected void doExecute() throws MojoExecutionException {
201         if (skip) {
202             getLog().info("Skipping javadoc generation");
203             return;
204         }
205 
206         if (!isAggregator() || !"pom".equalsIgnoreCase(project.getPackaging())) {
207             ArtifactHandler artifactHandler = project.getArtifact().getArtifactHandler();
208             if (!"java".equals(artifactHandler.getLanguage())) {
209                 getLog().info("Not executing Javadoc as the project is not a Java classpath-capable package");
210                 return;
211             }
212         }
213 
214         try {
215             executeReport(SiteTool.DEFAULT_LOCALE);
216         } catch (MavenReportException e) {
217             failOnError("MavenReportException: Error while generating Javadoc", e);
218         } catch (RuntimeException e) {
219             failOnError("RuntimeException: Error while generating Javadoc", e);
220         }
221 
222         File javadocOutputDirectory = new File(getPluginReportOutputDirectory());
223         if (javadocOutputDirectory.exists()) {
224             try {
225                 File outputFile = generateArchive(javadocOutputDirectory, finalName + "-" + getClassifier() + ".jar");
226 
227                 if (!attach) {
228                     getLog().info("NOT adding javadoc to attached artifacts list.");
229                 } else {
230                     // TODO: these introduced dependencies on the project are going to become problematic - can we
231                     // export it
232                     //  through metadata instead?
233                     projectHelper.attachArtifact(project, "javadoc", getClassifier(), outputFile);
234                 }
235             } catch (ArchiverException e) {
236                 failOnError("ArchiverException: Error while creating archive", e);
237             } catch (IOException e) {
238                 failOnError("IOException: Error while creating archive", e);
239             } catch (RuntimeException e) {
240                 failOnError("RuntimeException: Error while creating archive", e);
241             }
242         } else {
243             getLog().info("No Javadoc in project. Archive not created.");
244         }
245     }
246 
247     // ----------------------------------------------------------------------
248     // Protected methods
249     // ----------------------------------------------------------------------
250 
251     /**
252      * @return the wanted classifier, i.e. <code>javadoc</code> or <code>test-javadoc</code>
253      */
254     protected String getClassifier() {
255         return classifier;
256     }
257 
258     // ----------------------------------------------------------------------
259     // private methods
260     // ----------------------------------------------------------------------
261 
262     /**
263      * Method that creates the jar file
264      *
265      * @param javadocFiles the directory where the generated jar file will be put
266      * @param jarFileName the filename of the generated jar file
267      * @return a File object that contains the generated jar file
268      * @throws ArchiverException {@link ArchiverException}
269      * @throws IOException {@link IOException}
270      */
271     private File generateArchive(File javadocFiles, String jarFileName) throws ArchiverException, IOException {
272         MavenArchiver archiver = new MavenArchiver();
273         archiver.setArchiver(jarArchiver);
274         archiver.setCreatedBy("Maven Javadoc Plugin", "org.apache.maven.plugins", "maven-javadoc-plugin");
275 
276         // configure for Reproducible Builds based on outputTimestamp value
277         archiver.configureReproducibleBuild(outputTimestamp);
278 
279         if (javadocFiles.exists()) {
280             archiver.getArchiver().addDirectory(javadocFiles, DEFAULT_INCLUDES, DEFAULT_EXCLUDES);
281         }
282 
283         List<Resource> resources = project.getBuild().getResources();
284 
285         for (Resource r : resources) {
286             if (r.getDirectory().endsWith("maven-shared-archive-resources")) {
287                 archiver.getArchiver().addDirectory(new File(r.getDirectory()));
288             }
289         }
290 
291         if (useDefaultManifestFile && defaultManifestFile.exists() && archive.getManifestFile() == null) {
292             getLog().debug("Adding existing MANIFEST to archive. Found under: " + defaultManifestFile.getPath());
293             archive.setManifestFile(defaultManifestFile);
294         }
295 
296         File outputFile = new File(jarOutputDirectory, jarFileName);
297 
298         // Why do we do this?
299         if (outputFile.exists()) {
300             outputFile.delete();
301         }
302         archiver.setOutputFile(outputFile);
303 
304         try {
305             archiver.createArchive(session, project, archive);
306         } catch (ManifestException | DependencyResolutionRequiredException e) {
307             throw new ArchiverException("Error creating Javadoc archive: " + e.getMessage(), e);
308         }
309 
310         return outputFile;
311     }
312 
313     @Override
314     public boolean isDetectOfflineLinks() {
315         return detectOfflineLinks;
316     }
317 }