Haxe macro build order

Hej !

I’m sorry I must be tired but haven’t found anything on the net that could help me even if I think it’s a trivial question.

I know that build macro order isn’t specified so my first question is : even if I have a hub for all my build macros in order to “order” them, how to deal with 3rd party libs that don’t use my hub, how to do my builds run after all that 3rd party build macros please ?

Then my second question is : Can the build macro order do that when I try to get fields of a class (t.get().fields.get()), let’s say in the current processed class, I get a type of a field and I try to get the instance fields of that type, it gives me empty array ? And how to prevent that please or get the fields another way ?

Thanks for your enlighten

Another question that I have is while I understand types building order is unspecified, why the build macro order is also unspecified ? I mean just the @:build and @:autoBuild metas order/position ?
For example if on a type I have several @:build metas, I can see that the first positionned one isn’t always executed first.

Extending that question is, putting init macros, that add @:build and @:autoBuild metas on types using addGlobalMetadata, doesn’t ensure that these build macros will be executed in the order they where added by init macros, while init macros are, what I have seen, executed in the right order (the position they have in the build .hxml file).

And at the end, the thing I thought I could do in order to reorder all build macros was defining an init macro put in the right position that will add @:build on all types using addGlobalMetadata, so this build macro would be executed first, and for all @:buildmetadata on the types, remove them and “inline” execute all the build functions they were supposed to execute, in a specific order : so it would be reordered as wanted.
But here also, I’ve seen that removing @:buildmetadata on the types kind of doesn’t work and these builds are still executed as if they where yet added into a kind of stack, I don’t know…

I’m sure I lack knowledge here and this kind of thinking was too naive ?

I’m afraid I can’t help much, unless you’re willing to rethink your code. What are you trying to accomplish with your macros? Perhaps we could find another implementation that doesn’t rely on execution order?

Thanks for your answer.
In fact, I’ve resolved my problem but I still have these question pending just by curiosity, I would know for example why the build macros are not executed in the order the meta are written…

Well, after few experimentations it seems that I was yelling for nothing…
The types build order is unspecified BUT when a type is built, the @:build macros are applied in order they where written/added, so there is no more problem here.
So, in the .hxml file, if you add at the top a init macro that “manages” others build macros, it will be ok.
(And my misunderstanding came from that I also use libs -lib in the .hxml file that had also init macro that put a @:build meta, so they were executed before my init macro in the .hxml file… I d’ont know if my explanation is clear…)
So just in case somebody understand what I’m talking about can confirm me that the build macros are applied in order they are put please ?

Yup, you’re correct about the order.

It’s best to avoid inspecting other classes from a build macro, let alone an init macro. Whether or not that’s possible of course depends a lot on the use case :wink:

2 Likes

Hej,

I come back with another question a bit linked.

When you add (by hand or through an init macro) @:build metas, they are “executed” in the order that they where put. And here it’s ok.

But I noticed that the @:autoBuild meta puts always its “own” @:build metas (on its descendants) (@:autoBuild - Haxe - The Cross-platform Toolkit) on the top (of those put in an init macro or by hand) as you can see it here : Try Haxe !

I suppose that @:autoBuild meta is processed first and then comes the @:build that’s why all builds added by an autobuild are on the top…

In other words, what I understand is that an @:autoBuild always kind of “unshift” (Adds the element x at the start of this Array.) and not “push” (at the end).

If it’s the case that this works like that, I wonder if it would be interesting to add a last optional argument to the @:autoBuild meta, something like ?top : Bool on this @:autoBuild meta ?
So it would just say to the compiler it it has to “unshift” or “push” the @:build metas that belongs to an @:autoBuild meta ?

I also tried to think about an alternative solution because I always have weird cases and I often annoy Haxe community with my suggestions/askings, like maybe “simulate” @:autoBuild ?
Like add a @:build on all its descendants in an init macro i.e. ? But the goal would be to know all classes that extend a super one and Ithink it’s impossible in a init macro (without typing and running into other problems…)

