Tuesday, June 12, 2007

How to build ant scripts for Flex

Almost every project nowdays needs an automatic build server for integration tests, while this is common practice in java oriented companies it is fairly new to Flex developers.
First of all you need some kind of versioning system for your code (you really really really can't go without a versioning system for your code!).. I am using subversion (http://subversion.tigris.org/) for my projects.
At one point conflict resolution is not enough when you have multiple developers working on a piece of software with a versioning tool, you have to know day by day if the commits from you and youre co-workers are not conflicting with one another on a functional level (i'm not talking about compile time issues here). So you need to compile the code on the server and run some tests to automatically check if everything still works fine.
Herefore i use the cruisecontrol (http://cruisecontrol.sourceforge.net/) build server together with Ant build scripts and FlexUnit tests.
There are tons of tutorials and howto's about how you install cruisecontrol, subversion and flexUnit.

There is a project in adobe labs called Flex Ant Tasks , wich enables you to compile your flex/actionscript code from ant. It is pretty straightforward if you are familiar with Ant, the only thing i had some problems with is the wrapper creation. The 'standard' way for this is to use the predefined html-wrapper task, that works fine but takes a template from the FLEX-SDK install dir. I have custom html-wrapper in my project and i don't want to copy them on every development machine to the FLEX-SDK. I can't find a way to configure the ant script so that it points to some other directoy in my project. So i had to write that part myself..

The following code is a complete ant script and build.properties file which you can use as a template/reference for you own build scripts. Just place the build.xml and build.properties in the root of your project and alter the variables the way you like. The script is pretty ok documented so i won't go in detail on this. Don't forget that you have to install ANT to run the builds ;-P

build.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>
<project name="Flex Ant File Compiler" default="masterbuild" basedir=".">

<!-- Load Environment specific properties from properties file -->
<property file="build.properties" />

<!-- Load the flex tasks for compiling the actionScript code and running flexUnit -->
<taskdef resource="flexTasks.tasks" classpath="${FLEX.TASK.DIR}" />
<taskdef resource="com/adobe/ac/ant/tasks/tasks.properties" classpath="${FLEX.ANT.TASK.DIR}"/>

<!-- Init with echoing some info to the console -->
<target name="init" description="Initializes the build">
<tstamp/>
<echo message="============================================="/>
<echo message="${project.name}-${project.version} [${TODAY}]"/>
<echo message="Copyright (c) ${project.year} ${project.owner}"/>
<echo message="OS : ${os.name}" />
<echo message="Author: ${author}" />
<echo message="=============================================="/>
</target>

<!-- Compile Main application -->
<target name="compile" depends="init" description="Compiles the mxml/as source files">
<mxmlc
file="${main.class}"
output="${swf.export}"
actionscript-file-encoding="${ENCODING}"
keep-generated-actionscript="false"
incremental="false"
>
<load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
<source-path path-element="${FLEX_HOME}/frameworks"/>
<compiler.source-path path-element="${src.dir}"/>
<compiler.include-libraries dir="${basedir}" append="true">
<include name="${lib.dir}" />
</compiler.include-libraries>
</mxmlc>
</target>

<!-- Run unit test -->
<target name="unit-tests" depends="init" description="Compiles and runs the tests">
<mxmlc
file="${flex.unit.runner}"
output="${flex.unit.swf}"
actionscript-file-encoding="${ENCODING}"
keep-generated-actionscript="false"
incremental="false"
>
<load-config filename="${FLEX_HOME}/frameworks/flex-config.xml"/>
<source-path path-element="${FLEX_HOME}/frameworks"/>
<compiler.source-path path-element="${src.dir}"/>
<compiler.include-libraries dir="${lib.dir}" append="true">
<include name="flexunit.swc" />
<include name="FlexUnitOptional.swc" />
</compiler.include-libraries>
</mxmlc>

<flexunit
timeout="0"
swf="${flex.unit.swf}"
toDir="${report.dir}"
haltonfailure="true" />

<junitreport todir="${report.dir}">
<fileset dir="${report.dir}">
<include name="TEST-*.xml"/>
</fileset>
<report format="frames" todir="${report.dir}/html/"/>
</junitreport>
</target>

<!-- Generate ASDOC -->
<target name="doc" depends="init" description="Generates the asdoc of the source files">
<java className="${asdoc.tool}" fork="true" failonerror="true">
<classpath >
<fileset dir="${FLEX_HOME}/asdoc/lib" includes="*.jar" />
<fileset dir="${FLEX_HOME}/lib" includes="*.jar" />
</classpath>
<jvmarg line="-Dapplication.home=${FLEX_HOME} -Xms32m -Xmx768m -Dsun.io.useCanonCaches=false -Xbootclasspath/p:${FLEX_HOME}/asdoc/lib/xalan.jar"/>
<arg line="-library-path+='${basedir}/${lib.dir}/Cairngorm.swc'"/>
<arg line="-library-path+='${basedir}/${lib.dir}/flexunit.swc'"/>
<arg line="-library-path+='${basedir}/${lib.dir}/corelib.swc'"/>
<arg line="-library-path+='${basedir}/${lib.dir}/FlexUnitOptional.swc'"/>
<arg line="-library-path+='${FLEX_HOME}/frameworks/libs'"/>
<arg line="-library-path+='${FLEX_HOME}/frameworks/locale/en_US'"/>
<arg line="-doc-sources '${src.dir}'" />
<arg line="-main-title '${asdoc.docsname}'" />
<arg line="-window-title '${asdoc.docsname}'" />
<arg line="-output '${docs.asdoc.dir}'" />
<arg line="-footer 'CONFIDENTIAL. Copyright 2007 Your Company'" />
</java>

</target>

<!-- Clean output dirs -->
<target name="clean" description="clean all generated files">
<delete includeemptydirs="true">
<fileset dir="${docs.asdoc.dir}" includes="**/*"/>
<fileset dir="${bin.dir}" includes="**/*" />
<fileset dir="${report.dir}/html" includes="**/*" />
</delete>
<delete>
<fileset dir="${report.dir}" includes="**/*.xml" />
</delete>
</target>

<!-- Create HTML wrapper -->
<target name="wrapper" depends="compile" description="Creates the HTML wrapper">
<!-- Copy the html-wrapper dir except the index.template.html -->
<copy todir="${bin.dir}">
<fileset dir="${wrapper.dir}">
<exclude name="**/index.template.html" />
</fileset>
</copy>
<!-- Copy and rename the index.template.html -->
<copy file="${wrapper.dir}/index.template.html"
tofile="${html.file}" />

<!-- Replace placeholders in the html with our variables -->
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{width\}"
replace="100%"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{height\}"
replace="100%"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{title\}"
replace="{project.name}"
encoding="utf-8"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{version_major\}"
replace="9"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{version_minor\}"
replace="0"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{version_revision\}"
replace="0"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{application\}"
replace="${application.name}"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{bgcolor\}"
replace="#FFFFFF"/>
<replaceregexp
file="${html.file}"
flags="gs"
match="\$\{swf\}"
replace="${application.name}"/>
</target>

<!-- Run all, default -->
<target name="masterbuild"
depends="clean, compile, unit-tests, wrapper"
description="Complete build in efficient sequence"/>

</project>


build.properties

######################################
## Project information
######################################
author = Marcel Panse
project.owner = Your Company
project.owner.url = http://www.yourcompany.com
project.fullname = Your Project
project.version = 1.0.0 alpha # major.minor[.revision[.build]]
project.name = Project name
project.year = 2007
application.name = Application Name

# Where flex is installed:
FLEX_HOME=C:/DEVELOPMENT/flex_sdk_2
DEFAULT.LOCAL = en_US
ENCODING = UTF-8
######################################
## tools
######################################

### FlexTask
FLEX.TASK.DIR=tools/FlexTasks/lib/flexTasks.jar
FLEX.ANT.TASK.DIR=tools/FlexAntTasks/flexAntTasks.jar
wrapper.dir=html-template

### Asdoc
asdoc.tool=flex2.tools.ASDoc

# The name that will appear in the documentation that is generated.
asdoc.docsname=Project API Documentation

# Browsers
firefox=C:/Program Files/Mozilla Firefox/firefox.exe
ie=C:/Program Files/Internet Explorer/iexplore.exe

# Flash Player
saplayer=player/debug/SAFlashPlayer.exe

######################################
## Properties
######################################
src.dir=source
bin.dir=deploy
build.dir=build
docs.dir=docs
docs.asdoc.dir=${docs.dir}/asdocs
docs.junitreport.dir=${docs.dir}/junitreport
lib.dir=lib
html.file=${bin.dir}/${application.name}.html

main.class = ${src.dir}/Application.mxml
swf.export = ${bin.dir}/Application.swf

# ASSETS DIR
assets.dir=${src.dir}/assets/

######################################
## Flex Unit
######################################

flex.unit.runner = ${src.dir}/AntTestRunner.mxml
flex.unit.swf=${basedir}/${bin.dir}/AntTestRunner.swf
report.dir=${basedir}/docs/junitreport


I also included asDocs in my ant scripts, which generates javaDoc style documentation for your actionScript project. I don't have it fully running myself yet because the asDoc compiler is much stricter then the flex builder sdk compiler, so i get a lot of errors on unused imports and stuff and i just don't have the time to fix it (for now).

The directory structure of my project is as follows:

-root
  -deploy
  -docs
    -asdocs
    -junit report
     -html
  -html-template
  -lib
  -source
  -tools
   -FlexAntTasks (contains the FlexAntTasks.jar)
   -FlexTasks (contains the flexTasks.jar
-.actionScriptProperties
-.flexProperties
-build.properties
-build.xml


written by Marcel Panse

4 comments:

shahar said...

It looks great and took me 10 min to make it run. but there is still one more thing missing there - copy non embedded files to output folder

Niels Bjerg said...

Thank you for a nice detailed explanation of how to utilize ant with flex.
@shahar Copying resources is a walk in the park, use:
< copy file="foo.bar" todir="output/subdir" overwrite="false"/>

there are multiple options, also for copying whole directories.

Anonymous said...

Hi,

I'd appreciate some help with an error I can't get rid of.

Compiling a mxml file in Flex Builder IDE works fine, but from ANT the compiler seems to have problems finding components provided by Flex.

Error message is:
Error: Could not resolve <mx:OLAPCube> to a component implementation.

Any idea about how to tell the compiler where to find whatever needed?

Thanks a lot

Anonymous said...

Forget my question and sorry about that.

Flex Builder IDE SDK includes datavisualization_sdk3.3.

Flex SDK doesn't. It has to be downloaded.

Issue fixed.