The main change is the new BigProjectLogger that makes reading the results of very big chained/nested projects manageable. Some pulling up of helper methods into DefaultLogger, and a bit of cleanup there; plus the appopriate documentation changes git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@539477 13f79535-47bb-0310-9956-ffa450edef68master
| @@ -148,6 +148,8 @@ Other changes: | |||||
| <comment> nested element as nested text instead of using the 'value' | <comment> nested element as nested text instead of using the 'value' | ||||
| attribute. | attribute. | ||||
| * A new logger, BigProjectLogger, lists the project name with every target | |||||
| Changes from Ant 1.6.5 to Ant 1.7.0 | Changes from Ant 1.6.5 to Ant 1.7.0 | ||||
| =================================== | =================================== | ||||
| @@ -44,13 +44,19 @@ listeners and loggers.</p> | |||||
| <li>message logged</li> | <li>message logged</li> | ||||
| </ul> | </ul> | ||||
| <p> | |||||
| These are used internally for various recording and housekeeping operations, | |||||
| however new listeners may registered on the command line through the <code>-listener</code> | |||||
| argument. | |||||
| </p> | |||||
| <h3><a name="Loggers">Loggers</a></h3> | <h3><a name="Loggers">Loggers</a></h3> | ||||
| <p>Loggers extend the capabilities of listeners and add the following features:</p> | <p>Loggers extend the capabilities of listeners and add the following features:</p> | ||||
| <ul> | <ul> | ||||
| <li>Receives a handle to the standard output and error print streams and | <li>Receives a handle to the standard output and error print streams and | ||||
| therefore can log information to the console or the -logfile specified file.</li> | |||||
| therefore can log information to the console or the <code>-logfile</code> specified file.</li> | |||||
| <li>Logging level (-quiet, -verbose, -debug) aware</li> | <li>Logging level (-quiet, -verbose, -debug) aware</li> | ||||
| <li>Emacs-mode aware</li> | <li>Emacs-mode aware</li> | ||||
| </ul> | </ul> | ||||
| @@ -104,7 +110,11 @@ listeners and loggers.</p> | |||||
| <td width="33%">Prints the time that a build finished</td> | <td width="33%">Prints the time that a build finished</td> | ||||
| <td width="34%">BuildLogger</td> | <td width="34%">BuildLogger</td> | ||||
| </tr> | </tr> | ||||
| <tr> | |||||
| <td width="33%"><code><a href="#BigProjectLogger">org.apache.tools.ant.BigProjectLogger</a></code></td> | |||||
| <td width="33%">Prints the project name every target</td> | |||||
| <td width="34%">BuildLogger</td> | |||||
| </tr> | |||||
| </table> | </table> | ||||
| <h3><a name="DefaultLogger">DefaultLogger</a></h3> | <h3><a name="DefaultLogger">DefaultLogger</a></h3> | ||||
| @@ -235,7 +245,7 @@ in the console using applications like cat, more, etc.</p> | |||||
| color codes. It works on XTerm, ETerm, Win9x Console | color codes. It works on XTerm, ETerm, Win9x Console | ||||
| (with ANSI.SYS loaded.), etc.</p> | (with ANSI.SYS loaded.), etc.</p> | ||||
| <p><Strong>NOTE:</Strong> | <p><Strong>NOTE:</Strong> | ||||
| It doesn't work on WinNT even when a COMMAND.COM console loaded with | |||||
| It doesn't work on WinNT and successors, even when a COMMAND.COM console loaded with | |||||
| ANSI.SYS is used.</p> | ANSI.SYS is used.</p> | ||||
| <p>If the user wishes to override the default colors | <p>If the user wishes to override the default colors | ||||
| with custom ones, a file containing zero or more of the | with custom ones, a file containing zero or more of the | ||||
| @@ -318,7 +328,7 @@ corresponding Log4j level.</p> | |||||
| </blockquote> | </blockquote> | ||||
| <p>To use Log4j you will need the Log4j jar file and a 'log4j.properties' | |||||
| <p>To use Log4j you will need the Log4j JAR file and a 'log4j.properties' | |||||
| configuration file. Both should be placed somewhere in your Ant | configuration file. Both should be placed somewhere in your Ant | ||||
| classpath. If the log4j.properties is in your project root folder you can | classpath. If the log4j.properties is in your project root folder you can | ||||
| add this with <i>-lib</i> option:</p> | add this with <i>-lib</i> option:</p> | ||||
| @@ -386,6 +396,7 @@ is declared at all. | |||||
| <pre> | <pre> | ||||
| BUILD SUCCESSFUL - at 16/08/05 16:24 | BUILD SUCCESSFUL - at 16/08/05 16:24 | ||||
| </pre> | </pre> | ||||
| <p>To use this listener, use the command:</p> | |||||
| <blockquote> | <blockquote> | ||||
| @@ -393,6 +404,63 @@ is declared at all. | |||||
| </blockquote> | </blockquote> | ||||
| <h3><a name="BigProjectLogger">BigProjectLogger</a></h3> | |||||
| <p> | |||||
| This logger is designed to make examining the logs of a big build easier, | |||||
| especially those run under continuous integration tools. It | |||||
| </p> | |||||
| <ol> | |||||
| <li>When entering a child project, prints its name and directory</li> | |||||
| <li>When exiting a child project, prints its name</li> | |||||
| <li>Includes the name of the project when printing a target</li> | |||||
| <li>Omits logging the names of all targets that have no direct task output</li> | |||||
| <li>Includes the build finished timestamp of the TimeStamp logger</li> | |||||
| </ol> | |||||
| <p> | |||||
| This is useful when using <subant> to build a large project | |||||
| from many smaller projects -the output shows which particular | |||||
| project is building. Here is an example in which "clean" is being called | |||||
| on all a number of child projects, only some of which perform work: | |||||
| </p> | |||||
| <pre> | |||||
| ====================================================================== | |||||
| Entering project "xunit" | |||||
| In /home/ant/components/xunit | |||||
| ====================================================================== | |||||
| xunit.clean: | |||||
| [delete] Deleting directory /home/ant/components/xunit/build | |||||
| [delete] Deleting directory /home/ant/components/xunit/dist | |||||
| ====================================================================== | |||||
| Exiting project "xunit" | |||||
| ====================================================================== | |||||
| ====================================================================== | |||||
| Entering project "junit" | |||||
| In /home/ant/components/junit | |||||
| ====================================================================== | |||||
| ====================================================================== | |||||
| Exiting project "junit" | |||||
| ====================================================================== | |||||
| </pre> | |||||
| <p> | |||||
| The entry and exit messages are very verbose in this example, but in | |||||
| a big project compiling or testing many child components, the messages | |||||
| are reduced to becoming clear delimiters of where different projects | |||||
| are in charge -or more importantly, which project is failing. | |||||
| </p> | |||||
| <p>To use this listener, use the command:</p> | |||||
| <blockquote> | |||||
| <code>ant -logger org.apache.tools.ant.listener.BigProjectLogger</code> | |||||
| </blockquote> | |||||
| <h2><a name="dev">Writing your own</a></h2> | <h2><a name="dev">Writing your own</a></h2> | ||||
| @@ -402,8 +470,25 @@ developers.</p> | |||||
| <p>Notes:</p> | <p>Notes:</p> | ||||
| <ul> | <ul> | ||||
| <li>A listener or logger should not write to standard output or error; Ant | |||||
| captures these internally and may cause an infinite loop.</li> | |||||
| <li> | |||||
| A listener or logger should not write to standard output or error in the <code>messageLogged() method</code>; | |||||
| Ant captures these internally and it will trigger an infinite loop. | |||||
| </li> | |||||
| <li> | |||||
| Logging is synchronous; all listeners and loggers are called one after the other, with the build blocking until | |||||
| the output is processed. Slow logging means a slow build. | |||||
| </li> | |||||
| <li>When a build is started, and <code>BuildListener.buildStarted(BuildEvent event)</code> is called, | |||||
| the project is not fully functional. The build has started, yes, and the event.getProject() method call | |||||
| returns the Project instance, but that project is initialized with JVM and ant properties, nor has it | |||||
| parsed the build file yet. You cannot call <code>Project.getProperty()</code> for property lookup, or | |||||
| <code>Project.getName()</code> to get the project name (it will return null). | |||||
| </li> | |||||
| <li> | |||||
| Classes that implement <code>org.apache.tools.ant.SubBuildListener</code> receive notifications when child projects | |||||
| start and stop. | |||||
| </li> | |||||
| </ul> | </ul> | ||||
| @@ -22,9 +22,12 @@ import java.io.BufferedReader; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| import java.io.PrintStream; | import java.io.PrintStream; | ||||
| import java.io.StringReader; | import java.io.StringReader; | ||||
| import java.util.Date; | |||||
| import java.text.DateFormat; | |||||
| import org.apache.tools.ant.util.DateUtils; | import org.apache.tools.ant.util.DateUtils; | ||||
| import org.apache.tools.ant.util.StringUtils; | import org.apache.tools.ant.util.StringUtils; | ||||
| import org.apache.tools.ant.util.FileUtils; | |||||
| /** | /** | ||||
| * Writes build events to a PrintStream. Currently, it | * Writes build events to a PrintStream. Currently, it | ||||
| @@ -251,9 +254,9 @@ public class DefaultLogger implements BuildLogger { | |||||
| tmp.append(label); | tmp.append(label); | ||||
| label = tmp.toString(); | label = tmp.toString(); | ||||
| BufferedReader r = null; | |||||
| try { | try { | ||||
| BufferedReader r = | |||||
| new BufferedReader( | |||||
| r = new BufferedReader( | |||||
| new StringReader(event.getMessage())); | new StringReader(event.getMessage())); | ||||
| String line = r.readLine(); | String line = r.readLine(); | ||||
| boolean first = true; | boolean first = true; | ||||
| @@ -273,8 +276,14 @@ public class DefaultLogger implements BuildLogger { | |||||
| } catch (IOException e) { | } catch (IOException e) { | ||||
| // shouldn't be possible | // shouldn't be possible | ||||
| message.append(label).append(event.getMessage()); | message.append(label).append(event.getMessage()); | ||||
| } finally { | |||||
| if (r != null) { | |||||
| FileUtils.close(r); | |||||
| } | |||||
| } | } | ||||
| } else { | } else { | ||||
| //emacs mode or there is no task | |||||
| message.append(event.getMessage()); | message.append(event.getMessage()); | ||||
| } | } | ||||
| Throwable ex = event.getException(); | Throwable ex = event.getException(); | ||||
| @@ -329,4 +338,27 @@ public class DefaultLogger implements BuildLogger { | |||||
| */ | */ | ||||
| protected void log(String message) { | protected void log(String message) { | ||||
| } | } | ||||
| /** | |||||
| * Get the current time. | |||||
| * @return the current time as a formatted string. | |||||
| * @since Ant1.7.1 | |||||
| */ | |||||
| protected String getTimestamp() { | |||||
| Date date = new Date(System.currentTimeMillis()); | |||||
| DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); | |||||
| String finishTime = formatter.format(date); | |||||
| return finishTime; | |||||
| } | |||||
| /** | |||||
| * Get the project name or null | |||||
| * @param event the event | |||||
| * @return the project that raised this event | |||||
| * @since Ant1.7.1 | |||||
| */ | |||||
| protected String extractProjectName(BuildEvent event) { | |||||
| Project project = event.getProject(); | |||||
| return project!=null?project.getName():null; | |||||
| } | |||||
| } | } | ||||
| @@ -49,7 +49,17 @@ public class NoBannerLogger extends DefaultLogger { | |||||
| * Must not be <code>null</code>. | * Must not be <code>null</code>. | ||||
| */ | */ | ||||
| public void targetStarted(BuildEvent event) { | public void targetStarted(BuildEvent event) { | ||||
| targetName = event.getTarget().getName(); | |||||
| targetName = extractTargetName(event); | |||||
| } | |||||
| /** | |||||
| * Override point, extract the target name | |||||
| * @param event the event to work on | |||||
| * @return the target name to print | |||||
| * @since Ant1.7.1 | |||||
| */ | |||||
| protected String extractTargetName(BuildEvent event) { | |||||
| return event.getTarget().getName(); | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -96,7 +96,7 @@ import org.apache.tools.ant.Project; | |||||
| * 47 -> White | * 47 -> White | ||||
| * | * | ||||
| */ | */ | ||||
| public final class AnsiColorLogger extends DefaultLogger { | |||||
| public class AnsiColorLogger extends DefaultLogger { | |||||
| // private static final int ATTR_NORMAL = 0; | // private static final int ATTR_NORMAL = 0; | ||||
| // private static final int ATTR_BRIGHT = 1; | // private static final int ATTR_BRIGHT = 1; | ||||
| private static final int ATTR_DIM = 2; | private static final int ATTR_DIM = 2; | ||||
| @@ -0,0 +1,171 @@ | |||||
| /* | |||||
| * Copyright 2007 The Apache Software Foundation | |||||
| * | |||||
| * Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| * you may not use this file except in compliance with the License. | |||||
| * You may obtain a copy of the License at | |||||
| * | |||||
| * http://www.apache.org/licenses/LICENSE-2.0 | |||||
| * | |||||
| * Unless required by applicable law or agreed to in writing, software | |||||
| * distributed under the License is distributed on an "AS IS" BASIS, | |||||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| * See the License for the specific language governing permissions and | |||||
| * limitations under the License. | |||||
| * | |||||
| */ | |||||
| package org.apache.tools.ant.listener; | |||||
| import org.apache.tools.ant.BuildEvent; | |||||
| import org.apache.tools.ant.NoBannerLogger; | |||||
| import org.apache.tools.ant.SubBuildListener; | |||||
| import org.apache.tools.ant.Project; | |||||
| import org.apache.tools.ant.util.StringUtils; | |||||
| import java.io.File; | |||||
| /** | |||||
| * This is a special logger that is designed to make it easier to work with big projects, those that use imports and | |||||
| * subant to build complex systems. | |||||
| * | |||||
| * @since Ant1.7.1 | |||||
| */ | |||||
| public class BigProjectLogger extends NoBannerLogger implements SubBuildListener { | |||||
| /** | |||||
| * Header string for the log. | |||||
| * {@value} | |||||
| */ | |||||
| public static final String HEADER="======================================================================"; | |||||
| /** | |||||
| * Footer string for the log. | |||||
| * {@value} | |||||
| */ | |||||
| public static final String FOOTER=HEADER; | |||||
| /** | |||||
| * This is an override point: the message that indicates whether a build failed. Subclasses can change/enhance the | |||||
| * message. | |||||
| * | |||||
| * @return The classic "BUILD FAILED" plus a timestamp | |||||
| */ | |||||
| protected String getBuildFailedMessage() { | |||||
| return super.getBuildFailedMessage() + TimestampedLogger.SPACER + getTimestamp(); | |||||
| } | |||||
| /** | |||||
| * This is an override point: the message that indicates that a build succeeded. Subclasses can change/enhance the | |||||
| * message. | |||||
| * | |||||
| * @return The classic "BUILD SUCCESSFUL" plus a timestamp | |||||
| */ | |||||
| protected String getBuildSuccessfulMessage() { | |||||
| return super.getBuildSuccessfulMessage() + TimestampedLogger.SPACER + getTimestamp(); | |||||
| } | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| * | |||||
| * @param event | |||||
| */ | |||||
| public void buildStarted(BuildEvent event) { | |||||
| super.buildStarted(event); | |||||
| subBuildStarted(event); | |||||
| } | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| * | |||||
| * @param event | |||||
| */ | |||||
| public void buildFinished(BuildEvent event) { | |||||
| subBuildFinished(event); | |||||
| super.buildFinished(event); | |||||
| } | |||||
| /** | |||||
| * Override point, extract the target name | |||||
| * | |||||
| * @param event the event to work on | |||||
| * @return the target name -including the owning project name (if non-null) | |||||
| */ | |||||
| protected String extractTargetName(BuildEvent event) { | |||||
| String targetName = event.getTarget().getName(); | |||||
| String projectName = extractProjectName(event); | |||||
| if (projectName != null && targetName != null) { | |||||
| return projectName + '.' + targetName; | |||||
| } else { | |||||
| return targetName; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * {@inheritDoc} | |||||
| * | |||||
| * @param event An event with any relevant extra information. Must not be <code>null</code>. | |||||
| */ | |||||
| public void subBuildStarted(BuildEvent event) { | |||||
| String name = extractNameOrDefault(event); | |||||
| Project project = event.getProject(); | |||||
| File base = project == null ? null : project.getBaseDir(); | |||||
| String path = base == null ? | |||||
| "With no base directory" | |||||
| : "In " + base.getAbsolutePath(); | |||||
| printMessage(StringUtils.LINE_SEP + getHeader() | |||||
| + StringUtils.LINE_SEP +"Entering project " + name | |||||
| + StringUtils.LINE_SEP + path | |||||
| +StringUtils.LINE_SEP + getFooter(), | |||||
| out, | |||||
| event.getPriority()); | |||||
| } | |||||
| /** | |||||
| * Get the name of an event | |||||
| * | |||||
| * @param event the event name | |||||
| * @return the name or a default string | |||||
| */ | |||||
| protected String extractNameOrDefault(BuildEvent event) { | |||||
| String name = extractProjectName(event); | |||||
| if (name == null) { | |||||
| name = ""; | |||||
| } else { | |||||
| name = '"'+name+'"'; | |||||
| } | |||||
| return name; | |||||
| } | |||||
| /** {@inheritDoc} */ | |||||
| public void subBuildFinished(BuildEvent event) { | |||||
| String name = extractNameOrDefault(event); | |||||
| String failed = event.getException() != null ? "failing " : ""; | |||||
| printMessage(StringUtils.LINE_SEP + getHeader() | |||||
| + StringUtils.LINE_SEP + "Exiting " + failed + "project " | |||||
| + name | |||||
| + StringUtils.LINE_SEP + getFooter(), | |||||
| out, | |||||
| event.getPriority()); | |||||
| } | |||||
| /** | |||||
| * Override point: return the header string for the entry/exit message | |||||
| * @return the header string | |||||
| */ | |||||
| protected String getHeader() { | |||||
| return HEADER; | |||||
| } | |||||
| /** | |||||
| * Override point: return the footer string for the entry/exit message | |||||
| * @return the footer string | |||||
| */ | |||||
| protected String getFooter() { | |||||
| return FOOTER; | |||||
| } | |||||
| } | |||||
| @@ -31,14 +31,14 @@ public class TimestampedLogger extends DefaultLogger { | |||||
| /** | /** | ||||
| * what appears between the old message and the new | * what appears between the old message and the new | ||||
| */ | */ | ||||
| private static final String SPACER = " - at "; | |||||
| public static final String SPACER = " - at "; | |||||
| /** | /** | ||||
| * This is an override point: the message that indicates whether a build failed. | * This is an override point: the message that indicates whether a build failed. | ||||
| * Subclasses can change/enhance the message. | * Subclasses can change/enhance the message. | ||||
| * | * | ||||
| * @return The classic "BUILD FAILED" | |||||
| * @return The classic "BUILD FAILED" plus a timestamp | |||||
| */ | */ | ||||
| protected String getBuildFailedMessage() { | protected String getBuildFailedMessage() { | ||||
| return super.getBuildFailedMessage() + SPACER + getTimestamp(); | return super.getBuildFailedMessage() + SPACER + getTimestamp(); | ||||
| @@ -48,20 +48,10 @@ public class TimestampedLogger extends DefaultLogger { | |||||
| * This is an override point: the message that indicates that a build succeeded. | * This is an override point: the message that indicates that a build succeeded. | ||||
| * Subclasses can change/enhance the message. | * Subclasses can change/enhance the message. | ||||
| * | * | ||||
| * @return The classic "BUILD SUCCESSFUL" | |||||
| * @return The classic "BUILD SUCCESSFUL" plus a timestamp | |||||
| */ | */ | ||||
| protected String getBuildSuccessfulMessage() { | protected String getBuildSuccessfulMessage() { | ||||
| return super.getBuildSuccessfulMessage() + SPACER + getTimestamp(); | return super.getBuildSuccessfulMessage() + SPACER + getTimestamp(); | ||||
| } | } | ||||
| /** | |||||
| * Get the current time. | |||||
| * @return the current time as a formatted string. | |||||
| */ | |||||
| protected String getTimestamp() { | |||||
| Date date = new Date(System.currentTimeMillis()); | |||||
| DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); | |||||
| String finishTime = formatter.format(date); | |||||
| return finishTime; | |||||
| } | |||||
| } | } | ||||