Reply to comment

Is my file up to date? Ant and Flex compile dependencies

At Awen we use the Flex SDK to compile AS3 code and MXML from within Ant.

I'm currently in the process of updating our build system. Obviously, like all good build systems, it would be nice if a build only rebuilt things which have changed. Or rather, where the source code that affects a build product has changed. This makes a build process so much faster.

There is an Ant task called uptodate which deals with this; it basically checks a list of source files against a target file, and works out if any of the source files have been modified more recently than the target file. If so, it marks the target file as not up to date - it should be rebuilt.

But how to come up with the list of source files? We could specify that every time we rebuild; but that's not very flexible (particularly if you're adding classes at a rate of knots).

Happily, the mxmlc compiler can help out. It can generate a link report listing everything that your build target depends on.

Unfortunately, the format of the file isn't very friendly for Ant's purposes. It's a long XML file; what we really need for Ant is a list.

Here, roughly, is how I've solved the problem; using just Ant and the AntContrib library.

1) Generate a list of dependencies

Every time we need to rebuild a .swf file:

  • Use the mxmlc compiler option -link-report=someFile.xml to generate a full link list.
  • Read the XML file into Ant using the loadfile Ant task.
  • Loop through the XML file at every point where we find a < tag, using the for task from AntContrib.
  • Use the propertyregex tag from AntContrib to extract any of the dependencies that fall in our library path. We could include all the Flex dependencies too - but believe me, if I upgrade my Flex installation I'll just do a build clean. We're only interested in files in our own codebase.
  • Create a cache file specifically for this .swf file, using the urlencode AntContrib task to create a unique filename for it.
  • Write into that cache file the list of files generated in the previous steps; one file per line.

Yes, it's a lot of work - but it's not that slow, and, more importantly, we only have to do it on compilation - which is already slow.

2) Before compiling, is the file up to date?

Every time we think about rebuilding a .swf file:

  • Use urlencode to find the unique cache file.
  • Call Ant's built-in uptodate task, passing it that cachefile via the includesfile attribute of the srcfiles element.
  • If the file is up to date, don't build it!

That second part is very fast; so that achieves the goal. We can test to see if each .swf in our build process is up to date; and if it is, we don't bother building it.

Here's some source:

<!-- This script defines a couple of handy macros for checking dependencies of 
.swf files built by the mxmlc compiler -->
<!-- by Ian Thomas at Awen, 2008 -->
<!-- NOTE: This script assumes you have the AntContrib tasks defined and ready 
to go in your ant file. See the AntContrib documentation for details -->
 
<!-- Load environment variables into Ant, so we can get at the temp path. Feel free to set
the cache path to whatever is appropriate instead... -->
<property environment="env"/>
<property name="cacheFolder" value="${env.TEMP}/flexcache"/><!-- alter this if required -->
 
<!-- This property is the root of all your source files for all of your code which
might be used by this script. This root should not include the path to Flex, as
we don't want to check dependencies on Flex code, only on code in your project
and your shared code libraries. -->
<property name="sourceRoot" location="C:\projects"/>
 
<!-- 
Should be run after any flex compile. Make sure you add the option:
  -link-report=someLinkReportFileName.xml
to your mxmlc call.
-->
<macrodef name="flex-generate-dependencies">
  <attribute name="target"/><!-- Compilation target e.g. e:\myfiles\myswf.swf -->
  <attribute name="link-report"/><!-- Path to link report e.g. e:\myfiles\link-report.xml -->
  <sequential>
    <!-- convert the target name into a unique cache filename -->
    <urlencode property=".tmp" override="true" location="@{target}"/>
    <var name=".out" value="${cacheFolder}/${.tmp}.txt"/>
 
    <!-- load the link report -->
    <if>
      <not><available file="@{link-report}"/></not>
      <then><fail>Can't read link report file:@{link-report}</fail></then>
    </if>
    <var name=".rawText" unset="true"/>
    <loadfile property=".rawText" srcFile="@{link-report}"/>
 
    <!-- convert the source root into a form suitable for putting into a regular expression -->
    <propertyregex property=".rootRegEx" override="true" input="${sourceRoot}"
        regexp="\\" replace="\\\\\\\\" global="true" defaultValue="${sourceRoot}"/>
 
    <!-- an empty var for us to append to -->
    <var name=".dependsFiles" value=""/>
 
    <!-- Use propertyregex to parse the XML -->
    <for list="${.rawText}" delimiter="&lt;" param=".scriptLine" trim="true">
      <sequential> 
        <var name=".scriptName" unset="true"/>
        <propertyregex property=".scriptName"
            input="@{.scriptLine}"
            regexp="script name=&quot;${.rootRegEx}\\([^\&quot;]*)&quot;"
            select="\1"/>
        <if>
          <isset property=".scriptName"/>
          <then>
            <!-- Found a relevant file - add it to the list of files -->
            <var name=".dependsFiles" value="${.dependsFiles}&amp;#x0D;${.scriptName}"/>
          </then>
        </if>
      </sequential>
    </for>
    <!-- Write all the files out to our cache file -->
    <concat destfile="${.out}">${.dependsFiles}</concat>
  </sequential>
</macrodef>
 
<!-- Sets a property if the target file is up to date -->
<macrodef name="flex-uptodate">
  <attribute name="target"/><!-- Compilation target e.g. e:\myfiles\myswf.swf -->
  <attribute name="property"/><!-- Property to set e.g. appUpToDate -->
  <sequential>
    <!-- convert the target name into a unique cache filename -->
    <urlencode property=".tmp" location="@{target}" override="true"/>
    <var name=".in" value="${cacheFolder}/${.tmp}.txt"/>
 
    <var name="@{property}" unset="true"/>
 
    <if>
        <available file="${.in}"/>
        <then>
          <!-- Use the Ant built-in uptodate task, passing it 
                 the list of files generated in the previous step -->
          <uptodate property="@{property}" targetfile="@{target}">
            <srcfiles dir="${sourceRoot}" includesfile="${.in}"/>
          </uptodate>
        </then>
    </if>
  </sequential>
</macrodef>

And usage might be something like this:

<target name="compile">
  <var name="swf" value="c:\myprojects\project\bin\mySwf.swf"/>
  <flex-uptodate target="${swf}" property="isUpToDate"/>
  <if>
    <not><isset property="isUpToDate"/></not>
    <then>
      <exec executable="mxmlc.exe" dir="c:\myprojects\project\src" failonerror="true">
        <arg value="MySwf.mxml"/>
        <arg line="-output ${swf}"/>
        <arg line="-sp ."/>
        <arg line="-link-report=${env.TEMP}/link-report.xml"/>
      </exec>
      <flex-generatedependencies target="${swf}" link-report="${env.TEMP}/link-report.xml"/>
    </then>
  </if>
</target>

As an aside, our process is slightly faster than normal anyway, as we're using fcsh to queue up the building of all our .swfs rather than the standard mxmlc.

Of course, ideally I'd go away and write an Ant task in Java which actually does all this without having to hack in Ant. But - for the moment - I've got things working as I want them to. :-)

Reply

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <i> <b> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <blockcode> <h1> <h2> <h3> <h4> <h5> <h6>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Copy the characters (respecting upper/lower case) from the image.