View Javadoc
1   package org.codehaus.mojo.natives.compiler;
2   
3   /*
4    * The MIT License
5    *
6    * Copyright (c) 2004, The Codehaus
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9    * associated documentation files (the "Software"), to deal in the Software without restriction,
10   * including without limitation the rights to use, copy, modify, merge, publish, distribute,
11   * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in all copies or
15   * substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
18   * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20   * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22   */
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.List;
28  
29  import org.codehaus.mojo.natives.NativeBuildException;
30  import org.codehaus.mojo.natives.SourceDependencyAnalyzer;
31  import org.codehaus.mojo.natives.parser.Parser;
32  import org.codehaus.mojo.natives.util.CommandLineUtil;
33  import org.codehaus.mojo.natives.util.EnvUtil;
34  import org.codehaus.plexus.logging.AbstractLogEnabled;
35  import org.codehaus.plexus.logging.Logger;
36  import org.codehaus.plexus.util.FileUtils;
37  import org.codehaus.plexus.util.Os;
38  import org.codehaus.plexus.util.cli.Commandline;
39  
40  import edu.emory.mathcs.backport.java.util.concurrent.ArrayBlockingQueue;
41  import edu.emory.mathcs.backport.java.util.concurrent.RejectedExecutionException;
42  import edu.emory.mathcs.backport.java.util.concurrent.ThreadPoolExecutor;
43  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
44  
45  public abstract class AbstractCompiler
46      extends AbstractLogEnabled
47      implements Compiler
48  {
49  
50      protected abstract Parser getParser();
51  
52      protected abstract Commandline getCommandLine( File src, File dest, CompilerConfiguration config )
53          throws NativeBuildException;
54  
55      public List compile( CompilerConfiguration config, File[] sourceFiles )
56          throws NativeBuildException
57      {
58          if ( !config.getOutputDirectory().exists() )
59          {
60              config.getOutputDirectory().mkdirs();
61          }
62  
63          List compilerOutputFiles = new ArrayList( sourceFiles.length );
64  
65          CompilerThreadPoolExecutor compilerThreadPoolExecutor = null;
66  
67          if ( config.getNumberOfConcurrentCompilation() > 1 )
68          {
69              compilerThreadPoolExecutor = new CompilerThreadPoolExecutor( config.getNumberOfConcurrentCompilation() );
70          }
71  
72          for ( int i = 0; i < sourceFiles.length; ++i )
73          {
74              File source = sourceFiles[i];
75  
76              File objectFile = getObjectFile( source, config.getOutputDirectory(), config.getObjectFileExtension() );
77  
78              compilerOutputFiles.add( objectFile );
79  
80              Parser parser = this.getParser();
81  
82              if ( SourceDependencyAnalyzer.isStaled( source, objectFile, parser, config.getIncludePaths() ) )
83              {
84                  if ( compilerThreadPoolExecutor != null && compilerThreadPoolExecutor.isErrorFound() )
85                  {
86                      break;
87                  }
88  
89                  Commandline cl = getCommandLine( source, objectFile, config );
90                  EnvUtil.setupCommandlineEnv( cl, config.getEnvFactory() );
91  
92                  if ( compilerThreadPoolExecutor != null )
93                  {
94                      try
95                      {
96                          compilerThreadPoolExecutor.execute( new CompilerRunnable( cl, this.getLogger() ) );
97                      }
98                      catch ( RejectedExecutionException e )
99                      {
100                         CommandLineUtil.execute( cl, this.getLogger() );
101                     }
102                 }
103                 else
104                 {
105                     CommandLineUtil.execute( cl, this.getLogger() );
106                 }
107 
108             }
109             else
110             {
111                 this.getLogger().debug( ( objectFile + " is up to date." ) );
112             }
113         }
114 
115         if ( compilerThreadPoolExecutor != null )
116         {
117             if ( !compilerThreadPoolExecutor.isErrorFound() )
118             {
119                 compilerThreadPoolExecutor.shutdown();
120             }
121 
122             try
123             {
124                 compilerThreadPoolExecutor.awaitTermination( Integer.MAX_VALUE, TimeUnit.SECONDS );
125             }
126             catch ( InterruptedException e )
127             {
128             }
129 
130             if ( compilerThreadPoolExecutor.isErrorFound() )
131             {
132                 throw new NativeBuildException( "Compilation failure detected." );
133             }
134         }
135 
136         return compilerOutputFiles;
137     }
138 
139     /**
140      * return "obj" or "o" when file extension is not given based on current platform
141      * 
142      * @return
143      */
144     protected static String getObjectFileExtension( String fileExtension )
145     {
146         if ( fileExtension != null )
147         {
148             return fileExtension;
149         }
150         else
151         {
152             if ( Os.isFamily( "windows" ) )
153             {
154                 return "obj";
155             }
156             else
157             {
158                 return "o";
159             }
160         }
161     }
162 
163     /**
164      * Figure out the object file relative path from a given source file
165      * 
166      * @param sourceFile
167      * @param workingDirectory
168      * @param outputDirectory
169      * @param config
170      * @return
171      */
172     protected static File getObjectFile( File sourceFile, File outputDirectory, String objectFileExtension )
173         throws NativeBuildException
174     {
175         String objectFileName;
176 
177         try
178         {
179             objectFileExtension = AbstractCompiler.getObjectFileExtension( objectFileExtension );
180 
181             // plexus-util requires that we remove all ".." in the the file source, so getCanonicalPath is required
182             // other filename with .. and no extension will throw StringIndexOutOfBoundsException
183 
184             objectFileName = FileUtils.basename( sourceFile.getCanonicalPath() );
185 
186             if ( objectFileName.charAt( objectFileName.length() - 1 ) != '.' )
187             {
188                 objectFileName += "." + objectFileExtension;
189             }
190             else
191             {
192                 objectFileName += objectFileExtension;
193             }
194         }
195         catch ( IOException e )
196         {
197             throw new NativeBuildException( "Failed to figure out object file name for " + sourceFile + ": "
198                 + e.getMessage(), e );
199         }
200 
201         File objectFile = new File( outputDirectory, objectFileName );
202 
203         return objectFile;
204 
205     }
206 
207     private class CompilerThreadPoolExecutor
208         extends ThreadPoolExecutor
209     {
210         private boolean errorFound = false;
211 
212         public synchronized void setErrorFound( boolean errorFound )
213         {
214             this.errorFound = errorFound;
215         }
216 
217         public synchronized boolean isErrorFound()
218         {
219             return errorFound;
220         }
221 
222         public CompilerThreadPoolExecutor( int corePoolSize )
223         {
224             super( corePoolSize, corePoolSize, 30, TimeUnit.SECONDS, new ArrayBlockingQueue( corePoolSize * 2 ) );
225         }
226 
227         protected void afterExecute( Runnable r, Throwable t )
228         {
229             super.afterExecute( r, t );
230 
231             if ( t != null )
232             {
233                 this.setErrorFound( true );
234 
235                 this.shutdown();
236             }
237         }
238 
239         protected void beforeExecute( Thread t, Runnable r )
240         {
241             super.beforeExecute( t, r );
242 
243             // fail fast
244             if ( this.isErrorFound() )
245             {
246                 ( (CompilerRunnable) r ).setSkip( true );
247             }
248         }
249     }
250 
251     public class CompilerRunnable
252         implements Runnable
253     {
254         private Commandline cl;
255 
256         private Logger logger;
257 
258         private boolean skip = false;
259 
260         public void setSkip( boolean skip )
261         {
262             this.skip = skip;
263         }
264 
265         public CompilerRunnable( Commandline cl, Logger logger )
266         {
267             this.cl = cl;
268             this.logger = logger;
269         }
270 
271         public void run()
272             throws NativeBuildException
273         {
274             if ( skip )
275             {
276                 return;
277             }
278 
279             CommandLineUtil.execute( cl, logger );
280         }
281 
282     }
283 
284 }