How to build any Flex project with Ant the right way

Objective

To build flex application with ant without depending on FlexBuilder™ and to have statically linked and dynamically loaded libraries from separate flex projects. Adobe calls them “Component Libraries” and “Runtime Shared Libraries”.

The Problem

Adobe Flex ecosystem is in dark ages of automation and enterprise tools. Their flagship product in automation department is an ant task written by amateur intern. They don’t even bother to escape file paths when the call to mxml compiler are made from this ant task wrapper …(Ask me how I know). So the ant tasks do not work and the documentation for the mxmlc and compc are confusing and cover 50% of the features. But enough about the bad, lets get on to the solution.

I needed to create a pretty sophisticated app: We have a web application that talks JSON to a bunch of REST services. This app depends on some already prebuilt components that come in form of a library. This app also has to function as a stand alone demo application with no internet connection. For this we have split out the rest calls into 2 separate projects – the API (interface definitions) and demo implementation. if a flag is passed in via query parameter to the application it loads demo data in form of exampleDemData RSL.

The Solution

To build all this on maven I needed to create ant build script to produce the main app, html wrapper and demo data
RSL (runtime shared library). Here is how we do it. The project structure is set up like this:

flexBuilds      ... ( where this build file resides )
common-flex45   ... ( common components - component library )
exampleApi      ... ( backend Access API - component library )
exampleDemoData ... ( demo implementation of the API  - RSL )
example         ... ( main Application )

For building we will be calling mxmlc and compc compilers ourselves instead of going through the adobe provided ant task wrappers, since they do not expose all the features and do not work if you path has a single space in it.
And now the build script:

<?xml version="1.0"?>
<project name="Example Flex Build" default="buildAll" basedir="../">

  <description>
      Example Flex Build script.
    </description>

  <property name="artifacts.dir" value="${basedir}/flexBuilds/atrifact" />
  <property name="flex.sdk.home" value="/...../sdks/4.5.1" />


  <target name="init">
    <mkdir dir="${artifacts.dir}"/>
    <delete includeemptydirs="true">
      <fileset dir="${artifacts.dir}" includes="**/*" />
    </delete>  
  </target>

  <target name="buildAll" depends="init">
    
    <antcall target="compile-library" >
      <param name="output.dir" value="${artifacts.dir}" />
      <param name="project.dir" value="common-flex45" />
      <param name="extra.stuff" value="" />
    </antcall>
    
    <antcall target="compile-library" >
      <param name="output.dir" value="${artifacts.dir}" />
      <param name="project.dir" value="exampleApi" />
      <param name="extra.stuff" value="" />
    </antcall>
    
    <antcall target="compile-library" >
      <param name="output.dir" value="${artifacts.dir}" />
      <param name="project.dir" value="exampleDemoData" />
      <param name="extra.stuff" value="-directory=true" />
    </antcall>

    <antcall target="compile-webApp" >
      <param name="app.name" value="ExampleApplication" />
      <param name="app.file" 
        value="example/src/com/wetfeetblog/example/ExampleApplication.mxml" />
      <param name="app.title" value="Example Application" />
      <param name="src.dir" value="example" />
      <param name="bin.dir" value="app" />
      <param name="extra.stuff" value="-runtime-shared-library-path=${artifacts.dir}/exampleDemoData.swc,exampleDemoData.swf -verify-digests=false" />
    </antcall>
    <copy file="${artifacts.dir}/exampleDemoData.swc/library.swf" 
        toFile="${artifacts.dir}/app/exampleDemoData.swf"/>

    <echo message="All Flex Apps Compiled!" />
    
  </target>

  <target name="compile-library" >

    <fileset id="sources" dir="${basedir}/${project.dir}/src">
      <include name="**/*.mxml"/>
      <include name="**/*.as"/>
    </fileset>
    
    <pathconvert property="classes" pathsep=" " refid="sources">
      <compositemapper>
        <chainedmapper>
           <globmapper from="${basedir}/${project.dir}/src/*" 
              to="*" handledirsep="true" />
           <mapper type="package" from="*.as" to="*"/>
        </chainedmapper>
        <chainedmapper>
           <globmapper from="${basedir}/${project.dir}/src/*" 
              to="*" handledirsep="true" />
           <mapper type="package" from="*.mxml" to="*"/>
        </chainedmapper>
      </compositemapper>
    </pathconvert>
    
    <exec executable="${flex.sdk.home}/bin/compc" outputproperty="compile.output" dir="${basedir}">
      <arg line="-debug=false -output '${output.dir}/${project.dir}.swc' ${extra.stuff} -library-path+=${project.dir}/libs -library-path+=${artifacts.dir} -source-path ${project.dir}/src/ -include-classes ${classes} " />
    </exec>
    <echo message="${compile.output}" />
    
  </target>

  <target name="compile-webApp" >

    <echo message="Compiling ${app.name}" />
    
    <exec executable="${flex.sdk.home}/bin/mxmlc" outputproperty="compile.output" dir="${basedir}">
      <arg line="-debug=false -show-actionscript-warnings=false -output '${artifacts.dir}/${bin.dir}/${app.name}.swf' -library-path+='${artifacts.dir}' -library-path+=${src.dir}/libs ${extra.stuff} -source-path ${src.dir}/src/ --  ${app.file}" />
    </exec>
    <echo message="${compile.output}" />

    <copy file="${basedir}/${src.dir}/html-template/swfobject.js" 
          todir="${artifacts.dir}/${bin.dir}/"/>
    <copy file="${basedir}/${src.dir}/html-template/playerProductInstall.swf" 
          todir="${artifacts.dir}/${bin.dir}/"/>
    <copy file="${basedir}/${src.dir}/html-template/index.template.html" 
          tofile="${artifacts.dir}/${bin.dir}/index.html"/>
    
    
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="${app.title}" token="${title}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="#ffffff" token="${bgcolor}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="${app.name}" token="${application}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="${app.name}" token="${swf}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="100%" token="${width}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="100%" token="${height}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="10" token="${version_major}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="0" token="${version_minor}" />
    <replace file="${artifacts.dir}/${bin.dir}/index.html" 
       value="0" token="${version_revision}" />
    
  </target>


  <target name="deploy-content" >
    <echo message="Deploying ${deploy.local.location} to ${deploy.host} " />
    <sshexec host="${deploy.host}" username="${deploy.user}" 
         password="${deploy.password}" command="rm ${deploy.remote.location}/*" 
         trust="true" failonerror="false"/>
    <scp todir="${deploy.user}:${deploy.password}@${deploy.host}:${deploy.remote.location}/" 
         verbose="true" trust="true" >
      <fileset dir="${deploy.local.location}" />
    </scp>      
  </target>
  
