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