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 }