View Javadoc

1   /*
2    $Id: Groovyc.java,v 1.15 2005/08/10 09:52:08 hmeling Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package org.codehaus.groovy.ant;
47  
48  import groovy.lang.GroovyClassLoader;
49  
50  import java.io.File;
51  import java.io.PrintWriter;
52  import java.io.StringWriter;
53  import java.nio.charset.Charset;
54  import java.util.Iterator;
55  import java.util.List;
56  
57  import org.apache.tools.ant.AntClassLoader;
58  import org.apache.tools.ant.BuildException;
59  import org.apache.tools.ant.DirectoryScanner;
60  import org.apache.tools.ant.Project;
61  import org.apache.tools.ant.listener.AnsiColorLogger;
62  import org.apache.tools.ant.taskdefs.MatchingTask;
63  import org.apache.tools.ant.types.Path;
64  import org.apache.tools.ant.types.Reference;
65  import org.apache.tools.ant.util.GlobPatternMapper;
66  import org.apache.tools.ant.util.SourceFileScanner;
67  import org.codehaus.groovy.control.CompilationUnit;
68  import org.codehaus.groovy.control.CompilerConfiguration;
69  import org.codehaus.groovy.tools.ErrorReporter;
70  
71  
72  /***
73   * Compiles Groovy source files. This task can take the following
74   * arguments:
75   * <ul>
76   * <li>sourcedir
77   * <li>destdir
78   * <li>classpath
79   * <li>stacktrace
80   * </ul>
81   * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
82   * <p>
83   * When this task executes, it will recursively scan the sourcedir and
84   * destdir looking for Groovy source files to compile. This task makes its
85   * compile decision based on timestamp.
86   * 
87   * Based heavily on the Javac implementation in Ant
88   *
89   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
90   * @author Hein Meling
91   * @version $Revision: 1.15 $ 
92   */
93  public class Groovyc extends MatchingTask {
94  
95      private CompilerConfiguration configuration = new CompilerConfiguration();
96      private Path src;
97      private File destDir;
98      private Path compileClasspath;
99      private Path compileSourcepath;
100     private String encoding;
101 
102     protected boolean failOnError = true;
103     protected boolean listFiles = false;
104     protected File[] compileList = new File[0];
105 
106     public static void main(String[] args) {
107         String dest = ".";
108         String src = ".";
109         boolean listFiles = false;
110         if (args.length > 0) {
111             dest = args[0];
112         }
113         if (args.length > 1) {
114             src = args[1];
115         }
116         if (args.length > 2) {
117             String flag = args[2];
118             if (flag.equalsIgnoreCase("true")) {
119                 listFiles = true;
120             }
121         }
122 
123         Project project = new Project();
124         project.addBuildListener(new AnsiColorLogger());
125 
126         Groovyc compiler = new Groovyc();
127         compiler.setProject(project);
128         compiler.setSrcdir(new Path(project, src));
129         compiler.setDestdir(project.resolveFile(dest));
130         compiler.setListfiles(listFiles);
131         compiler.execute();
132     }
133 
134     public Groovyc() {
135     }
136 
137     /***
138      * Adds a path for source compilation.
139      *
140      * @return a nested src element.
141      */
142     public Path createSrc() {
143         if (src == null) {
144             src = new Path(getProject());
145         }
146         return src.createPath();
147     }
148 
149     /***
150      * Recreate src.
151      *
152      * @return a nested src element.
153      */
154     protected Path recreateSrc() {
155         src = null;
156         return createSrc();
157     }
158 
159     /***
160      * Set the source directories to find the source Java files.
161      * @param srcDir the source directories as a path
162      */
163     public void setSrcdir(Path srcDir) {
164         if (src == null) {
165             src = srcDir;
166         }
167         else {
168             src.append(srcDir);
169         }
170     }
171 
172     /***
173      * Gets the source dirs to find the source java files.
174      * @return the source directorys as a path
175      */
176     public Path getSrcdir() {
177         return src;
178     }
179 
180     /***
181      * Set the destination directory into which the Java source
182      * files should be compiled.
183      * @param destDir the destination director
184      */
185     public void setDestdir(File destDir) {
186         this.destDir = destDir;
187     }
188 
189     /***
190      * Enable verbose compiling which will display which files
191      * are being compiled
192      * @param verbose
193      */
194     public void setVerbose(boolean verbose) {
195         configuration.setVerbose( verbose );
196     }
197 
198     /***
199      * Enable compiler to report stack trace information if a problem occurs
200      * during compilation.
201      * @param stacktrace
202      */
203     public void setStacktrace(boolean stacktrace) {
204         configuration.setDebug(stacktrace);
205     }
206 
207     /***
208      * Gets the destination directory into which the java source files
209      * should be compiled.
210      * @return the destination directory
211      */
212     public File getDestdir() {
213         return destDir;
214     }
215 
216     /***
217      * Set the sourcepath to be used for this compilation.
218      * @param sourcepath the source path
219      */
220     public void setSourcepath(Path sourcepath) {
221         if (compileSourcepath == null) {
222             compileSourcepath = sourcepath;
223         }
224         else {
225             compileSourcepath.append(sourcepath);
226         }
227     }
228 
229     /***
230      * Gets the sourcepath to be used for this compilation.
231      * @return the source path
232      */
233     public Path getSourcepath() {
234         return compileSourcepath;
235     }
236 
237     /***
238      * Adds a path to sourcepath.
239      * @return a sourcepath to be configured
240      */
241     public Path createSourcepath() {
242         if (compileSourcepath == null) {
243             compileSourcepath = new Path(getProject());
244         }
245         return compileSourcepath.createPath();
246     }
247 
248     /***
249      * Adds a reference to a source path defined elsewhere.
250      * @param r a reference to a source path
251      */
252     public void setSourcepathRef(Reference r) {
253         createSourcepath().setRefid(r);
254     }
255 
256     /***
257      * Set the classpath to be used for this compilation.
258      *
259      * @param classpath an Ant Path object containing the compilation classpath.
260      */
261     public void setClasspath(Path classpath) {
262         if (compileClasspath == null) {
263             compileClasspath = classpath;
264         }
265         else {
266             compileClasspath.append(classpath);
267         }
268     }
269 
270     /***
271      * Gets the classpath to be used for this compilation.
272      * @return the class path
273      */
274     public Path getClasspath() {
275         return compileClasspath;
276     }
277 
278     /***
279      * Adds a path to the classpath.
280      * @return a class path to be configured
281      */
282     public Path createClasspath() {
283         if (compileClasspath == null) {
284             compileClasspath = new Path(getProject());
285         }
286         return compileClasspath.createPath();
287     }
288 
289     /***
290      * Adds a reference to a classpath defined elsewhere.
291      * @param r a reference to a classpath
292      */
293     public void setClasspathRef(Reference r) {
294         createClasspath().setRefid(r);
295     }
296 
297     public String createEncoding() {
298         if (encoding == null) {
299             encoding = System.getProperty("file.encoding");
300         }
301         return encoding;
302     }
303 
304     public void setEncoding(String encoding) {
305         this.encoding = encoding;
306     }
307 
308     public String getEncoding() {
309         return encoding;
310     }
311 
312     /***
313      * If true, list the source files being handed off to the compiler.
314      * @param list if true list the source files
315      */
316     public void setListfiles(boolean list) {
317         listFiles = list;
318     }
319 
320     /***
321      * Get the listfiles flag.
322      * @return the listfiles flag
323      */
324     public boolean getListfiles() {
325         return listFiles;
326     }
327 
328     /***
329      * Indicates whether the build will continue
330      * even if there are compilation errors; defaults to true.
331      * @param fail if true halt the build on failure
332      */
333     public void setFailonerror(boolean fail) {
334         failOnError = fail;
335     }
336 
337     /***
338      * @ant.attribute ignore="true"
339      * @param proceed inverse of failoferror
340      */
341     public void setProceed(boolean proceed) {
342         failOnError = !proceed;
343     }
344 
345     /***
346      * Gets the failonerror flag.
347      * @return the failonerror flag
348      */
349     public boolean getFailonerror() {
350         return failOnError;
351     }
352 
353     /***
354      * Executes the task.
355      * @exception BuildException if an error occurs
356      */
357     public void execute() throws BuildException {
358         checkParameters();
359         resetFileLists();
360 
361         // scan source directories and dest directory to build up
362         // compile lists
363         String[] list = src.list();
364         for (int i = 0; i < list.length; i++) {
365             File srcDir = getProject().resolveFile(list[i]);
366             if (!srcDir.exists()) {
367                 throw new BuildException("srcdir \"" + srcDir.getPath() + "\" does not exist!", getLocation());
368             }
369 
370             DirectoryScanner ds = this.getDirectoryScanner(srcDir);
371             String[] files = ds.getIncludedFiles();
372 
373             scanDir(srcDir, destDir != null ? destDir : srcDir, files);
374         }
375 
376         compile();
377     }
378 
379     /***
380      * Clear the list of files to be compiled and copied..
381      */
382     protected void resetFileLists() {
383         compileList = new File[0];
384     }
385 
386     /***
387      * Scans the directory looking for source files to be compiled.
388      * The results are returned in the class variable compileList
389      *
390      * @param srcDir   The source directory
391      * @param destDir  The destination directory
392      * @param files    An array of filenames
393      */
394     protected void scanDir(File srcDir, File destDir, String[] files) {
395         GlobPatternMapper m = new GlobPatternMapper();
396         m.setFrom("*.groovy");
397         m.setTo("*.class");
398         SourceFileScanner sfs = new SourceFileScanner(this);
399         File[] newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m);
400 
401         if (newFiles.length > 0) {
402             File[] newCompileList = new File[compileList.length + newFiles.length];
403             System.arraycopy(compileList, 0, newCompileList, 0, compileList.length);
404             System.arraycopy(newFiles, 0, newCompileList, compileList.length, newFiles.length);
405             compileList = newCompileList;
406         }
407     }
408 
409     /***
410      * Gets the list of files to be compiled.
411      * @return the list of files as an array
412      */
413     public File[] getFileList() {
414         return compileList;
415     }
416 
417     protected void checkParameters() throws BuildException {
418         if (src == null) {
419             throw new BuildException("srcdir attribute must be set!", getLocation());
420         }
421         if (src.size() == 0) {
422             throw new BuildException("srcdir attribute must be set!", getLocation());
423         }
424 
425         if (destDir != null && !destDir.isDirectory()) {
426             throw new BuildException(
427                 "destination directory \"" + destDir + "\" does not exist " + "or is not a directory",
428                 getLocation());
429         }
430 
431         if (encoding != null && !Charset.isSupported(encoding)) {
432             throw new BuildException("encoding \"\" not supported");
433         }
434     }
435 
436     protected void compile() {
437 
438         if (compileList.length > 0) {
439             log(
440                 "Compiling "
441                     + compileList.length
442                     + " source file"
443                     + (compileList.length == 1 ? "" : "s")
444                     + (destDir != null ? " to " + destDir : ""));
445 
446             if (listFiles) {
447                 for (int i = 0; i < compileList.length; i++) {
448                     String filename = compileList[i].getAbsolutePath();
449 
450                     // TODO this logging does not seem to appear in the maven build??
451                     // COMMENT Hein: This is not ant's problem;
452                     // fix it in maven instead if you really need this from maven!
453                     log(filename);
454 //                    System.out.println("compiling: " + filename);
455                 }
456             }
457 
458             try {
459                 Path classpath = getClasspath();
460                 if (classpath != null) {
461                     configuration.setClasspath(classpath.toString());
462                 }
463                 configuration.setTargetDirectory(destDir);
464 
465                 if (encoding != null) {
466                     configuration.setSourceEncoding(encoding);
467                 }
468 
469                 CompilationUnit unit = new CompilationUnit(configuration, null, buildClassLoaderFor());
470                 unit.addSources(compileList);
471                 unit.compile();
472             }
473             catch (Exception e) {
474 
475                 StringWriter writer = new StringWriter();
476                 new ErrorReporter( e, false ).write( new PrintWriter(writer) );
477                 String message = writer.toString();
478 
479                 if (failOnError) {
480                     throw new BuildException(message, e, getLocation());
481                 }
482                 else {
483                     log(message, Project.MSG_ERR);
484                 }
485 
486             }
487         }
488     }
489 
490     private GroovyClassLoader buildClassLoaderFor() {
491         ClassLoader parent = this.getClass().getClassLoader();
492         if (parent instanceof AntClassLoader) {
493             AntClassLoader antLoader = (AntClassLoader) parent;
494             String[] pathElm = antLoader.getClasspath().split(File.pathSeparator);
495             List classpath = configuration.getClasspath();
496             /*
497              * Iterate over the classpath provided to groovyc, and add any missing path
498              * entries to the AntClassLoader.  This is a workaround, since for some reason
499              * 'directory' classpath entries were not added to the AntClassLoader' classpath. 
500              */
501             for (Iterator iter = classpath.iterator(); iter.hasNext();) {
502                 String cpEntry = (String) iter.next();
503                 boolean found = false;
504                 for (int i = 0; i < pathElm.length; i++) {
505                     if (cpEntry.equals(pathElm[i])) {
506                         found = true;
507                         break;
508                     }
509                 }
510                 if (!found)
511                     antLoader.addPathElement(cpEntry);
512             }
513         }
514         return new GroovyClassLoader(parent, configuration);
515     }
516 
517 }