If someone has advices please ?

Thanks for reading me,
Cheers

I auto-answer myself : I think the only way to “control” @:build and @:autoBuild order is to “override” the @:autoBuild and set an “ignore” list of classes (that have to be skipped for the regular @:autoBuild) and then, in the skipped classes (when their build runs, or if not have a @:build so set it a build…), retrieve all super classes, look at the @:autoBuild and execute it if wanted and in the wanted order*

So here is the “normal” version ( “not controlled build metas order” ) : Try Haxe !
And here there is an example of “controlled build metas order” : Try Haxe !
Main.hx :

@:autoBuild(Macro.autoBuildOverride(Macro.build2(), ["B"]))
class A {}

@:build(Macro.build1())
// @:build(Macro.build2())
@:build(Macro.build3())
class B extends A {}

Macro.hx :

public static function build3() {
		var cl = Context.getLocalClass();

		var sucl = cl.get().superClass.t.get();

		if (sucl.meta.has(":autoBuild")) {
			var mexp = sucl.meta.extract(":autoBuild")[0].params[0];
			var sm = mexp.toString();
			if (sm.indexOf("Macro.autoBuildOverride(") == 0) { // Call the normal autoBuild
        // dirty quick call
				var split = sm.split("(")[1].split(".");
				var o = Type.resolveClass(split[0]);
				var field = Reflect.field(o, split[1]);
				Reflect.callMethod(o, field, []);
			}
		}

		return null;
	}

	public static function autoBuildOverride(e:Expr, ignore:Array<String>) {
		var cl = Context.getLocalClass();

		if (ignore.indexOf(cl.toString()) > -1)	// Skip if needed or now
			return null;

		trace(e.toString()); // Normal autoBuild, call e ...

		return null;
	}

This is the only way I found, If someone has better way and/or advices I’ll be thankfull.
Thanks for reading me again,
Regards,
Michal

Note : I’ll expose quickly my use case from space : haxe.macro.Compiler.setFieldType() becomes deprecated in the new Haxe version, so I want an alternative, I do it with a @:build macro, and it works fine.
BUT, a 3rd part lib, use an @:autoBuild (so a @:build is put “on the top” on my class (where I need to change a field type…)) to create getter/setters from some fields.
But since the getter setter creation runs BEFORE my custom @:build macro that changes the type, the getter setters are wrong…

Here again, if you have any advice or better way to do that it would be great.

The final way here if interested :

Here we will get :

Macro.hx:11: bar
Macro.hx:6: foo

With :

@:autoBuild(Macro.bar())
interface IFoo {}

@:build(Macro.foo())
class A implements IFoo {}

If you want change the order and get :

Macro.hx:11: foo
Macro.hx:6: bar

Just do that :

@:autoBuild(Macro.autoBuildIgnore(["A"], "Macro.bar", []))
interface IFoo {}

@:build(Macro.foo())
@:build(Macro.bar())
class A implements IFoo {}

Mainly it just about the autoBuildIgnore() function that just ignores given classpathes when running an @:autoBuild so you can put the corresponding @:build where you want.

An init macro can also be used that does that “dynamically”, so you can switch all that when using haxe.macro.Compiler.addGlobalMetadata()

public static function autoBuild2Build(path:String, children:Array<String>, sf:String, ?args:Array<String>) {
		args ??= [];
		var autoBuildMeta = '@:autoBuild( Macro.autoBuildIgnore( [' + children.map(s -> '"$s"').join(", ") + '], "$sf", ['
			+ args.map(s -> '"$s"').join(", ") + '] ) )';
		haxe.macro.Compiler.addGlobalMetadata(path, autoBuildMeta, false);

		for (child in children) {
			var buildMeta = '@:build( Macro.callBuild( "$sf", [' + args.map(s -> '"$s"').join(", ") + '] ) )';
			haxe.macro.Compiler.addGlobalMetadata(child, buildMeta, false);
		}
	}

Here’s the full example : Try Haxe !

For now it works so it’s ok for me…