<![CDATA[hxml - ruby0x1.notes]]>https://notes.underscorediscovery.com/Ghost 0.11Sat, 13 Feb 2021 14:53:42 GMT60<![CDATA[Haxe Entry Point]]>A title with two meanings, what have we here!

Recently I wrote about Haxe from 1000ft, which looks a the way Haxe fits together, how it's flexibility makes it difficult to explain, and how an onlooker might better understand it.

This post is a follow up, and discusses what happens

]]>
https://notes.underscorediscovery.com/haxe-entry-point/5d5cba3d-2506-4d4c-ac78-abe860e0e3ebSat, 07 Mar 2015 08:03:56 GMTA title with two meanings, what have we here!

Recently I wrote about Haxe from 1000ft, which looks a the way Haxe fits together, how it's flexibility makes it difficult to explain, and how an onlooker might better understand it.

This post is a follow up, and discusses what happens if you were interested in using Haxe for something, and were curious about the entry point from a user perspective.

To tackle the basic usage and understanding of the Haxe environment, we will write an example command line tool using Haxe.

This is a continuation of a series on Haxe itself, which you can find all related and future posts under the “haxe” tag.

haxe.org

Here is part one.
This is part two.


User entry

When you install Haxe, it installs a few command line tools that include the compiler, haxe, the package manager, haxelib and some other tools that it needs.

Let's start at haxelib, the first step usually.

haxelib

haxelib is a tool that manages haxe libraries, which is where the name comes from. In python you would be familiar with pip or pypa, on ruby there are gems, node.js has npm - if you've used any package manager before you should be quite at home.

haxelib repository
haxelib has a central repository of libraries. Libraries are distributed in zip form and are easily installed/removed from your computer at your discretion. Libraries are submitted by third party developers and offer a diverse range of tools, frameworks, and more through a simple command line interface.

haxelib install
If you want to install a library, it's as simple as saying haxelib install hxcpp. That would install a library named ”hxcpp”, which you will need to develop against the Haxe C++ target.

haxelib run
A library is allowed to include a "run script", which is a small neko module (mentioned here before) that can do certain tasks - perhaps some post-install configuration or thats how you interact with the library. For example - flow - the build tool used by snowkit libraries - is used entirely via the haxelib run command. To run the script, you would call haxelib run flow, which would show it's options.

This varies by library, so you would usually want to check the docs or readme about the library before executing the command blindly.

haxelib config and versions
A lib would download and unzip into a location you can set or view by calling haxelib setup(set) or haxelib config(view). On OS X for example the default should be /usr/lib/haxe/lib/, and within that folder it would put all your libraries.

Libraries have versions, and are unpacked into a folder structure which looks like this: hxcpp/3,1,68/. To install a specific version, I would call haxelib install hxcpp 3.1.48, which would install that version alongside the existing ones.

switching library versions
To switch between multiple versions of the same library you use haxelib set. To switch to an older version of hxcpp, I would call haxelib set hxcpp 3.1.48, which would make that the active version that's used by subsequent builds.

To see which versions you have installed, you can run haxelib list which shows all library versions. If you wanted to know where exactly the current version is located, you would use haxelib path hxcpp (which also for some reason includes some additional lib definitions).

haxelib updates
To update to a newer version of a library (if there is one) you would run haxelib update hxcpp.

You can also try haxelib upgrade, which would check and ask about upgrading every installed library - but be aware that you should check what was updated against your projects. You don't want to accidentally update to an incompatible version without realizing. I find it easier to be intentional about it, so I can switch to a version that is compatible again later if needed.

dev libraries
Sometimes a library won't be uploaded to haxelib repository yet, or you might want to make your own folder into a haxelib library under a given name. To do this you can use the haxelib dev command, which would specify the dev version and which specific folder on your computer you want it to use to represent it.

haxelib dev mylibrary /Users/Sven/dev/mylibrary/ would "install" this folder as a haxelib called "mylibrary", allowing me to use -lib mylibrary when building haxe projects later.

You can also install a library from a zip package, maybe you manually downloaded hxcpp 3.1.39 at work and wanted to install it offline at home. To do this, you would use haxelib local file.zip, which would be the same as if it were downloaded. This is also useful for testing your own libraries while developing them.

Let's say I was editing hxcpp for something I was testing, I could make a local copy, and point to it with dev, haxelib dev hxcpp ~/dev/hxcpp/. This allows me to say which hxcpp is used for all projects. If I wanted to go back to using the stable version, I would just call haxelib dev hxcpp which would stop using the dev version again.

