Haxe is great. Greater than unicorns. Unfortunately decent solutions for handling different Haxe versions are like unicorns too: would be awesome but don't exist. Haxeshim tries to fill that gap. It does the following things:
- Allow having different haxe versions.
- Manage
-lib
params without haxelib. - Add
-scoped-hxml <file>
to process hxml files while resolving paths relative to their location. - Adds a few "extensions" for easier IDE integration.
- Interpolate compiler arguments.
- Handle
--cwd
in an arguably more meaningful way.
At the bottom line this project merely decouples a number of things that are currently just mushed together in the standard Haxe distribution. The decomposition itself would be highly advisable to apply there too, but attempts to argue for that have shown no effect, so an independent effort would seem to be the only way forward.
The project is currently based on nodejs for execution and distributed through NPM, due to their ubiquity. The command line interfaces aside, much of the code is written against Haxe's sys APIs and should thus be portable to other targets, as it should be, because currently it adds quite an overhead to compilation time (~100ms). Some of it can be optimized away, but given that invoking nodejs alone comes with quite an overhead, a truly optimized solution will have to rely on a different runtime.
Haxeshim has a "root" directory, depending on platform:
- on Windows it is
%APPDATA%/haxe
- elsewhere it is
${HOME}/haxe
It can always be overwritten with the HAXESHIM_ROOT
environment variable.
When running the haxe
command, we scan from the CWD up for a .haxerc
and if non is found we look in the "root" directory. Every .haxerc
defines what we consider a "scope" for all subdirectories (except those which contain .haxerc
files to define new scopes).
The contents of this file are stored as JSON and defined like so:
typedef Config = {
var version(default, null):String;
var resolveLibs(default, null):LibResolution;
}
@:enum abstract LibResolution(String) {
var Scoped = null;
var Mixed = 'mixed';
var Haxelib = 'haxelib';
}
We'll cover library resultion below. As for execution of the haxe compiler itself, the binary in <HAXESHIM_ROOT>/versions/<version>
is picked, with HAXE_STD_PATH
set to the accompanying std lib.
Currently haxe
depends on haxelib
to resolve -lib
parameters. Haxeshim breaks this dependency apart, because it's the only sensible thing to do really. Haxelib was an excellent tool when it was released over a decade ago, but it's a bit dusty and improving it is extremely hard because of this strong dependency. Compare this to nodejs, where require
follows a set of very specific rules where to look for modules and thus looks in the appropriate node_modules
. NPM simply leverages this behavior to install packages where they are expected.
Haxeshim builds on the idea that every scope has a single version of a library. Normally you will want scopes to coincide with projects. There are three different resolution strategies:
Any parameters that are passed to haxeshim are parsed, including hxmls and the -lib
parameters are "intercepted". To resolve these, we look for a haxe_libraries/<libName>.hxml
and parse the parameters therein. If they are -lib
parameters we process them accordingly. Note that in this case, specifying library versions as with -lib name:version
is not allowed.
Parameters are still parsed and then passed to haxelib path
for resolution. In this case -lib name:version
syntax is allowed.
This is a mix of both approaches. Libraries that are not found using scoped resolution or that use -lib name:version
format are process with haxelib path
.
Suppose you have set up a project in ~/projects/piratepig/
and you want to use its build configuration defined in arrrrr.hxml
from another project. If you use -scoped-hxml ~/projects/piratepig/aye.hxml
then all the libraries referenced therein are resolved within the scope of the project.
If a project has a specific build configuration that you wish to reuse, you can also add it as a git submodule and reuse its hxmls in such a manner.
There are two things to be aware of:
- because your project may be configured to use a different Haxe version, the build configuration may be incompatible
--next
and--each
are not properly supported in scoped hxmls. Also, if both the scoped and the callinghxml
have the same-lib depenency
then both results are added to the build and class path shaddowing determines the result of that.
You can run haxeshim extensions through haxe --run <extension-name> [...args]
. Any extension-name
must be lower case and contain at least one -
sign to avoid collisions with haxe --run <some.path.ClassName>
.
Currently, the following extensions are implemented:
-
install-libs
: this will go through all library hxmls inhaxe_libraries
and in case of missing class paths will pick up@install:
and@post-install
directives and execute them or report an error if none are present. Note that first all@install
directives are run, and then all@post-install
directives are run. Beyond that, consider the order strictly undefined. -
resolve-args
: will resolve all the following arguments based on haxeshim's rules and prints each resulting argument on a single line. -
show-version
: will report the current haxe version like so:-D haxe-ver=<theVersion> -cp <pathToStdLib>
All arguments are interpolated, using environment variables. The interpolation syntax is ${name}
and $name
will not be interpreted as a variable. If interpolation leads to an empty argument, the argument is simply skipped.
With standard haxe, --cwd
will simply switch the current working directory. Consider the following arguments:
--cwd /path/to/foo
-cp src1
--cwd /path/to/bar
-cp src2
--cwd /path/to/baz
-cp src3
In standard haxe, all relative class paths are looked up within the final cwd, i.e. /path/to/baz
in this case. Arguably, this is not what was intended.
Haxeshim makes all class paths absolute during resolution, then uses the final cwd as basis and makes all class paths relative to that one if possible. The above arguments are thus interpreted as follows:
--cwd /path/to/baz
-cp /path/to/foo/src1
-cp /path/to/bar/src2
-cp src3
If the very first argument pair is a --cwd dir
then that is used not only as a cwd for the haxe process, but also as the initial entrypoint for scope resolution. Note that --cwd
inside hxmls are not taken into account for scope selection.
It is true that haxeshim kinda bypasses access control, allowing users to accidentally have their haxe command hijacked in some malicious way. Given though that anything running with the current user's privileges can tamper with the installed haxelibs and every haxelib using extraParams.hxml can execute arbitrary code with whatever privileges haxe
was invoked with, we're not making it any worse.
To build, you will obviously need a Haxe version (haxe 3.4.0-rc.2 is known to work). Clone the repo recursively and then you can build.
Windows support seems ok on Windows 8.1 and 10.
The tool attemps to place a .exe
files near the haxe.cmd
and haxelib.cmd
files that NPM creates. The .exe
files to nothing but call the .cmd
file of the same name. This is because calling .cmd
in batch stops execution. Try running this from a batch file to observe the behavior:
haxe -version
haxe -version
haxe.cmd -version
haxe.cmd -version
It also places a fake CHANGES.txt
into the npm command directory in a rather futile attempt to please FlashDevelop/HaxeDevelop.
Tested on the Ubuntu. Because it is nodejs based, chances are it works on other distros too, but if not, please report an issue.
According to feedback so far, haxeshim works on MacOS.