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;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.PrintStream;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.Map;
32 import java.util.Properties;
33 import java.util.Set;
34 import java.util.Vector;
35
36 import org.apache.tools.ant.input.DefaultInputHandler;
37 import org.apache.tools.ant.input.InputHandler;
38 import org.apache.tools.ant.launch.AntMain;
39 import org.apache.tools.ant.util.ClasspathUtils;
40 import org.apache.tools.ant.util.FileUtils;
41 import org.apache.tools.ant.util.ProxySetup;
42
43
44 /**
45 * Command line entry point into Ant. This class is entered via the
46 * canonical `public static void main` entry point and reads the
47 * command line arguments. It then assembles and executes an Ant
48 * project.
49 * <p>
50 * If you integrating Ant into some other tool, this is not the class
51 * to use as an entry point. Please see the source code of this
52 * class to see how it manipulates the Ant project classes.
53 *
54 */
55 public class Main implements AntMain {
56
57 /**
58 * A Set of args are are handled by the launcher and should
59 * not be seen by Main.
60 */
61 private static final Set LAUNCH_COMMANDS = new HashSet();
62 static {
63 LAUNCH_COMMANDS.add("-lib");
64 LAUNCH_COMMANDS.add("-cp");
65 LAUNCH_COMMANDS.add("-noclasspath");
66 LAUNCH_COMMANDS.add("--noclasspath");
67 LAUNCH_COMMANDS.add("-nouserlib");
68 LAUNCH_COMMANDS.add("-main");
69 }
70
71 /** The default build file name. {@value} */
72 public static final String DEFAULT_BUILD_FILENAME = "build.xml";
73
74 /** Our current message output status. Follows Project.MSG_XXX. */
75 private int msgOutputLevel = Project.MSG_INFO;
76
77 /** File that we are using for configuration. */
78 private File buildFile; /* null */
79
80 /** Stream to use for logging. */
81 private static PrintStream out = System.out;
82
83 /** Stream that we are using for logging error messages. */
84 private static PrintStream err = System.err;
85
86 /** The build targets. */
87 private Vector targets = new Vector();
88
89 /** Set of properties that can be used by tasks. */
90 private Properties definedProps = new Properties();
91
92 /** Names of classes to add as listeners to project. */
93 private Vector listeners = new Vector(1);
94
95 /** File names of property files to load on startup. */
96 private Vector propertyFiles = new Vector(1);
97
98 /** Indicates whether this build is to support interactive input */
99 private boolean allowInput = true;
100
101 /** keep going mode */
102 private boolean keepGoingMode = false;
103
104 /**
105 * The Ant logger class. There may be only one logger. It will have
106 * the right to use the 'out' PrintStream. The class must implements the
107 * BuildLogger interface.
108 */
109 private String loggerClassname = null;
110
111 /**
112 * The Ant InputHandler class. There may be only one input
113 * handler.
114 */
115 private String inputHandlerClassname = null;
116
117 /**
118 * Whether or not output to the log is to be unadorned.
119 */
120 private boolean emacsMode = false;
121
122 /**
123 * Whether or not this instance has successfully been
124 * constructed and is ready to run.
125 */
126 private boolean readyToRun = false;
127
128 /**
129 * Whether or not we should only parse and display the project help
130 * information.
131 */
132 private boolean projectHelp = false;
133
134 /**
135 * Whether or not a logfile is being used. This is used to
136 * check if the output streams must be closed.
137 */
138 private static boolean isLogFileUsed = false;
139
140 /**
141 * optional thread priority
142 */
143 private Integer threadPriority = null;
144
145 /**
146 * proxy flag: default is false
147 */
148 private boolean proxy = false;
149
150 /**
151 * Prints the message of the Throwable if it (the message) is not
152 * <code>null</code>.
153 *
154 * @param t Throwable to print the message of.
155 * Must not be <code>null</code>.
156 */
157 private static void printMessage(Throwable t) {
158 String message = t.getMessage();
159 if (message != null) {
160 System.err.println(message);
161 }
162 }
163
164 /**
165 * Creates a new instance of this class using the
166 * arguments specified, gives it any extra user properties which have been
167 * specified, and then runs the build using the classloader provided.
168 *
169 * @param args Command line arguments. Must not be <code>null</code>.
170 * @param additionalUserProperties Any extra properties to use in this
171 * build. May be <code>null</code>, which is the equivalent to
172 * passing in an empty set of properties.
173 * @param coreLoader Classloader used for core classes. May be
174 * <code>null</code> in which case the system classloader is used.
175 */
176 public static void start(String[] args, Properties additionalUserProperties,
177 ClassLoader coreLoader) {
178 Main m = new Main();
179 m.startAnt(args, additionalUserProperties, coreLoader);
180 }
181
182 /**
183 * Start Ant
184 * @param args command line args
185 * @param additionalUserProperties properties to set beyond those that
186 * may be specified on the args list
187 * @param coreLoader - not used
188 *
189 * @since Ant 1.6
190 */
191 public void startAnt(String[] args, Properties additionalUserProperties,
192 ClassLoader coreLoader) {
193
194 try {
195 Diagnostics.validateVersion();
196 processArgs(args);
197 } catch (Throwable exc) {
198 handleLogfile();
199 printMessage(exc);
200 exit(1);
201 return;
202 }
203
204 if (additionalUserProperties != null) {
205 for (Enumeration e = additionalUserProperties.keys();
206 e.hasMoreElements();) {
207 String key = (String) e.nextElement();
208 String property = additionalUserProperties.getProperty(key);
209 definedProps.put(key, property);
210 }
211 }
212
213 // expect the worst
214 int exitCode = 1;
215 try {
216 try {
217 runBuild(coreLoader);
218 exitCode = 0;
219 } catch (ExitStatusException ese) {
220 exitCode = ese.getStatus();
221 if (exitCode != 0) {
222 throw ese;
223 }
224 }
225 } catch (BuildException be) {
226 if (err != System.err) {
227 printMessage(be);
228 }
229 } catch (Throwable exc) {
230 exc.printStackTrace();
231 printMessage(exc);
232 } finally {
233 handleLogfile();
234 }
235 exit(exitCode);
236 }
237
238 /**
239 * This operation is expected to call {@link System#exit(int)}, which
240 * is what the base version does.
241 * However, it is possible to do something else.
242 * @param exitCode code to exit with
243 */
244 protected void exit(int exitCode) {
245 System.exit(exitCode);
246 }
247
248 /**
249 * Close logfiles, if we have been writing to them.
250 *
251 * @since Ant 1.6
252 */
253 private static void handleLogfile() {
254 if (isLogFileUsed) {
255 FileUtils.close(out);
256 FileUtils.close(err);
257 }
258 }
259
260 /**
261 * Command line entry point. This method kicks off the building
262 * of a project object and executes a build using either a given
263 * target or the default target.
264 *
265 * @param args Command line arguments. Must not be <code>null</code>.
266 */
267 public static void main(String[] args) {
268 start(args, null, null);
269 }
270
271 /**
272 * Constructor used when creating Main for later arg processing
273 * and startup
274 */
275 public Main() {
276 }
277
278 /**
279 * Sole constructor, which parses and deals with command line
280 * arguments.
281 *
282 * @param args Command line arguments. Must not be <code>null</code>.
283 *
284 * @exception BuildException if the specified build file doesn't exist
285 * or is a directory.
286 *
287 * @deprecated since 1.6.x
288 */
289 protected Main(String[] args) throws BuildException {
290 processArgs(args);
291 }
292
293 /**
294 * Process command line arguments.
295 * When ant is started from Launcher, launcher-only arguments do not get
296 * passed through to this routine.
297 *
298 * @param args the command line arguments.
299 *
300 * @since Ant 1.6
301 */
302 private void processArgs(String[] args) {
303 String searchForThis = null;
304 PrintStream logTo = null;
305
306 // cycle through given args
307
308 for (int i = 0; i < args.length; i++) {
309 String arg = args[i];
310
311 if (arg.equals("-help") || arg.equals("-h")) {
312 printUsage();
313 return;
314 } else if (arg.equals("-version")) {
315 printVersion();
316 return;
317 } else if (arg.equals("-diagnostics")) {
318 Diagnostics.doReport(System.out);
319 return;
320 } else if (arg.equals("-quiet") || arg.equals("-q")) {
321 msgOutputLevel = Project.MSG_WARN;
322 } else if (arg.equals("-verbose") || arg.equals("-v")) {
323 printVersion();
324 msgOutputLevel = Project.MSG_VERBOSE;
325 } else if (arg.equals("-debug") || arg.equals("-d")) {
326 printVersion();
327 msgOutputLevel = Project.MSG_DEBUG;
328 } else if (arg.equals("-noinput")) {
329 allowInput = false;
330 } else if (arg.equals("-logfile") || arg.equals("-l")) {
331 try {
332 File logFile = new File(args[i + 1]);
333 i++;
334 logTo = new PrintStream(new FileOutputStream(logFile));
335 isLogFileUsed = true;
336 } catch (IOException ioe) {
337 String msg = "Cannot write on the specified log file. "
338 + "Make sure the path exists and you have write "
339 + "permissions.";
340 throw new BuildException(msg);
341 } catch (ArrayIndexOutOfBoundsException aioobe) {
342 String msg = "You must specify a log file when "
343 + "using the -log argument";
344 throw new BuildException(msg);
345 }
346 } else if (arg.equals("-buildfile") || arg.equals("-file")
347 || arg.equals("-f")) {
348 i = handleArgBuildFile(args, i);
349 } else if (arg.equals("-listener")) {
350 i = handleArgListener(args, i);
351 } else if (arg.startsWith("-D")) {
352 i = handleArgDefine(args, i);
353 } else if (arg.equals("-logger")) {
354 i = handleArgLogger(args, i);
355 } else if (arg.equals("-inputhandler")) {
356 i = handleArgInputHandler(args, i);
357 } else if (arg.equals("-emacs") || arg.equals("-e")) {
358 emacsMode = true;
359 } else if (arg.equals("-projecthelp") || arg.equals("-p")) {
360 // set the flag to display the targets and quit
361 projectHelp = true;
362 } else if (arg.equals("-find") || arg.equals("-s")) {
363 // eat up next arg if present, default to build.xml
364 if (i < args.length - 1) {
365 searchForThis = args[++i];
366 } else {
367 searchForThis = DEFAULT_BUILD_FILENAME;
368 }
369 } else if (arg.startsWith("-propertyfile")) {
370 i = handleArgPropertyFile(args, i);
371 } else if (arg.equals("-k") || arg.equals("-keep-going")) {
372 keepGoingMode = true;
373 } else if (arg.equals("-nice")) {
374 i = handleArgNice(args, i);
375 } else if (LAUNCH_COMMANDS.contains(arg)) {
376 //catch script/ant mismatch with a meaningful message
377 //we could ignore it, but there are likely to be other
378 //version problems, so we stamp down on the configuration now
379 String msg = "Ant's Main method is being handed "
380 + "an option " + arg + " that is only for the launcher class."
381 + "\nThis can be caused by a version mismatch between "
382 + "the ant script/.bat file and Ant itself.";
383 throw new BuildException(msg);
384 } else if (arg.equals("-autoproxy")) {
385 proxy = true;
386 } else if (arg.startsWith("-")) {
387 // we don't have any more args to recognize!
388 String msg = "Unknown argument: " + arg;
389 System.err.println(msg);
390 printUsage();
391 throw new BuildException("");
392 } else {
393 // if it's no other arg, it may be the target
394 targets.addElement(arg);
395 }
396 }
397
398 // if buildFile was not specified on the command line,
399 if (buildFile == null) {
400 // but -find then search for it
401 if (searchForThis != null) {
402 buildFile = findBuildFile(System.getProperty("user.dir"),
403 searchForThis);
404 } else {
405 buildFile = new File(DEFAULT_BUILD_FILENAME);
406 }
407 }
408
409 // make sure buildfile exists
410 if (!buildFile.exists()) {
411 System.out.println("Buildfile: " + buildFile + " does not exist!");
412 throw new BuildException("Build failed");
413 }
414
415 // make sure it's not a directory (this falls into the ultra
416 // paranoid lets check everything category
417
418 if (buildFile.isDirectory()) {
419 System.out.println("What? Buildfile: " + buildFile + " is a dir!");
420 throw new BuildException("Build failed");
421 }
422
423 // Load the property files specified by -propertyfile
424 loadPropertyFiles();
425
426 if (msgOutputLevel >= Project.MSG_INFO) {
427 System.out.println("Buildfile: " + buildFile);
428 }
429
430 if (logTo != null) {
431 out = logTo;
432 err = logTo;
433 System.setOut(out);
434 System.setErr(err);
435 }
436 readyToRun = true;
437 }
438
439 // --------------------------------------------------------
440 // Methods for handling the command line arguments
441 // --------------------------------------------------------
442
443 /** Handle the -buildfile, -file, -f argument */
444 private int handleArgBuildFile(String[] args, int pos) {
445 try {
446 buildFile = new File(
447 args[++pos].replace('/', File.separatorChar));
448 } catch (ArrayIndexOutOfBoundsException aioobe) {
449 throw new BuildException(
450 "You must specify a buildfile when using the -buildfile argument");
451 }
452 return pos;
453 }
454
455 /** Handle -listener argument */
456 private int handleArgListener(String[] args, int pos) {
457 try {
458 listeners.addElement(args[pos + 1]);
459 pos++;
460 } catch (ArrayIndexOutOfBoundsException aioobe) {
461 String msg = "You must specify a classname when "
462 + "using the -listener argument";
463 throw new BuildException(msg);
464 }
465 return pos;
466 }
467
468 /** Handler -D argument */
469 private int handleArgDefine(String[] args, int argPos) {
470 /* Interestingly enough, we get to here when a user
471 * uses -Dname=value. However, in some cases, the OS
472 * goes ahead and parses this out to args
473 * {"-Dname", "value"}
474 * so instead of parsing on "=", we just make the "-D"
475 * characters go away and skip one argument forward.
476 *
477 * I don't know how to predict when the JDK is going
478 * to help or not, so we simply look for the equals sign.
479 */
480 String arg = args[argPos];
481 String name = arg.substring(2, arg.length());
482 String value = null;
483 int posEq = name.indexOf("=");
484 if (posEq > 0) {
485 value = name.substring(posEq + 1);
486 name = name.substring(0, posEq);
487 } else if (argPos < args.length - 1) {
488 value = args[++argPos];
489 } else {
490 throw new BuildException("Missing value for property "
491 + name);
492 }
493 definedProps.put(name, value);
494 return argPos;
495 }
496
497 /** Handle the -logger argument. */
498 private int handleArgLogger(String[] args, int pos) {
499 if (loggerClassname != null) {
500 throw new BuildException(
501 "Only one logger class may be specified.");
502 }
503 try {
504 loggerClassname = args[++pos];
505 } catch (ArrayIndexOutOfBoundsException aioobe) {
506 throw new BuildException(
507 "You must specify a classname when using the -logger argument");
508 }
509 return pos;
510 }
511
512 /** Handle the -inputhandler argument. */
513 private int handleArgInputHandler(String[] args, int pos) {
514 if (inputHandlerClassname != null) {
515 throw new BuildException("Only one input handler class may "
516 + "be specified.");
517 }
518 try {
519 inputHandlerClassname = args[++pos];
520 } catch (ArrayIndexOutOfBoundsException aioobe) {
521 throw new BuildException("You must specify a classname when"
522 + " using the -inputhandler"
523 + " argument");
524 }
525 return pos;
526 }
527
528 /** Handle the -propertyfile argument. */
529 private int handleArgPropertyFile(String[] args, int pos) {
530 try {
531 propertyFiles.addElement(args[++pos]);
532 } catch (ArrayIndexOutOfBoundsException aioobe) {
533 String msg = "You must specify a property filename when "
534 + "using the -propertyfile argument";
535 throw new BuildException(msg);
536 }
537 return pos;
538 }
539
540 /** Handle the -nice argument. */
541 private int handleArgNice(String[] args, int pos) {
542 try {
543 threadPriority = Integer.decode(args[++pos]);
544 } catch (ArrayIndexOutOfBoundsException aioobe) {
545 throw new BuildException(
546 "You must supply a niceness value (1-10)"
547 + " after the -nice option");
548 } catch (NumberFormatException e) {
549 throw new BuildException("Unrecognized niceness value: "
550 + args[pos]);
551 }
552
553 if (threadPriority.intValue() < Thread.MIN_PRIORITY
554 || threadPriority.intValue() > Thread.MAX_PRIORITY) {
555 throw new BuildException(
556 "Niceness value is out of the range 1-10");
557 }
558 return pos;
559 }
560
561 // --------------------------------------------------------
562 // other methods
563 // --------------------------------------------------------
564
565 /** Load the property files specified by -propertyfile */
566 private void loadPropertyFiles() {
567 for (int propertyFileIndex = 0;
568 propertyFileIndex < propertyFiles.size();
569 propertyFileIndex++) {
570 String filename
571 = (String) propertyFiles.elementAt(propertyFileIndex);
572 Properties props = new Properties();
573 FileInputStream fis = null;
574 try {
575 fis = new FileInputStream(filename);
576 props.load(fis);
577 } catch (IOException e) {
578 System.out.println("Could not load property file "
579 + filename + ": " + e.getMessage());
580 } finally {
581 FileUtils.close(fis);
582 }
583
584 // ensure that -D properties take precedence
585 Enumeration propertyNames = props.propertyNames();
586 while (propertyNames.hasMoreElements()) {
587 String name = (String) propertyNames.nextElement();
588 if (definedProps.getProperty(name) == null) {
589 definedProps.put(name, props.getProperty(name));
590 }
591 }
592 }
593 }
594
595 /**
596 * Helper to get the parent file for a given file.
597 * <p>
598 * Added to simulate File.getParentFile() from JDK 1.2.
599 * @deprecated since 1.6.x
600 *
601 * @param file File to find parent of. Must not be <code>null</code>.
602 * @return Parent file or null if none
603 */
604 private File getParentFile(File file) {
605 File parent = file.getParentFile();
606
607 if (parent != null && msgOutputLevel >= Project.MSG_VERBOSE) {
608 System.out.println("Searching in " + parent.getAbsolutePath());
609 }
610
611 return parent;
612 }
613
614 /**
615 * Search parent directories for the build file.
616 * <p>
617 * Takes the given target as a suffix to append to each
618 * parent directory in search of a build file. Once the
619 * root of the file-system has been reached an exception
620 * is thrown.
621 *
622 * @param start Leaf directory of search.
623 * Must not be <code>null</code>.
624 * @param suffix Suffix filename to look for in parents.
625 * Must not be <code>null</code>.
626 *
627 * @return A handle to the build file if one is found
628 *
629 * @exception BuildException if no build file is found
630 */
631 private File findBuildFile(String start, String suffix)
632 throws BuildException {
633 if (msgOutputLevel >= Project.MSG_INFO) {
634 System.out.println("Searching for " + suffix + " ...");
635 }
636
637 File parent = new File(new File(start).getAbsolutePath());
638 File file = new File(parent, suffix);
639
640 // check if the target file exists in the current directory
641 while (!file.exists()) {
642 // change to parent directory
643 parent = getParentFile(parent);
644
645 // if parent is null, then we are at the root of the fs,
646 // complain that we can't find the build file.
647 if (parent == null) {
648 throw new BuildException("Could not locate a build file!");
649 }
650
651 // refresh our file handle
652 file = new File(parent, suffix);
653 }
654
655 return file;
656 }
657
658 /**
659 * Executes the build. If the constructor for this instance failed
660 * (e.g. returned after issuing a warning), this method returns
661 * immediately.
662 *
663 * @param coreLoader The classloader to use to find core classes.
664 * May be <code>null</code>, in which case the
665 * system classloader is used.
666 *
667 * @exception BuildException if the build fails
668 */
669 private void runBuild(ClassLoader coreLoader) throws BuildException {
670
671 if (!readyToRun) {
672 return;
673 }
674
675 final Project project = new Project();
676 project.setCoreLoader(coreLoader);
677
678 Throwable error = null;
679
680 try {
681 addBuildListeners(project);
682 addInputHandler(project);
683
684 PrintStream savedErr = System.err;
685 PrintStream savedOut = System.out;
686 InputStream savedIn = System.in;
687
688 // use a system manager that prevents from System.exit()
689 SecurityManager oldsm = null;
690 oldsm = System.getSecurityManager();
691
692 //SecurityManager can not be installed here for backwards
693 //compatibility reasons (PD). Needs to be loaded prior to
694 //ant class if we are going to implement it.
695 //System.setSecurityManager(new NoExitSecurityManager());
696 try {
697 if (allowInput) {
698 project.setDefaultInputStream(System.in);
699 }
700 System.setIn(new DemuxInputStream(project));
701 System.setOut(new PrintStream(new DemuxOutputStream(project, false)));
702 System.setErr(new PrintStream(new DemuxOutputStream(project, true)));
703
704
705 if (!projectHelp) {
706 project.fireBuildStarted();
707 }
708
709 // set the thread priorities
710 if (threadPriority != null) {
711 try {
712 project.log("Setting Ant's thread priority to "
713 + threadPriority, Project.MSG_VERBOSE);
714 Thread.currentThread().setPriority(threadPriority.intValue());
715 } catch (SecurityException swallowed) {
716 //we cannot set the priority here.
717 project.log("A security manager refused to set the -nice value");
718 }
719 }
720
721
722
723 project.init();
724
725 // set user-define properties
726 Enumeration e = definedProps.keys();
727 while (e.hasMoreElements()) {
728 String arg = (String) e.nextElement();
729 String value = (String) definedProps.get(arg);
730 project.setUserProperty(arg, value);
731 }
732
733 project.setUserProperty(MagicNames.ANT_FILE,
734 buildFile.getAbsolutePath());
735
736 project.setKeepGoingMode(keepGoingMode);
737 if (proxy) {
738 //proxy setup if enabled
739 ProxySetup proxySetup = new ProxySetup(project);
740 proxySetup.enableProxies();
741 }
742
743 ProjectHelper.configureProject(project, buildFile);
744
745 if (projectHelp) {
746 printDescription(project);
747 printTargets(project, msgOutputLevel > Project.MSG_INFO);
748 return;
749 }
750
751 // make sure that we have a target to execute
752 if (targets.size() == 0) {
753 if (project.getDefaultTarget() != null) {
754 targets.addElement(project.getDefaultTarget());
755 }
756 }
757
758 project.executeTargets(targets);
759 } finally {
760 // put back the original security manager
761 //The following will never eval to true. (PD)
762 if (oldsm != null) {
763 System.setSecurityManager(oldsm);
764 }
765
766 System.setOut(savedOut);
767 System.setErr(savedErr);
768 System.setIn(savedIn);
769 }
770 } catch (RuntimeException exc) {
771 error = exc;
772 throw exc;
773 } catch (Error e) {
774 error = e;
775 throw e;
776 } finally {
777 if (!projectHelp) {
778 project.fireBuildFinished(error);
779 } else if (error != null) {
780 project.log(error.toString(), Project.MSG_ERR);
781 }
782 }
783 }
784
785 /**
786 * Adds the listeners specified in the command line arguments,
787 * along with the default listener, to the specified project.
788 *
789 * @param project The project to add listeners to.
790 * Must not be <code>null</code>.
791 */
792 protected void addBuildListeners(Project project) {
793
794 // Add the default listener
795 project.addBuildListener(createLogger());
796
797 for (int i = 0; i < listeners.size(); i++) {
798 String className = (String) listeners.elementAt(i);
799 BuildListener listener =
800 (BuildListener) ClasspathUtils.newInstance(className,
801 Main.class.getClassLoader(), BuildListener.class);
802 project.setProjectReference(listener);
803
804 project.addBuildListener(listener);
805 }
806 }
807
808 /**
809 * Creates the InputHandler and adds it to the project.
810 *
811 * @param project the project instance.
812 *
813 * @exception BuildException if a specified InputHandler
814 * implementation could not be loaded.
815 */
816 private void addInputHandler(Project project) throws BuildException {
817 InputHandler handler = null;
818 if (inputHandlerClassname == null) {
819 handler = new DefaultInputHandler();
820 } else {
821 handler = (InputHandler) ClasspathUtils.newInstance(
822 inputHandlerClassname, Main.class.getClassLoader(),
823 InputHandler.class);
824 project.setProjectReference(handler);
825 }
826 project.setInputHandler(handler);
827 }
828
829 // XXX: (Jon Skeet) Any reason for writing a message and then using a bare
830 // RuntimeException rather than just using a BuildException here? Is it
831 // in case the message could end up being written to no loggers (as the
832 // loggers could have failed to be created due to this failure)?
833 /**
834 * Creates the default build logger for sending build events to the ant
835 * log.
836 *
837 * @return the logger instance for this build.
838 */
839 private BuildLogger createLogger() {
840 BuildLogger logger = null;
841 if (loggerClassname != null) {
842 try {
843 logger = (BuildLogger) ClasspathUtils.newInstance(
844 loggerClassname, Main.class.getClassLoader(),
845 BuildLogger.class);
846 } catch (BuildException e) {
847 System.err.println("The specified logger class "
848 + loggerClassname
849 + " could not be used because " + e.getMessage());
850 throw new RuntimeException();
851 }
852 } else {
853 logger = new DefaultLogger();
854 }
855
856 logger.setMessageOutputLevel(msgOutputLevel);
857 logger.setOutputPrintStream(out);
858 logger.setErrorPrintStream(err);
859 logger.setEmacsMode(emacsMode);
860
861 return logger;
862 }
863
864 /**
865 * Prints the usage information for this class to <code>System.out</code>.
866 */
867 private static void printUsage() {
868 String lSep = System.getProperty("line.separator");
869 StringBuffer msg = new StringBuffer();
870 msg.append("ant [options] [target [target2 [target3] ...]]" + lSep);
871 msg.append("Options: " + lSep);
872 msg.append(" -help, -h print this message" + lSep);
873 msg.append(" -projecthelp, -p print project help information" + lSep);
874 msg.append(" -version print the version information and exit" + lSep);
875 msg.append(" -diagnostics print information that might be helpful to" + lSep);
876 msg.append(" diagnose or report problems." + lSep);
877 msg.append(" -quiet, -q be extra quiet" + lSep);
878 msg.append(" -verbose, -v be extra verbose" + lSep);
879 msg.append(" -debug, -d print debugging information" + lSep);
880 msg.append(" -emacs, -e produce logging information without adornments"
881 + lSep);
882 msg.append(" -lib <path> specifies a path to search for jars and classes"
883 + lSep);
884 msg.append(" -logfile <file> use given file for log" + lSep);
885 msg.append(" -l <file> ''" + lSep);
886 msg.append(" -logger <classname> the class which is to perform logging" + lSep);
887 msg.append(" -listener <classname> add an instance of class as a project listener"
888 + lSep);
889 msg.append(" -noinput do not allow interactive input" + lSep);
890 msg.append(" -buildfile <file> use given buildfile" + lSep);
891 msg.append(" -file <file> ''" + lSep);
892 msg.append(" -f <file> ''" + lSep);
893 msg.append(" -D<property>=<value> use value for given property" + lSep);
894 msg.append(" -keep-going, -k execute all targets that do not depend" + lSep);
895 msg.append(" on failed target(s)" + lSep);
896 msg.append(" -propertyfile <name> load all properties from file with -D" + lSep);
897 msg.append(" properties taking precedence" + lSep);
898 msg.append(" -inputhandler <class> the class which will handle input requests" + lSep);
899 msg.append(" -find <file> (s)earch for buildfile towards the root of" + lSep);
900 msg.append(" -s <file> the filesystem and use it" + lSep);
901 msg.append(" -nice number A niceness value for the main thread:" + lSep
902 + " 1 (lowest) to 10 (highest); 5 is the default"
903 + lSep);
904 msg.append(" -nouserlib Run ant without using the jar files from" + lSep
905 + " ${user.home}/.ant/lib" + lSep);
906 msg.append(" -noclasspath Run ant without using CLASSPATH" + lSep);
907 msg.append(" -autoproxy Java1.5+: use the OS proxy settings"
908 + lSep);
909 msg.append(" -main <class> override Ant's normal entry point");
910 System.out.println(msg.toString());
911 }
912
913 /**
914 * Prints the Ant version information to <code>System.out</code>.
915 *
916 * @exception BuildException if the version information is unavailable
917 */
918 private static void printVersion() throws BuildException {
919 System.out.println(getAntVersion());
920 }
921
922 /**
923 * Cache of the Ant version information when it has been loaded.
924 */
925 private static String antVersion = null;
926
927 /**
928 * Returns the Ant version information, if available. Once the information
929 * has been loaded once, it's cached and returned from the cache on future
930 * calls.
931 *
932 * @return the Ant version information as a String
933 * (always non-<code>null</code>)
934 *
935 * @exception BuildException if the version information is unavailable
936 */
937 public static synchronized String getAntVersion() throws BuildException {
938 if (antVersion == null) {
939 try {
940 Properties props = new Properties();
941 InputStream in =
942 Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt");
943 props.load(in);
944 in.close();
945
946 StringBuffer msg = new StringBuffer();
947 msg.append("Apache Ant version ");
948 msg.append(props.getProperty("VERSION"));
949 msg.append(" compiled on ");
950 msg.append(props.getProperty("DATE"));
951 antVersion = msg.toString();
952 } catch (IOException ioe) {
953 throw new BuildException("Could not load the version information:"
954 + ioe.getMessage());
955 } catch (NullPointerException npe) {
956 throw new BuildException("Could not load the version information.");
957 }
958 }
959 return antVersion;
960 }
961
962 /**
963 * Prints the description of a project (if there is one) to
964 * <code>System.out</code>.
965 *
966 * @param project The project to display a description of.
967 * Must not be <code>null</code>.
968 */
969 private static void printDescription(Project project) {
970 if (project.getDescription() != null) {
971 project.log(project.getDescription());
972 }
973 }
974
975 /**
976 * Targets in imported files with a project name
977 * and not overloaded by the main build file will
978 * be in the target map twice. This method
979 * removes the duplicate target.
980 * @param targets the targets to filter.
981 * @return the filtered targets.
982 */
983 private static Map removeDuplicateTargets(Map targets) {
984 Map locationMap = new HashMap();
985 for (Iterator i = targets.entrySet().iterator(); i.hasNext();) {
986 Map.Entry entry = (Map.Entry) i.next();
987 String name = (String) entry.getKey();
988 Target target = (Target) entry.getValue();
989 Target otherTarget =
990 (Target) locationMap.get(target.getLocation());
991 // Place this entry in the location map if
992 // a) location is not in the map
993 // b) location is in map, but it's name is longer
994 // (an imported target will have a name. prefix)
995 if (otherTarget == null
996 || otherTarget.getName().length() > name.length()) {
997 locationMap.put(
998 target.getLocation(), target); // Smallest name wins
999 }
1000 }
1001 Map ret = new HashMap();
1002 for (Iterator i = locationMap.values().iterator(); i.hasNext();) {
1003 Target target = (Target) i.next();
1004 ret.put(target.getName(), target);
1005 }
1006 return ret;
1007 }
1008
1009 /**
1010 * Prints a list of all targets in the specified project to
1011 * <code>System.out</code>, optionally including subtargets.
1012 *
1013 * @param project The project to display a description of.
1014 * Must not be <code>null</code>.
1015 * @param printSubTargets Whether or not subtarget names should also be
1016 * printed.
1017 */
1018 private static void printTargets(Project project, boolean printSubTargets) {
1019 // find the target with the longest name
1020 int maxLength = 0;
1021 Map ptargets = removeDuplicateTargets(project.getTargets());
1022 String targetName;
1023 String targetDescription;
1024 Target currentTarget;
1025 // split the targets in top-level and sub-targets depending
1026 // on the presence of a description
1027 Vector topNames = new Vector();
1028 Vector topDescriptions = new Vector();
1029 Vector subNames = new Vector();
1030
1031 for (Iterator i = ptargets.values().iterator(); i.hasNext();) {
1032 currentTarget = (Target) i.next();
1033 targetName = currentTarget.getName();
1034 if (targetName.equals("")) {
1035 continue;
1036 }
1037 targetDescription = currentTarget.getDescription();
1038 // maintain a sorted list of targets
1039 if (targetDescription == null) {
1040 int pos = findTargetPosition(subNames, targetName);
1041 subNames.insertElementAt(targetName, pos);
1042 } else {
1043 int pos = findTargetPosition(topNames, targetName);
1044 topNames.insertElementAt(targetName, pos);
1045 topDescriptions.insertElementAt(targetDescription, pos);
1046 if (targetName.length() > maxLength) {
1047 maxLength = targetName.length();
1048 }
1049 }
1050 }
1051
1052 printTargets(project, topNames, topDescriptions, "Main targets:",
1053 maxLength);
1054 //if there were no main targets, we list all subtargets
1055 //as it means nothing has a description
1056 if (topNames.size() == 0) {
1057 printSubTargets = true;
1058 }
1059 if (printSubTargets) {
1060 printTargets(project, subNames, null, "Other targets:", 0);
1061 }
1062
1063 String defaultTarget = project.getDefaultTarget();
1064 if (defaultTarget != null && !"".equals(defaultTarget)) {
1065 // shouldn't need to check but...
1066 project.log("Default target: " + defaultTarget);
1067 }
1068 }
1069
1070 /**
1071 * Searches for the correct place to insert a name into a list so as
1072 * to keep the list sorted alphabetically.
1073 *
1074 * @param names The current list of names. Must not be <code>null</code>.
1075 * @param name The name to find a place for.
1076 * Must not be <code>null</code>.
1077 *
1078 * @return the correct place in the list for the given name
1079 */
1080 private static int findTargetPosition(Vector names, String name) {
1081 int res = names.size();
1082 for (int i = 0; i < names.size() && res == names.size(); i++) {
1083 if (name.compareTo((String) names.elementAt(i)) < 0) {
1084 res = i;
1085 }
1086 }
1087 return res;
1088 }
1089
1090 /**
1091 * Writes a formatted list of target names to <code>System.out</code>
1092 * with an optional description.
1093 *
1094 *
1095 * @param project the project instance.
1096 * @param names The names to be printed.
1097 * Must not be <code>null</code>.
1098 * @param descriptions The associated target descriptions.
1099 * May be <code>null</code>, in which case
1100 * no descriptions are displayed.
1101 * If non-<code>null</code>, this should have
1102 * as many elements as <code>names</code>.
1103 * @param heading The heading to display.
1104 * Should not be <code>null</code>.
1105 * @param maxlen The maximum length of the names of the targets.
1106 * If descriptions are given, they are padded to this
1107 * position so they line up (so long as the names really
1108 * <i>are</i> shorter than this).
1109 */
1110 private static void printTargets(Project project, Vector names,
1111 Vector descriptions, String heading,
1112 int maxlen) {
1113 // now, start printing the targets and their descriptions
1114 String lSep = System.getProperty("line.separator");
1115 // got a bit annoyed that I couldn't find a pad function
1116 String spaces = " ";
1117 while (spaces.length() <= maxlen) {
1118 spaces += spaces;
1119 }
1120 StringBuffer msg = new StringBuffer();
1121 msg.append(heading + lSep + lSep);
1122 for (int i = 0; i < names.size(); i++) {
1123 msg.append(" ");
1124 msg.append(names.elementAt(i));
1125 if (descriptions != null) {
1126 msg.append(
1127 spaces.substring(0, maxlen - ((String) names.elementAt(i)).length() + 2));
1128 msg.append(descriptions.elementAt(i));
1129 }
1130 msg.append(lSep);
1131 }
1132 project.log(msg.toString(), Project.MSG_WARN);
1133 }
1134 }