git haxelib repositories
Another great use case is installing a dev version directly from a git URL. This is useful as many libraries might not yet be ready for haxelib releases, due to active development. Or, sometimes libraries get stuck in limbo waiting for a new haxe release.

The usage would be the same as the dev command, except it would use git to pull down the repository. It would use git to update it as well, when running update/upgrade. Using hxcpp as a continued example, if you wanted to try the bleeding edge version, you could run haxelib git hxcpp https://github.com/HaxeFoundation/hxcpp.git which would pull down the latest git version for you. To switch back to stable, you would just run haxelib dev hxcpp as before.

other haxelib info
More information about haxelib can be found on the Haxe website or by running haxelib with no arguments.

hxml

Now that we know how to install and manage libraries, how do we use them?

The Haxe intro post mentions hxml briefly, but here we will consider it as step two, in getting started with Haxe.

hxml = haxe markup language
The meaning of hxml is a guess from my part (but it would make sense). It's a very simple markup file that acts as command line arguments to the Haxe compiler - and that's it.

Let's take the simplest example, the haxe -version command. That second bit, the argument, we can put that inside of a text file and split each argument up, one on each line.

# haxe_version_example.hxml
-version

It helps to use the hxml extension, because then people know what to do with the file.

using hxml files
To use the hxml file, you simply hand it to haxe as an argument:
haxe ./haxe_version_example.hxml

The results would be identical to the above haxe -version command. This seems silly for one command, but for large projects with many flags, defines, library dependencies and targets it would quickly become impractical to use the command line directly. hxml files solve this easily.

hxml in practice
Now we know where to put the hxml file, what do we put inside it? There's a LOT of options actually, if you run haxe -help you would see a small section of the possible commands to Haxe.

To continue on our path though (we are at step two), we will only look at a few useful pieces of hxml to move forward.

  • -main ClassName
    • The class where the literal entry point will be
    • The entry point is a static function main() { }
  • a target to build to
    • Could be one of many, in our example -neko
  • -lib libraryname
    • Specify a haxelib dependency
  • -D mydefine
    • Specify a conditional define for #if mydefine within code
  • --next
    • Split between subsequent sections of the same hxml
    • You could consider this as starting a new hxml, within the existing one. This allows multiple targets or steps to be executed in order.
  • -cmd
    • Runs an arbitrary command after previous commands complete.

Example: “haxelibcounter”

To further our journey, we will ground it in practice. To do this, we will write a simple tool to count the number of haxelib libraries using the knowledge above. Here's what we'll do :

  • Use a library to parse command line arguments
  • Use hxml to build our program into a neko binary
  • Turn this command line tool into a haxelib of it's own

Note: The code is in snippets below, but the entire code is embedded at the end for clarity, and is also at the github repo in full.

This seems trivial, but it's the basis of every haxe game, application, website or tool. We are going to be using the haxe neko target (more about that here) to generate a small, cross platform binary that haxelib will use to run our tool from a user perpsective, as we discussed above.

step one: dependencies
So to begin, if you were paying attention, we need our lib dependencies! We are going to need the library we want to use to parse arguments. For the sake of example I have used my own simple library called “arguable” which I use in my own tools to handle command line parameters. It's very simple in itself, so it might serve as an interesting place to poke at if you're curious.

First, we need it installed. From a command line, we run haxelib install arguable.

There - it shows us what was inside the library, it has no run.n file, so we can't use haxelib run arguable, and we can see it has a test/build.hxml file - if we wanted to build the test we could pass that file to haxe.

The rest doesn't matter for now,

step two: create a hxml file

To do this we create a blank file, and fill it Haxe command line arguments. We will name our file build.hxml, as I find this easy to spot as the method to (re)build something.

