1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28
29 import org.apache.tools.ant.BuildException;
30 import org.apache.tools.ant.DirectoryScanner;
31 import org.apache.tools.ant.MagicNames;
32 import org.apache.tools.ant.Project;
33 import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter;
34 import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory;
35 import org.apache.tools.ant.types.Path;
36 import org.apache.tools.ant.types.Reference;
37 import org.apache.tools.ant.util.FileUtils;
38 import org.apache.tools.ant.util.GlobPatternMapper;
39 import org.apache.tools.ant.util.JavaEnvUtils;
40 import org.apache.tools.ant.util.SourceFileScanner;
41 import org.apache.tools.ant.util.facade.FacadeTaskHelper;
42
43 /**
44 * Compiles Java source files. This task can take the following
45 * arguments:
46 * <ul>
47 * <li>sourcedir
48 * <li>destdir
49 * <li>deprecation
50 * <li>classpath
51 * <li>bootclasspath
52 * <li>extdirs
53 * <li>optimize
54 * <li>debug
55 * <li>encoding
56 * <li>target
57 * <li>depend
58 * <li>verbose
59 * <li>failonerror
60 * <li>includeantruntime
61 * <li>includejavaruntime
62 * <li>source
63 * <li>compiler
64 * </ul>
65 * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
66 * <p>
67 * When this task executes, it will recursively scan the sourcedir and
68 * destdir looking for Java source files to compile. This task makes its
69 * compile decision based on timestamp.
70 *
71 *
72 * @since Ant 1.1
73 *
74 * @ant.task category="java"
75 */
76
77 public class Javac extends MatchingTask {
78
79 private static final String FAIL_MSG
80 = "Compile failed; see the compiler error output for details.";
81
82 private static final String JAVAC16 = "javac1.6";
83 private static final String JAVAC15 = "javac1.5";
84 private static final String JAVAC14 = "javac1.4";
85 private static final String JAVAC13 = "javac1.3";
86 private static final String JAVAC12 = "javac1.2";
87 private static final String JAVAC11 = "javac1.1";
88 private static final String MODERN = "modern";
89 private static final String CLASSIC = "classic";
90 private static final String EXTJAVAC = "extJavac";
91
92 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
93
94 private Path src;
95 private File destDir;
96 private Path compileClasspath;
97 private Path compileSourcepath;
98 private String encoding;
99 private boolean debug = false;
100 private boolean optimize = false;
101 private boolean deprecation = false;
102 private boolean depend = false;
103 private boolean verbose = false;
104 private String targetAttribute;
105 private Path bootclasspath;
106 private Path extdirs;
107 private Boolean includeAntRuntime;
108 private boolean includeJavaRuntime = false;
109 private boolean fork = false;
110 private String forkedExecutable = null;
111 private boolean nowarn = false;
112 private String memoryInitialSize;
113 private String memoryMaximumSize;
114 private FacadeTaskHelper facade = null;
115
116 // CheckStyle:VisibilityModifier OFF - bc
117 protected boolean failOnError = true;
118 protected boolean listFiles = false;
119 protected File[] compileList = new File[0];
120 private Map/*<String,Long>*/ packageInfos = new HashMap();
121 // CheckStyle:VisibilityModifier ON
122
123 private String source;
124 private String debugLevel;
125 private File tmpDir;
126 private String updatedProperty;
127 private String errorProperty;
128 private boolean taskSuccess = true; // assume the best
129 private boolean includeDestClasses = true;
130 private CompilerAdapter nestedAdapter = null;
131
132 /**
133 * Javac task for compilation of Java files.
134 */
135 public Javac() {
136 facade = new FacadeTaskHelper(assumedJavaVersion());
137 }
138
139 private String assumedJavaVersion() {
140 if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_4)) {
141 return JAVAC14;
142 } else if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_5)) {
143 return JAVAC15;
144 } else if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_6)) {
145 return JAVAC16;
146 } else {
147 return CLASSIC;
148 }
149 }
150
151 /**
152 * Get the value of debugLevel.
153 * @return value of debugLevel.
154 */
155 public String getDebugLevel() {
156 return debugLevel;
157 }
158
159 /**
160 * Keyword list to be appended to the -g command-line switch.
161 *
162 * This will be ignored by all implementations except modern
163 * and classic(ver >= 1.2). Legal values are none or a
164 * comma-separated list of the following keywords: lines, vars,
165 * and source. If debuglevel is not specified, by default, :none
166 * will be appended to -g. If debug is not turned on, this attribute
167 * will be ignored.
168 *
169 * @param v Value to assign to debugLevel.
170 */
171 public void setDebugLevel(String v) {
172 this.debugLevel = v;
173 }
174
175 /**
176 * Get the value of source.
177 * @return value of source.
178 */
179 public String getSource() {
180 return source != null
181 ? source : getProject().getProperty(MagicNames.BUILD_JAVAC_SOURCE);
182 }
183
184 /**
185 * Value of the -source command-line switch; will be ignored by
186 * all implementations except modern, jikes and gcj (gcj uses
187 * -fsource).
188 *
189 * <p>If you use this attribute together with jikes or gcj, you
190 * must make sure that your version of jikes supports the -source
191 * switch.</p>
192 *
193 * <p>Legal values are 1.3, 1.4, 1.5, and 5 - by default, no
194 * -source argument will be used at all.</p>
195 *
196 * @param v Value to assign to source.
197 */
198 public void setSource(String v) {
199 this.source = v;
200 }
201
202 /**
203 * Adds a path for source compilation.
204 *
205 * @return a nested src element.
206 */
207 public Path createSrc() {
208 if (src == null) {
209 src = new Path(getProject());
210 }
211 return src.createPath();
212 }
213
214 /**
215 * Recreate src.
216 *
217 * @return a nested src element.
218 */
219 protected Path recreateSrc() {
220 src = null;
221 return createSrc();
222 }
223
224 /**
225 * Set the source directories to find the source Java files.
226 * @param srcDir the source directories as a path
227 */
228 public void setSrcdir(Path srcDir) {
229 if (src == null) {
230 src = srcDir;
231 } else {
232 src.append(srcDir);
233 }
234 }
235
236 /**
237 * Gets the source dirs to find the source java files.
238 * @return the source directories as a path
239 */
240 public Path getSrcdir() {
241 return src;
242 }
243
244 /**
245 * Set the destination directory into which the Java source
246 * files should be compiled.
247 * @param destDir the destination director
248 */
249 public void setDestdir(File destDir) {
250 this.destDir = destDir;
251 }
252
253 /**
254 * Gets the destination directory into which the java source files
255 * should be compiled.
256 * @return the destination directory
257 */
258 public File getDestdir() {
259 return destDir;
260 }
261
262 /**
263 * Set the sourcepath to be used for this compilation.
264 * @param sourcepath the source path
265 */
266 public void setSourcepath(Path sourcepath) {
267 if (compileSourcepath == null) {
268 compileSourcepath = sourcepath;
269 } else {
270 compileSourcepath.append(sourcepath);
271 }
272 }
273
274 /**
275 * Gets the sourcepath to be used for this compilation.
276 * @return the source path
277 */
278 public Path getSourcepath() {
279 return compileSourcepath;
280 }
281
282 /**
283 * Adds a path to sourcepath.
284 * @return a sourcepath to be configured
285 */
286 public Path createSourcepath() {
287 if (compileSourcepath == null) {
288 compileSourcepath = new Path(getProject());
289 }
290 return compileSourcepath.createPath();
291 }
292
293 /**
294 * Adds a reference to a source path defined elsewhere.
295 * @param r a reference to a source path
296 */
297 public void setSourcepathRef(Reference r) {
298 createSourcepath().setRefid(r);
299 }
300
301 /**
302 * Set the classpath to be used for this compilation.
303 *
304 * @param classpath an Ant Path object containing the compilation classpath.
305 */
306 public void setClasspath(Path classpath) {
307 if (compileClasspath == null) {
308 compileClasspath = classpath;
309 } else {
310 compileClasspath.append(classpath);
311 }
312 }
313
314 /**
315 * Gets the classpath to be used for this compilation.
316 * @return the class path
317 */
318 public Path getClasspath() {
319 return compileClasspath;
320 }
321
322 /**
323 * Adds a path to the classpath.
324 * @return a class path to be configured
325 */
326 public Path createClasspath() {
327 if (compileClasspath == null) {
328 compileClasspath = new Path(getProject());
329 }
330 return compileClasspath.createPath();
331 }
332
333 /**
334 * Adds a reference to a classpath defined elsewhere.
335 * @param r a reference to a classpath
336 */
337 public void setClasspathRef(Reference r) {
338 createClasspath().setRefid(r);
339 }
340
341 /**
342 * Sets the bootclasspath that will be used to compile the classes
343 * against.
344 * @param bootclasspath a path to use as a boot class path (may be more
345 * than one)
346 */
347 public void setBootclasspath(Path bootclasspath) {
348 if (this.bootclasspath == null) {
349 this.bootclasspath = bootclasspath;
350 } else {
351 this.bootclasspath.append(bootclasspath);
352 }
353 }
354
355 /**
356 * Gets the bootclasspath that will be used to compile the classes
357 * against.
358 * @return the boot path
359 */
360 public Path getBootclasspath() {
361 return bootclasspath;
362 }
363
364 /**
365 * Adds a path to the bootclasspath.
366 * @return a path to be configured
367 */
368 public Path createBootclasspath() {
369 if (bootclasspath == null) {
370 bootclasspath = new Path(getProject());
371 }
372 return bootclasspath.createPath();
373 }
374
375 /**
376 * Adds a reference to a classpath defined elsewhere.
377 * @param r a reference to a classpath
378 */
379 public void setBootClasspathRef(Reference r) {
380 createBootclasspath().setRefid(r);
381 }
382
383 /**
384 * Sets the extension directories that will be used during the
385 * compilation.
386 * @param extdirs a path
387 */
388 public void setExtdirs(Path extdirs) {
389 if (this.extdirs == null) {
390 this.extdirs = extdirs;
391 } else {
392 this.extdirs.append(extdirs);
393 }
394 }
395
396 /**
397 * Gets the extension directories that will be used during the
398 * compilation.
399 * @return the extension directories as a path
400 */
401 public Path getExtdirs() {
402 return extdirs;
403 }
404
405 /**
406 * Adds a path to extdirs.
407 * @return a path to be configured
408 */
409 public Path createExtdirs() {
410 if (extdirs == null) {
411 extdirs = new Path(getProject());
412 }
413 return extdirs.createPath();
414 }
415
416 /**
417 * If true, list the source files being handed off to the compiler.
418 * @param list if true list the source files
419 */
420 public void setListfiles(boolean list) {
421 listFiles = list;
422 }
423
424 /**
425 * Get the listfiles flag.
426 * @return the listfiles flag
427 */
428 public boolean getListfiles() {
429 return listFiles;
430 }
431
432 /**
433 * Indicates whether the build will continue
434 * even if there are compilation errors; defaults to true.
435 * @param fail if true halt the build on failure
436 */
437 public void setFailonerror(boolean fail) {
438 failOnError = fail;
439 }
440
441 /**
442 * @ant.attribute ignore="true"
443 * @param proceed inverse of failoferror
444 */
445 public void setProceed(boolean proceed) {
446 failOnError = !proceed;
447 }
448
449 /**
450 * Gets the failonerror flag.
451 * @return the failonerror flag
452 */
453 public boolean getFailonerror() {
454 return failOnError;
455 }
456
457 /**
458 * Indicates whether source should be
459 * compiled with deprecation information; defaults to off.
460 * @param deprecation if true turn on deprecation information
461 */
462 public void setDeprecation(boolean deprecation) {
463 this.deprecation = deprecation;
464 }
465
466 /**
467 * Gets the deprecation flag.
468 * @return the deprecation flag
469 */
470 public boolean getDeprecation() {
471 return deprecation;
472 }
473
474 /**
475 * The initial size of the memory for the underlying VM
476 * if javac is run externally; ignored otherwise.
477 * Defaults to the standard VM memory setting.
478 * (Examples: 83886080, 81920k, or 80m)
479 * @param memoryInitialSize string to pass to VM
480 */
481 public void setMemoryInitialSize(String memoryInitialSize) {
482 this.memoryInitialSize = memoryInitialSize;
483 }
484
485 /**
486 * Gets the memoryInitialSize flag.
487 * @return the memoryInitialSize flag
488 */
489 public String getMemoryInitialSize() {
490 return memoryInitialSize;
491 }
492
493 /**
494 * The maximum size of the memory for the underlying VM
495 * if javac is run externally; ignored otherwise.
496 * Defaults to the standard VM memory setting.
497 * (Examples: 83886080, 81920k, or 80m)
498 * @param memoryMaximumSize string to pass to VM
499 */
500 public void setMemoryMaximumSize(String memoryMaximumSize) {
501 this.memoryMaximumSize = memoryMaximumSize;
502 }
503
504 /**
505 * Gets the memoryMaximumSize flag.
506 * @return the memoryMaximumSize flag
507 */
508 public String getMemoryMaximumSize() {
509 return memoryMaximumSize;
510 }
511
512 /**
513 * Set the Java source file encoding name.
514 * @param encoding the source file encoding
515 */
516 public void setEncoding(String encoding) {
517 this.encoding = encoding;
518 }
519
520 /**
521 * Gets the java source file encoding name.
522 * @return the source file encoding name
523 */
524 public String getEncoding() {
525 return encoding;
526 }
527
528 /**
529 * Indicates whether source should be compiled
530 * with debug information; defaults to off.
531 * @param debug if true compile with debug information
532 */
533 public void setDebug(boolean debug) {
534 this.debug = debug;
535 }
536
537 /**
538 * Gets the debug flag.
539 * @return the debug flag
540 */
541 public boolean getDebug() {
542 return debug;
543 }
544
545 /**
546 * If true, compiles with optimization enabled.
547 * @param optimize if true compile with optimization enabled
548 */
549 public void setOptimize(boolean optimize) {
550 this.optimize = optimize;
551 }
552
553 /**
554 * Gets the optimize flag.
555 * @return the optimize flag
556 */
557 public boolean getOptimize() {
558 return optimize;
559 }
560
561 /**
562 * Enables dependency-tracking for compilers
563 * that support this (jikes and classic).
564 * @param depend if true enable dependency-tracking
565 */
566 public void setDepend(boolean depend) {
567 this.depend = depend;
568 }
569
570 /**
571 * Gets the depend flag.
572 * @return the depend flag
573 */
574 public boolean getDepend() {
575 return depend;
576 }
577
578 /**
579 * If true, asks the compiler for verbose output.
580 * @param verbose if true, asks the compiler for verbose output
581 */
582 public void setVerbose(boolean verbose) {
583 this.verbose = verbose;
584 }
585
586 /**
587 * Gets the verbose flag.
588 * @return the verbose flag
589 */
590 public boolean getVerbose() {
591 return verbose;
592 }
593
594 /**
595 * Sets the target VM that the classes will be compiled for. Valid
596 * values depend on the compiler, for jdk 1.4 the valid values are
597 * "1.1", "1.2", "1.3", "1.4", "1.5", "1.6", "5" and "6".
598 * @param target the target VM
599 */
600 public void setTarget(String target) {
601 this.targetAttribute = target;
602 }
603
604 /**
605 * Gets the target VM that the classes will be compiled for.
606 * @return the target VM
607 */
608 public String getTarget() {
609 return targetAttribute != null
610 ? targetAttribute
611 : getProject().getProperty(MagicNames.BUILD_JAVAC_TARGET);
612 }
613
614 /**
615 * If true, includes Ant's own classpath in the classpath.
616 * @param include if true, includes Ant's own classpath in the classpath
617 */
618 public void setIncludeantruntime(boolean include) {
619 includeAntRuntime = Boolean.valueOf(include);
620 }
621
622 /**
623 * Gets whether or not the ant classpath is to be included in the classpath.
624 * @return whether or not the ant classpath is to be included in the classpath
625 */
626 public boolean getIncludeantruntime() {
627 return includeAntRuntime != null ? includeAntRuntime.booleanValue() : true;
628 }
629
630 /**
631 * If true, includes the Java runtime libraries in the classpath.
632 * @param include if true, includes the Java runtime libraries in the classpath
633 */
634 public void setIncludejavaruntime(boolean include) {
635 includeJavaRuntime = include;
636 }
637
638 /**
639 * Gets whether or not the java runtime should be included in this
640 * task's classpath.
641 * @return the includejavaruntime attribute
642 */
643 public boolean getIncludejavaruntime() {
644 return includeJavaRuntime;
645 }
646
647 /**
648 * If true, forks the javac compiler.
649 *
650 * @param f "true|false|on|off|yes|no"
651 */
652 public void setFork(boolean f) {
653 fork = f;
654 }
655
656 /**
657 * Sets the name of the javac executable.
658 *
659 * <p>Ignored unless fork is true or extJavac has been specified
660 * as the compiler.</p>
661 * @param forkExec the name of the executable
662 */
663 public void setExecutable(String forkExec) {
664 forkedExecutable = forkExec;
665 }
666
667 /**
668 * The value of the executable attribute, if any.
669 *
670 * @since Ant 1.6
671 * @return the name of the java executable
672 */
673 public String getExecutable() {
674 return forkedExecutable;
675 }
676
677 /**
678 * Is this a forked invocation of JDK's javac?
679 * @return true if this is a forked invocation
680 */
681 public boolean isForkedJavac() {
682 return fork || EXTJAVAC.equalsIgnoreCase(getCompiler());
683 }
684
685 /**
686 * The name of the javac executable to use in fork-mode.
687 *
688 * <p>This is either the name specified with the executable
689 * attribute or the full path of the javac compiler of the VM Ant
690 * is currently running in - guessed by Ant.</p>
691 *
692 * <p>You should <strong>not</strong> invoke this method if you
693 * want to get the value of the executable command - use {@link
694 * #getExecutable getExecutable} for this.</p>
695 * @return the name of the javac executable
696 */
697 public String getJavacExecutable() {
698 if (forkedExecutable == null && isForkedJavac()) {
699 forkedExecutable = getSystemJavac();
700 } else if (forkedExecutable != null && !isForkedJavac()) {
701 forkedExecutable = null;
702 }
703 return forkedExecutable;
704 }
705
706 /**
707 * If true, enables the -nowarn option.
708 * @param flag if true, enable the -nowarn option
709 */
710 public void setNowarn(boolean flag) {
711 this.nowarn = flag;
712 }
713
714 /**
715 * Should the -nowarn option be used.
716 * @return true if the -nowarn option should be used
717 */
718 public boolean getNowarn() {
719 return nowarn;
720 }
721
722 /**
723 * Adds an implementation specific command-line argument.
724 * @return a ImplementationSpecificArgument to be configured
725 */
726 public ImplementationSpecificArgument createCompilerArg() {
727 ImplementationSpecificArgument arg =
728 new ImplementationSpecificArgument();
729 facade.addImplementationArgument(arg);
730 return arg;
731 }
732
733 /**
734 * Get the additional implementation specific command line arguments.
735 * @return array of command line arguments, guaranteed to be non-null.
736 */
737 public String[] getCurrentCompilerArgs() {
738 String chosen = facade.getExplicitChoice();
739 try {
740 // make sure facade knows about magic properties and fork setting
741 String appliedCompiler = getCompiler();
742 facade.setImplementation(appliedCompiler);
743
744 String[] result = facade.getArgs();
745
746 String altCompilerName = getAltCompilerName(facade.getImplementation());
747
748 if (result.length == 0 && altCompilerName != null) {
749 facade.setImplementation(altCompilerName);
750 result = facade.getArgs();
751 }
752
753 return result;
754
755 } finally {
756 facade.setImplementation(chosen);
757 }
758 }
759
760 private String getAltCompilerName(String anImplementation) {
761 if (JAVAC16.equalsIgnoreCase(anImplementation)
762 || JAVAC15.equalsIgnoreCase(anImplementation)
763 || JAVAC14.equalsIgnoreCase(anImplementation)
764 || JAVAC13.equalsIgnoreCase(anImplementation)) {
765 return MODERN;
766 }
767 if (JAVAC12.equalsIgnoreCase(anImplementation)
768 || JAVAC11.equalsIgnoreCase(anImplementation)) {
769 return CLASSIC;
770 }
771 if (MODERN.equalsIgnoreCase(anImplementation)) {
772 String nextSelected = assumedJavaVersion();
773 if (JAVAC16.equalsIgnoreCase(nextSelected)
774 || JAVAC15.equalsIgnoreCase(nextSelected)
775 || JAVAC14.equalsIgnoreCase(nextSelected)
776 || JAVAC13.equalsIgnoreCase(nextSelected)) {
777 return nextSelected;
778 }
779 }
780 if (CLASSIC.equalsIgnoreCase(anImplementation)) {
781 return assumedJavaVersion();
782 }
783 if (EXTJAVAC.equalsIgnoreCase(anImplementation)) {
784 return assumedJavaVersion();
785 }
786 return null;
787 }
788
789 /**
790 * Where Ant should place temporary files.
791 *
792 * @since Ant 1.6
793 * @param tmpDir the temporary directory
794 */
795 public void setTempdir(File tmpDir) {
796 this.tmpDir = tmpDir;
797 }
798
799 /**
800 * Where Ant should place temporary files.
801 *
802 * @since Ant 1.6
803 * @return the temporary directory
804 */
805 public File getTempdir() {
806 return tmpDir;
807 }
808
809 /**
810 * The property to set on compliation success.
811 * This property will not be set if the compilation
812 * fails, or if there are no files to compile.
813 * @param updatedProperty the property name to use.
814 * @since Ant 1.7.1.
815 */
816 public void setUpdatedProperty(String updatedProperty) {
817 this.updatedProperty = updatedProperty;
818 }
819
820 /**
821 * The property to set on compliation failure.
822 * This property will be set if the compilation
823 * fails.
824 * @param errorProperty the property name to use.
825 * @since Ant 1.7.1.
826 */
827 public void setErrorProperty(String errorProperty) {
828 this.errorProperty = errorProperty;
829 }
830
831 /**
832 * This property controls whether to include the
833 * destination classes directory in the classpath
834 * given to the compiler.
835 * The default value is "true".
836 * @param includeDestClasses the value to use.
837 */
838 public void setIncludeDestClasses(boolean includeDestClasses) {
839 this.includeDestClasses = includeDestClasses;
840 }
841
842 /**
843 * Get the value of the includeDestClasses property.
844 * @return the value.
845 */
846 public boolean isIncludeDestClasses() {
847 return includeDestClasses;
848 }
849
850 /**
851 * Get the result of the javac task (success or failure).
852 * @return true if compilation succeeded, or
853 * was not neccessary, false if the compilation failed.
854 */
855 public boolean getTaskSuccess() {
856 return taskSuccess;
857 }
858
859 /**
860 * The classpath to use when loading the compiler implementation
861 * if it is not a built-in one.
862 *
863 * @since Ant 1.8.0
864 */
865 public Path createCompilerClasspath() {
866 return facade.getImplementationClasspath(getProject());
867 }
868
869 /**
870 * Set the compiler adapter explicitly.
871 * @since Ant 1.8.0
872 */
873 public void add(CompilerAdapter adapter) {
874 if (nestedAdapter != null) {
875 throw new BuildException("Can't have more than one compiler"
876 + " adapter");
877 }
878 nestedAdapter = adapter;
879 }
880
881 /**
882 * Executes the task.
883 * @exception BuildException if an error occurs
884 */
885 public void execute() throws BuildException {
886 checkParameters();
887 resetFileLists();
888
889 // scan source directories and dest directory to build up
890 // compile lists
891 String[] list = src.list();
892 for (int i = 0; i < list.length; i++) {
893 File srcDir = getProject().resolveFile(list[i]);
894 if (!srcDir.exists()) {
895 throw new BuildException("srcdir \""
896 + srcDir.getPath()
897 + "\" does not exist!", getLocation());
898 }
899
900 DirectoryScanner ds = this.getDirectoryScanner(srcDir);
901 String[] files = ds.getIncludedFiles();
902
903 scanDir(srcDir, destDir != null ? destDir : srcDir, files);
904 }
905
906 compile();
907 if (updatedProperty != null
908 && taskSuccess
909 && compileList.length != 0) {
910 getProject().setNewProperty(updatedProperty, "true");
911 }
912 }
913
914 /**
915 * Clear the list of files to be compiled and copied..
916 */
917 protected void resetFileLists() {
918 compileList = new File[0];
919 packageInfos = new HashMap();
920 }
921
922 /**
923 * Scans the directory looking for source files to be compiled.
924 * The results are returned in the class variable compileList
925 *
926 * @param srcDir The source directory
927 * @param destDir The destination directory
928 * @param files An array of filenames
929 */
930 protected void scanDir(File srcDir, File destDir, String[] files) {
931 GlobPatternMapper m = new GlobPatternMapper();
932 m.setFrom("*.java");
933 m.setTo("*.class");
934 SourceFileScanner sfs = new SourceFileScanner(this);
935 File[] newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m);
936
937 if (newFiles.length > 0) {
938 lookForPackageInfos(srcDir, newFiles);
939 File[] newCompileList
940 = new File[compileList.length + newFiles.length];
941 System.arraycopy(compileList, 0, newCompileList, 0,
942 compileList.length);
943 System.arraycopy(newFiles, 0, newCompileList,
944 compileList.length, newFiles.length);
945 compileList = newCompileList;
946 }
947 }
948
949 /**
950 * Gets the list of files to be compiled.
951 * @return the list of files as an array
952 */
953 public File[] getFileList() {
954 return compileList;
955 }
956
957 /**
958 * Is the compiler implementation a jdk compiler
959 *
960 * @param compilerImpl the name of the compiler implementation
961 * @return true if compilerImpl is "modern", "classic",
962 * "javac1.1", "javac1.2", "javac1.3", "javac1.4", "javac1.5" or
963 * "javac1.6".
964 */
965 protected boolean isJdkCompiler(String compilerImpl) {
966 return MODERN.equals(compilerImpl)
967 || CLASSIC.equals(compilerImpl)
968 || JAVAC16.equals(compilerImpl)
969 || JAVAC15.equals(compilerImpl)
970 || JAVAC14.equals(compilerImpl)
971 || JAVAC13.equals(compilerImpl)
972 || JAVAC12.equals(compilerImpl)
973 || JAVAC11.equals(compilerImpl);
974 }
975
976 /**
977 * @return the executable name of the java compiler
978 */
979 protected String getSystemJavac() {
980 return JavaEnvUtils.getJdkExecutable("javac");
981 }
982
983 /**
984 * Choose the implementation for this particular task.
985 * @param compiler the name of the compiler
986 * @since Ant 1.5
987 */
988 public void setCompiler(String compiler) {
989 facade.setImplementation(compiler);
990 }
991
992 /**
993 * The implementation for this particular task.
994 *
995 * <p>Defaults to the build.compiler property but can be overridden
996 * via the compiler and fork attributes.</p>
997 *
998 * <p>If fork has been set to true, the result will be extJavac
999 * and not classic or java1.2 - no matter what the compiler
1000 * attribute looks like.</p>
1001 *
1002 * @see #getCompilerVersion
1003 * @return the compiler.
1004 * @since Ant 1.5
1005 */
1006 public String getCompiler() {
1007 String compilerImpl = getCompilerVersion();
1008 if (fork) {
1009 if (isJdkCompiler(compilerImpl)) {
1010 compilerImpl = EXTJAVAC;
1011 } else {
1012 log("Since compiler setting isn't classic or modern, "
1013 + "ignoring fork setting.", Project.MSG_WARN);
1014 }
1015 }
1016 return compilerImpl;
1017 }
1018
1019 /**
1020 * The implementation for this particular task.
1021 *
1022 * <p>Defaults to the build.compiler property but can be overridden
1023 * via the compiler attribute.</p>
1024 *
1025 * <p>This method does not take the fork attribute into
1026 * account.</p>
1027 *
1028 * @see #getCompiler
1029 * @return the compiler.
1030 *
1031 * @since Ant 1.5
1032 */
1033 public String getCompilerVersion() {
1034 facade.setMagicValue(getProject().getProperty("build.compiler"));
1035 return facade.getImplementation();
1036 }
1037
1038 /**
1039 * Check that all required attributes have been set and nothing
1040 * silly has been entered.
1041 *
1042 * @since Ant 1.5
1043 * @exception BuildException if an error occurs
1044 */
1045 protected void checkParameters() throws BuildException {
1046 if (src == null) {
1047 throw new BuildException("srcdir attribute must be set!",
1048 getLocation());
1049 }
1050 if (src.size() == 0) {
1051 throw new BuildException("srcdir attribute must be set!",
1052 getLocation());
1053 }
1054
1055 if (destDir != null && !destDir.isDirectory()) {
1056 throw new BuildException("destination directory \""
1057 + destDir
1058 + "\" does not exist "
1059 + "or is not a directory", getLocation());
1060 }
1061 if (includeAntRuntime == null && getProject().getProperty("build.sysclasspath") == null) {
1062 log(getLocation() + "warning: 'includeantruntime' was not set, " +
1063 "defaulting to build.sysclasspath=last; set to false for repeatable builds",
1064 Project.MSG_WARN);
1065 }
1066 }
1067
1068 /**
1069 * Perform the compilation.
1070 *
1071 * @since Ant 1.5
1072 */
1073 protected void compile() {
1074 String compilerImpl = getCompiler();
1075
1076 if (compileList.length > 0) {
1077 log("Compiling " + compileList.length + " source file"
1078 + (compileList.length == 1 ? "" : "s")
1079 + (destDir != null ? " to " + destDir : ""));
1080
1081 if (listFiles) {
1082 for (int i = 0; i < compileList.length; i++) {
1083 String filename = compileList[i].getAbsolutePath();
1084 log(filename);
1085 }
1086 }
1087
1088 CompilerAdapter adapter =
1089 nestedAdapter != null ? nestedAdapter :
1090 CompilerAdapterFactory.getCompiler(compilerImpl, this,
1091 createCompilerClasspath());
1092
1093 // now we need to populate the compiler adapter
1094 adapter.setJavac(this);
1095
1096 // finally, lets execute the compiler!!
1097 if (adapter.execute()) {
1098 // Success
1099 try {
1100 generateMissingPackageInfoClasses();
1101 } catch (IOException x) {
1102 // Should this be made a nonfatal warning?
1103 throw new BuildException(x, getLocation());
1104 }
1105 } else {
1106 // Fail path
1107 this.taskSuccess = false;
1108 if (errorProperty != null) {
1109 getProject().setNewProperty(
1110 errorProperty, "true");
1111 }
1112 if (failOnError) {
1113 throw new BuildException(FAIL_MSG, getLocation());
1114 } else {
1115 log(FAIL_MSG, Project.MSG_ERR);
1116 }
1117 }
1118 }
1119 }
1120
1121 /**
1122 * Adds an "compiler" attribute to Commandline$Attribute used to
1123 * filter command line attributes based on the current
1124 * implementation.
1125 */
1126 public class ImplementationSpecificArgument extends
1127 org.apache.tools.ant.util.facade.ImplementationSpecificArgument {
1128
1129 /**
1130 * @param impl the name of the compiler
1131 */
1132 public void setCompiler(String impl) {
1133 super.setImplementation(impl);
1134 }
1135 }
1136
1137 private void lookForPackageInfos(File srcDir, File[] newFiles) {
1138 for (int i = 0; i < newFiles.length; i++) {
1139 File f = newFiles[i];
1140 if (!f.getName().equals("package-info.java")) {
1141 continue;
1142 }
1143 String path = FILE_UTILS.removeLeadingPath(srcDir, f).
1144 replace(File.separatorChar, '/');
1145 String suffix = "/package-info.java";
1146 if (!path.endsWith(suffix)) {
1147 log("anomalous package-info.java path: " + path, Project.MSG_WARN);
1148 continue;
1149 }
1150 String pkg = path.substring(0, path.length() - suffix.length());
1151 packageInfos.put(pkg, new Long(f.lastModified()));
1152 }
1153 }
1154
1155 /**
1156 * Ensure that every {@code package-info.java} produced a {@code package-info.class}.
1157 * Otherwise this task's up-to-date tracking mechanisms do not work.
1158 * @see <a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=43114">Bug #43114</a>
1159 */
1160 private void generateMissingPackageInfoClasses() throws IOException {
1161 for (Iterator i = packageInfos.entrySet().iterator(); i.hasNext(); ) {
1162 Map.Entry entry = (Map.Entry) i.next();
1163 String pkg = (String) entry.getKey();
1164 Long sourceLastMod = (Long) entry.getValue();
1165 File pkgBinDir = new File(destDir, pkg.replace('/', File.separatorChar));
1166 pkgBinDir.mkdirs();
1167 File pkgInfoClass = new File(pkgBinDir, "package-info.class");
1168 if (pkgInfoClass.isFile() && pkgInfoClass.lastModified() >= sourceLastMod.longValue()) {
1169 continue;
1170 }
1171 log("Creating empty " + pkgInfoClass);
1172 OutputStream os = new FileOutputStream(pkgInfoClass);
1173 try {
1174 os.write(PACKAGE_INFO_CLASS_HEADER);
1175 byte[] name = pkg.getBytes("UTF-8");
1176 int length = name.length + /* "/package-info" */ 13;
1177 os.write((byte) length / 256);
1178 os.write((byte) length % 256);
1179 os.write(name);
1180 os.write(PACKAGE_INFO_CLASS_FOOTER);
1181 } finally {
1182 os.close();
1183 }
1184 }
1185 }
1186 private static final byte[] PACKAGE_INFO_CLASS_HEADER = {
1187 (byte) 0xca, (byte) 0xfe, (byte) 0xba, (byte) 0xbe, 0x00, 0x00, 0x00,
1188 0x31, 0x00, 0x07, 0x07, 0x00, 0x05, 0x07, 0x00, 0x06, 0x01, 0x00, 0x0a,
1189 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x01, 0x00,
1190 0x11, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2d, 0x69, 0x6e, 0x66,
1191 0x6f, 0x2e, 0x6a, 0x61, 0x76, 0x61, 0x01
1192 };
1193 private static final byte[] PACKAGE_INFO_CLASS_FOOTER = {
1194 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2d, 0x69, 0x6e, 0x66,
1195 0x6f, 0x01, 0x00, 0x10, 0x6a, 0x61, 0x76, 0x61, 0x2f, 0x6c, 0x61, 0x6e,
1196 0x67, 0x2f, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x02, 0x00, 0x00, 0x01,
1197 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
1198 0x00, 0x00, 0x00, 0x02, 0x00, 0x04
1199 };
1200
1201 }