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.plugin.compiler;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.lang.reflect.InvocationTargetException;
25  import java.lang.reflect.Method;
26  import java.nio.charset.Charset;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.Date;
34  import java.util.HashSet;
35  import java.util.Iterator;
36  import java.util.LinkedHashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Properties;
40  import java.util.Set;
41  
42  import org.apache.maven.artifact.handler.ArtifactHandler;
43  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
44  import org.apache.maven.execution.MavenExecutionRequest;
45  import org.apache.maven.execution.MavenSession;
46  import org.apache.maven.plugin.AbstractMojo;
47  import org.apache.maven.plugin.MojoExecution;
48  import org.apache.maven.plugin.MojoExecutionException;
49  import org.apache.maven.plugins.annotations.Component;
50  import org.apache.maven.plugins.annotations.Parameter;
51  import org.apache.maven.project.MavenProject;
52  import org.apache.maven.shared.incremental.IncrementalBuildHelper;
53  import org.apache.maven.shared.incremental.IncrementalBuildHelperRequest;
54  import org.apache.maven.shared.utils.ReaderFactory;
55  import org.apache.maven.shared.utils.StringUtils;
56  import org.apache.maven.shared.utils.io.DirectoryScanResult;
57  import org.apache.maven.shared.utils.io.DirectoryScanner;
58  import org.apache.maven.shared.utils.io.FileUtils;
59  import org.apache.maven.shared.utils.logging.MessageBuilder;
60  import org.apache.maven.shared.utils.logging.MessageUtils;
61  import org.apache.maven.toolchain.Toolchain;
62  import org.apache.maven.toolchain.ToolchainManager;
63  import org.codehaus.plexus.compiler.Compiler;
64  import org.codehaus.plexus.compiler.CompilerConfiguration;
65  import org.codehaus.plexus.compiler.CompilerException;
66  import org.codehaus.plexus.compiler.CompilerMessage;
67  import org.codehaus.plexus.compiler.CompilerOutputStyle;
68  import org.codehaus.plexus.compiler.CompilerResult;
69  import org.codehaus.plexus.compiler.manager.CompilerManager;
70  import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
71  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
72  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
73  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
74  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
75  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
76  import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
77  import org.codehaus.plexus.languages.java.version.JavaVersion;
78  import org.eclipse.aether.RepositorySystem;
79  import org.eclipse.aether.artifact.Artifact;
80  import org.eclipse.aether.artifact.DefaultArtifact;
81  import org.eclipse.aether.collection.CollectRequest;
82  import org.eclipse.aether.graph.Dependency;
83  import org.eclipse.aether.graph.Exclusion;
84  import org.eclipse.aether.resolution.ArtifactResult;
85  import org.eclipse.aether.resolution.DependencyRequest;
86  import org.eclipse.aether.resolution.DependencyResult;
87  import org.eclipse.aether.util.artifact.JavaScopes;
88  import org.objectweb.asm.ClassWriter;
89  import org.objectweb.asm.Opcodes;
90  
91  /**
92   * TODO: At least one step could be optimized, currently the plugin will do two
93   * scans of all the source code if the compiler has to have the entire set of
94   * sources. This is currently the case for at least the C# compiler and most
95   * likely all the other .NET compilers too.
96   *
97   * @author others
98   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
99   * @since 2.0
100  */
101 public abstract class AbstractCompilerMojo extends AbstractMojo {
102     protected static final String PS = System.getProperty("path.separator");
103 
104     private static final String INPUT_FILES_LST_FILENAME = "inputFiles.lst";
105 
106     static final String DEFAULT_SOURCE = "1.8";
107 
108     static final String DEFAULT_TARGET = "1.8";
109 
110     // Used to compare with older targets
111     static final String MODULE_INFO_TARGET = "1.9";
112 
113     // ----------------------------------------------------------------------
114     // Configurables
115     // ----------------------------------------------------------------------
116 
117     /**
118      * Indicates whether the build will continue even if there are compilation errors.
119      *
120      * @since 2.0.2
121      */
122     @Parameter(property = "maven.compiler.failOnError", defaultValue = "true")
123     private boolean failOnError = true;
124 
125     /**
126      * Indicates whether the build will continue even if there are compilation warnings.
127      *
128      * @since 3.6
129      */
130     @Parameter(property = "maven.compiler.failOnWarning", defaultValue = "false")
131     private boolean failOnWarning;
132 
133     /**
134      * Set to <code>true</code> to include debugging information in the compiled class files.
135      */
136     @Parameter(property = "maven.compiler.debug", defaultValue = "true")
137     private boolean debug = true;
138 
139     /**
140      * Set to <code>true</code> to generate metadata for reflection on method parameters.
141      * @since 3.6.2
142      */
143     @Parameter(property = "maven.compiler.parameters", defaultValue = "false")
144     private boolean parameters;
145 
146     /**
147      * Set to <code>true</code> to Enable preview language features of the java compiler
148      * @since 3.10.1
149      */
150     @Parameter(property = "maven.compiler.enablePreview", defaultValue = "false")
151     private boolean enablePreview;
152 
153     /**
154      * Set to <code>true</code> to show messages about what the compiler is doing.
155      */
156     @Parameter(property = "maven.compiler.verbose", defaultValue = "false")
157     private boolean verbose;
158 
159     /**
160      * Sets whether to show source locations where deprecated APIs are used.
161      */
162     @Parameter(property = "maven.compiler.showDeprecation", defaultValue = "false")
163     private boolean showDeprecation;
164 
165     /**
166      * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
167      * @deprecated This property is a no-op in {@code javac}.
168      */
169     @Deprecated
170     @Parameter(property = "maven.compiler.optimize", defaultValue = "false")
171     private boolean optimize;
172 
173     /**
174      * Set to <code>false</code> to disable warnings during compilation.
175      */
176     @Parameter(property = "maven.compiler.showWarnings", defaultValue = "true")
177     private boolean showWarnings;
178 
179     /**
180      * <p>The -source argument for the Java compiler.</p>
181      *
182      * <p><b>NOTE: </b></p>
183      * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p>
184      * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p>
185      * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p>
186      */
187     @Parameter(property = "maven.compiler.source", defaultValue = DEFAULT_SOURCE)
188     protected String source;
189 
190     /**
191      * <p>The -target argument for the Java compiler.</p>
192      *
193      * <p><b>NOTE: </b></p>
194      * <p>Since 3.8.0 the default value has changed from 1.5 to 1.6</p>
195      * <p>Since 3.9.0 the default value has changed from 1.6 to 1.7</p>
196      * <p>Since 3.11.0 the default value has changed from 1.7 to 1.8</p>
197      */
198     @Parameter(property = "maven.compiler.target", defaultValue = DEFAULT_TARGET)
199     protected String target;
200 
201     /**
202      * The -release argument for the Java compiler, supported since Java9
203      *
204      * @since 3.6
205      */
206     @Parameter(property = "maven.compiler.release")
207     protected String release;
208 
209     /**
210      * The -encoding argument for the Java compiler.
211      *
212      * @since 2.1
213      */
214     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
215     private String encoding;
216 
217     /**
218      * Sets the granularity in milliseconds of the last modification
219      * date for testing whether a source needs recompilation.
220      */
221     @Parameter(property = "lastModGranularityMs", defaultValue = "0")
222     private int staleMillis;
223 
224     /**
225      * The compiler id of the compiler to use. See this
226      * <a href="non-javac-compilers.html">guide</a> for more information.
227      */
228     @Parameter(property = "maven.compiler.compilerId", defaultValue = "javac")
229     private String compilerId;
230 
231     /**
232      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
233      */
234     @Parameter(property = "maven.compiler.compilerVersion")
235     private String compilerVersion;
236 
237     /**
238      * Allows running the compiler in a separate process.
239      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
240      */
241     @Parameter(property = "maven.compiler.fork", defaultValue = "false")
242     private boolean fork;
243 
244     /**
245      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
246      * if {@link #fork} is set to <code>true</code>.
247      *
248      * @since 2.0.1
249      */
250     @Parameter(property = "maven.compiler.meminitial")
251     private String meminitial;
252 
253     /**
254      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
255      * if {@link #fork} is set to <code>true</code>.
256      *
257      * @since 2.0.1
258      */
259     @Parameter(property = "maven.compiler.maxmem")
260     private String maxmem;
261 
262     /**
263      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
264      */
265     @Parameter(property = "maven.compiler.executable")
266     private String executable;
267 
268     /**
269      * <p>
270      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
271      * If not set, both compilation and annotation processing are performed at the same time.
272      * </p>
273      * <p>Allowed values are:</p>
274      * <ul>
275      * <li><code>none</code> - no annotation processing is performed.</li>
276      * <li><code>only</code> - only annotation processing is done, no compilation.</li>
277      * </ul>
278      *
279      * @since 2.2
280      */
281     @Parameter
282     private String proc;
283 
284     /**
285      * <p>
286      * Names of annotation processors to run. Only applies to JDK 1.6+
287      * If not set, the default annotation processors discovery process applies.
288      * </p>
289      *
290      * @since 2.2
291      */
292     @Parameter
293     private String[] annotationProcessors;
294 
295     /**
296      * <p>
297      * Classpath elements to supply as annotation processor path. If specified, the compiler will detect annotation
298      * processors only in those classpath elements. If omitted, the default classpath is used to detect annotation
299      * processors. The detection itself depends on the configuration of {@code annotationProcessors}.
300      * </p>
301      * <p>
302      * Each classpath element is specified using their Maven coordinates (groupId, artifactId, version, classifier,
303      * type). Transitive dependencies are added automatically. Exclusions are supported as well. Example:
304      * </p>
305      *
306      * <pre>
307      * &lt;configuration&gt;
308      *   &lt;annotationProcessorPaths&gt;
309      *     &lt;path&gt;
310      *       &lt;groupId&gt;org.sample&lt;/groupId&gt;
311      *       &lt;artifactId&gt;sample-annotation-processor&lt;/artifactId&gt;
312      *       &lt;version&gt;1.2.3&lt;/version&gt;
313      *       &lt;!-- Optionally exclude transitive dependencies --&gt;
314      *       &lt;exclusions&gt;
315      *         &lt;exclusion&gt;
316      *           &lt;groupId&gt;org.sample&lt;/groupId&gt;
317      *           &lt;artifactId&gt;sample-dependency&lt;/artifactId&gt;
318      *         &lt;/exclusion&gt;
319      *       &lt;/exclusions&gt;
320      *     &lt;/path&gt;
321      *     &lt;!-- ... more ... --&gt;
322      *   &lt;/annotationProcessorPaths&gt;
323      * &lt;/configuration&gt;
324      * </pre>
325      *
326      * <b>Note:</b> Exclusions are supported from version 3.11.0.
327      *
328      * @since 3.5
329      */
330     @Parameter
331     private List<DependencyCoordinate> annotationProcessorPaths;
332 
333     /**
334      * <p>
335      * Sets the arguments to be passed to the compiler (prepending a dash).
336      * </p>
337      * <p>
338      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
339      * </p>
340      * <p>
341      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
342      * </p>
343      * <p>
344      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
345      * </p>
346      *
347      * <pre>
348      * &lt;compilerArguments&gt;
349      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
350      *   &lt;Xlint/&gt;
351      *   &lt;Xlint:-path/&gt;
352      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
353      * &lt;/compilerArguments&gt;
354      * </pre>
355      *
356      * @since 2.0.1
357      * @deprecated use {@link #compilerArgs} instead.
358      */
359     @Parameter
360     @Deprecated
361     protected Map<String, String> compilerArguments;
362 
363     /**
364      * <p>
365      * Sets the arguments to be passed to the compiler.
366      * </p>
367      * <p>
368      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
369      * </p>
370      * Example:
371      * <pre>
372      * &lt;compilerArgs&gt;
373      *   &lt;arg&gt;-Xmaxerrs&lt;/arg&gt;
374      *   &lt;arg&gt;1000&lt;/arg&gt;
375      *   &lt;arg&gt;-Xlint&lt;/arg&gt;
376      *   &lt;arg&gt;-J-Duser.language=en_us&lt;/arg&gt;
377      * &lt;/compilerArgs&gt;
378      * </pre>
379      *
380      * @since 3.1
381      */
382     @Parameter
383     protected List<String> compilerArgs;
384 
385     /**
386      * <p>
387      * Sets the unformatted single argument string to be passed to the compiler. To pass multiple arguments such as
388      * <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArguments}.
389      * </p>
390      * <p>
391      * This is because the list of valid arguments passed to a Java compiler varies based on the compiler version.
392      * </p>
393      * <p>
394      * Note that {@code -J} options are only passed through if {@link #fork} is set to {@code true}.
395      * </p>
396      */
397     @Parameter
398     protected String compilerArgument;
399 
400     /**
401      * Sets the name of the output file when compiling a set of
402      * sources to a single file.
403      * <p/>
404      * expression="${project.build.finalName}"
405      */
406     @Parameter
407     private String outputFileName;
408 
409     /**
410      * Keyword list to be appended to the <code>-g</code> command-line switch. Legal values are none or a
411      * comma-separated list of the following keywords: <code>lines</code>, <code>vars</code>, and <code>source</code>.
412      * If debug level is not specified, by default, nothing will be appended to <code>-g</code>.
413      * If debug is not turned on, this attribute will be ignored.
414      *
415      * @since 2.1
416      */
417     @Parameter(property = "maven.compiler.debuglevel")
418     private String debuglevel;
419 
420     /**
421      * Keyword to be appended to the <code>-implicit:</code> command-line switch.
422      *
423      * @since 3.10.2
424      */
425     @Parameter(property = "maven.compiler.implicit")
426     private String implicit;
427 
428     /**
429      *
430      */
431     @Component
432     private ToolchainManager toolchainManager;
433 
434     /**
435      * <p>
436      * Specify the requirements for this jdk toolchain for using a different {@code javac} than the one of the JRE used
437      * by Maven. This overrules the toolchain selected by the
438      * <a href="https://maven.apache.org/plugins/maven-toolchains-plugin/">maven-toolchain-plugin</a>.
439      * </p>
440      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html"> Guide to Toolchains</a> for more
441      * info)
442      *
443      * <pre>
444      * &lt;configuration&gt;
445      *   &lt;jdkToolchain&gt;
446      *     &lt;version&gt;11&lt;/version&gt;
447      *   &lt;/jdkToolchain&gt;
448      *   ...
449      * &lt;/configuration&gt;
450      *
451      * &lt;configuration&gt;
452      *   &lt;jdkToolchain&gt;
453      *     &lt;version&gt;1.8&lt;/version&gt;
454      *     &lt;vendor&gt;zulu&lt;/vendor&gt;
455      *   &lt;/jdkToolchain&gt;
456      *   ...
457      * &lt;/configuration&gt;
458      * </pre>
459      * <strong>note:</strong> requires at least Maven 3.3.1
460      *
461      * @since 3.6
462      */
463     @Parameter
464     private Map<String, String> jdkToolchain;
465 
466     // ----------------------------------------------------------------------
467     // Read-only parameters
468     // ----------------------------------------------------------------------
469 
470     /**
471      * The directory to run the compiler from if fork is true.
472      */
473     @Parameter(defaultValue = "${basedir}", required = true, readonly = true)
474     private File basedir;
475 
476     /**
477      * The target directory of the compiler if fork is true.
478      */
479     @Parameter(defaultValue = "${project.build.directory}", required = true, readonly = true)
480     private File buildDirectory;
481 
482     /**
483      * Plexus compiler manager.
484      */
485     @Component
486     private CompilerManager compilerManager;
487 
488     /**
489      * The current build session instance. This is used for toolchain manager API calls.
490      */
491     @Parameter(defaultValue = "${session}", readonly = true, required = true)
492     private MavenSession session;
493 
494     /**
495      * The current project instance. This is used for propagating generated-sources paths as compile/testCompile source
496      * roots.
497      */
498     @Parameter(defaultValue = "${project}", readonly = true, required = true)
499     private MavenProject project;
500 
501     /**
502      * Strategy to re use javacc class created:
503      * <ul>
504      * <li><code>reuseCreated</code> (default): will reuse already created but in case of multi-threaded builds, each
505      * thread will have its own instance</li>
506      * <li><code>reuseSame</code>: the same Javacc class will be used for each compilation even for multi-threaded build
507      * </li>
508      * <li><code>alwaysNew</code>: a new Javacc class will be created for each compilation</li>
509      * </ul>
510      * Note this parameter value depends on the os/jdk you are using, but the default value should work on most of env.
511      *
512      * @since 2.5
513      */
514     @Parameter(defaultValue = "${reuseCreated}", property = "maven.compiler.compilerReuseStrategy")
515     private String compilerReuseStrategy = "reuseCreated";
516 
517     /**
518      * @since 2.5
519      */
520     @Parameter(defaultValue = "false", property = "maven.compiler.skipMultiThreadWarning")
521     private boolean skipMultiThreadWarning;
522 
523     /**
524      * compiler can now use javax.tools if available in your current jdk, you can disable this feature
525      * using -Dmaven.compiler.forceJavacCompilerUse=true or in the plugin configuration
526      *
527      * @since 3.0
528      */
529     @Parameter(defaultValue = "false", property = "maven.compiler.forceJavacCompilerUse")
530     private boolean forceJavacCompilerUse;
531 
532     /**
533      * @since 3.0 needed for storing the status for the incremental build support.
534      */
535     @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
536     private MojoExecution mojoExecution;
537 
538     /**
539      * File extensions to check timestamp for incremental build.
540      * Default contains only <code>class</code> and <code>jar</code>.
541      *
542      * @since 3.1
543      */
544     @Parameter
545     private List<String> fileExtensions;
546 
547     /**
548      * <p>to enable/disable incremental compilation feature.</p>
549      * <p>This leads to two different modes depending on the underlying compiler. The default javac compiler does the
550      * following:</p>
551      * <ul>
552      * <li>true <strong>(default)</strong> in this mode the compiler plugin determines whether any JAR files the
553      * current module depends on have changed in the current build run; or any source file was added, removed or
554      * changed since the last compilation. If this is the case, the compiler plugin recompiles all sources.</li>
555      * <li>false <strong>(not recommended)</strong> this only compiles source files which are newer than their
556      * corresponding class files, namely which have changed since the last compilation. This does not
557      * recompile other classes which use the changed class, potentially leaving them with references to methods that no
558      * longer exist, leading to errors at runtime.</li>
559      * </ul>
560      *
561      * @since 3.1
562      */
563     @Parameter(defaultValue = "true", property = "maven.compiler.useIncrementalCompilation")
564     private boolean useIncrementalCompilation = true;
565 
566     /**
567      * Package info source files that only contain javadoc and no annotation on the package
568      * can lead to no class file being generated by the compiler.  This causes a file miss
569      * on the next compilations and forces an unnecessary recompilation. The default value
570      * of <code>true</code> causes an empty class file to be generated.  This behavior can
571      * be changed by setting this parameter to <code>false</code>.
572      *
573      * @since 3.10
574      */
575     @Parameter(defaultValue = "true", property = "maven.compiler.createMissingPackageInfoClass")
576     private boolean createMissingPackageInfoClass = true;
577 
578     @Parameter(defaultValue = "false", property = "maven.compiler.showCompilationChanges")
579     private boolean showCompilationChanges = false;
580     /**
581      * Resolves the artifacts needed.
582      */
583     @Component
584     private RepositorySystem repositorySystem;
585 
586     /**
587      * Artifact handler manager.
588      */
589     @Component
590     private ArtifactHandlerManager artifactHandlerManager;
591 
592     protected abstract SourceInclusionScanner getSourceInclusionScanner(int staleMillis);
593 
594     protected abstract SourceInclusionScanner getSourceInclusionScanner(String inputFileEnding);
595 
596     protected abstract List<String> getClasspathElements();
597 
598     protected abstract List<String> getModulepathElements();
599 
600     protected abstract Map<String, JavaModuleDescriptor> getPathElements();
601 
602     protected abstract List<String> getCompileSourceRoots();
603 
604     protected abstract void preparePaths(Set<File> sourceFiles);
605 
606     protected abstract File getOutputDirectory();
607 
608     protected abstract String getSource();
609 
610     protected abstract String getTarget();
611 
612     protected abstract String getRelease();
613 
614     protected abstract String getCompilerArgument();
615 
616     protected abstract Map<String, String> getCompilerArguments();
617 
618     protected abstract File getGeneratedSourcesDirectory();
619 
620     protected abstract String getDebugFileName();
621 
622     protected final MavenProject getProject() {
623         return project;
624     }
625 
626     private boolean targetOrReleaseSet;
627 
628     @Override
629     public void execute() throws MojoExecutionException, CompilationFailureException {
630         // ----------------------------------------------------------------------
631         // Look up the compiler. This is done before other code than can
632         // cause the mojo to return before the lookup is done possibly resulting
633         // in misconfigured POMs still building.
634         // ----------------------------------------------------------------------
635 
636         Compiler compiler;
637 
638         getLog().debug("Using compiler '" + compilerId + "'.");
639 
640         try {
641             compiler = compilerManager.getCompiler(compilerId);
642         } catch (NoSuchCompilerException e) {
643             throw new MojoExecutionException("No such compiler '" + e.getCompilerId() + "'.");
644         }
645 
646         // -----------toolchains start here ----------------------------------
647         // use the compilerId as identifier for toolchains as well.
648         Toolchain tc = getToolchain();
649         if (tc != null) {
650             getLog().info("Toolchain in maven-compiler-plugin: " + tc);
651             if (executable != null) {
652                 getLog().warn("Toolchains are ignored, 'executable' parameter is set to " + executable);
653             } else {
654                 fork = true;
655                 // TODO somehow shaky dependency between compilerId and tool executable.
656                 executable = tc.findTool(compilerId);
657             }
658         }
659         // ----------------------------------------------------------------------
660         //
661         // ----------------------------------------------------------------------
662 
663         List<String> compileSourceRoots = removeEmptyCompileSourceRoots(getCompileSourceRoots());
664 
665         if (compileSourceRoots.isEmpty()) {
666             getLog().info("No sources to compile");
667 
668             return;
669         }
670 
671         // Verify that target or release is set
672         if (!targetOrReleaseSet) {
673             MessageBuilder mb = MessageUtils.buffer()
674                     .a("No explicit value set for target or release! ")
675                     .a("To ensure the same result even after upgrading this plugin, please add ")
676                     .newline()
677                     .newline();
678 
679             writePlugin(mb);
680 
681             getLog().warn(mb.toString());
682         }
683 
684         // ----------------------------------------------------------------------
685         // Create the compiler configuration
686         // ----------------------------------------------------------------------
687 
688         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
689 
690         compilerConfiguration.setOutputLocation(getOutputDirectory().getAbsolutePath());
691 
692         compilerConfiguration.setOptimize(optimize);
693 
694         compilerConfiguration.setDebug(debug);
695 
696         compilerConfiguration.setDebugFileName(getDebugFileName());
697 
698         compilerConfiguration.setImplicitOption(implicit);
699 
700         if (debug && StringUtils.isNotEmpty(debuglevel)) {
701             String[] split = StringUtils.split(debuglevel, ",");
702             for (String aSplit : split) {
703                 if (!(aSplit.equalsIgnoreCase("none")
704                         || aSplit.equalsIgnoreCase("lines")
705                         || aSplit.equalsIgnoreCase("vars")
706                         || aSplit.equalsIgnoreCase("source"))) {
707                     throw new IllegalArgumentException("The specified debug level: '" + aSplit + "' is unsupported. "
708                             + "Legal values are 'none', 'lines', 'vars', and 'source'.");
709                 }
710             }
711             compilerConfiguration.setDebugLevel(debuglevel);
712         }
713 
714         compilerConfiguration.setParameters(parameters);
715 
716         compilerConfiguration.setEnablePreview(enablePreview);
717 
718         compilerConfiguration.setVerbose(verbose);
719 
720         compilerConfiguration.setShowWarnings(showWarnings);
721 
722         compilerConfiguration.setFailOnWarning(failOnWarning);
723 
724         if (failOnWarning && !showWarnings) {
725             getLog().warn("The property failOnWarning is set to true, but showWarnings is set to false.");
726             getLog().warn("With compiler's warnings silenced the failOnWarning has no effect.");
727         }
728 
729         compilerConfiguration.setShowDeprecation(showDeprecation);
730 
731         compilerConfiguration.setSourceVersion(getSource());
732 
733         compilerConfiguration.setTargetVersion(getTarget());
734 
735         compilerConfiguration.setReleaseVersion(getRelease());
736 
737         compilerConfiguration.setProc(proc);
738 
739         File generatedSourcesDirectory = getGeneratedSourcesDirectory();
740         compilerConfiguration.setGeneratedSourcesDirectory(
741                 generatedSourcesDirectory != null ? generatedSourcesDirectory.getAbsoluteFile() : null);
742 
743         if (generatedSourcesDirectory != null) {
744             if (!generatedSourcesDirectory.exists()) {
745                 generatedSourcesDirectory.mkdirs();
746             }
747 
748             String generatedSourcesPath = generatedSourcesDirectory.getAbsolutePath();
749 
750             compileSourceRoots.add(generatedSourcesPath);
751 
752             if (isTestCompile()) {
753                 getLog().debug("Adding " + generatedSourcesPath + " to test-compile source roots:\n  "
754                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
755 
756                 project.addTestCompileSourceRoot(generatedSourcesPath);
757 
758                 getLog().debug("New test-compile source roots:\n  "
759                         + StringUtils.join(project.getTestCompileSourceRoots().iterator(), "\n  "));
760             } else {
761                 getLog().debug("Adding " + generatedSourcesPath + " to compile source roots:\n  "
762                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
763 
764                 project.addCompileSourceRoot(generatedSourcesPath);
765 
766                 getLog().debug("New compile source roots:\n  "
767                         + StringUtils.join(project.getCompileSourceRoots().iterator(), "\n  "));
768             }
769         }
770 
771         compilerConfiguration.setSourceLocations(compileSourceRoots);
772 
773         compilerConfiguration.setAnnotationProcessors(annotationProcessors);
774 
775         compilerConfiguration.setProcessorPathEntries(resolveProcessorPathEntries());
776 
777         compilerConfiguration.setSourceEncoding(encoding);
778 
779         compilerConfiguration.setFork(fork);
780 
781         if (fork) {
782             if (!StringUtils.isEmpty(meminitial)) {
783                 String value = getMemoryValue(meminitial);
784 
785                 if (value != null) {
786                     compilerConfiguration.setMeminitial(value);
787                 } else {
788                     getLog().info("Invalid value for meminitial '" + meminitial + "'. Ignoring this option.");
789                 }
790             }
791 
792             if (!StringUtils.isEmpty(maxmem)) {
793                 String value = getMemoryValue(maxmem);
794 
795                 if (value != null) {
796                     compilerConfiguration.setMaxmem(value);
797                 } else {
798                     getLog().info("Invalid value for maxmem '" + maxmem + "'. Ignoring this option.");
799                 }
800             }
801         }
802 
803         compilerConfiguration.setExecutable(executable);
804 
805         compilerConfiguration.setWorkingDirectory(basedir);
806 
807         compilerConfiguration.setCompilerVersion(compilerVersion);
808 
809         compilerConfiguration.setBuildDirectory(buildDirectory);
810 
811         compilerConfiguration.setOutputFileName(outputFileName);
812 
813         if (CompilerConfiguration.CompilerReuseStrategy.AlwaysNew.getStrategy().equals(this.compilerReuseStrategy)) {
814             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.AlwaysNew);
815         } else if (CompilerConfiguration.CompilerReuseStrategy.ReuseSame.getStrategy()
816                 .equals(this.compilerReuseStrategy)) {
817             if (getRequestThreadCount() > 1) {
818                 if (!skipMultiThreadWarning) {
819                     getLog().warn("You are in a multi-thread build and compilerReuseStrategy is set to reuseSame."
820                             + " This can cause issues in some environments (os/jdk)!"
821                             + " Consider using reuseCreated strategy."
822                             + System.getProperty("line.separator")
823                             + "If your env is fine with reuseSame, you can skip this warning with the "
824                             + "configuration field skipMultiThreadWarning "
825                             + "or -Dmaven.compiler.skipMultiThreadWarning=true");
826                 }
827             }
828             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseSame);
829         } else {
830 
831             compilerConfiguration.setCompilerReuseStrategy(CompilerConfiguration.CompilerReuseStrategy.ReuseCreated);
832         }
833 
834         getLog().debug("CompilerReuseStrategy: "
835                 + compilerConfiguration.getCompilerReuseStrategy().getStrategy());
836 
837         compilerConfiguration.setForceJavacCompilerUse(forceJavacCompilerUse);
838 
839         boolean canUpdateTarget;
840 
841         IncrementalBuildHelper incrementalBuildHelper = new IncrementalBuildHelper(mojoExecution, session);
842 
843         final Set<File> sources;
844 
845         IncrementalBuildHelperRequest incrementalBuildHelperRequest = null;
846 
847         if (useIncrementalCompilation) {
848             getLog().debug("useIncrementalCompilation enabled");
849             try {
850                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
851 
852                 sources = getCompileSources(compiler, compilerConfiguration);
853 
854                 preparePaths(sources);
855 
856                 incrementalBuildHelperRequest = new IncrementalBuildHelperRequest().inputFiles(sources);
857 
858                 DirectoryScanResult dsr = computeInputFileTreeChanges(incrementalBuildHelper, sources);
859 
860                 boolean idk = compiler.getCompilerOutputStyle()
861                                 .equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
862                         && !canUpdateTarget;
863                 boolean dependencyChanged = isDependencyChanged();
864                 boolean sourceChanged = isSourceChanged(compilerConfiguration, compiler);
865                 boolean inputFileTreeChanged = hasInputFileTreeChanged(dsr);
866                 // CHECKSTYLE_OFF: LineLength
867                 if (idk || dependencyChanged || sourceChanged || inputFileTreeChanged)
868                 // CHECKSTYLE_ON: LineLength
869                 {
870                     String cause = idk
871                             ? "idk"
872                             : (dependencyChanged ? "dependency" : (sourceChanged ? "source" : "input tree"));
873                     getLog().info("Changes detected - recompiling the module! :" + cause);
874                     if (showCompilationChanges) {
875                         for (String fileAdded : dsr.getFilesAdded()) {
876                             getLog().info("\t+ " + fileAdded);
877                         }
878                         for (String fileRemoved : dsr.getFilesRemoved()) {
879                             getLog().info("\t- " + fileRemoved);
880                         }
881                     }
882 
883                     compilerConfiguration.setSourceFiles(sources);
884                 } else {
885                     getLog().info("Nothing to compile - all classes are up to date");
886 
887                     return;
888                 }
889             } catch (CompilerException e) {
890                 throw new MojoExecutionException("Error while computing stale sources.", e);
891             }
892         } else {
893             getLog().debug("useIncrementalCompilation disabled");
894 
895             Set<File> staleSources;
896             try {
897                 staleSources =
898                         computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis));
899 
900                 canUpdateTarget = compiler.canUpdateTarget(compilerConfiguration);
901 
902                 if (compiler.getCompilerOutputStyle().equals(CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES)
903                         && !canUpdateTarget) {
904                     getLog().info("RESCANNING!");
905                     // TODO: This second scan for source files is sub-optimal
906                     String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
907 
908                     staleSources = computeStaleSources(
909                             compilerConfiguration, compiler, getSourceInclusionScanner(inputFileEnding));
910                 }
911 
912             } catch (CompilerException e) {
913                 throw new MojoExecutionException("Error while computing stale sources.", e);
914             }
915 
916             if (staleSources.isEmpty()) {
917                 getLog().info("Nothing to compile - all classes are up to date");
918 
919                 return;
920             }
921 
922             compilerConfiguration.setSourceFiles(staleSources);
923 
924             try {
925                 // MCOMPILER-366: if sources contain the module-descriptor it must be used to define the modulepath
926                 sources = getCompileSources(compiler, compilerConfiguration);
927 
928                 if (getLog().isDebugEnabled()) {
929                     getLog().debug("#sources: " + sources.size());
930                     for (File file : sources) {
931                         getLog().debug(file.getPath());
932                     }
933                 }
934 
935                 preparePaths(sources);
936             } catch (CompilerException e) {
937                 throw new MojoExecutionException("Error while computing stale sources.", e);
938             }
939         }
940 
941         // Dividing pathElements of classPath and modulePath is based on sourceFiles
942         compilerConfiguration.setClasspathEntries(getClasspathElements());
943 
944         compilerConfiguration.setModulepathEntries(getModulepathElements());
945 
946         compilerConfiguration.setIncludes(getIncludes());
947 
948         compilerConfiguration.setExcludes(getExcludes());
949 
950         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
951 
952         String effectiveCompilerArgument = getCompilerArgument();
953 
954         if ((effectiveCompilerArguments != null) || (effectiveCompilerArgument != null) || (compilerArgs != null)) {
955             if (effectiveCompilerArguments != null) {
956                 for (Map.Entry<String, String> me : effectiveCompilerArguments.entrySet()) {
957                     String key = me.getKey();
958                     String value = me.getValue();
959                     if (!key.startsWith("-")) {
960                         key = "-" + key;
961                     }
962 
963                     if (key.startsWith("-A") && StringUtils.isNotEmpty(value)) {
964                         compilerConfiguration.addCompilerCustomArgument(key + "=" + value, null);
965                     } else {
966                         compilerConfiguration.addCompilerCustomArgument(key, value);
967                     }
968                 }
969             }
970             if (!StringUtils.isEmpty(effectiveCompilerArgument)) {
971                 compilerConfiguration.addCompilerCustomArgument(effectiveCompilerArgument, null);
972             }
973             if (compilerArgs != null) {
974                 for (String arg : compilerArgs) {
975                     compilerConfiguration.addCompilerCustomArgument(arg, null);
976                 }
977             }
978         }
979 
980         // ----------------------------------------------------------------------
981         // Dump configuration
982         // ----------------------------------------------------------------------
983         if (getLog().isDebugEnabled()) {
984             getLog().debug("Classpath:");
985 
986             for (String s : getClasspathElements()) {
987                 getLog().debug(" " + s);
988             }
989 
990             if (!getModulepathElements().isEmpty()) {
991                 getLog().debug("Modulepath:");
992                 for (String s : getModulepathElements()) {
993                     getLog().debug(" " + s);
994                 }
995             }
996 
997             getLog().debug("Source roots:");
998 
999             for (String root : getCompileSourceRoots()) {
1000                 getLog().debug(" " + root);
1001             }
1002 
1003             try {
1004                 if (fork) {
1005                     if (compilerConfiguration.getExecutable() != null) {
1006                         getLog().debug("Excutable: ");
1007                         getLog().debug(" " + compilerConfiguration.getExecutable());
1008                     }
1009                 }
1010 
1011                 String[] cl = compiler.createCommandLine(compilerConfiguration);
1012                 if (cl != null && cl.length > 0) {
1013                     StringBuilder sb = new StringBuilder();
1014                     sb.append(cl[0]);
1015                     for (int i = 1; i < cl.length; i++) {
1016                         sb.append(" ");
1017                         sb.append(cl[i]);
1018                     }
1019                     getLog().debug("Command line options:");
1020                     getLog().debug(sb);
1021                 }
1022             } catch (CompilerException ce) {
1023                 getLog().debug(ce);
1024             }
1025         }
1026 
1027         List<String> jpmsLines = new ArrayList<>();
1028 
1029         // See http://openjdk.java.net/jeps/261
1030         final List<String> runtimeArgs = Arrays.asList(
1031                 "--upgrade-module-path", "--add-exports", "--add-reads", "--add-modules", "--limit-modules");
1032 
1033         // Custom arguments are all added as keys to an ordered Map
1034         Iterator<Map.Entry<String, String>> entryIter =
1035                 compilerConfiguration.getCustomCompilerArgumentsEntries().iterator();
1036         while (entryIter.hasNext()) {
1037             Map.Entry<String, String> entry = entryIter.next();
1038 
1039             if (runtimeArgs.contains(entry.getKey())) {
1040                 jpmsLines.add(entry.getKey());
1041 
1042                 String value = entry.getValue();
1043                 if (value == null) {
1044                     entry = entryIter.next();
1045                     value = entry.getKey();
1046                 }
1047                 jpmsLines.add(value);
1048             } else if ("--patch-module".equals(entry.getKey())) {
1049                 String value = entry.getValue();
1050                 if (value == null) {
1051                     entry = entryIter.next();
1052                     value = entry.getKey();
1053                 }
1054 
1055                 String[] values = value.split("=");
1056 
1057                 StringBuilder patchModule = new StringBuilder(values[0]);
1058                 patchModule.append('=');
1059 
1060                 Set<String> patchModules = new LinkedHashSet<>();
1061                 Set<Path> sourceRoots = new HashSet<>(getCompileSourceRoots().size());
1062                 for (String sourceRoot : getCompileSourceRoots()) {
1063                     sourceRoots.add(Paths.get(sourceRoot));
1064                 }
1065 
1066                 String[] files = values[1].split(PS);
1067 
1068                 for (String file : files) {
1069                     Path filePath = Paths.get(file);
1070                     if (getOutputDirectory().toPath().equals(filePath)) {
1071                         patchModules.add("_"); // this jar
1072                     } else if (getOutputDirectory().toPath().startsWith(filePath)) {
1073                         // multirelease, can be ignored
1074                         continue;
1075                     } else if (sourceRoots.contains(filePath)) {
1076                         patchModules.add("_"); // this jar
1077                     } else {
1078                         JavaModuleDescriptor descriptor = getPathElements().get(file);
1079 
1080                         if (descriptor == null) {
1081                             if (Files.isDirectory(filePath)) {
1082                                 patchModules.add(file);
1083                             } else {
1084                                 getLog().warn("Can't locate " + file);
1085                             }
1086                         } else if (!values[0].equals(descriptor.name())) {
1087                             patchModules.add(descriptor.name());
1088                         }
1089                     }
1090                 }
1091 
1092                 StringBuilder sb = new StringBuilder();
1093 
1094                 if (!patchModules.isEmpty()) {
1095                     for (String mod : patchModules) {
1096                         if (sb.length() > 0) {
1097                             sb.append(", ");
1098                         }
1099                         // use 'invalid' separator to ensure values are transformed
1100                         sb.append(mod);
1101                     }
1102 
1103                     jpmsLines.add("--patch-module");
1104                     jpmsLines.add(patchModule + sb.toString());
1105                 }
1106             }
1107         }
1108 
1109         if (!jpmsLines.isEmpty()) {
1110             Path jpmsArgs = Paths.get(getOutputDirectory().getAbsolutePath(), "META-INF/jpms.args");
1111             try {
1112                 Files.createDirectories(jpmsArgs.getParent());
1113 
1114                 Files.write(jpmsArgs, jpmsLines, Charset.defaultCharset());
1115             } catch (IOException e) {
1116                 getLog().warn(e.getMessage());
1117             }
1118         }
1119 
1120         // ----------------------------------------------------------------------
1121         // Compile!
1122         // ----------------------------------------------------------------------
1123 
1124         if (StringUtils.isEmpty(compilerConfiguration.getSourceEncoding())) {
1125             getLog().warn("File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
1126                     + ", i.e. build is platform dependent!");
1127         }
1128 
1129         CompilerResult compilerResult;
1130 
1131         if (useIncrementalCompilation) {
1132             incrementalBuildHelperRequest.outputDirectory(getOutputDirectory());
1133 
1134             incrementalBuildHelper.beforeRebuildExecution(incrementalBuildHelperRequest);
1135 
1136             getLog().debug("incrementalBuildHelper#beforeRebuildExecution");
1137         }
1138 
1139         try {
1140             compilerResult = compiler.performCompile(compilerConfiguration);
1141         } catch (Exception e) {
1142             // TODO: don't catch Exception
1143             throw new MojoExecutionException("Fatal error compiling", e);
1144         }
1145 
1146         if (createMissingPackageInfoClass
1147                 && compilerResult.isSuccess()
1148                 && compiler.getCompilerOutputStyle() == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1149             try {
1150                 SourceMapping sourceMapping = getSourceMapping(compilerConfiguration, compiler);
1151                 createMissingPackageInfoClasses(compilerConfiguration, sourceMapping, sources);
1152             } catch (Exception e) {
1153                 getLog().warn("Error creating missing package info classes", e);
1154             }
1155         }
1156 
1157         if (useIncrementalCompilation) {
1158             if (incrementalBuildHelperRequest.getOutputDirectory().exists()) {
1159                 getLog().debug("incrementalBuildHelper#afterRebuildExecution");
1160                 // now scan the same directory again and create a diff
1161                 incrementalBuildHelper.afterRebuildExecution(incrementalBuildHelperRequest);
1162             } else {
1163                 getLog().debug(
1164                                 "skip incrementalBuildHelper#afterRebuildExecution as the output directory doesn't exist");
1165             }
1166         }
1167 
1168         List<CompilerMessage> warnings = new ArrayList<>();
1169         List<CompilerMessage> errors = new ArrayList<>();
1170         List<CompilerMessage> others = new ArrayList<>();
1171         for (CompilerMessage message : compilerResult.getCompilerMessages()) {
1172             if (message.getKind() == CompilerMessage.Kind.ERROR) {
1173                 errors.add(message);
1174             } else if (message.getKind() == CompilerMessage.Kind.WARNING
1175                     || message.getKind() == CompilerMessage.Kind.MANDATORY_WARNING) {
1176                 warnings.add(message);
1177             } else {
1178                 others.add(message);
1179             }
1180         }
1181 
1182         if (failOnError && !compilerResult.isSuccess()) {
1183             for (CompilerMessage message : others) {
1184                 assert message.getKind() != CompilerMessage.Kind.ERROR
1185                         && message.getKind() != CompilerMessage.Kind.WARNING
1186                         && message.getKind() != CompilerMessage.Kind.MANDATORY_WARNING;
1187                 getLog().info(message.toString());
1188             }
1189             if (!warnings.isEmpty()) {
1190                 getLog().info("-------------------------------------------------------------");
1191                 getLog().warn("COMPILATION WARNING : ");
1192                 getLog().info("-------------------------------------------------------------");
1193                 for (CompilerMessage warning : warnings) {
1194                     getLog().warn(warning.toString());
1195                 }
1196                 getLog().info(warnings.size() + ((warnings.size() > 1) ? " warnings " : " warning"));
1197                 getLog().info("-------------------------------------------------------------");
1198             }
1199 
1200             if (!errors.isEmpty()) {
1201                 getLog().info("-------------------------------------------------------------");
1202                 getLog().error("COMPILATION ERROR : ");
1203                 getLog().info("-------------------------------------------------------------");
1204                 for (CompilerMessage error : errors) {
1205                     getLog().error(error.toString());
1206                 }
1207                 getLog().info(errors.size() + ((errors.size() > 1) ? " errors " : " error"));
1208                 getLog().info("-------------------------------------------------------------");
1209             }
1210 
1211             if (!errors.isEmpty()) {
1212                 throw new CompilationFailureException(errors);
1213             } else {
1214                 throw new CompilationFailureException(warnings);
1215             }
1216         } else {
1217             for (CompilerMessage message : compilerResult.getCompilerMessages()) {
1218                 switch (message.getKind()) {
1219                     case NOTE:
1220                     case OTHER:
1221                         getLog().info(message.toString());
1222                         break;
1223 
1224                     case ERROR:
1225                         getLog().error(message.toString());
1226                         break;
1227 
1228                     case MANDATORY_WARNING:
1229                     case WARNING:
1230                     default:
1231                         getLog().warn(message.toString());
1232                         break;
1233                 }
1234             }
1235         }
1236     }
1237 
1238     private void createMissingPackageInfoClasses(
1239             CompilerConfiguration compilerConfiguration, SourceMapping sourceMapping, Set<File> sources)
1240             throws InclusionScanException, IOException {
1241         for (File source : sources) {
1242             String path = source.toString();
1243             if (path.endsWith(File.separator + "package-info.java")) {
1244                 for (String root : getCompileSourceRoots()) {
1245                     root = root + File.separator;
1246                     if (path.startsWith(root)) {
1247                         String rel = path.substring(root.length());
1248                         Set<File> files = sourceMapping.getTargetFiles(getOutputDirectory(), rel);
1249                         for (File file : files) {
1250                             if (!file.exists()) {
1251                                 File parentFile = file.getParentFile();
1252 
1253                                 if (!parentFile.exists()) {
1254                                     Files.createDirectories(parentFile.toPath());
1255                                 }
1256 
1257                                 byte[] bytes = generatePackage(compilerConfiguration, rel);
1258                                 Files.write(file.toPath(), bytes);
1259                             }
1260                         }
1261                     }
1262                 }
1263             }
1264         }
1265     }
1266 
1267     private byte[] generatePackage(CompilerConfiguration compilerConfiguration, String javaFile) {
1268         int version = getOpcode(compilerConfiguration);
1269         String internalPackageName = javaFile.substring(0, javaFile.length() - ".java".length());
1270         if (File.separatorChar != '/') {
1271             internalPackageName = internalPackageName.replace(File.separatorChar, '/');
1272         }
1273         ClassWriter cw = new ClassWriter(0);
1274         cw.visit(
1275                 version,
1276                 Opcodes.ACC_SYNTHETIC | Opcodes.ACC_ABSTRACT | Opcodes.ACC_INTERFACE,
1277                 internalPackageName,
1278                 null,
1279                 "java/lang/Object",
1280                 null);
1281         cw.visitSource("package-info.java", null);
1282         return cw.toByteArray();
1283     }
1284 
1285     private int getOpcode(CompilerConfiguration compilerConfiguration) {
1286         String version = compilerConfiguration.getReleaseVersion();
1287         if (version == null) {
1288             version = compilerConfiguration.getTargetVersion();
1289             if (version == null) {
1290                 version = "1.5";
1291             }
1292         }
1293         if (version.startsWith("1.")) {
1294             version = version.substring(2);
1295         }
1296         int iVersion = Integer.parseInt(version);
1297         if (iVersion < 2) {
1298             throw new IllegalArgumentException("Unsupported java version '" + version + "'");
1299         }
1300         return iVersion - 2 + Opcodes.V1_2;
1301     }
1302 
1303     protected boolean isTestCompile() {
1304         return false;
1305     }
1306 
1307     /**
1308      * @return all source files for the compiler
1309      */
1310     private Set<File> getCompileSources(Compiler compiler, CompilerConfiguration compilerConfiguration)
1311             throws MojoExecutionException, CompilerException {
1312         String inputFileEnding = compiler.getInputFileEnding(compilerConfiguration);
1313         if (StringUtils.isEmpty(inputFileEnding)) {
1314             // see MCOMPILER-199 GroovyEclipseCompiler doesn't set inputFileEnding
1315             // so we can presume it's all files from the source directory
1316             inputFileEnding = ".*";
1317         }
1318         SourceInclusionScanner scanner = getSourceInclusionScanner(inputFileEnding);
1319 
1320         SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
1321 
1322         scanner.addSourceMapping(mapping);
1323 
1324         Set<File> compileSources = new HashSet<>();
1325 
1326         for (String sourceRoot : getCompileSourceRoots()) {
1327             File rootFile = new File(sourceRoot);
1328 
1329             if (!rootFile.isDirectory()
1330                     || rootFile.getAbsoluteFile().equals(compilerConfiguration.getGeneratedSourcesDirectory())) {
1331                 continue;
1332             }
1333 
1334             try {
1335                 compileSources.addAll(scanner.getIncludedSources(rootFile, null));
1336             } catch (InclusionScanException e) {
1337                 throw new MojoExecutionException(
1338                         "Error scanning source root: '" + sourceRoot + "' for stale files to recompile.", e);
1339             }
1340         }
1341 
1342         return compileSources;
1343     }
1344 
1345     protected abstract Set<String> getIncludes();
1346 
1347     protected abstract Set<String> getExcludes();
1348 
1349     /**
1350      * @param compilerConfiguration
1351      * @param compiler
1352      * @return <code>true</code> if at least a single source file is newer than it's class file
1353      */
1354     private boolean isSourceChanged(CompilerConfiguration compilerConfiguration, Compiler compiler)
1355             throws CompilerException, MojoExecutionException {
1356         Set<File> staleSources =
1357                 computeStaleSources(compilerConfiguration, compiler, getSourceInclusionScanner(staleMillis));
1358 
1359         if (getLog().isDebugEnabled() || showCompilationChanges) {
1360             for (File f : staleSources) {
1361                 if (showCompilationChanges) {
1362                     getLog().info("Stale source detected: " + f.getAbsolutePath());
1363                 } else {
1364                     getLog().debug("Stale source detected: " + f.getAbsolutePath());
1365                 }
1366             }
1367         }
1368         return !staleSources.isEmpty();
1369     }
1370 
1371     /**
1372      * try to get thread count if a Maven 3 build, using reflection as the plugin must not be maven3 api dependent
1373      *
1374      * @return number of thread for this build or 1 if not multi-thread build
1375      */
1376     protected int getRequestThreadCount() {
1377         return session.getRequest().getDegreeOfConcurrency();
1378     }
1379 
1380     protected Date getBuildStartTime() {
1381         MavenExecutionRequest request = session.getRequest();
1382         Date buildStartTime = request == null ? new Date() : request.getStartTime();
1383         return buildStartTime == null ? new Date() : buildStartTime;
1384     }
1385 
1386     private String getMemoryValue(String setting) {
1387         String value = null;
1388 
1389         // Allow '128' or '128m'
1390         if (isDigits(setting)) {
1391             value = setting + "m";
1392         } else if ((isDigits(setting.substring(0, setting.length() - 1)))
1393                 && (setting.toLowerCase().endsWith("m"))) {
1394             value = setting;
1395         }
1396         return value;
1397     }
1398 
1399     // TODO remove the part with ToolchainManager lookup once we depend on
1400     // 3.0.9 (have it as prerequisite). Define as regular component field then.
1401     protected final Toolchain getToolchain() {
1402         Toolchain tc = null;
1403 
1404         if (jdkToolchain != null) {
1405             // Maven 3.3.1 has plugin execution scoped Toolchain Support
1406             try {
1407                 Method getToolchainsMethod = toolchainManager
1408                         .getClass()
1409                         .getMethod("getToolchains", MavenSession.class, String.class, Map.class);
1410 
1411                 @SuppressWarnings("unchecked")
1412                 List<Toolchain> tcs =
1413                         (List<Toolchain>) getToolchainsMethod.invoke(toolchainManager, session, "jdk", jdkToolchain);
1414 
1415                 if (tcs != null && !tcs.isEmpty()) {
1416                     tc = tcs.get(0);
1417                 }
1418             } catch (NoSuchMethodException
1419                     | SecurityException
1420                     | IllegalAccessException
1421                     | IllegalArgumentException
1422                     | InvocationTargetException e) {
1423                 // ignore
1424             }
1425         }
1426 
1427         if (tc == null) {
1428             tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
1429         }
1430 
1431         return tc;
1432     }
1433 
1434     private boolean isDigits(String string) {
1435         for (int i = 0; i < string.length(); i++) {
1436             if (!Character.isDigit(string.charAt(i))) {
1437                 return false;
1438             }
1439         }
1440         return true;
1441     }
1442 
1443     private Set<File> computeStaleSources(
1444             CompilerConfiguration compilerConfiguration, Compiler compiler, SourceInclusionScanner scanner)
1445             throws MojoExecutionException, CompilerException {
1446         SourceMapping mapping = getSourceMapping(compilerConfiguration, compiler);
1447 
1448         File outputDirectory;
1449         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1450         if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) {
1451             outputDirectory = buildDirectory;
1452         } else {
1453             outputDirectory = getOutputDirectory();
1454         }
1455 
1456         scanner.addSourceMapping(mapping);
1457 
1458         Set<File> staleSources = new HashSet<>();
1459 
1460         for (String sourceRoot : getCompileSourceRoots()) {
1461             File rootFile = new File(sourceRoot);
1462 
1463             if (!rootFile.isDirectory()) {
1464                 continue;
1465             }
1466 
1467             try {
1468                 staleSources.addAll(scanner.getIncludedSources(rootFile, outputDirectory));
1469             } catch (InclusionScanException e) {
1470                 throw new MojoExecutionException(
1471                         "Error scanning source root: \'" + sourceRoot + "\' for stale files to recompile.", e);
1472             }
1473         }
1474 
1475         return staleSources;
1476     }
1477 
1478     private SourceMapping getSourceMapping(CompilerConfiguration compilerConfiguration, Compiler compiler)
1479             throws CompilerException, MojoExecutionException {
1480         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
1481 
1482         SourceMapping mapping;
1483         if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE) {
1484             mapping = new SuffixMapping(
1485                     compiler.getInputFileEnding(compilerConfiguration),
1486                     compiler.getOutputFileEnding(compilerConfiguration));
1487         } else if (outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES) {
1488             mapping = new SingleTargetSourceMapping(
1489                     compiler.getInputFileEnding(compilerConfiguration), compiler.getOutputFile(compilerConfiguration));
1490 
1491         } else {
1492             throw new MojoExecutionException("Unknown compiler output style: '" + outputStyle + "'.");
1493         }
1494         return mapping;
1495     }
1496 
1497     /**
1498      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
1499      * be calculated continuously - or should the plugins accept empty source roots as is?
1500      */
1501     private static List<String> removeEmptyCompileSourceRoots(List<String> compileSourceRootsList) {
1502         List<String> newCompileSourceRootsList = new ArrayList<>();
1503         if (compileSourceRootsList != null) {
1504             // copy as I may be modifying it
1505             for (String srcDir : compileSourceRootsList) {
1506                 if (!newCompileSourceRootsList.contains(srcDir) && new File(srcDir).exists()) {
1507                     newCompileSourceRootsList.add(srcDir);
1508                 }
1509             }
1510         }
1511         return newCompileSourceRootsList;
1512     }
1513 
1514     /**
1515      * We just compare the timestamps of all local dependency files (inter-module dependency classpath) and the own
1516      * generated classes and if we got a file which is &gt;= the build-started timestamp, then we caught a file which
1517      * got changed during this build.
1518      *
1519      * @return <code>true</code> if at least one single dependency has changed.
1520      */
1521     protected boolean isDependencyChanged() {
1522         if (session == null) {
1523             // we just cannot determine it, so don't do anything beside logging
1524             getLog().info("Cannot determine build start date, skipping incremental build detection.");
1525             return false;
1526         }
1527 
1528         if (fileExtensions == null || fileExtensions.isEmpty()) {
1529             fileExtensions = Collections.unmodifiableList(Arrays.asList("class", "jar"));
1530         }
1531 
1532         Date buildStartTime = getBuildStartTime();
1533 
1534         List<String> pathElements = new ArrayList<>();
1535         pathElements.addAll(getClasspathElements());
1536         pathElements.addAll(getModulepathElements());
1537 
1538         for (String pathElement : pathElements) {
1539             File artifactPath = new File(pathElement);
1540             if (artifactPath.isDirectory() || artifactPath.isFile()) {
1541                 if (!artifactPath.equals(getOutputDirectory()) && hasNewFile(artifactPath, buildStartTime)) {
1542                     if (showCompilationChanges) {
1543                         getLog().info("New dependency detected: " + artifactPath.getAbsolutePath());
1544                     } else {
1545                         getLog().debug("New dependency detected: " + artifactPath.getAbsolutePath());
1546                     }
1547                     return true;
1548                 }
1549             }
1550         }
1551 
1552         // obviously there was no new file detected.
1553         return false;
1554     }
1555 
1556     /**
1557      * @param classPathEntry entry to check
1558      * @param buildStartTime time build start
1559      * @return if any changes occurred
1560      */
1561     private boolean hasNewFile(File classPathEntry, Date buildStartTime) {
1562         if (!classPathEntry.exists()) {
1563             return false;
1564         }
1565 
1566         if (classPathEntry.isFile()) {
1567             return classPathEntry.lastModified() >= buildStartTime.getTime()
1568                     && fileExtensions.contains(FileUtils.getExtension(classPathEntry.getName()));
1569         }
1570 
1571         File[] children = classPathEntry.listFiles();
1572 
1573         for (File child : children) {
1574             if (hasNewFile(child, buildStartTime)) {
1575                 return true;
1576             }
1577         }
1578 
1579         return false;
1580     }
1581 
1582     private List<String> resolveProcessorPathEntries() throws MojoExecutionException {
1583         if (annotationProcessorPaths == null || annotationProcessorPaths.isEmpty()) {
1584             return null;
1585         }
1586 
1587         Set<String> elements = new LinkedHashSet<>();
1588         try {
1589             List<Dependency> dependencies = convertToDependencies(annotationProcessorPaths);
1590             CollectRequest collectRequest =
1591                     new CollectRequest(dependencies, Collections.emptyList(), project.getRemoteProjectRepositories());
1592             DependencyRequest dependencyRequest = new DependencyRequest();
1593             dependencyRequest.setCollectRequest(collectRequest);
1594             DependencyResult dependencyResult =
1595                     repositorySystem.resolveDependencies(session.getRepositorySession(), dependencyRequest);
1596 
1597             for (ArtifactResult resolved : dependencyResult.getArtifactResults()) {
1598                 elements.add(resolved.getArtifact().getFile().getAbsolutePath());
1599             }
1600             return new ArrayList<>(elements);
1601         } catch (Exception e) {
1602             throw new MojoExecutionException(
1603                     "Resolution of annotationProcessorPath dependencies failed: " + e.getLocalizedMessage(), e);
1604         }
1605     }
1606 
1607     private List<Dependency> convertToDependencies(List<DependencyCoordinate> annotationProcessorPaths) {
1608         List<Dependency> dependencies = new ArrayList<>();
1609         for (DependencyCoordinate annotationProcessorPath : annotationProcessorPaths) {
1610             ArtifactHandler handler = artifactHandlerManager.getArtifactHandler(annotationProcessorPath.getType());
1611             Artifact artifact = new DefaultArtifact(
1612                     annotationProcessorPath.getGroupId(),
1613                     annotationProcessorPath.getArtifactId(),
1614                     annotationProcessorPath.getClassifier(),
1615                     handler.getExtension(),
1616                     annotationProcessorPath.getVersion());
1617             Set<Exclusion> exclusions = convertToAetherExclusions(annotationProcessorPath.getExclusions());
1618             dependencies.add(new Dependency(artifact, JavaScopes.RUNTIME, false, exclusions));
1619         }
1620         return dependencies;
1621     }
1622 
1623     private Set<Exclusion> convertToAetherExclusions(Set<DependencyExclusion> exclusions) {
1624         if (exclusions == null || exclusions.isEmpty()) {
1625             return Collections.emptySet();
1626         }
1627         Set<Exclusion> aetherExclusions = new HashSet<>();
1628         for (DependencyExclusion exclusion : exclusions) {
1629             Exclusion aetherExclusion = new Exclusion(
1630                     exclusion.getGroupId(),
1631                     exclusion.getArtifactId(),
1632                     exclusion.getClassifier(),
1633                     exclusion.getExtension());
1634             aetherExclusions.add(aetherExclusion);
1635         }
1636         return aetherExclusions;
1637     }
1638 
1639     private void writePlugin(MessageBuilder mb) {
1640         mb.a("    <plugin>").newline();
1641         mb.a("      <groupId>org.apache.maven.plugins</groupId>").newline();
1642         mb.a("      <artifactId>maven-compiler-plugin</artifactId>").newline();
1643 
1644         String version = getMavenCompilerPluginVersion();
1645         if (version != null) {
1646             mb.a("      <version>").a(version).a("</version>").newline();
1647         }
1648         writeConfig(mb);
1649 
1650         mb.a("    </plugin>").newline();
1651     }
1652 
1653     private void writeConfig(MessageBuilder mb) {
1654         mb.a("      <configuration>").newline();
1655 
1656         if (release != null) {
1657             mb.a("        <release>").a(release).a("</release>").newline();
1658         } else if (JavaVersion.JAVA_VERSION.isAtLeast("9")) {
1659             String rls = target.replaceAll(".\\.", "");
1660             // when using Java9+, motivate to use release instead of source/target
1661             mb.a("        <release>").a(rls).a("</release>").newline();
1662         } else {
1663             mb.a("        <source>").a(source).a("</source>").newline();
1664             mb.a("        <target>").a(target).a("</target>").newline();
1665         }
1666         mb.a("      </configuration>").newline();
1667     }
1668 
1669     private String getMavenCompilerPluginVersion() {
1670         Properties pomProperties = new Properties();
1671 
1672         try (InputStream is = AbstractCompilerMojo.class.getResourceAsStream(
1673                 "/META-INF/maven/org.apache.maven.plugins/maven-compiler-plugin/pom.properties")) {
1674             if (is != null) {
1675                 pomProperties.load(is);
1676             }
1677         } catch (IOException e) {
1678             // noop
1679         }
1680 
1681         return pomProperties.getProperty("version");
1682     }
1683 
1684     private DirectoryScanResult computeInputFileTreeChanges(IncrementalBuildHelper ibh, Set<File> inputFiles)
1685             throws MojoExecutionException {
1686         File mojoConfigBase = ibh.getMojoStatusDirectory();
1687         File mojoConfigFile = new File(mojoConfigBase, INPUT_FILES_LST_FILENAME);
1688 
1689         String[] oldInputFiles = new String[0];
1690 
1691         if (mojoConfigFile.exists()) {
1692             try {
1693                 oldInputFiles = FileUtils.fileReadArray(mojoConfigFile);
1694             } catch (IOException e) {
1695                 throw new MojoExecutionException("Error reading old mojo status " + mojoConfigFile, e);
1696             }
1697         }
1698 
1699         String[] inputFileNames = inputFiles.stream().map(File::getAbsolutePath).toArray(String[]::new);
1700 
1701         DirectoryScanResult dsr = DirectoryScanner.diffFiles(oldInputFiles, inputFileNames);
1702 
1703         try {
1704             FileUtils.fileWriteArray(mojoConfigFile, inputFileNames);
1705         } catch (IOException e) {
1706             throw new MojoExecutionException("Error while storing the mojo status", e);
1707         }
1708 
1709         return dsr;
1710     }
1711 
1712     private boolean hasInputFileTreeChanged(DirectoryScanResult dsr) {
1713         return (dsr.getFilesAdded().length > 0 || dsr.getFilesRemoved().length > 0);
1714     }
1715 
1716     public void setTarget(String target) {
1717         this.target = target;
1718         targetOrReleaseSet = true;
1719     }
1720 
1721     public void setRelease(String release) {
1722         this.release = release;
1723         targetOrReleaseSet = true;
1724     }
1725 
1726     final String getImplicit() {
1727         return implicit;
1728     }
1729 }