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