You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ant.apache.org by Steve Loughran <st...@apache.org> on 2007/05/18 14:32:48 UTC

BigProjectLogger

I'm just coding up a new logger to make it easier to analyse big 
projects built with subant. From the patched listeners.html:


<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>Includes the build finished timestamp of the TimeStamp logger</li>
   <li>Omits logging the names of all targets that have no direct task 
output</li>
   <li>Includes the name of the project when printing a target</li>
</ol>
<p>
   This is useful when using &lt;subant&gt; to build a large project
   from many smaller projects -the output shows which particular
   project is building. Here is an example
</p>
<pre>
   kernel.init:
        [echo] building /home/ant/examples/kernel/lib/kernel-3.10.jar

   ... (many lines of log output omitted) ...

   kernel.all:
        [echo] completed kernel build

   tasks.init:
        [echo] building /home/ant/examples/tasks/lib/tasks-3.10.jar
        [echo] system.tests.enabled=true
</pre>

One question I have here, is : how should I present teh project name.

(a) with a .  for example:  kernel.init and kernel.common.init
   -good for simple things, but imports can confuse it
(b) a /         for example:  kernel/init and kernel/common.init
   -may cause confusion as project names != dir names
(c) colon    for example:  kernel:init and kernel:common.init

(d) brackets       [kernel]:init and [kernel]:common.init
   -makes targets look like task output
(e) XML Qualified names :)  {kernel}#init and {somewith with 
spaces}#common.init
   -awful

I'd started off with the '.' symbol, but worry that overriden imported 
projects would introduce confusion... and you can have spaces in a 
project name (which could trigger us quoting them)

I suppose I could make the separator and such like configurable through 
project properties.

-steve

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org


Re: BigProjectLogger

Posted by Steve Loughran <st...@apache.org>.
Dominique Devienne wrote:
> On 5/18/07, Steve Loughran <st...@apache.org> wrote:
>> OK. You've done what I've done, except with some stacking.
> 
> I had too, some of my builds had 3 or 4 levels, Makefile-style. Which
> is not that surprising, since it was building mostly native stuff with
> <ac:cc>. I think it's essential to do the stacking.
> 
> Someone remarked at the time it wasn't safe in the face of <parallel>,
> but this wasn't a pb for me. --DD

yes, I saw that when I looked at it. Now, if we defined one of two 
things we could address this

1. have a ProjectInfo class that includes (name,location URI,resource 
ref,basedir) of a build file.  Attach one to every project, give child 
project's info about their parent

2. have every Project retain a ref to its parent.

#2 worries me from a memory perspective; as long as each child project 
has a lifespan shorter than its parent all is well. Some embedded uses 
could maybe do bad things. Also it lets people write tasks that 
manipulate their parent and grandparent's state.

-steve

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org


Re: BigProjectLogger

Posted by Dominique Devienne <dd...@gmail.com>.
On 5/18/07, Steve Loughran <st...@apache.org> wrote:
> OK. You've done what I've done, except with some stacking.

I had too, some of my builds had 3 or 4 levels, Makefile-style. Which
is not that surprising, since it was building mostly native stuff with
<ac:cc>. I think it's essential to do the stacking.

Someone remarked at the time it wasn't safe in the face of <parallel>,
but this wasn't a pb for me. --DD

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org


Re: BigProjectLogger

Posted by Steve Loughran <st...@apache.org>.
Dominique Devienne wrote:
> On 5/18/07, Steve Loughran <st...@apache.org> wrote:
>> One question I have here, is : how should I present teh project name.
>>
>> (a) with a .  for example:  kernel.init and kernel.common.init
>>    -good for simple things, but imports can confuse it
>> (b) a /         for example:  kernel/init and kernel/common.init
>>    -may cause confusion as project names != dir names
>> (c) colon    for example:  kernel:init and kernel:common.init
>>
>> (d) brackets       [kernel]:init and [kernel]:common.init
>>    -makes targets look like task output
>> (e) XML Qualified names :)  {kernel}#init and {somewith with
>> spaces}#common.init
>>    -awful
> 
> For NoBannerSubBuildLogger, for reference, I used
> 
> target-name:     [main-project-name/level1-subbuild/level2-subsubbuild]
> 
> at a fixed offset unless the target name was too long.


OK. You've done what I've done, except with some stacking. Now, if 
projects had an optional parent project, we could work it out dynamically.

