View Javadoc
1   /*
2    * Copyright (C) 2011 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.codehaus.gmavenplus.mojo;
18  
19  import org.apache.maven.plugins.annotations.Parameter;
20  import org.codehaus.gmavenplus.model.IncludeClasspath;
21  import org.codehaus.gmavenplus.model.internal.Version;
22  import org.codehaus.gmavenplus.util.GroovyCompiler;
23  
24  import java.io.File;
25  import java.lang.reflect.InvocationTargetException;
26  import java.net.MalformedURLException;
27  import java.util.List;
28  import java.util.Set;
29  
30  
31  /**
32   * The base generate stubs mojo, which all generate stubs mojos extend.
33   *
34   * @author Keegan Witt
35   */
36  public abstract class AbstractGenerateStubsMojo extends AbstractGroovyStubSourcesMojo {
37  
38      /**
39       * Groovy 1.8.2 version.
40       */
41      protected static final Version GROOVY_1_8_2 = new Version(1, 8, 2);
42  
43      /**
44       * The encoding of source files.
45       */
46      @Parameter(defaultValue = "${project.build.sourceEncoding}")
47      protected String sourceEncoding;
48  
49      /**
50       * The Groovy compiler bytecode compatibility. One of
51       * <ul>
52       *   <li>1.4 (or 4)</li>
53       *   <li>1.5 (or 5)</li>
54       *   <li>1.6 (or 6)</li>
55       *   <li>1.7 (or 7)</li>
56       *   <li>1.8 (or 8)</li>
57       *   <li>9 (or 1.9)</li>
58       *   <li>10</li>
59       *   <li>11</li>
60       *   <li>12</li>
61       *   <li>13</li>
62       *   <li>14</li>
63       *   <li>15</li>
64       *   <li>16</li>
65       *   <li>17</li>
66       *   <li>18</li>
67       *   <li>19</li>
68       *   <li>20</li>
69       *   <li>21</li>
70       *   <li>22</li>
71       *   <li>23</li>
72       *   <li>24</li>
73       *   <li>25</li>
74       * </ul>
75       * Using 1.6 (or 6) or 1.7 (or 7) requires Groovy &gt;= 2.1.3.
76       * Using 1.8 (or 8) requires Groovy &gt;= 2.3.3.
77       * Using 9 (or 1.9) requires Groovy &gt;= 2.5.3, or Groovy &gt;= 2.6.0 alpha 4, or Groovy &gt;= 3.0.0 alpha 2.
78       * Using 9 (or 1.9) with invokedynamic requires Groovy &gt;= 2.5.3, or Groovy &gt;= 3.0.0 alpha 2, but not any 2.6 versions.
79       * Using 10, 11, or 12 requires Groovy &gt;= 2.5.3, or Groovy &gt;= 3.0.0 alpha 4, but not any 2.6 versions.
80       * Using 13 requires Groovy &gt;= 2.5.7, or Groovy &gt;= 3.0.0-beta-1, but not any 2.6 versions.
81       * Using 14 requires Groovy &gt;= 3.0.0 beta-2.
82       * Using 15 requires Groovy &gt;= 3.0.3.
83       * Using 16 requires Groovy &gt;= 3.0.6.
84       * Using 17 requires Groovy &gt;= 3.0.8 or Groovy &gt; 4.0.0-alpha-3.
85       * Using 18 requires Groovy &gt; 4.0.0-beta-1.
86       * Using 19 requires Groovy &gt; 4.0.2.
87       * Using 20 requires Groovy &gt; 4.0.6.
88       * Using 21 requires Groovy &gt; 4.0.11.
89       * Using 22 requires Groovy &gt; 4.0.16 or Groovy &gt; 5.0.0-alpha-3.
90       * Using 23 requires Groovy &gt; 4.0.21 or Groovy &gt; 5.0.0-alpha-8.
91       * Using 24 requires Groovy &gt; 4.0.24 or Groovy &gt; 5.0.0-alpha-11.
92       * Using 25 requires Groovy &gt; 4.0.27 or Groovy &gt; 5.0.0-alpha-13.
93       *
94       * @since 1.0-beta-3
95       */
96      @Parameter(property = "maven.compiler.target", defaultValue = "1.8")
97      protected String targetBytecode;
98  
99      /**
100      * Whether to check that the version of Groovy used is able to use the requested <code>targetBytecode</code>.
101      *
102      * @since 1.9.0
103      */
104     @Parameter(property = "skipBytecodeCheck", defaultValue = "false")
105     protected boolean skipBytecodeCheck;
106 
107     /**
108      * Whether Groovy compiler should be set to debug.
109      */
110     @Parameter(defaultValue = "false")
111     protected boolean debug;
112 
113     /**
114      * Whether Groovy compiler should be set to verbose.
115      */
116     @Parameter(defaultValue = "false")
117     protected boolean verbose;
118 
119     /**
120      * Groovy compiler warning level. Should be one of:
121      * <dl>
122      *   <dt>0</dt>
123      *     <dd>None</dd>
124      *   <dt>1</dt>
125      *     <dd>Likely Errors</dd>
126      *   <dt>2</dt>
127      *     <dd>Possible Errors</dd>
128      *   <dt>3</dt>
129      *     <dd>Paranoia</dd>
130      * </dl>
131      */
132     @Parameter(defaultValue = "1")
133     protected int warningLevel;
134 
135     /**
136      * Groovy compiler error tolerance (the number of non-fatal errors (per unit) that should be tolerated before compilation is aborted).
137      */
138     @Parameter(defaultValue = "0")
139     protected int tolerance;
140 
141     /**
142      * What classpath to include. One of
143      * <ul>
144      *   <li>PROJECT_ONLY</li>
145      *   <li>PROJECT_AND_PLUGIN</li>
146      *   <li>PLUGIN_ONLY</li>
147      * </ul>
148      * Uses the same scope as the required dependency resolution of this mojo. Use only if you know what you're doing.
149      *
150      * @since 1.8.0
151      */
152     @Parameter(defaultValue = "PROJECT_ONLY")
153     protected IncludeClasspath includeClasspath;
154 
155     /**
156      * The Maven ToolchainManager.
157      */
158     @javax.inject.Inject
159     protected org.apache.maven.toolchain.ToolchainManager toolchainManager;
160 
161     /**
162      * The Maven Session.
163      */
164     @Parameter(defaultValue = "${session}", readonly = true, required = true)
165     protected org.apache.maven.execution.MavenSession session;
166 
167     /**
168      * Whether to execute in a forked process.
169      *
170      * @since 4.3.0
171      */
172     @Parameter(property = "fork", defaultValue = "false")
173     protected boolean fork;
174 
175     /**
176      * Performs the stub generation on the specified source files.
177      *
178      * @param stubSources     the sources to perform stub generation on
179      * @param classpath       The classpath to use for compilation
180      * @param outputDirectory the directory to write the stub files to
181      * @throws ClassNotFoundException    when a class needed for stub generation cannot be found
182      * @throws InstantiationException    when a class needed for stub generation cannot be instantiated
183      * @throws IllegalAccessException    when a method needed for stub generation cannot be accessed
184      * @throws InvocationTargetException when a reflection invocation needed for stub generation cannot be completed
185      * @throws MalformedURLException     when a classpath element provides a malformed URL
186      */
187     protected synchronized void doStubGeneration(final Set<File> stubSources, final List<?> classpath, final File outputDirectory) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, InstantiationException, MalformedURLException {
188         if (stubSources == null || stubSources.isEmpty()) {
189             getLog().info("No sources specified for stub generation. Skipping.");
190             return;
191         }
192 
193         org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration = new org.codehaus.gmavenplus.model.GroovyStubConfiguration(stubSources, classpath, outputDirectory);
194         configuration.setIncludeClasspath(includeClasspath);
195         configuration.setSkipBytecodeCheck(skipBytecodeCheck);
196         configuration.setDebug(debug);
197         configuration.setVerbose(verbose);
198         configuration.setWarningLevel(warningLevel);
199         configuration.setTolerance(tolerance);
200         configuration.setSourceEncoding(sourceEncoding);
201         configuration.setTargetBytecode(targetBytecode);
202 
203         org.apache.maven.toolchain.Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
204         if (toolchain != null) {
205             getLog().info("Toolchain in gmavenplus-plugin: " + toolchain);
206             performForkedStubGeneration(configuration, toolchain.findTool("java"));
207         } else if (fork) {
208             String javaExecutable = getJavaExecutable();
209             getLog().info("Forking stub generation using " + javaExecutable);
210             performForkedStubGeneration(configuration, javaExecutable);
211         } else {
212             performInProcessStubGeneration(configuration, classpath);
213         }
214     }
215 
216     protected void performInProcessStubGeneration(org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration, List<?> classpath) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
217         setupClassWrangler(classpath, includeClasspath);
218         logPluginClasspath();
219         classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
220 
221         // Note: GroovyCompiler handles minGroovyVersion check now in generateStubs
222 
223         GroovyCompiler compiler = new GroovyCompiler(classWrangler, getLog());
224         compiler.generateStubs(configuration);
225     }
226 
227     protected void performForkedStubGeneration(org.codehaus.gmavenplus.model.GroovyStubConfiguration configuration, String javaExecutable) throws InvocationTargetException {
228         try {
229             // Write configuration to file
230             File configFile = File.createTempFile("groovy-stub-config", ".ser");
231             configFile.deleteOnExit();
232             try (java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(java.nio.file.Files.newOutputStream(configFile.toPath()))) {
233                 oos.writeObject(configuration);
234             }
235 
236             // Build classpath for forked process (plugin + dependencies)
237             String forkClasspath = buildForkClasspath();
238 
239             List<String> command = new java.util.ArrayList<>();
240             command.add(javaExecutable);
241             command.add("-cp");
242             command.add(forkClasspath);
243             command.add("org.codehaus.gmavenplus.util.ForkedGroovyCompiler");
244             command.add(configFile.getAbsolutePath());
245 
246             ProcessBuilder pb = new ProcessBuilder(command);
247             pb.inheritIO();
248             Process process = pb.start();
249             int exitCode = process.waitFor();
250 
251             if (exitCode != 0) {
252                 throw new InvocationTargetException(new RuntimeException("Forked stub generation failed with exit code " + exitCode));
253             }
254 
255         } catch (java.io.IOException | InterruptedException e) {
256             throw new InvocationTargetException(e);
257         }
258     }
259 
260     protected String buildForkClasspath() {
261         StringBuilder cp = new StringBuilder();
262         // Add plugin artifact
263         cp.append(pluginDescriptor.getPluginArtifact().getFile().getAbsolutePath());
264 
265         // Add plugin dependencies
266         for (org.apache.maven.artifact.Artifact artifact : pluginDescriptor.getArtifacts()) {
267             cp.append(File.pathSeparator);
268             cp.append(artifact.getFile().getAbsolutePath());
269         }
270 
271         // Add maven-plugin-api jar which is 'provided' so not in getArtifacts()
272         try {
273             Class<?> logClass = org.apache.maven.plugin.logging.Log.class;
274             java.security.CodeSource codeSource = logClass.getProtectionDomain().getCodeSource();
275             if (codeSource != null) {
276                 String logJar = new File(codeSource.getLocation().toURI()).getAbsolutePath();
277                 cp.append(File.pathSeparator).append(logJar);
278             }
279         } catch (Exception e) {
280             getLog().warn("Could not find maven-plugin-api jar to add to fork classpath", e);
281         }
282 
283         return cp.toString();
284     }
285 
286     /**
287      * Logs the stubs that have been generated.
288      *
289      * @param outputDirectory the output directory for the stubs
290      */
291     protected void logGeneratedStubs(File outputDirectory) {
292         Set<File> stubs = getStubs(outputDirectory);
293         getLog().info("Generated " + stubs.size() + " stub" + (stubs.size() != 1 ? "s" : "") + ".");
294     }
295 
296     /**
297      * This is a fix for <a href="http://jira.codehaus.org/browse/MGROOVY-187">...</a>
298      * It modifies the dates of the created stubs to 1/1/1970, ensuring that the Java compiler will not overwrite perfectly
299      * good compiled Groovy just because it has a newer source stub. Basically, this prevents the stubs from causing a
300      * side effect with the Java compiler, but still allows stubs to work with JavaDoc.
301      *
302      * @param stubs the files on which to reset the modified date
303      */
304     protected void resetStubModifiedDates(final Set<File> stubs) {
305         for (File stub : stubs) {
306             boolean success = stub.setLastModified(0L);
307             if (!success) {
308                 getLog().warn("Unable to set modified time on stub " + stub.getAbsolutePath() + ".");
309             }
310         }
311     }
312 
313 
314 
315 
316 }