Industrial-strength builds with Flex

If you have a large Flex project with a significant number of developers on the project, you need a Dailybuild. For all sorts of reasons, a dailybuild has been best practice in the software industry for ages. I’m not going to try to justify the significant amount of work you need to do to get dailybuilds to work – loads of other people have done that already. The one I refer to most is by Joel Spolsky

But how do you do this in Flex? Well it turns out that Ant is your friend. Ant can call the mxmlc and compc compilers just like it can call any other external program. That also goes for Subversion, so you’ll need the commandline version.

Before I continue… this is really just a short taster of how we now do daily builds – to include everything would just take too long. We also have a fairly complex way of doing production builds that has a couple of nifty tricks in it. For example, how do you guarantee that when you do another production release, the previous version isn’t cached on a proxy server somewhere? Well, that will hopefully be in another post. In the meantime, do drop me a comment if you have any specific questions.

We have a dailybuild script that performs the following steps:

  1. Updates its copy of the source from Subversion
  2. Builds everything
  3. Copies it to a JBOSS instance so that everybody can view a snapshot of the build.

Before we get into the details, here’s a quick note on how our applications are put together. It made sense for us to use this architecture, so I’m guessing that other people with the need to build large flex apps will adopt a similar, if not identical, architecture. So the techniques I describe here might be useful to other people as well.

We begin with a main application swf that loads up two Runtime Shared Libraries (RSLs).

One is a common library that contains general purpose classes that we expect to use on more than one application.

The second is an application specific library of classes that will only be used in that one application. However, it is still an RSL because we want those classes to be shared with all the modules that get dynamically loaded.

Pointing to the compilers

We have a small file called build.properties that looks like this:

flexsdk.bin.dir = C:/Program Files/Adobe/Flex Builder 3 Plug-in/sdks/3.0.0/bin flexsdk.framework.lib.dir = C:/Program Files/Adobe/Flex Builder 3 Plug-in/sdks/3.0.0/frameworks/libs compc.exe = ${flexsdk.bin.dir}/compc.exe mxmlc.exe = ${flexsdk.bin.dir}/mxmlc.exe asdoc.exe = ${flexsdk.bin.dir}/asdoc.exe # Note that the locale dir uses the {locale} token at the end to specify the directory # of language-specific files. This is replaced by the compiler with the locale defined # by the locale property below. flexsdk.locale = en_US flexsdk.locale.dir = C:/Program Files/Adobe/Flex Builder 3 Plug-in/sdks/3.0.0/frameworks/locale

This way, we only define the location of the Flex SDK in as few places as possible. You’ll find uses of these properties (i.e. ${compc.exe} etc) dotted all through the rest of this page – Ant will automatically do a textual replace on these tokens with the values defined in the properties.

Updating source from Subversion

This bit’s easy – you’ll need to have the svnant jars in your Ant lib directory.

<target name="update"> <echo>Updating source in directory ${common.root}</echo> <svn username="${svn.user}" password="${svn.password}"> <update dir="${common.root}"/> </svn> </target>

Building a library

This just needs to call the compc compiler:

<exec executable="${compc.exe}" dir="${basedir}" failonerror="true"> <!-- Specify the config file--> <arg line="-load-config+=build-config.xml" /> <!-- Pass in the sdk libs directory --> <arg line="+flexsdk.framework.lib.dir='${flexsdk.framework.lib.dir}'"/> <!-- Pass in the debug setting --> <arg line="+debug_build='true'"/> <!-- Place the built .swc file in the "bin" directory --> <arg line="-output 'bin/common.swc'" /> </exec>

You’re probably already familiar with everything except the “load-config” flag.  This points to an XML file that among other things holds the list of classes that you want to include in the library.  Unfortunately this file has to duplicate the information that is currently held in .flexLibProperties.  So here’s a sample:

<?xml version="1.0"?> <flex-config xmlns="http://www.adobe.com/2006/flex-config"> <compiler> <source-path> <path-element>.</path-element> </source-path> <debug>${debug_build}</debug> <include-libraries> <library>com\kn\tls\common\framework\library\ResizeManagerFX_3.0-2.1.swc</library> <library>com\kn\tls\common\components\thirdparty\CheckBoxTreeFX_3.0-1.5.swc</library> <!-- Stick all your bought-in libraries here --> <library>${flexsdk.framework.lib.dir}</library> </include-libraries> </compiler> <include-classes> <class>com.kn.tls.common.events.CoverFlowEvent</class> <class>com.kn.tls.common.interfaces.IWindowToolbar</class> <class> ...all the classes to be compiled in ...</class> </include-classes> </flex-config>

