HXML Overview for Haxe

If you've just started using Haxe or have been using OpenFL or Kha to build your projects you may not be familiar with Haxe's built-in build files, hxml. They use the same syntax as the arguments you'd pass to Haxe on the command line with a few other nice features built in. Even if you are a seasoned pro there may be one or two features you didn't know about in this post.

A Basic Example

Haxe can be overwhelming at first because of the number of targets it has available. Instead of trying to cover all of the targets this overview will just cover a few of them. Neko is a good target for command line tools and testing since it can be compiled quickly. To get started you should create a new project folder with the following two files in it.

Main.hx

class Main {
    public static function main() {
        trace("hello world");
    }
}

build.hxml

-neko output.n  # this is the compiled neko binary
-main Main      # your main class (must have static function main defined)

To build this example you enter haxe build.hxml on the command line. If everything goes as planned you should end up with a compiled neko binary named output.n in the same folder. To run this file you can enter neko output.n on the command line and it should print "hello world". If you aren't getting results you may want to make sure Haxe and Neko have been properly installed and that you typed everything as shown above.

Class Paths and Running Programs

So far you have a very simple project compiling in Haxe but what if you want to put Main.hx in a different folder? This is where class paths come in. Haxe needs a way to find the classes you create so it can compile them. You should create a source folder now and move Main.hx into it. Now, update the build.hxml file to look like the one below.

-neko output.n
-main Main
-cp source          # this is the root of the new class path you created
-cmd neko output.n  # a quick way to run a command after compiling

Notice that you add the -cp flag with the same name as the folder you just created. This can be a relative path to any folder including a parent folder using ellipsis, ../my_parent. You may have also seen the additional -cmd flag which runs the command neko output.n after compiling the program. There is another way to execute neko code using the -x flag which is demonstrated below. Try it and see what happens.

-x Main     # compiles the class Main into a neko binary file named Main.n and executes it
-cp source  # you still need the class path to point to the source folder

Hopefully you can see that the -x flag is a quick way to test a class using the neko target in Haxe.

Multiple Targets

So by now you should have a better grasp on the neko target and some of the flags you can use to develop for neko. What if you also want to compile to javascript as well? One way would be to create another hxml file and build them separately but wouldn't it be better if you could do it all in one command?

-neko output.n
-main Main
-cp source

--next         # tell Haxe to start the next task

-js output.js  # create a javascript file
-main Main
-cp source

You've probably noticed there are two new flags that haven't been used before. The first is --next which tells the Haxe compiler to start a new task which could be a command, another target, or a completely different program. The second flag is -js which will tell the Haxe compiler to build a javascript file.

This works fine but you might not want so much duplication between the two targets. Wouldn't it be nice if there was a way to specify the -main and -cp flags globally for the hxml file? There is!

-main Main
-cp source

--each           # use the flags above for every task below

-neko output.n

--next

-js output.js

--next

-swf out.swf     # compile to a flash swf file
-swf-header 320:240:60:FFFFFF # defines properties for the swf file (width:height:fps:background color)

That's better. There is even a new target added just for fun. So you can see that the --each flag uses all of the flags above it for all of the tasks below. It's a bit like copying and pasting the flags multiple times.

Defines and Debug

Errors are bound to happen and trying to figure out where something broke can be a frustrating process. Thankfully Haxe has included stack traces to help you figure out what went wrong. There is one flag that will help improve stack traces while developing and that is -debug.

Error.hx

class Error {
    public static function main() {
        throw "This is an error";
    }
}

test-error.hxml

-x Error
-debug

Now if you build this code with haxe test-error.hxml you can see that Haxe dumps a stack trace where the throw is. If you were to remove this flag you'll see that the stack trace still occurs but doesn't have as much useful information. When building a release version you will want to remove this flag.

So that's all well and good but what if you want to compile something differently based on a defined value? You can do that with the -D flag. Here is an updated version of the Error class that handles a user defined value.

Error.hx

class Error {
    public static function main() {
        #if mydefine
        trace("this only happens if -D mydefine is included");
        #end
        #if debug
        trace("I'm in debug mode");
        #end
        throw "This is an error";
    }
}
-x Error
-debug
-D mydefine

You might have noticed that there is a condition for a debug define. By using the -debug flag it also automatically includes -D debug as well. You can read more about conditional compilation in the Haxe manual.

External Libraries

Up until now you've only been using the standard library for Haxe. What about adding external code from haxelib? There's a flag for that too! First you should install a library through haxelib.

haxelib install format

Now you are ready to compile with the format library.

-x Main      # compile and execute the Main class for neko
-cp source
-lib format  # include the format library from haxelib

Were you expecting something more difficult? Hopefully this is all making sense and you are ready to move on to the next compile target, C++. But first you should learn about reducing the size of your compiled output.

Dead Code Elimination

When compiling to javascript you may notice that the output can get pretty large. By default Haxe includes a lot of unused functions and classes based on what you import. This is a good thing because it prevents your code from crashing if a class isn't included in the output. However, Haxe is very smart in that it knows what code can be tossed if a function is never called.

-x Main
-cp source
-dce full   # can be "no", "std", or "full"

The new flag is -dce which has a few options. The first is to pass "no", which is the default, and simply doesn't perform dead code elimination. The second is "std" which will strip any unused code in the Haxe standard library. The final one is "full" which eliminates all unused code from your compiled output.

One thing to note however is that Haxe can sometimes remove functions that you need if you are using fancy tricks like reflection. There may also be libraries that do not work well with dead code elimination so be careful that you test thoroughly when this flag is turned on. You may want to read more about how to retain functions and classes in the Haxe manual on dead code elimination.

C++

haxelib install hxcpp

hxcpp is the required library to compile C++ code. It contains a handful of tools and a way to add native extensions with CFFI. Here's an example of how you can compile to C++.

-cpp out    # slightly different than other targets because this defines a directory
-main Main
-cp source

This looks familiar, right? The -cpp flag works almost identical to other targets but instead of naming an output file it names the output directory. If you run haxe build.hxml now you may get an error if no compiler is found on your computer. You can either go through the step to remedy that or use the next method.

Cppia

As of writing this Cppia, pronounced "sepia", is a new feature for Haxe and hxcpp. It is a very quick way to compile C++ code and runs equal to or faster than Neko. That said there are still some early issues especially with null values. So how do you compile for Cppia?

-cpp out.cppia  # unlike before this is actually a file name and not a directory
-D cppia        # this is where the magic happens
-main Main
-cp source
-cmd haxelib run hxcpp out.cppia  # execute the cppia file

There are a few differences you might see. The first is that the -cpp flag no longer takes a directory name but instead it takes a file name. The second is that you need to define a value to tell hxcpp to output Cppia instead of C++. Finally there is a command flag at the bottom which executes the code by passing the newly created cppia file to hxcpp.

Where to go from here

There are a lot of flags that were not covered in this post. If you want to find out what they are the first thing to do is to run haxe -help which will return a list of all the supported flags in Haxe. There is also some useful information in the Haxe manual that may be helpful.

Posted in Haxe

Tags: hxml, hxcpp, cppia, neko

comments powered by Disqus