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