</project>

Lets examine all the little tricks one by one. We will start with “compile-library” target. It has multiple gotchas. For start compc is dumb enough not to recognize what needs to be compiled in, it needs a full list of classes fed into it to produce a library. But we do not want to list all the classes by hand, so when we add one more we will have to modify our build script. Lets get on with some ant magic.

 
  <target name="compile-library" >

    <fileset id="sources" dir="${basedir}/${project.dir}/src">
      <include name="**/*.mxml"/>
      <include name="**/*.as"/>
    </fileset>
    
    <pathconvert property="classes" pathsep=" " refid="sources">
      <compositemapper>
        <chainedmapper>
           <globmapper from="${basedir}/${project.dir}/src/*"  to="*" handledirsep="true" />
           <mapper type="package" from="*.as" to="*"/>
        </chainedmapper>
        <chainedmapper>
           <globmapper from="${basedir}/${project.dir}/src/*" to="*" handledirsep="true" />
           <mapper type="package" from="*.mxml" to="*"/>
        </chainedmapper>
      </compositemapper>
    </pathconvert>
    
    <exec executable="${flex.sdk.home}/bin/compc" outputproperty="compile.output" dir="${basedir}">
      <arg line="-debug=false -output '${output.dir}/${project.dir}.swc' ${extra.stuff} -library-path+=${project.dir}/libs -library-path+=${artifacts.dir} -source-path ${project.dir}/src/ -include-classes ${classes} " />
    </exec>
    <echo message="${compile.output}" />
    
  </target>

For compiling the main app the mxmlc compiler a little bit more sophisticated, so we can show it the main app mxml file and it figures out what needs to be included in the application. But the html wrapper we need to handle by hand, since mxmlc has no capabilities to do so. Luckily there is no code, just some placeholders needs to be replaced with real values. Ant can handle that with no problem.

No the tricky bits come into play when we want our exampleDemoData project to become the RSL. for that we need to specify that we wan the exploded output format, so we add “-directory=true” flag when compiling the library. In the main application we need to add a flag to use this swc as RSL. The flag to do so is “-runtime-shared-library-path=${artifacts.dir}/exampleDemoData.swc,exampleDemoData.swf”. If you want to rebuild the exampleDemoDAta project wihtout rebuilding the main application we need to add “-verify-digests=false” to the main app also, so the rsl version digest is not checked.

And that is it in a nutshel. You may find one more handy target for deploying your freshly built application on a unix based host via scp in this build script.

Leave a Reply

Your email address will not be published. Required fields are marked *