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 }