Build Configuration Files for HXCPP

If you've ever attempted to write an extension for Haxe using hxcpp you probably hit a wall trying to figure out how to create a Build.xml file. The common "solution" is to copy and paste what other projects have done until something works. However, if you want to really understand the inner workings of hxcpp's xml format you'll want to continue reading.

The Most Basic Build.xml File

First, instead of diving in to all of the specifics of the file format let's look at the bare minimum example of a Build.xml file.

Build.xml

<xml> <!-- Theoretically this tag could be named anything but hxcpp uses "xml" so we'll use that too -->
    <!-- A target with the id "default" must be defined or hxcpp will issue a warning -->
    <target id="default"></target>
</xml>

If you created this file it can be "compiled" using the command haxelib run hxcpp Build.xml. If the hxcpp command can't be found make sure that you have hxcpp installed using haxelib install hxcpp. For now it won't actually do anything but hxcpp will gladly read this file and not print out any errors or warnings.

Before we cover targets, and how they can be used, let's take a look at some of the other root level elements that can be defined.

<echo value="Can anyone hear me?" />

Echo is only allowed at the base level of the xml file. It's useful for printing out detailed information about what is being compiled.

<error value="This message will self destruct in... NOW." />

You can cause an intentional error to occur in hxcpp with this element. Why would you want to do that? Perhaps you don't want to support certain build conditions so instead you decide to show a useful error message to the person compiling your project.

Define Variables

<set name="foo" value="bar" /> <!-- define "foo" with the value "bar" -->
<unset name="foo" /> <!-- remove the "foo" define -->

<setenv name="foo" value="bar" /> <!-- define "foo" again and set an environment variable -->

I decided to combine set and setenv because they are easy to get mixed up. First, set and unset are used to assign and remove defined values. These are useful for conditional checks as we will see in a moment. The difference between set and setenv is that the latter not only defines a value but also sets it in the operating system environment variables. Note that all of the attributes shown above are required and hxcpp will fail if you forget them.

<!-- NOTE: this uses the "name" attribute and not "value" like you may expect -->
<path name="/path/to/binaries" /> <!-- append to PATH variable -->

The path element works almost identically to setenv with one major difference. It appends to the PATH variable instead of replacing it.

Conditional Attributes

Every element in the build.xml file can have conditional attributes applied to it. These will help customize the build for each operating system you may be targeting. There are only a handful so let's take a look at each one.

If

<set name="on_boat" value="yep" />
<!-- only checks for the definition of a variable not the actual value -->
<echo value="I'm on a boat" if="on_boat" />
<!-- multiple conditions can be chained together with or statements "||" -->
<echo value="Apple fanboys forever!" if="macos || ios" />
<!-- separating values by spaces means that both must be defined to pass the condition check -->
<echo value="Compiling for a 64-bit linux machine" if="linux HXCPP_M64" />

You can add the if attribute to an element to check when values have been defined.

Unless

<set name="have_pants" value="false" />
<!-- this will not execute because the condition passes, note that the value doesn't matter -->
<echo value="I can't find my pants" unless="have_pants" />
<!-- multiple conditions can be chained together with or statements "||" -->
<echo value="We don't like Apple fanboys!" unless="macos || ios" />

unless is the "everything but" condition. Useful for when you want to exclude certain portions of your build configuration.

IfExists

<echo value="Phew, it exists." ifExists="path/to/file.txt" />

This is a special condition for checking if a file exists in the file system.

Grouping Configurations and Including Other Files

<section if="windows">
    <echo "I'm in another section!" />
</section>
<!-- You can identify sections with an id attribute. This is useful for including in other xml files -->
<section id="my-section"></section>

You can use the section element to define a group of elements. It can have conditions like all the other elements so it's a good way to skip large portions of the build file.

<include name="include/more.xml" />
<!-- Adding the noerror attribute will prevent an error if the include file doesn't exist -->
<include name="does_not_exist.xml" noerror="true" />
<!-- The section attribute lets you restrict the include to a specific section. You must have a coresponding id in the included file. -->
<include name="other.xml" section="my-section" />

Including other configuration files is a good way to split up your build process as it grows larger. You may also want to split out platform specific configuration details into another file as well.

Miscellaneous Top Level Elements

Before we get to defining targets I want to cover the rest of the other top level elements.

<copyFile name="README.md" from="." />
<!-- Normally copyFile will fail if a file doesn't exist. "allowMissing" prevents that error. -->
<copyFile name="graphics/logo.png" from="assets" allowMissing="true" />
<!-- Adding "toolId" only runs copy file on targets where the toolId attributes match -->
<copyFile name="logo.png" from="assets/graphics" toolId="dll" />

Contrary to what you may think this doesn't copy files immediately, it only happens when a target executes. The name attribute should be a filename and the from attribute is the directory where a file of that name exists. The file will be placed in the target's output directory with the same filename. If you include a directory in the name attribute it has the potential to fail because hxcpp does not create folders for you.

<!-- allowed name values (androidNdk, blackberry, msvc, mingw, emscripten) -->
<setup name="androidNdk" />

The setup element is used to set defines for specific build environments. It takes a name for a build environment and then passes the currently defined values to that tool.

<pleaseUpdateHxcppTool version="0.1" />

Used by hxcpp when the version is outdated. I would advise against using this element unless you know what you're doing.

