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