Headless Building on Linux

Aug 24, 2010

Tags: , , , , , ,

Recently I started an experiment, the server powering this blog and the rest of my website is a Ubuntu Server machine with an Intel Core i7 processor @2.66ghz and 12gb of ram. I know a bit over kill for a web server hence why it has a ton of other things running on it that I'm not going to go into detail on. One of the projects I recently released (Halo Stats Archiver: Desktop Edition) has a ton of code in it north of 10,000 lines counting formatting, mxml, ActionScript and the ActionScript in some of the compiled libraries I wrote and use in HSA. Plus it comes with allot of assets 391 files [1.88mb] (not counting the files ADT builds), plus all the embedded assets. All in all it can take quite awhile to build it, and I've noticed sometimes Flex builder 3 just gives up the guts, it just doesn't "Export for Release" it stews over it for awhile then the export dialog closes with no resulting AIR package. Likely this is due to the number of assets and the sheer number of lines of code.

So I decided to try and utilize my shiny i7 processor with 12gb of ram to build AIR applications sort of based on the idea of a render farm, but with only one machine instead of dozens. Now I have very limited knowledge of shell programming, or well I did coming into this endeavor so I spend many many hours looking searching the internet for information on how to do this and how to do that. Even once I figured out the basics on how to get user input and command line arguments I still had to figure out how to use adt and amxmlc. Hit the jump and I'll go into detail on how I managed to get a few working scripts and a download link so you can use these files on your Linux box.

amxmlc is the Flex compiler for Adobe AIR, its included with all versions of the Flex SDK greater than version 3.2. This application is the same as mxmlc it just includes the libraries necessary to build Adobe AIR applications automatically, you can do this via mxmlc as well but its much simpler to use amxmlc. As for adt well its the everything else button, it allows you to package your application, sign it, create a signing cert, etc. Using the these two applications you can build a Flex Project to a AIR package relatively simply. However in building a few of my applications I noticed something pretty annoying about Flex Builder and what it lets you get away with. All mxml, css and ActionScript files use paths relative to the root of the src folder not relative to their location. So say you have a ActionScript class located in com.display for example and it embeds a png from a folder outside the src folder. Flex Builder will allow you to have a path only going up one directory and that is the path it will generate to when you select an asset from outside the src folder or inside it. When in fact logically you should have it going up several, in the case of our example it should go up three folders not one. Well amxmlc does not like that too much you must use the paths that line up with the actual directory structure of the project. Which is one error (well it actually appeared quite allot in several projects) I had to sort through on the Flex/ActionScript side. Of course Linux is also a case sensitive file system so if you didn't pay attention to the actual names of files and folders that's one you'll have to fix as well if you develop on a Windows based system. Building on Linux requires that you have the Java Runtime environment installed and on the system's path, see your vendor's documentation on how to install the JRE and ensure it's on the system path.

