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: * Compiles Groovy source files. This task can take the following
073: * arguments:
074: * <ul>
075: * <li>sourcedir
076: * <li>destdir
077: * <li>classpath
078: * <li>stacktrace
079: * </ul>
080: * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
081: * <p>
082: * When this task executes, it will recursively scan the sourcedir and
083: * destdir looking for Groovy source files to compile. This task makes its
084: * compile decision based on timestamp.
085: *
086: * Based heavily on the Javac implementation in Ant
087: *
088: * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
089: * @author Hein Meling
090: * @version $Revision: 2690 $
091: */
092: public class Groovyc extends MatchingTask {
093:
094: private CompilerConfiguration configuration = new CompilerConfiguration();
095: private Path src;
096: private File destDir;
097: private Path compileClasspath;
098: private Path compileSourcepath;
099: private String encoding;
100:
101: protected boolean failOnError = true;
102: protected boolean listFiles = false;
103: protected File[] compileList = new File[0];
104:
105: public static void main(String[] args) {
106: String dest = ".";
107: String src = ".";
108: boolean listFiles = false;
109: if (args.length > 0) {
110: dest = args[0];
111: }
112: if (args.length > 1) {
113: src = args[1];
114: }
115: if (args.length > 2) {
116: String flag = args[2];
117: if (flag.equalsIgnoreCase("true")) {
118: listFiles = true;
119: }
120: }
121:
122: Project project = new Project();
123: project.addBuildListener(new AnsiColorLogger());
124:
125: Groovyc compiler = new Groovyc();
126: compiler.setProject(project);
127: compiler.setSrcdir(new Path(project, src));
128: compiler.setDestdir(project.resolveFile(dest));
129: compiler.setListfiles(listFiles);
130: compiler.execute();
131: }
132:
133: public Groovyc() {
134: }
135:
136: /**
137: * Adds a path for source compilation.
138: *
139: * @return a nested src element.
140: */
141: public Path createSrc() {
142: if (src == null) {
143: src = new Path(getProject());
144: }
145: return src.createPath();
146: }
147:
148: /**
149: * Recreate src.
150: *
151: * @return a nested src element.
152: */
153: protected Path recreateSrc() {
154: src = null;
155: return createSrc();
156: }
157:
158: /**
159: * Set the source directories to find the source Java files.
160: * @param srcDir the source directories as a path
161: */
162: public void setSrcdir(Path srcDir) {
163: if (src == null) {
164: src = srcDir;
165: } else {
166: src.append(srcDir);
167: }
168: }
169:
170: /**
171: * Gets the source dirs to find the source java files.
172: * @return the source directorys as a path
173: */
174: public Path getSrcdir() {
175: return src;
176: }
177:
178: /**
179: * Set the destination directory into which the Java source
180: * files should be compiled.
181: * @param destDir the destination director
182: */
183: public void setDestdir(File destDir) {
184: this .destDir = destDir;
185: }
186:
187: /**
188: * Enable verbose compiling which will display which files
189: * are being compiled
190: * @param verbose
191: */
192: public void setVerbose(boolean verbose) {
193: configuration.setVerbose(verbose);
194: }
195:
196: /**
197: * Enable compiler to report stack trace information if a problem occurs
198: * during compilation.
199: * @param stacktrace
200: */
201: public void setStacktrace(boolean stacktrace) {
202: configuration.setDebug(stacktrace);
203: }
204:
205: /**
206: * Gets the destination directory into which the java source files
207: * should be compiled.
208: * @return the destination directory
209: */
210: public File getDestdir() {
211: return destDir;
212: }
213:
214: /**
215: * Set the sourcepath to be used for this compilation.
216: * @param sourcepath the source path
217: */
218: public void setSourcepath(Path sourcepath) {
219: if (compileSourcepath == null) {
220: compileSourcepath = sourcepath;
221: } else {
222: compileSourcepath.append(sourcepath);
223: }
224: }
225:
226: /**
227: * Gets the sourcepath to be used for this compilation.
228: * @return the source path
229: */
230: public Path getSourcepath() {
231: return compileSourcepath;
232: }
233:
234: /**
235: * Adds a path to sourcepath.
236: * @return a sourcepath to be configured
237: */
238: public Path createSourcepath() {
239: if (compileSourcepath == null) {
240: compileSourcepath = new Path(getProject());
241: }
242: return compileSourcepath.createPath();
243: }
244:
245: /**
246: * Adds a reference to a source path defined elsewhere.
247: * @param r a reference to a source path
248: */
249: public void setSourcepathRef(Reference r) {
250: createSourcepath().setRefid(r);
251: }
252:
253: /**
254: * Set the classpath to be used for this compilation.
255: *
256: * @param classpath an Ant Path object containing the compilation classpath.
257: */
258: public void setClasspath(Path classpath) {
259: if (compileClasspath == null) {
260: compileClasspath = classpath;
261: } else {
262: compileClasspath.append(classpath);
263: }
264: }
265:
266: /**
267: * Gets the classpath to be used for this compilation.
268: * @return the class path
269: */
270: public Path getClasspath() {
271: return compileClasspath;
272: }
273:
274: /**
275: * Adds a path to the classpath.
276: * @return a class path to be configured
277: */
278: public Path createClasspath() {
279: if (compileClasspath == null) {
280: compileClasspath = new Path(getProject());
281: }
282: return compileClasspath.createPath();
283: }
284:
285: /**
286: * Adds a reference to a classpath defined elsewhere.
287: * @param r a reference to a classpath
288: */
289: public void setClasspathRef(Reference r) {
290: createClasspath().setRefid(r);
291: }
292:
293: public String createEncoding() {
294: if (encoding == null) {
295: encoding = System.getProperty("file.encoding");
296: }
297: return encoding;
298: }
299:
300: public void setEncoding(String encoding) {
301: this .encoding = encoding;
302: }
303:
304: public String getEncoding() {
305: return encoding;
306: }
307:
308: /**
309: * If true, list the source files being handed off to the compiler.
310: * @param list if true list the source files
311: */
312: public void setListfiles(boolean list) {
313: listFiles = list;
314: }
315:
316: /**
317: * Get the listfiles flag.
318: * @return the listfiles flag
319: */
320: public boolean getListfiles() {
321: return listFiles;
322: }
323:
324: /**
325: * Indicates whether the build will continue
326: * even if there are compilation errors; defaults to true.
327: * @param fail if true halt the build on failure
328: */
329: public void setFailonerror(boolean fail) {
330: failOnError = fail;
331: }
332:
333: /**
334: * @ant.attribute ignore="true"
335: * @param proceed inverse of failoferror
336: */
337: public void setProceed(boolean proceed) {
338: failOnError = !proceed;
339: }
340:
341: /**
342: * Gets the failonerror flag.
343: * @return the failonerror flag
344: */
345: public boolean getFailonerror() {
346: return failOnError;
347: }
348:
349: /**
350: * Executes the task.
351: * @exception BuildException if an error occurs
352: */
353: public void execute() throws BuildException {
354: checkParameters();
355: resetFileLists();
356:
357: // scan source directories and dest directory to build up
358: // compile lists
359: String[] list = src.list();
360: for (int i = 0; i < list.length; i++) {
361: File srcDir = getProject().resolveFile(list[i]);
362: if (!srcDir.exists()) {
363: throw new BuildException("srcdir \"" + srcDir.getPath()
364: + "\" does not exist!", getLocation());
365: }
366:
367: DirectoryScanner ds = this .getDirectoryScanner(srcDir);
368: String[] files = ds.getIncludedFiles();
369:
370: scanDir(srcDir, destDir != null ? destDir : srcDir, files);
371: }
372:
373: compile();
374: }
375:
376: /**
377: * Clear the list of files to be compiled and copied..
378: */
379: protected void resetFileLists() {
380: compileList = new File[0];
381: }
382:
383: /**
384: * Scans the directory looking for source files to be compiled.
385: * The results are returned in the class variable compileList
386: *
387: * @param srcDir The source directory
388: * @param destDir The destination directory
389: * @param files An array of filenames
390: */
391: protected void scanDir(File srcDir, File destDir, String[] files) {
392: GlobPatternMapper m = new GlobPatternMapper();
393: m.setFrom("*.groovy");
394: m.setTo("*.class");
395: SourceFileScanner sfs = new SourceFileScanner(this );
396: File[] newFiles = sfs
397: .restrictAsFiles(files, srcDir, destDir, m);
398:
399: if (newFiles.length > 0) {
400: File[] newCompileList = new File[compileList.length
401: + newFiles.length];
402: System.arraycopy(compileList, 0, newCompileList, 0,
403: compileList.length);
404: System.arraycopy(newFiles, 0, newCompileList,
405: compileList.length, newFiles.length);
406: compileList = newCompileList;
407: }
408: }
409:
410: /**
411: * Gets the list of files to be compiled.
412: * @return the list of files as an array
413: */
414: public File[] getFileList() {
415: return compileList;
416: }
417:
418: protected void checkParameters() throws BuildException {
419: if (src == null) {
420: throw new BuildException("srcdir attribute must be set!",
421: getLocation());
422: }
423: if (src.size() == 0) {
424: throw new BuildException("srcdir attribute must be set!",
425: getLocation());
426: }
427:
428: if (destDir != null && !destDir.isDirectory()) {
429: throw new BuildException("destination directory \""
430: + destDir + "\" does not exist "
431: + "or is not a directory", getLocation());
432: }
433:
434: if (encoding != null && !Charset.isSupported(encoding)) {
435: throw new BuildException("encoding \"\" not supported");
436: }
437: }
438:
439: protected void compile() {
440:
441: if (compileList.length > 0) {
442: log("Compiling " + compileList.length + " 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(
470: configuration, null, buildClassLoaderFor());
471: unit.addSources(compileList);
472: unit.compile();
473: } catch (Exception e) {
474:
475: StringWriter writer = new StringWriter();
476: new ErrorReporter(e, false).write(new PrintWriter(
477: writer));
478: String message = writer.toString();
479:
480: if (failOnError) {
481: throw new BuildException(message, e, getLocation());
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(
495: File.pathSeparator);
496: List classpath = configuration.getClasspath();
497: /*
498: * Iterate over the classpath provided to groovyc, and add any missing path
499: * entries to the AntClassLoader. This is a workaround, since for some reason
500: * 'directory' classpath entries were not added to the AntClassLoader' classpath.
501: */
502: for (Iterator iter = classpath.iterator(); iter.hasNext();) {
503: String cpEntry = (String) iter.next();
504: boolean found = false;
505: for (int i = 0; i < pathElm.length; i++) {
506: if (cpEntry.equals(pathElm[i])) {
507: found = true;
508: break;
509: }
510: }
511: if (!found)
512: antLoader.addPathElement(cpEntry);
513: }
514: }
515: return new GroovyClassLoader(parent, configuration);
516: }
517:
518: }
|