Klas gives you more control on the order build macros are run. With Klas you only
have to add implements Klas
to your class and any build macro that self registers
with Klas can be accessed with metadata.
Installation | Setup | Register w/ Klas | Build Hooks | Build Order |
Utilities | Defines | Metadata | Example | Libs using Klas |
You can find more information on how klas works at the end of the README.
With haxelib git.
haxelib git klas https://github.com/skial/klas master src
With haxelib local.
# Download the archive.
https://github.com/skial/klas/archive/master.zip
# Install archive contents.
haxelib local master.zip
Add implements Klas
to any class. Make sure -lib klas
is in your .hxml
build
file.
To add your build macro to Klas you need to do two things.
-
Add the following
initialize
method to your build macro.private static function initialize() { try { KlasImp.initialize(); } catch (e:Dynamic) { // This assumes that `implements Klas` is not being used // but `@:autoBuild` or `@:build` metadata is being used // with the provided `your.macro.Class.build()` method. } }
-
You should also provide a normal entry point for people not using Klas, who will be using the
@:autoBuild
or@:build
metadata. -
If your build macro does not already have an
extraParams.hxml
file, create one in the root of your library. -
Add
--macro path.to.your.Class.initialize()
to yourextraParams.hxml
file. -
Anyone using Klas and your macro library, with all the correct
-lib
entries will automatically bootstrap themselves into Klas.
Klas provides the following hooks/variables you can register with. You would place
the hook after the line KlasImp.initialize()
in your initialize
method.
-
The
info
string map allows you to register a callback which allows you to inspect theType
and its build fields if theType
has any. The string map key should be the path to the type you are interested in and your handler type should beArray<Type->Array<Field>->Void>
.// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.info.set( 'path.to.your.Type', ClsMacro.handler ); } catch (e:Dynamic) { } }
You can request to inspect a type by calling
uhx.macro.KlasImp.inspect('path.to.your.Type', Your.callback)
which will runYour.callback
once theType
has been processed by Klas. Your callback should have the type ofType->Array<Field>->Void
. -
The
allMetadata
signal allows you to register your callback which will be run for each class thatimplements Klas
. Your handler should be of the typeClassType->Array<Field>->Array<Field>
.// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.allMetadata.add( ClsMacro.handler ); } catch (e:Dynamic) { } }
-
The
classMetadata
signal allows you to register your interest in classes that have a specific metadata attached to them. Your handler should be of the typeClassType->Array<Field>->Array<Field>
.package path.to.your; @:metadata('value1', 'value2') class Cls { }
// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.classMetadata.add( ':metadata', ClsMacro.handler ); } catch (e:Dynamic) { } }
-
The
fieldMetadata
signal allows you to register your interest in methods and variables that have specific metadata attached to them. Your handler should be of the typeClassType->Field->Field
.package path.to.your; class Cls { @:metadata public static var hello = 'hello'; @:metadata public static function main() {} }
// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.fieldMetadata.add( ':metadata', ClsMacro.handler ); } catch (e:Dynamic) { } }
-
The
inlineMetadata
signal allows you to register your interest in methods that contain inline metadata. Your handler should be of the typeClassType->Field->Field
.package path.to.your; class Cls { public function new() {} public function setup():Void { var a = @:metadata 100; } }
// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.inlineMetadata.add( ~/@:metadata\s/, ClsMacro.handler ); } catch (e:Dynamic) { } }
-
The
rebuild
string map allows you to register a handler which will return a rebuilt type. Your handler should be of the typeClassType->Array<Field>->Null<TypeDefinition>
. To trigger a rebuild, you have to calluhx.macro.KlasImp.triggerRebuild('your.Class', ':metadata')
. This should only be called during the macro context.// Hooking into Klas. private static function initialize() { try { KlasImp.initialize(); KlasImp.rebuild.set( ':metadata', ClsMacro.handler ); } catch (e:Dynamic) { } }
// Triggering a retype. private static macro function hello_world():ExprOf<String> { uhx.macro.KlasImp.triggerRebuild( 'path.to.your.Cls', ':metadata' ); return macro 'Hello World'; }
info
classMetadata
fieldMetadata
inlineMetadata
allMetadata
rebuild
The info
hook might be processed before all other hooks.
rebuild
will only run after allMetadata
if any pending calls to uhx.macro.KlasImp.rebuild
exist.
Otherwise it runs whenever it is called with uhx.macro.KlasImp.triggerRebuild
.
You can call these utility methods from your macro methods.
-
KlasImp.triggerRebuild(path:String, metadata:String):Null<TypeInfo>
. Attempt to rebuild the type specified bypath
if it has matchingmetadata
. If successful, aTypeInfo
object will be returned, otherwisenull
is returned. -
KlasImp.onRebuild:Signal1<TypeInfo>
. A signal which will notify you when a type has been rebuilt. -
KlasImp.inspect(path:String, callback:Type->Array<Field>->Void):Bool
. Register a callback to be called when the specifiedpath
is detected.
Add the following defines to your hxml
file.
-
-D klas_verbose
. Setting this will leave certain utility fields in your output and print debug information to your terminal. -
-D klas_rebuild
. Used internally when types have been rebuilt.
@:KLAS_SKIP
. Used internally to completely skip a type from being processed by Klas.
The following initialize
, build
and handler
methods are taken from Wait.hx.
private static function initialize() {
try {
KlasImp.initialize();
KlasImp.inlineMetadata.add( ~/@:wait\s/, Wait.handler );
} catch (e:Dynamic) {
// This assumes that `implements Klas` is not being used
// but `@:autoBuild` or `@:build` metadata is being used
// with the provided `uhx.macro.Wait.build()` method.
}
}
public static function build():Array<Field> {
var cls = Context.getLocalClass().get();
var fields = Context.getBuildFields();
for (i in 0...fields.length) {
fields[i] = handler( cls, fields[i] );
}
return fields;
}
public static function handler(cls:ClassType, field:Field):Field {
switch(field.kind) {
case FFun(method) if (method.expr != null): loop( method.expr );
case _:
}
return field;
}
Klas adds one build macro to every single type which gets initialized via
its extraParams.hxml
file. This KlasImp.inspection
build macro allows Klas
to gather information about all types which is later available via KlasImp.inspect
.
You can rebuild a type by calling KlasImp.rebuild
. In this method
Klas checks that its details have been gathered and the metadata you
specified exists. It then passes all the information about the type to a
pre registered callback and expects to receive a TypeDefintion
object back.
Klas then saves this TypeDefinition
object to $cwd/klas/gen/
, reconstructing
the types package. For example, rebuilding a type called a.b.Test
will save the
rebuilt type to $cwd/klas/gen/a/b/Test.hx
. At the end of compiling,
Klas re-invokes the compiler with the same arguments you compiled with
but with the additional arguments -cp $cwd/klas/gen/
and -D klas_rebuild
.
This is currently the only cross platform, target independant way to rebuild a type where a combination of initialization macros, build macros, generic macros and macro methods are just not enough to achieve your goal.