Once again, Ant has tasks that can update your build-config.xml from .flexLibProperties.  Its only really a find/replace – in simple terms this…

<classEntry path="com.kn.tls.common.events.CoverFlowEvent"/>

..becomes

<class>com.kn.tls.common.events.CoverFlowEvent</class>

In other words replace:

<classEntry path=” with <class>

“/> with </class>

So here’s how I did it – I created a template that has all the other build flags, and a replacable token for the list of classes:

<?xml version="1.0"?> <flex-config xmlns="http://www.adobe.com/2006/flex-config">    <compiler>         <source-path>             <path-element>.</path-element>         </source-path>         <debug>${debug_build}</debug>         <include-libraries>             <library>com\kn\tls\common\framework\library\ResizeManagerFX_3.0-2.1.swc</library>             <library>com\kn\tls\common\components\thirdparty\PV3.swc</library>             <library>com\kn\tls\common\components\thirdparty\Tweener.swc</library>             <library>com\kn\tls\common\components\thirdparty\CheckBoxTreeFX_3.0-1.5.swc</library>             <library>${flexsdk.framework.lib.dir}</library>         </include-libraries>   </compiler>   <include-classes>     $include.classes$   </include-classes> </flex-config>

And here’s the Ant task to get the list of classes from .flexLibProperties, do some textual replaces, and then insert the list where $include.classes$ is.

    <target name="update-build-config">         <echo>Reading list of classes from flexLibProperties to include in build-config.xml</echo>         <loadfile property="include.classes" srcfile=".flexLibProperties">             <filterchain>                 <linecontains>                     <contains value='<classEntry path="'/>                 </linecontains>                 <tokenfilter>                     <replacestring from='<classEntry path="' to="<class>" />                     <replacestring from='"/>' to="</class>" />                 </tokenfilter>                 <deletecharacters chars="\t\n\r "/>             </filterchain>         </loadfile>         <copy file="build-config-template.xml" toFile="build-config.xml" overwrite="true"/>         <replace file="build-config.xml" token="$include.classes$" value="${include.classes}"/>     </target>

This particular task runs as follows:

  1. Filters on lines containing the <classEntry> element and loads this list into the property include.classes
  2. Replaces the elements at either end of the line as shown above.
  3. Deletes any whitespace characters
  4. Copies build-config-template.xml to build-config.xml (overwriting any existing file)
  5. Replaces the $include.classes$ token in build-config.xml with the contents of include.classes

Building the main application

This is the swf that gets loaded first. It is responsible for doing the initial load of the RSLs.

<exec executable="${mxmlc.exe}" dir="${basedir}" failonerror="true"> <!-- Point to the mxml file --> <arg line="Sceptre.mxml" /> <!-- Generate debug swf --> <arg line="-debug=true" /> <!-- Place the built .swf file in the "bin" directory --> <arg line="-output 'bin-debug/Sceptre.swf'" /> <!-- Link against these but don't include actual definitions in the final SWF --> <arg line="-external-library-path+='${common_bin.debug.dir}'" /> <arg line="-external-library-path+='${applib_bin.debug.dir}'" /> <!-- Link against these at runtime --> <arg line="-runtime-shared-libraries=common.swf,sceptrelibrary.swf"/> <!-- Create a linker report that lists all the classes already in the main app This gets passed to the modules so that they don't load up the shared libs again. --> <arg line="-link-report='c:/temp/link-report.xml'"/> </exec>

As mentioned previously, we have two libraries – one that is common across multiple apps, and one that is specific to one app. We need to link statically to those as compile time using the external-library-path argument. Also these libraries are loaded as Runtime Shared Libraries (RSLs) so we need to tell the compiler where these libraries will be at runtime using the runtime-shared-libraries argument.