The minimum we would be doing is specifying an entry point (-main) and a target (-neko), but we are also using a library, adding a define, and running the tool while we develop (this gives us quick turn around time).

  • Specify the entry point:
    In the hxml file, we start with -main HaxelibCounter. This tells Haxe to look for a file called HaxeCounter.hx - and inside it look for a static function main {} so that it knows where to start.

  • Specify a target:
    Since we want to build to the neko target with haxe, the docs tell us that we use -neko file_to_create.n. Our exact usage is -neko run.n, we are using run.n, because we want to use this library from haxelib run.
    This file is platform independent - as long as you have neko, you can run it. This is why it's used for the haxelib run command, it works anywhere Haxe is installed. If you wanted to create a platform dependent binary/exe, you would use nekotools boot file.n and what you would get out of that is file or file.exe, depending on the platform.

  • Specify the dependency:
    -lib arguable will tell Haxe that we are using haxelib to depend on this library. What this does is tell Haxe where the code from arguable will be on your computer, via haxelib. This way, when you reference code from within an arguable package, it can find it (more on this below).

  • Specify a define:
    This is just an example, so we will make a define called haxelibcounter_verbose which we will use to output more information in a debug type of build.

  • Tell neko to run the file it just built:
    As mentioned above, the -cmd argument will execute something for us. We can add -cmd neko run.n, immediately running the results if any of the build.

Onward
This is our starting hxml file, for this project:
(# is a comment line, it's ignored by Haxe)

Writing the tool

Now we get to the code. We create a blank file, name it HaxelibCounter.hx, and put it in the same folder with the build.hxml. Inside the file, we need a class named HaxelibCounter, and we need a function within that class called static function main. This is the Haxe entry point.

class HaxelibCounter {

    static function main() {
        trace("This will do stuff soon!");
    } //main

} //HaxelibCount

First run

Now we have :

  • A file to build
  • A file to tell Haxe how to build it
  • And that will run the program when it's done

We can run our application using Haxe \o/
From within the same folder from a command line, run: haxe build.hxml

And you should see:

> haxe build.hxml
HaxelibCounter.hx:6: This will do stuff soon!

Importing code from the library

Now we need to reference some code from arguable. We do this using the import statement. Import statements must be before everything else in the file, so it goes right at the top of our class.

Class paths
The import statement basically checks a list of locations called the class path to see if the module you are looking for is in there. Our example: import arguable.ArgParser;

If you look at the haxelib install log above, you will notice that arguable/ArgParser.hx is actually just a file within a folder. This is no coincidence! A . within the import statement is stepping one depth lower into the class path. This is true for a folder (a package), or a Module (multiple types within one hx file) - more on that below.

By using -lib arguable, we add the location of that folder that haxelib is keeping track of - to the class path. Now, when some Haxe code says import arguable.ArgParser;, the modules within the class path are checked. Since a single file on disk can contain multiple types, the file that contains them is called a Module. hint: You can also look up the -cp command, it adds a package folder manually.

Since that's all we need to know about that for now,

What about packages?
A package is a folder within the class path. arguable. is the package, ArgParser is the module. import arguable.ArgParser; tells Haxe to look inside the arguable package folder(s) and find a module called ArgParser. Simple enough!

This brings up an important rule of understanding in Haxe:

  • Modules/Classes MUST start with a Capital Letter
  • packages/folders MUST start with lower case

This is how you tell the difference between game.Game and Game.game.
Small letter: package (reaching into a folder).
Capital letter: A haxe class (module).

In this obtuse example, Game.game is a static variable on the class called Game, and game.Game implies that the Game class is in a folder called game/. Ideally you wouldn't name things in such convoluted ways, but with this simple rule you can always tell the difference.

after import
So, we imported the ArgParser module which is a Haxe class, this gives us a shortcut name for arguable.ArgParser. We no longer have to explicitly say arguable.ArgParser every time we want to talk about that class in our code - we have now explained to Haxe which ArgParser we are referring to, later.

Using the ArgParser class

Hopefully the library you are using is documented, as that's usually where you check for how to use things. If not, you should probably file an issue for documentation with the developer as, well, that's a major issue.

arguable is a very simple library, so the documentation is housed in the readme file. To use it, we simply give it the list of system arguments from the haxe std api.

The Sys.args() function gives us the command line arguments to our haxe program on language targets that have access to Sys (neko does). Note again, Capital = Class. There is also a sys package, which contains various other useful modules but we are using the Top Level class called Sys.

Let's change our code to the following and run it again haxe build.hxml:

static function main() {

    var args = ArgParser.parse( Sys.args() );
    trace(args);

} //main

We get
HaxelibCounter.hx:10: { any => false, length => 0, valid => [], invalid => [] }

So far so good, we didn't give it any arguments, so that result makes sense.

Using our define

Since we are making a tool we might want to print a lot of information during development to help us understand what path the tool is taking. We made a define to allow us to turn this on and off easily, so it doesn't burden the user unless they need it.

The defines are used with #if define_name and allow opposites with ! (not). #if !neko. You can combine them with ( ), like #if (cpp && neko && !mydefine). The Haxe manual covers these further, that's all we need. Let's make a debug function to do some logging:

Now our code looks like this:

var args = ArgParser.parse( Sys.args() );  
debug('> found args $args');  

And without that define (commented out in the hxml) we don't see that output. This will come in handy.

Implementation details

What we really want to do in this example is:

  • If the user has no arguments provided, show the usage information.
  • If the user requested we show the count, do that.
  • Additionally, if the user asks, print the names while we are at it.

We can now use arguable to do the simple handling, and add the printing of the usage. We'll fill in the do_count function next :

do_count()
What we are doing next is pretty straight forward, we ask haxelib list for all installed libraries, we split that up one per line, and then print the total.

Some notes: I have intentionally specified the 'full name' for sys.io.Process. This is an alternative to using import, and is important when you might have to modules/classes with overlapping names. I like to keep haxe API names explicit, so myself and others can easily look them up.

and --show-names

The last bit of functionality, we can display the names since we already have the list.

To do that, we can add:

Let's run it again. You'll probably find it only ever returns the usage info! We need to pass the arguments via our hxml file for now. You can try it out by just removing or adding the arguments here:

-cmd neko run.n --count --show-names

Now my output says:

Tidying up

One thing you'll notice is the HaxelibCounter.hx:48: stuff, that comes from trace, which is really important for debugging effectively. But since our tool will be user facing, we want to remove that from the output. For this, Sys.println is available, which will print to the command line without any prefixes.

We can use a built in define for this: #if debug. Haxe has a -debug command line option which enables more debug related features, depending on the target. It also defines the debug conditional which we can use to swap between clean logging and debug logging. When we want to debug, we uncomment the #-debug line from inside our hxml (shown later) and in the code :

Turning this into a haxelib for haxelib run

We already know that a haxelib needs a json file - and optionally a run.n file (which we have!) so let's create the haxelib.json, which is pretty much copied from the template in the documentation:

Now we can tell haxelib that it exists. From the project folder, we tell it to use "this folder" (./):

haxelib dev haxelibcounter ./

Now, it should show up in haxelib list, and we can try haxelib run. I've left debug and verbose flags enabled, for testing:

Notice anything new?

haxelib run adds a needed argument

When running from haxelib the last argument is always the path at which the command is being run from! That's useful, because we often need to know that. What haxelib is doing is basically neko run.n path/that/this/is/running/from. Keep that in mind when writing tools, and how that affects your argument processing.

Let's try with arguments to haxelib run. This time I have disabled -debug and verbose logging, so what we get is the end result we were looking for (i've trimmed the output obviously):

