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  
22  import java.io.File;
23  
24  import org.apache.maven.plugins.annotations.Component;
25  import org.apache.maven.toolchain.Toolchain;
26  import org.apache.maven.toolchain.ToolchainManager;
27  import org.codehaus.gmavenplus.model.GroovyCompileConfiguration;
28  import org.codehaus.gmavenplus.util.ForkedGroovyCompiler;
29  import org.codehaus.gmavenplus.util.GroovyCompiler;
30  
31  import java.io.*;
32  import java.lang.reflect.InvocationTargetException;
33  import java.net.MalformedURLException;
34  import java.nio.file.Files;
35  import java.util.ArrayList;
36  import java.util.List;
37  import java.util.Set;
38  
39  
40  /**
41   * The base compile mojo, which all compile mojos extend.
42   *
43   * @author Keegan Witt
44   */
45  public abstract class AbstractCompileMojo extends AbstractGroovySourcesMojo {
46  
47      /**
48       * The encoding of source files.
49       */
50      @Parameter(defaultValue = "${project.build.sourceEncoding}")
51      protected String sourceEncoding;
52  
53      /**
54       * The Groovy compiler bytecode compatibility. One of
55       * <ul>
56       *   <li>1.4 (or 4)</li>
57       *   <li>1.5 (or 5)</li>
58       *   <li>1.6 (or 6)</li>
59       *   <li>1.7 (or 7)</li>
60       *   <li>1.8 (or 8)</li>
61       *   <li>9 (or 1.9)</li>
62       *   <li>10</li>
63       *   <li>11</li>
64       *   <li>12</li>
65       *   <li>13</li>
66       *   <li>14</li>
67       *   <li>15</li>
68       *   <li>16</li>
69       *   <li>17</li>
70       *   <li>18</li>
71       *   <li>19</li>
72       *   <li>20</li>
73       *   <li>21</li>
74       *   <li>22</li>
75       *   <li>23</li>
76       *   <li>24</li>
77       *   <li>25</li>
78       * </ul>
79       * Using 1.6 (or 6) or 1.7 (or 7) requires Groovy &gt;= 2.1.3.
80       * Using 1.8 (or 8) requires Groovy &gt;= 2.3.3.
81       * 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.
82       * 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.
83       * 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.
84       * Using 13 requires Groovy &gt;= 2.5.7, or Groovy &gt;= 3.0.0-beta-1, but not any 2.6 versions.
85       * Using 14 requires Groovy &gt;= 3.0.0 beta-2.
86       * Using 15 requires Groovy &gt;= 3.0.3.
87       * Using 16 requires Groovy &gt;= 3.0.6.
88       * Using 17 requires Groovy &gt;= 3.0.8 or Groovy &gt; 4.0.0-alpha-3.
89       * Using 18 requires Groovy &gt; 4.0.0-beta-1.
90       * Using 19 requires Groovy &gt; 4.0.2.
91       * Using 20 requires Groovy &gt; 4.0.6.
92       * Using 21 requires Groovy &gt; 4.0.11.
93       * Using 22 requires Groovy &gt; 4.0.16 or Groovy &gt; 5.0.0-alpha-3.
94       * Using 23 requires Groovy &gt; 4.0.21 or Groovy &gt; 5.0.0-alpha-8.
95       * Using 24 requires Groovy &gt; 4.0.24 or Groovy &gt; 5.0.0-alpha-11.
96       * Using 25 requires Groovy &gt; 4.0.27 or Groovy &gt; 5.0.0-alpha-13.
97       */
98      @Parameter(property = "maven.compiler.target", defaultValue = "1.8")
99      protected String targetBytecode;
100 
101     /**
102      * Whether to check that the version of Groovy used is able to use the requested <code>targetBytecode</code>.
103      *
104      * @since 1.9.0
105      */
106     @Parameter(property = "skipBytecodeCheck", defaultValue = "false")
107     protected boolean skipBytecodeCheck;
108 
109     /**
110      * Whether Groovy compiler should be set to debug.
111      */
112     @Parameter(defaultValue = "false")
113     protected boolean debug;
114 
115     /**
116      * Whether Groovy compiler should be set to verbose.
117      */
118     @Parameter(defaultValue = "false")
119     protected boolean verbose;
120 
121     /**
122      * Groovy compiler warning level. Should be one of:
123      * <dl>
124      *   <dt>0</dt>
125      *     <dd>None</dd>
126      *   <dt>1</dt>
127      *     <dd>Likely Errors</dd>
128      *   <dt>2</dt>
129      *     <dd>Possible Errors</dd>
130      *   <dt>3</dt>
131      *     <dd>Paranoia</dd>
132      * </dl>
133      */
134     @Parameter(defaultValue = "1")
135     protected int warningLevel;
136 
137     /**
138      * Groovy compiler error tolerance (the number of non-fatal errors (per unit) that should be tolerated before compilation is aborted).
139      */
140     @Parameter(defaultValue = "0")
141     protected int tolerance;
142 
143     /**
144      * Whether to support invokeDynamic (requires Java 7 or greater and Groovy indy 2.0.0-beta-3 or greater).
145      * Has no effect for Groovy 4, as it is always enabled.
146      */
147     @Parameter(defaultValue = "false")
148     protected boolean invokeDynamic;
149 
150     /**
151      * Whether to enable Groovy's parallel parsing. Requires Groovy 3.0.5.
152      * Is enabled by default for Groovy 4.0.0-alpha-1 or newer.
153      *
154      * @since 1.11.0
155      */
156     @Parameter
157     protected Boolean parallelParsing = null;
158 
159     /**
160      * A <a href="http://groovy-lang.org/dsls.html#compilation-customizers">script</a> for tweaking the configuration options
161      * (requires Groovy 2.1.0-beta-1 or greater). Note that its encoding must match your source encoding.
162      */
163     @Parameter
164     protected File configScript;
165 
166     /**
167      * Generate metadata for reflection on method parameter names using the functionality provided by JEP 118
168      * (requires Java 8 or greater and Groovy 2.5.0-alpha-1 or greater).
169      */
170     @Parameter(defaultValue = "false")
171     protected boolean parameters;
172 
173     /**
174      * What classpath to include. One of
175      * <ul>
176      *   <li>PROJECT_ONLY</li>
177      *   <li>PROJECT_AND_PLUGIN</li>
178      *   <li>PLUGIN_ONLY</li>
179      * </ul>
180      * Uses the same scope as the required dependency resolution of this mojo. Use only if you know what you're doing.
181      *
182      * @since 1.8.0
183      */
184     @Parameter(defaultValue = "PROJECT_ONLY")
185     protected IncludeClasspath includeClasspath;
186 
187     /**
188      * Whether the bytecode version has preview features enabled (JEP 12).
189      * Requires Groovy &gt;= 3.0.0-beta-1 or Groovy &gt;= 2.5.7, but not any 2.6 versions and Java &gt;= 12.
190      *
191      * @since 1.7.1
192      */
193     @Parameter(defaultValue = "false")
194     protected boolean previewFeatures;
195 
196     /**
197      * The ToolchainManager.
198      */
199     @Component
200     protected ToolchainManager toolchainManager;
201 
202     /**
203      * whether to fork the compilation.
204      */
205     @Parameter(defaultValue = "false")
206     protected boolean fork;
207 
208     /**
209      * Performs compilation of compile mojos.
210      *
211      * @param sources                the sources to compile
212      * @param classpath              the classpath to use for compilation
213      * @param compileOutputDirectory the directory to write the compiled class files to
214      * @throws ClassNotFoundException    when a class needed for compilation cannot be found
215      * @throws InstantiationException    when a class needed for compilation cannot be instantiated
216      * @throws IllegalAccessException    when a method needed for compilation cannot be accessed
217      * @throws InvocationTargetException when a reflection invocation needed for compilation cannot be completed
218      * @throws MalformedURLException     when a classpath element provides a malformed URL
219      */
220     @SuppressWarnings({"rawtypes"})
221     protected synchronized void doCompile(final Set<File> sources, final List classpath, final File compileOutputDirectory)
222             throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException, MalformedURLException {
223         GroovyCompileConfiguration configuration = new GroovyCompileConfiguration(sources, classpath, compileOutputDirectory);
224         configuration.setIncludeClasspath(includeClasspath);
225         configuration.setSkipBytecodeCheck(skipBytecodeCheck);
226         configuration.setDebug(debug);
227         configuration.setVerbose(verbose);
228         configuration.setWarningLevel(warningLevel);
229         configuration.setTolerance(tolerance);
230         configuration.setInvokeDynamic(invokeDynamic);
231         configuration.setParallelParsing(parallelParsing);
232         configuration.setConfigScript(configScript);
233         configuration.setParameters(parameters);
234         configuration.setPreviewFeatures(previewFeatures);
235         configuration.setSourceEncoding(sourceEncoding);
236         configuration.setTargetBytecode(targetBytecode);
237 
238         Toolchain toolchain = toolchainManager.getToolchainFromBuildContext("jdk", session);
239         if (toolchain != null) {
240             getLog().info("Toolchain in gmavenplus-plugin: " + toolchain);
241             performForkedCompilation(configuration, toolchain.findTool("java"));
242         } else if (fork) {
243             String javaExecutable = getJavaExecutable();
244             getLog().info("Forking compilation using " + javaExecutable);
245             performForkedCompilation(configuration, javaExecutable);
246         } else {
247             performInProcessCompilation(configuration, classpath);
248         }
249     }
250 
251     protected void performInProcessCompilation(GroovyCompileConfiguration configuration, List<?> classpath) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
252         setupClassWrangler(classpath, includeClasspath);
253         logPluginClasspath();
254         classWrangler.logGroovyVersion(mojoExecution.getMojoDescriptor().getGoal());
255 
256         if (!groovyVersionSupportsAction()) {
257             getLog().error("Your Groovy version (" + classWrangler.getGroovyVersionString() + ") doesn't support compilation. The minimum version of Groovy required is " + minGroovyVersion + ". Skipping compiling.");
258             return;
259         }
260 
261         GroovyCompiler compiler = new GroovyCompiler(classWrangler, getLog());
262         compiler.compile(configuration);
263     }
264 
265     protected void performForkedCompilation(GroovyCompileConfiguration configuration, String javaExecutable) {
266         if (javaExecutable == null) {
267             getLog().warn("Unable to find 'java' executable for toolchain. Falling back to in-process compilation.");
268             try {
269                 performInProcessCompilation(configuration, configuration.getClasspath());
270             } catch (Exception e) {
271                 throw new RuntimeException("Compilation failed", e);
272             }
273             return;
274         }
275 
276         try {
277             File configFile = File.createTempFile("gmavenplus-compile-config", ".ser");
278             configFile.deleteOnExit();
279             try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(configFile.toPath()))) {
280                 oos.writeObject(configuration);
281             }
282 
283             List<String> command = new ArrayList<>();
284             command.add(javaExecutable);
285             command.add("-cp");
286             command.add(buildForkClasspath());
287             command.add(ForkedGroovyCompiler.class.getName());
288             command.add(configFile.getAbsolutePath());
289 
290             getLog().info("Forking compilation using " + javaExecutable);
291             getLog().debug("Command: " + command);
292 
293             ProcessBuilder pb = new ProcessBuilder(command);
294             pb.inheritIO();
295             Process process = pb.start();
296             int exitCode = process.waitFor();
297             if (exitCode != 0) {
298                 throw new RuntimeException("Groovy compilation failed with exit code " + exitCode);
299             }
300         } catch (IOException | InterruptedException e) {
301             throw new RuntimeException("Unable to fork compilation", e);
302         }
303     }
304 
305     protected String buildForkClasspath() {
306         StringBuilder cp = new StringBuilder();
307         // Add plugin artifact
308         cp.append(pluginDescriptor.getPluginArtifact().getFile().getAbsolutePath());
309 
310         // Add plugin dependencies
311         for (org.apache.maven.artifact.Artifact artifact : pluginDescriptor.getArtifacts()) {
312             cp.append(File.pathSeparator);
313             cp.append(artifact.getFile().getAbsolutePath());
314         }
315 
316         // Add maven-plugin-api jar which is 'provided' so not in getArtifacts()
317         try {
318             Class<?> logClass = org.apache.maven.plugin.logging.Log.class;
319             java.security.CodeSource codeSource = logClass.getProtectionDomain().getCodeSource();
320             if (codeSource != null) {
321                 String logJar = new File(codeSource.getLocation().toURI()).getAbsolutePath();
322                 cp.append(File.pathSeparator).append(logJar);
323             }
324         } catch (Exception e) {
325             getLog().warn("Could not find maven-plugin-api jar to add to fork classpath", e);
326         }
327 
328         return cp.toString();
329     }
330 
331 }