Showing posts with label ant. Show all posts
Showing posts with label ant. Show all posts

Sunday, April 07, 2013

Capture console output in ant - for any command or task - using Record

Keywords:
ant capture task console output stdout stderr javac jspc "Problems opening file using a recorder entry" absolute path

Problem:
Many tasks include options to capture output. Each of them different, such as exec - with the redirector option; java - with the @output attribute. What if you want to capture the output (stderr and stdout) from a task that doesn't support output capture (such as javac) or you'd like more control - start|stop|start appending output from many commands to shared file(s)?

Solution:
Record is the solution. Just BEWARE the @name attribute is treated as plain file path rather than a File attribute as in many other ant tasks. This means if supplied with a relative path it will be relative to where you're running ant from - a problem if you have ant scripts invoking other ant scripts. When path issues occur, you'll get this error:
Problems opening file using a recorder entry

Run ant with the -v option to get the full stack trace of the IOException. You'll see what path it's attempted - and failed - to use.

ALWAYS use absolute paths and you'll (hopefully) be fine.

To start recording:
<!-- (1) guarantee that you have an absolute path to the log file by setting a property with @location as the relative reference -->
<property name="build.logpath" location="${a.build.location}/sub_folder/log_file.log"/>
<!-- (2) start recording -->
<record action="start" name="${build.logpath}" loglevel="verbose"/>

All tasks that follow will have the console output (and logging if you set the loglevel attribute) go to the capture file:
<jasper2 ... />
<javac ... />
etc.

Then to stop:
<record action="stop" name="${build.logpath}"/>

Easy as that.


Monday, May 21, 2007

Avoid Copy & Paste with Ant MacroDef

Keywords:
ant java copy paste macrodef gwt compile task

Problem:
There's no convenient ant task to allow you to compile (or re-compile) the GWT client files, but you can get some decent up-to-date check & compile behaviour going with a few lines of ant

... problem is, you need to duplicate these lines of code for every module in the project. It would be nice if you could modularise this.

Solution:
The Ant "macrodef" task lets you define a sequence of ant steps in a parameterised way and then call it any number of times with different parameters.

As can be seen in the example below (which handles 2 modules), adding a new module will require:
  1. add a new entry to the "depends" list for gwt-generate (eg 'gwt-generate-newmod')
  2. create a gwt-compile target 'gwt-generate-newmod' with an "unless" attribute of 'gwtGenerate.newmod.notRequired'
  3. create a gwt-uptodate target to set the property 'gwtGenerate.newmod.notRequired' accordingly.
Yes, there's still some copy and paste, but you're just defining the module name and location rather that repeating the gwt-compile definition so there's less chance for a mistake.
<!-- this is the root target which will do all the generation (if required) -->
<target name="gwt-generate" description="Generate GWT code"
  depends="gwt-generate-common, gwt-generate-demo"/>

<target name="gwt-generate-common" depends="compile, gwt-check" unless="gwtGenerate.common.notRequired">
    <gwt-compile module="com.example.gwt.common.MyCommonModule"/>
</target>
<target name="gwt-generate-demo" depends="compile, gwt-check" unless="gwtGenerate.demo.notRequired">
    <gwt-compile module="com.example.gwt.demo.MyDemoModule"/>
</target>

<!-- this sets the *.notRequired properties if gwt code is uptodate -->
<target name="gwt-check">
  <!-- won't have any effect if the dirs are already there -->
  <mkdir dir="${build.home}/gwt"/>
  <mkdir dir="${build.home}/gwt/tmp"/>
  <mkdir dir="${build.home}/gwt/out"/>

    <gwt-uptodate property="gwtGenerate.common.notRequired"
            module="com.example.gwt.common.MyCommonModule"
            package="src/com/example/gwt/common"/>

    <gwt-uptodate property="gwtGenerate.demo.notRequired"
                    module="com.example.gwt.demo.MyDemoModule"
                    package="src/com/example/gwt/demo"/>          
</target>

<!-- this defines a macro task 'gwt-uptodate' -->
<macrodef name="gwt-uptodate" description="sets a property indicating if the module is up to date">
 <attribute name="property"/>
 <attribute name="module"/>
 <attribute name="package"/>
 <sequential>
         <!-- the module is uptodate if the .nocache.html is not older than any of the module src files -->
      <uptodate property="@{property}"
              targetfile="${build.home}/gwt/out/@{module}/@{module}.nocache.html">
          <srcfiles dir="@{package}" includes="**/*.*"/>
      </uptodate>                      
 </sequential>
</macrodef>  
<!-- this defines a macro task 'gwt-compile' -->
<macrodef name="gwt-compile" description="generates the GWT client code">
 <attribute name="module"/>
 <sequential>
         <!-- you must fork or it will fail -->
         <java dir="${build.home}/gwt/tmp" classname="com.google.gwt.dev.GWTCompiler" fork="true">
          <classpath>
              <!-- src directory containing module definition must be first! -->
              <pathelement location="src"/>
              <pathelement location="${build.home}/classes"/>
              <pathelement location="${gwt.user}"/>
              <pathelement location="${gwt.dev}"/>
              <pathelement location="${gwt.widgets}"/>
            </classpath>
          
          <sysproperty key="java.awt.headless" value="true"/>             
          <arg value="-out"/>
          <arg value="../out"/>
          <arg value="@{module}"/>  
      </java>
 </sequential>
</macrodef>


Notes:
Note, you only need to GWT-compile modules with an EntryPoint class in them. This is because it's only EntryPoint classes that you need to reference from your HTML and on compilation all the classes (and files in com.example.gwt.package/public!) are compiled/copied to this module. This means if you have multiple EntryPoint modules in your project (as I do) that reference a common Module, they will have their own compiled copy of this (and the /public files) rather than referencing it in a common location.

Note also, this means that the gwt-uptodate won't trigger a recompile if there's a change to a referenced module - as it's only checking the source folder containing the EntryPoint module ... so in those cases, you'll need to do a clean of the build folder containing generated source to guarantee everything is compiled up to date.

New Note: the sysproperty to set awt to headless is crucial on linux (if you're using ImageBundles). See Does the GWT 1.4 Compiler Need an X11 Window in Linux?