The keen observer might notice the number went from 42 -> 43 haxelibs, that's because I added this one during!

Submitting to haxelib for other users


Please note : Don't submit this tutorial yourself. I have submitted it and you can install it from haxelib install haxelibcounter to mess around with it. But submitting a bunch of the same thing is of no real use to anyone.

First we add documentation. Since ours is really small we will just add it to the README.md file, but it's crucial.

Then, we need to make our project into a zip file, and name the zip file with the same version name for clarity. I called mine haxelibcounter-1.0.0.zip.

Since haxelib.json mentions the contributors, it will ask me for my user password (it read the file and knows who I am). Now I just run haxelib submit haxelibcounter-1.0.0.zip and it will upload it, and become available.

Conclusion

Hopefully this guide helps you further understand the underlying Haxe toolkit and it's eco system through a practical example. Once you start using big libraries and frameworks that abstract you away from the hxml and the command line - it can easily become unclear as to how things fit together.

My hope in writing this is that you are aware of how the Haxe part fits together without all that, so when you are writing tools, games, applications, websites or whatever else using Haxe (either manually, or through a framework) - you have a good understanding of what happens behind the scenes. The more you understand about the tools you're using, the better equipped you'll be to tackle any problem. You'll also probably start to see Haxe in situations you didn't before, because of it's versatility.

Follow and find me on Twitter if you have feedback or questions, and as always I really appreciate the amount of people that share these articles with others!


If you would like something to try further, try listing the active version next to the name using --show-version or similar. You could also try compiling to a different target, but haxelib run only works with a run.n file and neko, and since we used sys api's, these won't be available on some targets.

All haxe related posts.


Full code listing:

All code is available directly here:
https://github.com/underscorediscovery/haxe-entry-point-example

build.hxml

HaxelibCounter.hx

]]>