Lets start building our own headless building script, this post will be probably the first and last time I ever post a tutorial on a language not directly related to Flash/Flex applications. First we need to get some information about the project from the user. We need to know the name of the application file (the root mxml file to be loaded), path to the root of the project, and the signing certificate path. All of which are required to build an AIR Application, we also need to know of any files or folders to include in the AIR package but this really isn't required. We'll also want a few more optional items such as the sdk version (in this tutorial I'm going to default it to 3.5.0) as well we want to know the path to put the final AIR package file. One thing to make note of is that this is a shell script I'm using a debian based headless Ubuntu Server so I'm using /bin/sh, you may need to use /bin/ksh depending on your distribution. This tutorial also assumes that you have a folder bellow the script called sdk, and this has all sdk versions you want available in it.

#!/bin/sh
SCRIPT_ROOT=$(pwd) #Current dir

#Get application name
while [ ! -n "$ApplicationName" ]; do
 echo -n "Enter application name: "
 read ApplicationName
done

#Get Project Path
while [ ! -n "$ProjectPath" ]; do
 echo -n "Enter path to application files: "
 read ProjectPath
done

#Get Path to Signing Certificate
while [ ! -n "$CertPath" ]; do
 echo -n "Enter signing certificate path: "
 read CertPath
done

#Check if the certificate exists
if [ ! -f "$CertPath" ]; then
 echo "Certificate could not be found"
 exit 0
fi

#Check that the project folder exists
if [ ! -d "$ProjectPath" ]; then
 echo "Project path could not be found"
 exit 0
fi

#Ask for additional files
echo -n "Enter additional files and folders to include in package (space seperate): "
read AdditionalFiles

#Get the SDK Version
echo -n "Enter the SDK Version [3.5.0]: "
read SDK

if [ ! -n "$SDK" ]; then
 SDK="3.5.0"
fi

#Ask for output directory
echo -n "Directory to place build package [default $ProjectPath/bin-release]: "
read OutputDir

#Check to see the SDK exists
if [ ! -f "$SCRIPT_ROOT/sdks/$SDK/bin/amxmlc" ]; then
    echo "------------ Could not find the Flex $SDK SDK with Adobe AIR Support ------------"
    exit 1
fi

#Change to project dir
cd $ProjectPath

#Check if application files exist
if [ ! -f "$ProjectPath/src/$ApplicationName.mxml" ] || [ ! -f "$ProjectPath/src/$ApplicationName-app.xml" ]; then
	echo "Application file could not be found"
	exit 0
fi

 

Basically the top area's were I get the application name, path to the files, and the signing certificate location are required so they will loop indefinitely until the user inputs something. The other questions such as the sdk version, additional files, and the output location are not required so there for if the user just hits enter then it gets skipped. With the Certificate and the Project Path locations I check to see if they actually exist in on the system. I also check to see if the sdk actually exists and has the amxmlc file in it (aka it has Adobe AIR support). Now that we have all the information from the user that we need for our script we need to start building the application, first we need to remove the bin-release folder and recreate it if it exists, if it does not it needs to be created, we'll use it later.

#Check if bin-release does not exist and create the folder
if [ ! -d "$ProjectPath/bin-release" ]; then
	mkdir bin-release
else
    #bin-release exists so delete and recreate
	rm -rf bin-release
	mkdir bin-release
fi

 

So now that, that is done we need to find any swc libraries in the project's libs folder, I find the individual swc files and add them to the library path because for some reason amxmlc does not properly scan folders. This is a pretty simple process, we need to use Linux'es always useful find command to look for all files that have names ending in swc that. Then we use the equally useful, but really complex awk command to add "-library-path=" to each result.

PROJECT_LIBS=$(find "$ProjectPath/libs/" \( ! -regex '.*/\..*/..*' \) -type f  -iname '*.swc' | awk '{print " -library-path+="$1}')

 

Once we have that we are finally ready to start building the project, so to do that we need to call amxmlc in the sdk's bin folder. Which brings me to a requirement for the sdk's make sure that the necessary files in bin have the execute permission failing to do this will cause amxmlc to not be usable. When we call amxmlc we need to pass in the path to the application's mxml file this is relative since earlier we moved into the project's folder. We also need to pass in the source path, I used the full path the user provided for this but it probably would work relative as well. Then we need to tell amxmlc were to put the output file, in this example I'm placing the final swf into the bin-release folder of the project, we also need to pass in a few more things "-headless-server=true" is because we are building on a headless server this does a few tweaks to how the compiler builds, I also use incremental builds just to improve build times. Lastly and most importantly depending on your project we need to include the swc libraries we found earlier in the call to amxmlc.

$SCRIPT_ROOT/sdks/$SDK/bin/amxmlc "src/$ApplicationName.mxml" -source-path+="$ProjectPath/src" -output "bin-release/$ApplicationName.swf" -headless-server=true -incremental=true $PROJECT_LIBS

 

Now we need to do a check to see if the build completed, since if it failed we don't want to try building the application without an application file (swf). So to do this we use an if statement and check the last commands result.

#Check that the build completed successfully
if [ ! "$?" -ne "0" ]; then

 

So if the build completed we now want to copy any non-source files (.mxml, and .as files) into the bin-release folder so they can be included in the package if the user requests it.

cd src #Change to src folder
#Copy files that are not as or mxml extensions perserving the directory structure
find . ! -iname '*.as' -and ! -iname '*.mxml' -type f -exec cp --parents -rv {} ../bin-release/ \;

echo "------------ Application Built, Building package ------------"
cd ../bin-release #Change to bin-release folder

 

Then we move into the bin-release folder to do the final packaging of the application. But just when you think something is easy, you find nice things like how Flex Builder puts the fallowing in the application descriptor which of course ADT does not know what to do with it.

<!-- Settings for the application's initial window. Required. -->
 <initialWindow>
 <!-- The main SWF or HTML file of the application. Required. -->
 <!-- Note: In Flex Builder, the SWF reference is set automatically. -->
 <content>[This value will be overwritten by Flex Builder in the output app.xml]</content>

 

Of course ADT doesn't know what to do with this, when Flex Builder goes to build a release build it will alter this line on its own to match the build swf file. So we need to do the same, this is where awk comes back in handy, we look for lines matching the expression /<content>.*<\/content>/ which will match this content line, and we need to replace the that line with a new one that contains the information that ADT expects when building. Then we redirect the output of awk to a temporary file then rename it back to an xml file.

#Update the application file to ensure the swf is properly set
eval "awk '{sub(/<content>.*<\/content>/,\"<content>$ApplicationName.swf</content>\");print}' $ApplicationName-app.xml > $ApplicationName-app.tmp"
mv "$ApplicationName-app.tmp" "$ApplicationName-app.xml"

 

Now finally we can finish off our script we generate the package using adt we need to tell adk that we are packaging using "-package" then we need to tell it what the type of the key store is, most common is pkcs12 so that is what I'm using in this example, we also need to tell it were that keystore is located this is our certificate path we asked the user for earlier. The last 3 things we pass into adt is the name of the AIR package we want built, the application descriptor file, and the application content file as well we are passing in the additional files that we requested from the user. ADT will prompt the user on its own for the password to the keystore if it is necessary which is pretty common as well. In the code bellow I am finishing the if condition from above so I have an output saying the build had failed in the else section.

#Start Packaging the application
   $SCRIPT_ROOT/sdks/$SDK/bin/adt -package -storetype pkcs12 -keystore "$CertPath" "$ApplicationName.air" "$ApplicationName-app.xml" "$ApplicationName.swf" $AdditionalFiles
else
    #Build Failed
    echo "------------ BUILD FAILED ------------"
fi

 

Now that that part is done we are very close to finished, the last part we need to do is check to see if the packaging completed successfully and if it has we need to see if the output folder has been requested by the user and it exists. If it does then we move the completed AIR file to the location the user requested.

#Check that the packaging completed successfully, if it has say the build completed
if [ ! "$?" -ne "0" ]; then
    if [ -n "$OutputDir" ]; then
        if [ -d "$OutputDir" ]; then
            mv "$ProjectPath/bin-release/$ApplicationName.air" "$OutputDir"
        fi
    fi

	echo "------------ BUILD COMPLETED ------------"
else
    #Packaging failed
	echo "------------ BUILD FAILED ------------"
    exit 1
fi

exit 0

 

That's it if you follow this tutorial through you should end up with something that looks similar to the following.

#!/bin/sh

#Title: AIR Application Builder (Build From Filesystem)
#Copyright: 2010 Ed Chipman
#License: Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License
#Version: 1.0.3

SCRIPT_ROOT=$(pwd) #Current dir

#Get application name
while [ ! -n "$ApplicationName" ]; do
    echo -n "Enter application name: "
    read ApplicationName
done

#Get Project Path
while [ ! -n "$ProjectPath" ]; do
    echo -n "Enter path to application files: "
    read ProjectPath
done

#Get Path to Signing Certificate
while [ ! -n "$CertPath" ]; do
    echo -n "Enter signing certificate path: "
    read CertPath
done

#Check if the certificate exists
if [ ! -f "$CertPath" ]; then
	echo "Certificate could not be found"
	exit 0
fi

#Check that the project folder exists
if [ ! -d "$ProjectPath" ]; then
	echo "Project path could not be found"
	exit 0
fi

#Ask for additional files
echo -n "Enter additional files and folders to include in package (space seperate): "
read AdditionalFiles

#Get the SDK Version
echo -n "Enter the SDK Version [3.5.0]: "
read SDK

if [ ! -n "$SDK" ]; then
    SDK="3.5.0"
fi

#Ask for output directory
echo -n "Directory to place build package [default $ProjectPath/bin-release]: "
read OutputDir

#Check to see the SDK exists
if [ ! -f "$SCRIPT_ROOT/sdks/$SDK/bin/amxmlc" ]; then
    echo "------------ Could not find the Flex $SDK SDK with Adobe AIR Support ------------"
    exit 1
fi

#Change to project dir
cd $ProjectPath

#Check if application files exist
if [ ! -f "$ProjectPath/src/$ApplicationName.mxml" ] || [ ! -f "$ProjectPath/src/$ApplicationName-app.xml" ]; then
	echo "Application file could not be found"
	exit 0
fi

#Check if bin-release does not exist and create the folder
if [ ! -d "$ProjectPath/bin-release" ]; then
	mkdir bin-release
else
    #bin-release exists so delete and recreate
	rm -rf bin-release
	mkdir bin-release
fi

#Build Project
echo "------------ Starting Application Build ------------"
PROJECT_LIBS=$(find "$ProjectPath/libs/" \( ! -regex '.*/\..*/..*' \) -type f  -iname '*.swc' | awk '{print " -library-path+="$1}')

$SCRIPT_ROOT/sdks/$SDK/bin/amxmlc "src/$ApplicationName.mxml" -source-path+="$ProjectPath/src" -output "bin-release/$ApplicationName.swf" -headless-server=true -incremental=true $PROJECT_LIBS

#Check that the build completed successfully
if [ ! "$?" -ne "0" ]; then
	echo "------------ Copying non-source files to bin-release ------------"
	cd src #Change to src folder
	#Copy files that are not as or mxml extensions perserving the directory structure
	find . ! -iname '*.as' -and ! -iname '*.mxml' -type f -exec cp --parents -rv {} ../bin-release/ \;

	echo "------------ Application Built, Building package ------------"
	cd ../bin-release #Change to bin-release folder

	#Update the application file to ensure the swf is properly set
	eval "awk '{sub(/<content>.*<\/content>/,\"<content>$ApplicationName.swf</content>\");print}' $ApplicationName-app.xml > $ApplicationName-app.tmp"
	mv "$ApplicationName-app.tmp" "$ApplicationName-app.xml"

    #Start Packaging the application
	$SCRIPT_ROOT/sdks/$SDK/bin/adt -package -storetype pkcs12 -keystore "$CertPath" "$ApplicationName.air" "$ApplicationName-app.xml" "$ApplicationName.swf" $AdditionalFiles
else
    #Build Failed
    echo "------------ BUILD FAILED ------------"
fi

#Check that the packaging completed successfully, if it has say the build completed
if [ ! "$?" -ne "0" ]; then
    if [ -n "$OutputDir" ]; then
        if [ -d "$OutputDir" ]; then
            mv "$ProjectPath/bin-release/$ApplicationName.air" "$OutputDir"
        fi
    fi

	echo "------------ BUILD COMPLETED ------------"
else
    #Packaging failed
	echo "------------ BUILD FAILED ------------"
    exit 1
fi

exit 0

 

You can download my scripts here, I've included a few variations including a script that you can use in a cron task, as well as building from svn (cron supported as well).

Posted in: Articles |