> 
> 
> import java.util.Stack;
> 
> import org.apache.tools.ant.Target;
> import org.apache.tools.ant.Project;
> import org.apache.tools.ant.BuildEvent;
> import org.apache.tools.ant.DefaultLogger;
> import org.apache.tools.ant.util.StringUtils;
> 
> /**
> * Build logger that allows to make sense of the nesting structure
> * generated by the use of &lt;ant&gt; and &lt;subant&gt; in Ant build 
> files.
> * <p>
> * The target banner (the target name followed by a colon, all in its own 
> line)
> * will not be printed until that target's tasks output any kind of message.
> * This greatly simplifies the build output for all those targets that do 
> not
> * execute, either because they are prevented to from their 'if' or 'unless'
> * attributes, or because all their input files are up-to-date versus their
> * output files.
> * <p>
> * In addition, the target banner (when output) will be postfixed with the
> * project path that lead to its execution, i.e. the list of project names
> * that were started using either &lt;ant&gt; and &lt;subant&gt;. Assuming
> * one calls the build target of 3 different sub-builds called A, B, and C
> * all called from a master build, one could get an output as follows:
> * <pre>
> * Buildfile: master.xml
> *
> * build:          [master/A]
> * Compiling 19 source file to /acme/A/classes
> *
> * build:          [master/B]
> * Compiling 15 source file to /acme/B/classes
> *
> * build:          [master/C]
> * Compiling 12 source file to /acme/C/classes
> *
> * BUILD SUCCESSFUL
> * Total time: 8 seconds
> * </pre>
> * <p>
> * Inspired from NoBannerLogger by Peter Donald.
> */
> public class NoBannerSubBuildLogger
>             extends DefaultLogger {
> 
>    /** The cached current target name, awaiting to be possibly printed. */
>    private String _targetName;
> 
>    /** The stack of nesting Ant projects. */
>    private Stack _projects = new Stack();
> 
>    /** The private buffer of this logger. */
>    protected StringBuffer _buffer = new StringBuffer(128);
> 
>    /**
>     * Gets the target banner for a given target name.
>     *
>     * @param  targetName the target name to get the banner for.
>     * @return the full target banner name.
>     */
>    protected String getTargetBanner(String targetName) {
>        _buffer.setLength(0);
> 
>        // Target banner as usual
>        _buffer.append(StringUtils.LINE_SEP);
>        _buffer.append(targetName);
>        _buffer.append(':');
> 
>        // Postfix the project path
>        fillToIndex(_buffer, 16, ' ', 1);
>        _buffer.append('[');
>        appendProjectPath(_buffer, '/');
>        _buffer.append(']');
> 
>        // Return the full target banner (toString() in bugged in JDK 1.4.1)
>        return _buffer.substring(0);
>    }
> 
>    /**
>     * Appends the current project path to a given buffer.
>     *
>     * @param  buffer the string buffer to append to.
>     * @param  separator the project path separator to use.
>     */
>    protected void appendProjectPath(StringBuffer buffer, char separator) {
>        final int count = _projects.size();
>        for (int i = 0; i < count; ++i) {
>            Project project = (Project)_projects.get(i);
>            buffer.append(project.getName());
>            buffer.append(separator);
>        }
>        if (count > 0) {
>            buffer.setLength(_buffer.length()-1);
>        }
>    }
> 
>    /**
>     * Fills a string buffer with a given character to reach a known length.
>     *
>     * @param  buffer the string buffer to fill (Cannot be 
> <code>null</code>).
>     * @param  column the column index to fill up to.
>     * @param  c the char to fill up with.
>     * @param  minLength the mininum number of character to add in case the
>     *         string buffer is already longer than <code>column</code>
>     * @return the number of characters actually added.
>     */
>    protected static int fillToIndex(StringBuffer buffer, int column,
>                                     char c, int minLength) {
>        final int fillCount = Math.max(column - buffer.length(), minLength);
>        for (int i = 0; i < fillCount; ++i) {
>            buffer.append(c);
>        }
>        return fillCount;
>    }
> 
>    /**
>     * Records/caches the target name and its project just started.
>     *
>     * @param  event the build event to extract the target from.
>     */
>    public void targetStarted(BuildEvent event) {
>        Target target = event.getTarget();
>        _targetName = target.getName();
>        _projects.push(target.getProject());
>    }
> 
>    /**
>     * Cleans up the record/cache of the target name and its project.
>     *
>     * @param  event the (ignored here) build event.
>     */
>    public void targetFinished(BuildEvent event) {
>        _targetName = null;
>        _projects.pop();
>    }
> 
>    /**
>     * Logs a task message, possibly displaying the target and project path
>     * that led to its execution, if they were not displayed earlier.
>     *
>     * @param  event the build event containing message information.
>     *               Must not be <code>null</code>.
>     */
>    public void messageLogged(BuildEvent event) {
>        if (event.getPriority() > msgOutputLevel
>            || null == event.getMessage()
>            || (_targetName != null && 
> "".equals(event.getMessage().trim()))) {
>                return;
>        }
> 
>        if (_targetName != null) {
>            out.println(getTargetBanner(_targetName));
>            _targetName = null;
>        }
> 
>        super.messageLogged(event);
>    }
> 
> } // END class NoBannerSubBuildLogger
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
> For additional commands, e-mail: dev-help@ant.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org


Re: BigProjectLogger

Posted by Dominique Devienne <dd...@gmail.com>.
On 5/18/07, Steve Loughran <st...@apache.org> wrote:
> One question I have here, is : how should I present teh project name.
>
> (a) with a .  for example:  kernel.init and kernel.common.init
>    -good for simple things, but imports can confuse it
> (b) a /         for example:  kernel/init and kernel/common.init
>    -may cause confusion as project names != dir names
> (c) colon    for example:  kernel:init and kernel:common.init
>
> (d) brackets       [kernel]:init and [kernel]:common.init
>    -makes targets look like task output
> (e) XML Qualified names :)  {kernel}#init and {somewith with
> spaces}#common.init
>    -awful

