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 }