View Javadoc

1   package org.apache.maven.plugin;
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 org.apache.maven.execution.MavenSession;
23  import org.apache.maven.toolchain.Toolchain;
24  import org.apache.maven.toolchain.ToolchainManager;
25  import org.codehaus.plexus.compiler.Compiler;
26  import org.codehaus.plexus.compiler.CompilerConfiguration;
27  import org.codehaus.plexus.compiler.CompilerError;
28  import org.codehaus.plexus.compiler.CompilerException;
29  import org.codehaus.plexus.compiler.CompilerOutputStyle;
30  import org.codehaus.plexus.compiler.manager.CompilerManager;
31  import org.codehaus.plexus.compiler.manager.NoSuchCompilerException;
32  import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
33  import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
34  import org.codehaus.plexus.compiler.util.scan.mapping.SingleTargetSourceMapping;
35  import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
36  import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
37  import org.codehaus.plexus.util.ReaderFactory;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  import java.io.File;
41  import java.util.ArrayList;
42  import java.util.HashSet;
43  import java.util.LinkedHashMap;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Set;
47  
48  /**
49   * TODO: At least one step could be optimized, currently the plugin will do two
50   * scans of all the source code if the compiler has to have the entire set of
51   * sources. This is currently the case for at least the C# compiler and most
52   * likely all the other .NET compilers too.
53   *
54   * @author others
55   * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
56   * @version $Id: AbstractCompilerMojo.html 816619 2012-05-08 13:09:26Z hboutemy $
57   * @since 2.0
58   */
59  public abstract class AbstractCompilerMojo
60      extends AbstractMojo
61  {
62      // ----------------------------------------------------------------------
63      // Configurables
64      // ----------------------------------------------------------------------
65  
66      /**
67       * Indicates whether the build will continue even if there are compilation errors.
68       *
69       * @parameter expression="${maven.compiler.failOnError}" default-value="true"
70       * @since 2.0.2
71       */
72      private boolean failOnError = true;
73  
74      /**
75       * Set to <code>true</code> to include debugging information in the compiled class files.
76       *
77       * @parameter expression="${maven.compiler.debug}" default-value="true"
78       */
79      private boolean debug = true;
80  
81      /**
82       * Set to <code>true</code> to show messages about what the compiler is doing.
83       *
84       * @parameter expression="${maven.compiler.verbose}" default-value="false"
85       */
86      private boolean verbose;
87  
88      /**
89       * Sets whether to show source locations where deprecated APIs are used.
90       *
91       * @parameter expression="${maven.compiler.showDeprecation}" default-value="false"
92       */
93      private boolean showDeprecation;
94  
95      /**
96       * Set to <code>true</code> to optimize the compiled code using the compiler's optimization methods.
97       *
98       * @parameter expression="${maven.compiler.optimize}" default-value="false"
99       */
100     private boolean optimize;
101 
102     /**
103      * Set to <code>true</code> to show compilation warnings.
104      *
105      * @parameter expression="${maven.compiler.showWarnings}" default-value="false"
106      */
107     private boolean showWarnings;
108 
109     /**
110      * The -source argument for the Java compiler.
111      *
112      * @parameter expression="${maven.compiler.source}" default-value="1.5"
113      */
114     protected String source;
115 
116     /**
117      * The -target argument for the Java compiler.
118      *
119      * @parameter expression="${maven.compiler.target}" default-value="1.5"
120      */
121     protected String target;
122 
123     /**
124      * The -encoding argument for the Java compiler.
125      *
126      * @parameter expression="${encoding}" default-value="${project.build.sourceEncoding}"
127      * @since 2.1
128      */
129     private String encoding;
130 
131     /**
132      * Sets the granularity in milliseconds of the last modification
133      * date for testing whether a source needs recompilation.
134      *
135      * @parameter expression="${lastModGranularityMs}" default-value="0"
136      */
137     private int staleMillis;
138 
139     /**
140      * The compiler id of the compiler to use. See this
141      * <a href="non-javac-compilers.html">guide</a> for more information.
142      *
143      * @parameter expression="${maven.compiler.compilerId}" default-value="javac"
144      */
145     private String compilerId;
146 
147     /**
148      * Version of the compiler to use, ex. "1.3", "1.5", if {@link #fork} is set to <code>true</code>.
149      *
150      * @parameter expression="${maven.compiler.compilerVersion}"
151      */
152     private String compilerVersion;
153 
154     /**
155      * Allows running the compiler in a separate process.
156      * If <code>false</code> it uses the built in compiler, while if <code>true</code> it will use an executable.
157      *
158      * @parameter expression="${maven.compiler.fork}" default-value="false"
159      */
160     private boolean fork;
161 
162     /**
163      * Initial size, in megabytes, of the memory allocation pool, ex. "64", "64m"
164      * if {@link #fork} is set to <code>true</code>.
165      *
166      * @parameter expression="${maven.compiler.meminitial}"
167      * @since 2.0.1
168      */
169     private String meminitial;
170 
171     /**
172      * Sets the maximum size, in megabytes, of the memory allocation pool, ex. "128", "128m"
173      * if {@link #fork} is set to <code>true</code>.
174      *
175      * @parameter expression="${maven.compiler.maxmem}"
176      * @since 2.0.1
177      */
178     private String maxmem;
179 
180     /**
181      * Sets the executable of the compiler to use when {@link #fork} is <code>true</code>.
182      *
183      * @parameter expression="${maven.compiler.executable}"
184      */
185     private String executable;
186 
187     /**
188      * <p>
189      * Sets whether annotation processing is performed or not. Only applies to JDK 1.6+
190      * If not set, both compilation and annotation processing are performed at the same time.
191      * </p>
192      * <p>
193      * Allowed values are:
194      * none - no annotation processing is performed.
195      * only - only annotation processing is done, no compilation.
196      * </p>
197      *
198      * @parameter
199      * @since 2.2
200      */
201     private String proc;
202 
203     /**
204      * <p>
205      * Names of annotation processors to run. Only applies to JDK 1.6+
206      * If not set, the default annotation processors discovery process applies.
207      * </p>
208      *
209      * @parameter
210      * @since 2.2
211      */
212     private String[] annotationProcessors;
213 
214     /**
215      * <p>
216      * Sets the arguments to be passed to the compiler (prepending a dash) if {@link #fork} is set to <code>true</code>.
217      * </p>
218      * <p>
219      * This is because the list of valid arguments passed to a Java compiler
220      * varies based on the compiler version.
221      * </p>
222      * <p>
223      * To pass <code>-Xmaxerrs 1000 -Xlint -Xlint:-path -Averbose=true</code> you should include the following:
224      * </p>
225      * <pre>
226      * &lt;compilerArguments&gt;
227      *   &lt;Xmaxerrs&gt;1000&lt;/Xmaxerrs&gt;
228      *   &lt;Xlint/&gt;
229      *   &lt;Xlint:-path/&gt;
230      *   &lt;Averbose&gt;true&lt;/Averbose&gt;
231      * &lt;/compilerArguments&gt;
232      * </pre>
233      *
234      * @parameter
235      * @since 2.0.1
236      */
237     protected Map<String, String> compilerArguments;
238 
239     /**
240      * <p>
241      * Sets the unformatted single argument string to be passed to the compiler if {@link #fork} is set to <code>true</code>.
242      * To pass multiple arguments such as <code>-Xmaxerrs 1000</code> (which are actually two arguments) you have to use {@link #compilerArguments}.
243      * </p>
244      * <p>
245      * This is because the list of valid arguments passed to a Java compiler
246      * varies based on the compiler version.
247      * </p>
248      *
249      * @parameter
250      */
251     protected String compilerArgument;
252 
253     /**
254      * Sets the name of the output file when compiling a set of
255      * sources to a single file.
256      *
257      * @parameter expression="${project.build.finalName}"
258      */
259     private String outputFileName;
260 
261     /**
262      * Keyword list to be appended to the -g  command-line switch. Legal values are none or a comma-separated list of the following keywords: lines, vars, and source.
263      * If debuglevel is not specified, by default, nothing will be appended to -g. If debug is not turned on, this attribute will be ignored.
264      *
265      * @parameter expression="${maven.compiler.debuglevel}"
266      * @since 2.1
267      */
268     private String debuglevel;
269 
270     /**
271      * @component
272      */
273     private ToolchainManager toolchainManager;
274 
275     // ----------------------------------------------------------------------
276     // Read-only parameters
277     // ----------------------------------------------------------------------
278 
279     /**
280      * The directory to run the compiler from if fork is true.
281      *
282      * @parameter default-value="${basedir}"
283      * @required
284      * @readonly
285      */
286     private File basedir;
287 
288     /**
289      * The target directory of the compiler if fork is true.
290      *
291      * @parameter default-value="${project.build.directory}"
292      * @required
293      * @readonly
294      */
295     private File buildDirectory;
296 
297     /**
298      * Plexus compiler manager.
299      *
300      * @component
301      */
302     private CompilerManager compilerManager;
303 
304     /**
305      * The current build session instance. This is used for
306      * toolchain manager API calls.
307      *
308      * @parameter default-value="${session}"
309      * @required
310      * @readonly
311      */
312     private MavenSession session;
313 
314     protected abstract SourceInclusionScanner getSourceInclusionScanner( int staleMillis );
315 
316     protected abstract SourceInclusionScanner getSourceInclusionScanner( String inputFileEnding );
317 
318     protected abstract List<String> getClasspathElements();
319 
320     protected abstract List<String> getCompileSourceRoots();
321 
322     protected abstract File getOutputDirectory();
323 
324     protected abstract String getSource();
325 
326     protected abstract String getTarget();
327 
328     protected abstract String getCompilerArgument();
329 
330     protected abstract Map<String, String> getCompilerArguments();
331 
332     protected abstract File getGeneratedSourcesDirectory();
333 
334     @SuppressWarnings( "unchecked" )
335     public void execute()
336         throws MojoExecutionException, CompilationFailureException
337     {
338         // ----------------------------------------------------------------------
339         // Look up the compiler. This is done before other code than can
340         // cause the mojo to return before the lookup is done possibly resulting
341         // in misconfigured POMs still building.
342         // ----------------------------------------------------------------------
343 
344         Compiler compiler;
345 
346         getLog().debug( "Using compiler '" + compilerId + "'." );
347 
348         try
349         {
350             compiler = compilerManager.getCompiler( compilerId );
351         }
352         catch ( NoSuchCompilerException e )
353         {
354             throw new MojoExecutionException( "No such compiler '" + e.getCompilerId() + "'." );
355         }
356 
357         //-----------toolchains start here ----------------------------------
358         //use the compilerId as identifier for toolchains as well.
359         Toolchain tc = getToolchain();
360         if ( tc != null )
361         {
362             getLog().info( "Toolchain in compiler-plugin: " + tc );
363             if ( executable != null )
364             {
365                 getLog().warn( "Toolchains are ignored, 'executable' parameter is set to " + executable );
366             }
367             else
368             {
369                 fork = true;
370                 //TODO somehow shaky dependency between compilerId and tool executable.
371                 executable = tc.findTool( compilerId );
372             }
373         }
374         // ----------------------------------------------------------------------
375         //
376         // ----------------------------------------------------------------------
377 
378         List<String> compileSourceRoots = removeEmptyCompileSourceRoots( getCompileSourceRoots() );
379 
380         if ( compileSourceRoots.isEmpty() )
381         {
382             getLog().info( "No sources to compile" );
383 
384             return;
385         }
386 
387         if ( getLog().isDebugEnabled() )
388         {
389             getLog().debug( "Source directories: " + compileSourceRoots.toString().replace( ',', '\n' ) );
390             getLog().debug( "Classpath: " + getClasspathElements().toString().replace( ',', '\n' ) );
391             getLog().debug( "Output directory: " + getOutputDirectory() );
392         }
393 
394         // ----------------------------------------------------------------------
395         // Create the compiler configuration
396         // ----------------------------------------------------------------------
397 
398         CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
399 
400         compilerConfiguration.setOutputLocation( getOutputDirectory().getAbsolutePath() );
401 
402         compilerConfiguration.setClasspathEntries( getClasspathElements() );
403 
404         compilerConfiguration.setSourceLocations( compileSourceRoots );
405 
406         compilerConfiguration.setOptimize( optimize );
407 
408         compilerConfiguration.setDebug( debug );
409 
410         if ( debug && StringUtils.isNotEmpty( debuglevel ) )
411         {
412             String[] split = StringUtils.split( debuglevel, "," );
413             for ( int i = 0; i < split.length; i++ )
414             {
415                 if ( !( split[i].equalsIgnoreCase( "none" ) || split[i].equalsIgnoreCase( "lines" )
416                     || split[i].equalsIgnoreCase( "vars" ) || split[i].equalsIgnoreCase( "source" ) ) )
417                 {
418                     throw new IllegalArgumentException( "The specified debug level: '" + split[i] + "' is unsupported. "
419                                                             + "Legal values are 'none', 'lines', 'vars', and 'source'." );
420                 }
421             }
422             compilerConfiguration.setDebugLevel( debuglevel );
423         }
424 
425         compilerConfiguration.setVerbose( verbose );
426 
427         compilerConfiguration.setShowWarnings( showWarnings );
428 
429         compilerConfiguration.setShowDeprecation( showDeprecation );
430 
431         compilerConfiguration.setSourceVersion( getSource() );
432 
433         compilerConfiguration.setTargetVersion( getTarget() );
434 
435         compilerConfiguration.setProc( proc );
436 
437         compilerConfiguration.setGeneratedSourcesDirectory( getGeneratedSourcesDirectory() );
438 
439         compilerConfiguration.setAnnotationProcessors( annotationProcessors );
440 
441         compilerConfiguration.setSourceEncoding( encoding );
442 
443         Map<String, String> effectiveCompilerArguments = getCompilerArguments();
444 
445         String effectiveCompilerArgument = getCompilerArgument();
446 
447         if ( ( effectiveCompilerArguments != null ) || ( effectiveCompilerArgument != null ) )
448         {
449             LinkedHashMap<String, String> cplrArgsCopy = new LinkedHashMap<String, String>();
450             if ( effectiveCompilerArguments != null )
451             {
452                 for ( Map.Entry<String, String> me : effectiveCompilerArguments.entrySet() )
453                 {
454                     String key = me.getKey();
455                     String value = me.getValue();
456                     if ( !key.startsWith( "-" ) )
457                     {
458                         key = "-" + key;
459                     }
460 
461                     if ( key.startsWith( "-A" ) && StringUtils.isNotEmpty( value ) )
462                     {
463                         cplrArgsCopy.put( key + "=" + value, null );
464                     }
465                     else
466                     {
467                         cplrArgsCopy.put( key, value );
468                     }
469                 }
470             }
471             if ( !StringUtils.isEmpty( effectiveCompilerArgument ) )
472             {
473                 cplrArgsCopy.put( effectiveCompilerArgument, null );
474             }
475             compilerConfiguration.setCustomCompilerArguments( cplrArgsCopy );
476         }
477 
478         compilerConfiguration.setFork( fork );
479 
480         if ( fork )
481         {
482             if ( !StringUtils.isEmpty( meminitial ) )
483             {
484                 String value = getMemoryValue( meminitial );
485 
486                 if ( value != null )
487                 {
488                     compilerConfiguration.setMeminitial( value );
489                 }
490                 else
491                 {
492                     getLog().info( "Invalid value for meminitial '" + meminitial + "'. Ignoring this option." );
493                 }
494             }
495 
496             if ( !StringUtils.isEmpty( maxmem ) )
497             {
498                 String value = getMemoryValue( maxmem );
499 
500                 if ( value != null )
501                 {
502                     compilerConfiguration.setMaxmem( value );
503                 }
504                 else
505                 {
506                     getLog().info( "Invalid value for maxmem '" + maxmem + "'. Ignoring this option." );
507                 }
508             }
509         }
510 
511         compilerConfiguration.setExecutable( executable );
512 
513         compilerConfiguration.setWorkingDirectory( basedir );
514 
515         compilerConfiguration.setCompilerVersion( compilerVersion );
516 
517         compilerConfiguration.setBuildDirectory( buildDirectory );
518 
519         compilerConfiguration.setOutputFileName( outputFileName );
520 
521         // TODO: have an option to always compile (without need to clean)
522         Set<File> staleSources;
523 
524         boolean canUpdateTarget;
525 
526         try
527         {
528             staleSources =
529                 computeStaleSources( compilerConfiguration, compiler, getSourceInclusionScanner( staleMillis ) );
530 
531             canUpdateTarget = compiler.canUpdateTarget( compilerConfiguration );
532 
533             if ( compiler.getCompilerOutputStyle().equals( CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
534                 && !canUpdateTarget )
535             {
536                 getLog().info( "RESCANNING!" );
537                 // TODO: This second scan for source files is sub-optimal
538                 String inputFileEnding = compiler.getInputFileEnding( compilerConfiguration );
539 
540                 Set<File> sources = computeStaleSources( compilerConfiguration, compiler,
541                                                          getSourceInclusionScanner( inputFileEnding ) );
542 
543                 compilerConfiguration.setSourceFiles( sources );
544             }
545             else
546             {
547                 compilerConfiguration.setSourceFiles( staleSources );
548             }
549         }
550         catch ( CompilerException e )
551         {
552             throw new MojoExecutionException( "Error while computing stale sources.", e );
553         }
554 
555         if ( staleSources.isEmpty() )
556         {
557             getLog().info( "Nothing to compile - all classes are up to date" );
558 
559             return;
560         }
561 
562         // ----------------------------------------------------------------------
563         // Dump configuration
564         // ----------------------------------------------------------------------
565 
566         if ( getLog().isDebugEnabled() )
567         {
568             getLog().debug( "Classpath:" );
569 
570             for ( String s : getClasspathElements() )
571             {
572                 getLog().debug( " " + s );
573             }
574 
575             getLog().debug( "Source roots:" );
576 
577             for ( String root : getCompileSourceRoots() )
578             {
579                 getLog().debug( " " + root );
580             }
581 
582             try
583             {
584                 if ( fork )
585                 {
586                     if ( compilerConfiguration.getExecutable() != null )
587                     {
588                         getLog().debug( "Excutable: " );
589                         getLog().debug( " " + compilerConfiguration.getExecutable() );
590                     }
591                 }
592 
593                 String[] cl = compiler.createCommandLine( compilerConfiguration );
594                 if ( cl != null && cl.length > 0 )
595                 {
596                     StringBuilder sb = new StringBuilder();
597                     sb.append( cl[0] );
598                     for ( int i = 1; i < cl.length; i++ )
599                     {
600                         sb.append( " " );
601                         sb.append( cl[i] );
602                     }
603                     getLog().debug( "Command line options:" );
604                     getLog().debug( sb );
605                 }
606             }
607             catch ( CompilerException ce )
608             {
609                 getLog().debug( ce );
610             }
611         }
612 
613         // ----------------------------------------------------------------------
614         // Compile!
615         // ----------------------------------------------------------------------
616 
617         if ( StringUtils.isEmpty( compilerConfiguration.getSourceEncoding() ) )
618         {
619             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
620                                + ", i.e. build is platform dependent!" );
621         }
622 
623         List<CompilerError> messages;
624 
625         try
626         {
627             messages = compiler.compile( compilerConfiguration );
628         }
629         catch ( Exception e )
630         {
631             // TODO: don't catch Exception
632             throw new MojoExecutionException( "Fatal error compiling", e );
633         }
634 
635         List<CompilerError> warnings = new ArrayList<CompilerError>();
636         List<CompilerError> errors = new ArrayList<CompilerError>();
637         if ( messages != null )
638         {
639             for ( CompilerError message : messages )
640             {
641                 if ( message.isError() )
642                 {
643                     errors.add( message );
644                 }
645                 else
646                 {
647                     warnings.add( message );
648                 }
649             }
650         }
651 
652         if ( failOnError && !errors.isEmpty() )
653         {
654             if ( !warnings.isEmpty() )
655             {
656                 getLog().info( "-------------------------------------------------------------" );
657                 getLog().warn( "COMPILATION WARNING : " );
658                 getLog().info( "-------------------------------------------------------------" );
659                 for ( CompilerError warning : warnings )
660                 {
661                     getLog().warn( warning.toString() );
662                 }
663                 getLog().info( warnings.size() + ( ( warnings.size() > 1 ) ? " warnings " : " warning" ) );
664                 getLog().info( "-------------------------------------------------------------" );
665             }
666 
667             getLog().info( "-------------------------------------------------------------" );
668             getLog().error( "COMPILATION ERROR : " );
669             getLog().info( "-------------------------------------------------------------" );
670 
671             for ( CompilerError error : errors )
672             {
673                 getLog().error( error.toString() );
674             }
675             getLog().info( errors.size() + ( ( errors.size() > 1 ) ? " errors " : " error" ) );
676             getLog().info( "-------------------------------------------------------------" );
677 
678             throw new CompilationFailureException( errors );
679         }
680         else
681         {
682             for ( CompilerError message : messages )
683             {
684                 getLog().warn( message.toString() );
685             }
686         }
687     }
688 
689     private String getMemoryValue( String setting )
690     {
691         String value = null;
692 
693         // Allow '128' or '128m'
694         if ( isDigits( setting ) )
695         {
696             value = setting + "m";
697         }
698         else
699         {
700             if ( ( isDigits( setting.substring( 0, setting.length() - 1 ) ) ) && ( setting.toLowerCase().endsWith(
701                 "m" ) ) )
702             {
703                 value = setting;
704             }
705         }
706         return value;
707     }
708 
709     //TODO remove the part with ToolchainManager lookup once we depend on
710     //3.0.9 (have it as prerequisite). Define as regular component field then.
711     private Toolchain getToolchain()
712     {
713         Toolchain tc = null;
714         if ( toolchainManager != null )
715         {
716             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
717         }
718         return tc;
719     }
720 
721     private boolean isDigits( String string )
722     {
723         for ( int i = 0; i < string.length(); i++ )
724         {
725             if ( !Character.isDigit( string.charAt( i ) ) )
726             {
727                 return false;
728             }
729         }
730         return true;
731     }
732 
733     @SuppressWarnings( "unchecked" )
734     private Set<File> computeStaleSources( CompilerConfiguration compilerConfiguration, Compiler compiler,
735                                            SourceInclusionScanner scanner )
736         throws MojoExecutionException, CompilerException
737     {
738         CompilerOutputStyle outputStyle = compiler.getCompilerOutputStyle();
739 
740         SourceMapping mapping;
741 
742         File outputDirectory;
743 
744         if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_PER_INPUT_FILE )
745         {
746             mapping = new SuffixMapping( compiler.getInputFileEnding( compilerConfiguration ),
747                                          compiler.getOutputFileEnding( compilerConfiguration ) );
748 
749             outputDirectory = getOutputDirectory();
750         }
751         else if ( outputStyle == CompilerOutputStyle.ONE_OUTPUT_FILE_FOR_ALL_INPUT_FILES )
752         {
753             mapping = new SingleTargetSourceMapping( compiler.getInputFileEnding( compilerConfiguration ),
754                                                      compiler.getOutputFile( compilerConfiguration ) );
755 
756             outputDirectory = buildDirectory;
757         }
758         else
759         {
760             throw new MojoExecutionException( "Unknown compiler output style: '" + outputStyle + "'." );
761         }
762 
763         scanner.addSourceMapping( mapping );
764 
765         Set<File> staleSources = new HashSet<File>();
766 
767         for ( String sourceRoot : getCompileSourceRoots() )
768         {
769             File rootFile = new File( sourceRoot );
770 
771             if ( !rootFile.isDirectory() )
772             {
773                 continue;
774             }
775 
776             try
777             {
778                 staleSources.addAll( scanner.getIncludedSources( rootFile, outputDirectory ) );
779             }
780             catch ( InclusionScanException e )
781             {
782                 throw new MojoExecutionException(
783                     "Error scanning source root: \'" + sourceRoot + "\' " + "for stale files to recompile.", e );
784             }
785         }
786 
787         return staleSources;
788     }
789 
790     /**
791      * @todo also in ant plugin. This should be resolved at some point so that it does not need to
792      * be calculated continuously - or should the plugins accept empty source roots as is?
793      */
794     private static List<String> removeEmptyCompileSourceRoots( List<String> compileSourceRootsList )
795     {
796         List<String> newCompileSourceRootsList = new ArrayList<String>();
797         if ( compileSourceRootsList != null )
798         {
799             // copy as I may be modifying it
800             for ( String srcDir : compileSourceRootsList )
801             {
802                 if ( !newCompileSourceRootsList.contains( srcDir ) && new File( srcDir ).exists() )
803                 {
804                     newCompileSourceRootsList.add( srcDir );
805                 }
806             }
807         }
808         return newCompileSourceRootsList;
809     }
810 }