For NoBannerSubBuildLogger, for reference, I used

target-name:     [main-project-name/level1-subbuild/level2-subsubbuild]

at a fixed offset unless the target name was too long.


import java.util.Stack;

import org.apache.tools.ant.Target;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.BuildEvent;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.util.StringUtils;

/**
 * Build logger that allows to make sense of the nesting structure
 * generated by the use of &lt;ant&gt; and &lt;subant&gt; in Ant build files.
 * <p>
 * The target banner (the target name followed by a colon, all in its own line)
 * will not be printed until that target's tasks output any kind of message.
 * This greatly simplifies the build output for all those targets that do not
 * execute, either because they are prevented to from their 'if' or 'unless'
 * attributes, or because all their input files are up-to-date versus their
 * output files.
 * <p>
 * In addition, the target banner (when output) will be postfixed with the
 * project path that lead to its execution, i.e. the list of project names
 * that were started using either &lt;ant&gt; and &lt;subant&gt;. Assuming
 * one calls the build target of 3 different sub-builds called A, B, and C
 * all called from a master build, one could get an output as follows:
 * <pre>
 * Buildfile: master.xml
 *
 * build:          [master/A]
 * Compiling 19 source file to /acme/A/classes
 *
 * build:          [master/B]
 * Compiling 15 source file to /acme/B/classes
 *
 * build:          [master/C]
 * Compiling 12 source file to /acme/C/classes
 *
 * BUILD SUCCESSFUL
 * Total time: 8 seconds
 * </pre>
 * <p>
 * Inspired from NoBannerLogger by Peter Donald.
 */
public class NoBannerSubBuildLogger
             extends DefaultLogger {

    /** The cached current target name, awaiting to be possibly printed. */
    private String _targetName;

    /** The stack of nesting Ant projects. */
    private Stack _projects = new Stack();

    /** The private buffer of this logger. */
    protected StringBuffer _buffer = new StringBuffer(128);

    /**
     * Gets the target banner for a given target name.
     *
     * @param  targetName the target name to get the banner for.
     * @return the full target banner name.
     */
    protected String getTargetBanner(String targetName) {
        _buffer.setLength(0);

        // Target banner as usual
        _buffer.append(StringUtils.LINE_SEP);
        _buffer.append(targetName);
        _buffer.append(':');

        // Postfix the project path
        fillToIndex(_buffer, 16, ' ', 1);
        _buffer.append('[');
        appendProjectPath(_buffer, '/');
        _buffer.append(']');

        // Return the full target banner (toString() in bugged in JDK 1.4.1)
        return _buffer.substring(0);
    }

    /**
     * Appends the current project path to a given buffer.
     *
     * @param  buffer the string buffer to append to.
     * @param  separator the project path separator to use.
     */
    protected void appendProjectPath(StringBuffer buffer, char separator) {
        final int count = _projects.size();
        for (int i = 0; i < count; ++i) {
            Project project = (Project)_projects.get(i);
            buffer.append(project.getName());
            buffer.append(separator);
        }
        if (count > 0) {
            buffer.setLength(_buffer.length()-1);
        }
    }

    /**
     * Fills a string buffer with a given character to reach a known length.
     *
     * @param  buffer the string buffer to fill (Cannot be <code>null</code>).
     * @param  column the column index to fill up to.
     * @param  c the char to fill up with.
     * @param  minLength the mininum number of character to add in case the
     *         string buffer is already longer than <code>column</code>
     * @return the number of characters actually added.
     */
    protected static int fillToIndex(StringBuffer buffer, int column,
                                     char c, int minLength) {
        final int fillCount = Math.max(column - buffer.length(), minLength);
        for (int i = 0; i < fillCount; ++i) {
            buffer.append(c);
        }
        return fillCount;
    }

    /**
     * Records/caches the target name and its project just started.
     *
     * @param  event the build event to extract the target from.
     */
    public void targetStarted(BuildEvent event) {
        Target target = event.getTarget();
        _targetName = target.getName();
        _projects.push(target.getProject());
    }

    /**
     * Cleans up the record/cache of the target name and its project.
     *
     * @param  event the (ignored here) build event.
     */
    public void targetFinished(BuildEvent event) {
        _targetName = null;
        _projects.pop();
    }

    /**
     * Logs a task message, possibly displaying the target and project path
     * that led to its execution, if they were not displayed earlier.
     *
     * @param  event the build event containing message information.
     *               Must not be <code>null</code>.
     */
    public void messageLogged(BuildEvent event) {
        if (event.getPriority() > msgOutputLevel
            || null == event.getMessage()
            || (_targetName != null && "".equals(event.getMessage().trim()))) {
                return;
        }

        if (_targetName != null) {
            out.println(getTargetBanner(_targetName));
            _targetName = null;
        }

        super.messageLogged(event);
    }

} // END class NoBannerSubBuildLogger

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@ant.apache.org
For additional commands, e-mail: dev-help@ant.apache.org