001    /*
002     $Id: Groovyc.java 2690 2005-08-10 09:52:08Z hmeling $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package org.codehaus.groovy.ant;
047    
048    import groovy.lang.GroovyClassLoader;
049    
050    import java.io.File;
051    import java.io.PrintWriter;
052    import java.io.StringWriter;
053    import java.nio.charset.Charset;
054    import java.util.Iterator;
055    import java.util.List;
056    
057    import org.apache.tools.ant.AntClassLoader;
058    import org.apache.tools.ant.BuildException;
059    import org.apache.tools.ant.DirectoryScanner;
060    import org.apache.tools.ant.Project;
061    import org.apache.tools.ant.listener.AnsiColorLogger;
062    import org.apache.tools.ant.taskdefs.MatchingTask;
063    import org.apache.tools.ant.types.Path;
064    import org.apache.tools.ant.types.Reference;
065    import org.apache.tools.ant.util.GlobPatternMapper;
066    import org.apache.tools.ant.util.SourceFileScanner;
067    import org.codehaus.groovy.control.CompilationUnit;
068    import org.codehaus.groovy.control.CompilerConfiguration;
069    import org.codehaus.groovy.tools.ErrorReporter;
070    
071    
072    /**
073     * Compiles Groovy source files. This task can take the following
074     * arguments:
075     * <ul>
076     * <li>sourcedir
077     * <li>destdir
078     * <li>classpath
079     * <li>stacktrace
080     * </ul>
081     * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
082     * <p>
083     * When this task executes, it will recursively scan the sourcedir and
084     * destdir looking for Groovy source files to compile. This task makes its
085     * compile decision based on timestamp.
086     * 
087     * Based heavily on the Javac implementation in Ant
088     *
089     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
090     * @author Hein Meling
091     * @version $Revision: 2690 $ 
092     */
093    public class Groovyc extends MatchingTask {
094    
095        private CompilerConfiguration configuration = new CompilerConfiguration();
096        private Path src;
097        private File destDir;
098        private Path compileClasspath;
099        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    }