Finally, the link-report argument tells the compiler to generate an xml file containing details of all the classes that will be available by the time the main app and all the libraries are loaded. This is used when we compile modules to tell the compiler to leave out implementations of these classes as we expect them to be already loaded.

Building modules

Here, you can benefit from some really neat aspects of Ant. Assuming that the modules all build in exactly the same way except for the module name, here’s a build.xml for a module.

<!-- Action Management Module --> <project basedir="." name="Action Management Module" default="build_debug"> <property name="module-name" value = "ActionManagement"/> <import file="../../Application/module-common.xml"/> </project>

Brief and to the point. Very nice. Here’s part of the module-common.xml that compiles a module:

<exec executable="${mxmlc.exe}" dir="${module_root.dir}/${module-name}" failonerror="true"> <!-- Point to the mxml file --> <arg line="src/${module-name}.mxml" /> <!-- Generate debug swf --> <arg line="-debug=true" /> <!-- Place the built .swf file in the "bin" directory --> <arg line="-output 'bin-debug/${module-name}.swf'" /> <!-- Link against these but don't included actual definitions --> <arg line="-external-library-path+='${common_bin.debug.dir}'" /> <arg line="-external-library-path+='${applib_bin.debug.dir}'" /> <!-- Exclude all classes that are already in the main application --> <arg line="-load-externs='c:/temp/link-report.xml'"/> </exec>

You can see where ${module-name} gets used as the name of the compiled swf. Also note how we link in the common and app-specific libraries. Finally, the load-externs, which references the link-report.xml that was built in the main app, and tells the compiler which classes to expect to be already available when the module is loaded.

And the last piece of the jigsaw – building all your modules with one Ant task. If all your modules exist under one root directory, you can do this:

<!-- Iterate through all subdirs of the module root and run each build.xml --> <subant inheritall="true" inheritrefs="true"> <target name="clean-debug"/> <target name="build-debug"/> <fileset dir="${module_root.dir}" includes="**/build.xml"/> </subant>

This will start at the root directory, set here by ${module_root.dir}, and run the clean-debug and build-debug targets of each build.xml.

Deployment

We use Java servlets hosted in JBOSS to perform our server-side tasks. So the deployment bit here is just a matter of copying a bunch of files. However, we do some fancy bits with the libraries as they need to be decompressed to get the swf out.

<echo>Copying Common ... (${common_bin.debug.dir})</echo> <unzip src="${common_bin.debug.dir}/common.swc" dest="${common_bin.debug.dir}"> <patternset> <include name="library.swf"/> </patternset> </unzip> <copy file="${common_bin.debug.dir}/library.swf" tofile="${deploy.dir}/common.swf"/>

This little snippet takes the compiled common.swc, and unzips library.swf. If you’re unfamiliar with swc files, basically a zip of library.swf, a manifest xml file, and any asset files (images, etc) that were linked in to library.swf. When compiling, mxmlc needs access to the swc. However, when the app runs, the main swf needs to know where the library.swf is. In this case we rename library.swf to common.swf, because we compiled that name into the main application with the runtime-shared-libraries argument.

OK, this is all fine and dandy, but it is really deficient in one way – every time you call compc.exe or mxmlc.exe you have to reload the Java runtime. A new process is spawned each time, and that’s a real waste of processor clock cycles when you really should only need to load it once and then compile all your code. Unfortunately, there doesn’t seem to be anything around for Flex 3.0 that will reuse a JVM to run the compiler again. There is a solution for the 2.0.1 – the Flex Compiler Shell. However, that uses some internal classes provided in the 2.0.1 that weren’t subsequently included in the 3.0.0 SDK for reasons best known to Adobe. There’s more on the hows and whys here by the guy who wrote FCSH, Clement Wong . As Adobe own the compilers, I guess it’s up to them to come up with a solution that reuses a JVM for multiple compiles. For now it looks like the above solution is as good as it gets if you want to compile with the 3.0.0 SDK.

3 Responses to Industrial-strength builds with Flex

  1. ArAgorrn says:

    Hi,

    I get this error :
    The value of attribute “value” associated with an element type “null” must not contain the ‘<' character.

    With this part :

    <contains value='

    <replacestring from='<classEntry path="' to="” />
    ‘ to=”” />

  2. Pingback: 2010 in review « Flexibility

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: