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