Targets and File Groups

Targets are the bread and butter of hxcpp's configuration files. Think of a target as a group of executed commands to eventually either build an executable or some other form of output. Let's go back to the basic example from the beginning of this post for a second.

<xml>
    <target id="default"></target>
</xml>

So we can see that a target at the most basic sense only requires an id attribute. It's also worth noting that the "default" target is important because it specifies the starting point for hxcpp. Without a default target hxcpp will display an error message. Let's add a bit to this example.

Hello.cpp

#include <iostream>
int main(int argc, char *argv[])
{
    std::cout << "Hello world!" << std::endl;
    return 0;
}

Build.xml

<xml>
    <!-- This is a file group, it contains a set of source files -->
    <files id="common">
        <file name="Hello.cpp" />
    </files>
    <target id="default" output="hello" tool="linker" toolid="exe">
        <files id="common" /> <!-- reference the file group we just created -->
    </target>
</xml>

If you create a Hello.cpp file with the code above and run haxelib run hxcpp Build.xml you'll see that it generates an executable file. Run that executable and you will see Hello world! in the console window. You just compiled a C++ program using hxcpp!

Let's dissect this a bit. First there is a file group, which in this case is just our main C++ file. Following that is our default target but it has a bunch more attributes added to it. The output attribute determines what the final output will be named. If you are on Windows you may have noticed that it automatically adds a ".exe" extension. The tool and toolid attributes work together to determine how hxcpp will compile the code. The only supported value for tool is linker right now and the toolid can be a variety of values (exe, dll, static_link are the most common). You can make your own custom linker tool but I'm not going to cover that in this post.

Compiler Flags

If you've compiled anything beyond the most basic programs in C++ you've probably hit the need to include libraries in your program. Thankfully hxcpp supports external libraries as we'll see below.

<target id="opengl">
    <files id="opengl" />
    <!-- compiler flags -->
    <flag value="-I/usr/local/include" />
    <lib name="-lgl" unless="macos" />
    <vflag name="-framework" value="OpenGL" if="macos" />
</target>

These flags can get a bit confusing because they all basically do the same thing. The main difference between the flag elements and lib is that the former comes before the objects passed to the compiler and the latter is placed after the objects.

Also, flag and vflag are nearly identical except that vflag takes two values and merges them together with a space. It's important to note that you should not add spaces in these values unless you want them to be wrapped in quotes. Which also means that sometimes you have to use workarounds like add multiple lib elements for flags that require spaces.

Directories

<target id="directories">
    <outdir name="build" /> <!-- directory to place final output -->
    <builddir name="source" /> <!-- directory where source files are -->
    <dir name="obj" /> <!-- mentioned in the hxcpp source but never used... -->
</target>

As you can see there are several directory options that can be set. The first is the outdir element that specifies where to put the final output, from the linker step. It should be noted that the outdir name has a forward slash, "/", appended to it. The builddir can be thought of as the base directory. It is where you will find the files you want to compile and it will set the base for the outdir as well.

Dependencies

<target id="misc">
    <depend name="parent" /> <!-- requires another target to finish before this one -->
    <section if="linux"> <!-- works just like the root section -->
        <ext value=".so" /> <!-- add an extension to the output -->
    </section>
</target>

Like other parts of the build configuration, targets can have sections as well. This will group target configuration options just like you could group options at the root level. I've also added the depend element which tells the build tool that it needs to finish a different target before running the current one. Note that this does not use the id attribute but instead uses name.

Using With Haxe

Up until this point we've been using hxcpp to compile a C++ file directly. This is a perfectly viable way to build C++ and could be used in place of makefiles. However, you are probably wondering how this ties in with the Haxe language. So let's take a look at an example.

Main.hx

@:buildXml('<echo value="I added something to Build.xml!" />
<target id="haxe">
    <lib name="-lgl" />
</target>')
class Main
{
    public static function main() { }
}

build.hxml

-cpp out
-main Main

The buildXml metadata allows you to insert additional elements at the bottom of the generated Build.xml file. Take a look at out/Build.xml and scroll to the bottom of the file and you will see what was added. When you compile you should see the echo line show up in the output.

Now you may be wondering why there is a target with the id of "haxe". This is a special target id that Haxe creates when transpiling to C++. One thing that hasn't been mentioned so far is that targets can be appended to if they have the same id value.

Appending and Overriding Targets

If two targets have the same id value they will be appended by default. You can change this by setting an override attribute. See the example below for clarification on how these attributes work.

<xml>
    <target id="foo"></target>
    <target id="foo">
        <!-- append to foo -->
    </target>
    <target id="foo" overwrite="true">
        <!-- remove foo's contents and replace them -->
    </target>
    <target id="foo" append="true">
        <!-- append to foo (same as not having the attribute) -->
    </target>
</xml>

You may want to append or override targets when including other build configuration files. This gives you a lot of flexibility in how you compile your project.

What's Next?

This post covered a lot of the basics of hxcpp's xml file format. The information covered should get you started and hopefully provide a clear explanation on many of the elements found in Build.xml files. In future posts I'll cover how to use a Build.xml file to compile a Haxe extension and also the inner workings of hxcpp's linker and compiler elements.

Posted in Haxe

Tags: hxcpp

comments powered by Disqus