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; | |||||
} | |||||
} | } |