001    /*
002     $Id: Groovyc.java,v 1.14 2005/06/10 09:55:30 cstein Exp $
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 java.io.File;
049    import java.io.PrintWriter;
050    import java.io.StringWriter;
051    import java.nio.charset.Charset;
052    
053    import org.apache.tools.ant.BuildException;
054    import org.apache.tools.ant.DirectoryScanner;
055    import org.apache.tools.ant.Project;
056    import org.apache.tools.ant.listener.AnsiColorLogger;
057    import org.apache.tools.ant.taskdefs.MatchingTask;
058    import org.apache.tools.ant.types.Path;
059    import org.apache.tools.ant.types.Reference;
060    import org.apache.tools.ant.util.GlobPatternMapper;
061    import org.apache.tools.ant.util.SourceFileScanner;
062    import org.codehaus.groovy.control.CompilationUnit;
063    import org.codehaus.groovy.control.CompilerConfiguration;
064    import org.codehaus.groovy.tools.ErrorReporter;
065    
066    
067    /**
068     * Compiles Groovy source files. This task can take the following
069     * arguments:
070     * <ul>
071     * <li>sourcedir
072     * <li>destdir
073     * <li>classpath
074     * </ul>
075     * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
076     * <p>
077     * When this task executes, it will recursively scan the sourcedir and
078     * destdir looking for Groovy source files to compile. This task makes its
079     * compile decision based on timestamp.
080     * 
081     * Based heavily on the Javac implementation in Ant
082     *
083     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
084     * @version $Revision: 1.14 $ 
085     */
086    public class Groovyc extends MatchingTask {
087    
088        private CompilerConfiguration configuration = new CompilerConfiguration();
089        private Path src;
090        private File destDir;
091        private Path compileClasspath;
092        private Path compileSourcepath;
093        private String encoding;
094    
095        protected boolean failOnError = true;
096        protected boolean listFiles = false;
097        protected File[] compileList = new File[0];
098    
099        public static void main(String[] args) {
100            String dest = ".";
101            String src = ".";
102            boolean listFiles = false;
103            if (args.length > 0) {
104                dest = args[0];
105            }
106            if (args.length > 1) {
107                src = args[1];
108            }
109            if (args.length > 2) {
110                String flag = args[2];
111                if (flag.equalsIgnoreCase("true")) {
112                    listFiles = true;
113                }
114            }
115    
116            Project project = new Project();
117            project.addBuildListener(new AnsiColorLogger());
118    
119            Groovyc compiler = new Groovyc();
120            compiler.setProject(project);
121            compiler.setSrcdir(new Path(project, src));
122            compiler.setDestdir(project.resolveFile(dest));
123            compiler.setListfiles(listFiles);
124            compiler.execute();
125        }
126    
127        public Groovyc() {
128        }
129    
130        /**
131         * Adds a path for source compilation.
132         *
133         * @return a nested src element.
134         */
135        public Path createSrc() {
136            if (src == null) {
137                src = new Path(getProject());
138            }
139            return src.createPath();
140        }
141    
142        /**
143         * Recreate src.
144         *
145         * @return a nested src element.
146         */
147        protected Path recreateSrc() {
148            src = null;
149            return createSrc();
150        }
151    
152        /**
153         * Set the source directories to find the source Java files.
154         * @param srcDir the source directories as a path
155         */
156        public void setSrcdir(Path srcDir) {
157            if (src == null) {
158                src = srcDir;
159            }
160            else {
161                src.append(srcDir);
162            }
163        }
164    
165        /**
166         * Gets the source dirs to find the source java files.
167         * @return the source directorys as a path
168         */
169        public Path getSrcdir() {
170            return src;
171        }
172    
173        /**
174         * Set the destination directory into which the Java source
175         * files should be compiled.
176         * @param destDir the destination director
177         */
178        public void setDestdir(File destDir) {
179            this.destDir = destDir;
180        }
181    
182        /**
183         * Enable verbose compiling which will display which files
184         * are being compiled
185         * @param verbose
186         */
187        public void setVerbose(boolean verbose) {
188            configuration.setVerbose( verbose );
189        }
190    
191        /**
192         * Gets the destination directory into which the java source files
193         * should be compiled.
194         * @return the destination directory
195         */
196        public File getDestdir() {
197            return destDir;
198        }
199    
200        /**
201         * Set the sourcepath to be used for this compilation.
202         * @param sourcepath the source path
203         */
204        public void setSourcepath(Path sourcepath) {
205            if (compileSourcepath == null) {
206                compileSourcepath = sourcepath;
207            }
208            else {
209                compileSourcepath.append(sourcepath);
210            }
211        }
212    
213        /**
214         * Gets the sourcepath to be used for this compilation.
215         * @return the source path
216         */
217        public Path getSourcepath() {
218            return compileSourcepath;
219        }
220    
221        /**
222         * Adds a path to sourcepath.
223         * @return a sourcepath to be configured
224         */
225        public Path createSourcepath() {
226            if (compileSourcepath == null) {
227                compileSourcepath = new Path(getProject());
228            }
229            return compileSourcepath.createPath();
230        }
231    
232        /**
233         * Adds a reference to a source path defined elsewhere.
234         * @param r a reference to a source path
235         */
236        public void setSourcepathRef(Reference r) {
237            createSourcepath().setRefid(r);
238        }
239    
240        /**
241         * Set the classpath to be used for this compilation.
242         *
243         * @param classpath an Ant Path object containing the compilation classpath.
244         */
245        public void setClasspath(Path classpath) {
246            if (compileClasspath == null) {
247                compileClasspath = classpath;
248            }
249            else {
250                compileClasspath.append(classpath);
251            }
252        }
253    
254        /**
255         * Gets the classpath to be used for this compilation.
256         * @return the class path
257         */
258        public Path getClasspath() {
259            return compileClasspath;
260        }
261    
262        /**
263         * Adds a path to the classpath.
264         * @return a class path to be configured
265         */
266        public Path createClasspath() {
267            if (compileClasspath == null) {
268                compileClasspath = new Path(getProject());
269            }
270            return compileClasspath.createPath();
271        }
272    
273        /**
274         * Adds a reference to a classpath defined elsewhere.
275         * @param r a reference to a classpath
276         */
277        public void setClasspathRef(Reference r) {
278            createClasspath().setRefid(r);
279        }
280    
281        public String createEncoding() {
282            if (encoding == null) {
283                encoding = System.getProperty("file.encoding");
284            }
285            return encoding;
286        }
287    
288        public void setEncoding(String encoding) {
289            this.encoding = encoding;
290        }
291    
292        public String getEncoding() {
293            return encoding;
294        }
295    
296        /**
297         * If true, list the source files being handed off to the compiler.
298         * @param list if true list the source files
299         */
300        public void setListfiles(boolean list) {
301            listFiles = list;
302        }
303    
304        /**
305         * Get the listfiles flag.
306         * @return the listfiles flag
307         */
308        public boolean getListfiles() {
309            return listFiles;
310        }
311    
312        /**
313         * Indicates whether the build will continue
314         * even if there are compilation errors; defaults to true.
315         * @param fail if true halt the build on failure
316         */
317        public void setFailonerror(boolean fail) {
318            failOnError = fail;
319        }
320    
321        /**
322         * @ant.attribute ignore="true"
323         * @param proceed inverse of failoferror
324         */
325        public void setProceed(boolean proceed) {
326            failOnError = !proceed;
327        }
328    
329        /**
330         * Gets the failonerror flag.
331         * @return the failonerror flag
332         */
333        public boolean getFailonerror() {
334            return failOnError;
335        }
336    
337        /**
338         * Executes the task.
339         * @exception BuildException if an error occurs
340         */
341        public void execute() throws BuildException {
342            checkParameters();
343            resetFileLists();
344    
345            // scan source directories and dest directory to build up
346            // compile lists
347            String[] list = src.list();
348            for (int i = 0; i < list.length; i++) {
349                File srcDir = getProject().resolveFile(list[i]);
350                if (!srcDir.exists()) {
351                    throw new BuildException("srcdir \"" + srcDir.getPath() + "\" does not exist!", getLocation());
352                }
353    
354                DirectoryScanner ds = this.getDirectoryScanner(srcDir);
355                String[] files = ds.getIncludedFiles();
356    
357                scanDir(srcDir, destDir != null ? destDir : srcDir, files);
358            }
359    
360            compile();
361        }
362    
363        /**
364         * Clear the list of files to be compiled and copied..
365         */
366        protected void resetFileLists() {
367            compileList = new File[0];
368        }
369    
370        /**
371         * Scans the directory looking for source files to be compiled.
372         * The results are returned in the class variable compileList
373         *
374         * @param srcDir   The source directory
375         * @param destDir  The destination directory
376         * @param files    An array of filenames
377         */
378        protected void scanDir(File srcDir, File destDir, String[] files) {
379            GlobPatternMapper m = new GlobPatternMapper();
380            m.setFrom("*.groovy");
381            m.setTo("*.class");
382            SourceFileScanner sfs = new SourceFileScanner(this);
383            File[] newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m);
384    
385            if (newFiles.length > 0) {
386                File[] newCompileList = new File[compileList.length + newFiles.length];
387                System.arraycopy(compileList, 0, newCompileList, 0, compileList.length);
388                System.arraycopy(newFiles, 0, newCompileList, compileList.length, newFiles.length);
389                compileList = newCompileList;
390            }
391        }
392    
393        /**
394         * Gets the list of files to be compiled.
395         * @return the list of files as an array
396         */
397        public File[] getFileList() {
398            return compileList;
399        }
400    
401        protected void checkParameters() throws BuildException {
402            if (src == null) {
403                throw new BuildException("srcdir attribute must be set!", getLocation());
404            }
405            if (src.size() == 0) {
406                throw new BuildException("srcdir attribute must be set!", getLocation());
407            }
408    
409            if (destDir != null && !destDir.isDirectory()) {
410                throw new BuildException(
411                    "destination directory \"" + destDir + "\" does not exist " + "or is not a directory",
412                    getLocation());
413            }
414    
415            if (encoding != null && !Charset.isSupported(encoding)) {
416                throw new BuildException("encoding \"\" not supported");
417            }
418        }
419    
420        protected void compile() {
421    
422            if (compileList.length > 0) {
423                log(
424                    "Compiling "
425                        + compileList.length
426                        + " source file"
427                        + (compileList.length == 1 ? "" : "s")
428                        + (destDir != null ? " to " + destDir : ""));
429    
430                if (listFiles) {
431                    for (int i = 0; i < compileList.length; i++) {
432                        String filename = compileList[i].getAbsolutePath();
433    
434                        // TODO this logging does not seem to appear in the maven build??
435                        log(filename);
436                        System.out.println("compiling: " + filename);
437                    }
438                }
439    
440                try {
441                    Path classpath = getClasspath();
442                    if (classpath != null) {
443                        configuration.setClasspath(classpath.toString());
444                    }
445                    configuration.setTargetDirectory(destDir);
446    
447                    if (encoding != null) {
448                        configuration.setSourceEncoding(encoding);
449                    }
450    
451                    CompilationUnit unit = new CompilationUnit( configuration );
452                    unit.addSources( compileList );
453                    unit.compile( );
454                }
455                catch (Exception e) {
456    
457                    StringWriter writer = new StringWriter();
458                    new ErrorReporter( e, false ).write( new PrintWriter(writer) );
459                    String message = writer.toString();
460    
461                    if (failOnError) {
462                        throw new BuildException(message, e, getLocation());
463                    }
464                    else {
465                        log(message, Project.MSG_ERR);
466                    }
467    
468                }
469            }
470        }
471    }