How to automate defines' checks?

I often find myself in the need to test compiler defines in specific ways and react accordingly to their values.

Let me try to expand on it with a simple example:

  #if (!no_io || (no_io == 0) || (no_io == "false"))
    trace("Attempting to write output to disk");
  #else
    trace("NOT writing to disk");
  #end
    
  #if (!verbose || (verbose == 0) || (verbose == "false"))
  #else
    trace("Verbose debug output");
  #end

Is there a way to put that shared logic in a macro function and use it?

(kind of like this)

  #if (defineIsFalseOrNull("no_io"))
    trace("Attempting to write output to disk");
  #else
    trace("NOT writing to disk");
  #end
    
  #if (defineIsFalseOrNull("verbose"))
  #else
    trace("Verbose debug output");
  #end

As a little side note, I’d welcome the addition of a --log-defines arg to the compiler (name just made up) that pretty-prints them out, without having to resort to the verbose ouput of -v.

You can’t use macro in #if but you can write one to do something similar:

import haxe.macro.Context;
import haxe.macro.Expr;

class Main {
	public static function main() {
		#if (!no_io || (no_io == 0) || (no_io == "false"))
			trace("Attempting to write output to disk");
		#else
			trace("NOT writing to disk");
		#end

		#if (!verbose || (verbose == 0) || (verbose == "false"))
		#else
			trace("Verbose debug output");
		#end

		trace("==");

		strace("no_io", {
			trace("Attempting to write output to disk");
		}, {
			trace("NOT writing to disk");
		});

		strace("verbose", {
		}, {
			trace("Verbose debug output");
		});
	}

	macro static function strace(define:String, ifno:Expr, ifyes:Expr) {
		var v = Context.definedValue(define);		
		if (v == null || v == "0" || v == "false") {
			return macro $ifno;
		}
		else {
			return macro $ifyes;
		}
	}
}

Another macro-powered idea would be to have an init macro (--macro in compiler args) that does Compiler.getDefine to check for other defines and then Compiler.define to set the common flag.

1 Like

I would just use this:

class Defines {
	public static macro function isDefined(define:String) {
		var v = haxe.macro.Context.definedValue(define);		
		if (v == null || v == "0" || v == "false") {
			return macro false;
		}
		else {
			return macro true;
		}
	}
}

Then you can just use

if (Defines.isDefined("no_io")) {

}
else
{

}

Haxe doesn’t output a real if-statement, because it optimizes constants away. I mean it always skips complete expression like this if(false) trace("aa"); in your output \o/.

http://try-haxe.mrcdk.com/#Ea51d

3 Likes

Thanks for the interesting answers!

Probably @mark.knol’s solution is the one I like best. But I can’t see a way to apply it to class methods, like in this:

#if (!logging || (logging == 0) || (logging == "false"))
  inline static function log(v:Dynamic, ?infos:haxe.PosInfos):Void { }
#else
  static function log(v:Dynamic, ?infos:haxe.PosInfos):Void {
    haxe.Log.trace(v, infos);
  }
#end

Well, thinking more about it, I can probably rewrite it as:

  static function log(v:Dynamic, ?infos:haxe.PosInfos):Void {
    if (Macro.isDefined("logging")) haxe.Log.trace(v, infos);
  }

which is almost perfect. “Almost” because then I don’t know how/if I can add the inline modifier conditionally.

May I ask why you do #if (!logging || (logging == 0) || (logging == "false")) and not just #if !logging?
The other checks arent really needed?

Sure, I should have mentioned my use case earlier.

The main reason is because frequently I want to override the define in the .hxml with a new value directly from the command line.
Like in haxe build.hxml -D logging=false where logging was enabled in the hxml.

But generally was investigating ways of having shared define logic in a centralized place, so that I could run it on arbitrary defines (say for example also check the upper-cased version of a define).

Running this with -D logging=false:

#if (!logging)
#else
  trace("Logged!");
#end

will actually run the trace line, as logging is defined, and has a value of "false".

In that case I’d go with @nadako’s idea:

macro static function fix_defines(defines:Array<String>) {
	for (define in defines) {
		var v = Context.definedValue(define);		
		if (!(v == null || v == "0" || v == "false")) {
			haxe.macro.Compiler.define("real_" + define, "1");
		}
	}
	return null;
}

and in your hxml:

--macro Main.fix_defines(["no_io", "verbose"])

Then you can use real_no_io and real_verbose like normal defines.

You can also prepend a # in hxml to comment, instead of setting it to false. Saves a few keystrokes, saves you some complexity too. Dont make something that is simple, complex :slight_smile:

@mark.knol: fair point, I’m currently doing exactly that! :slight_smile:

But I’m recently deep in the debugging process of a project with quite a few defines. The project itself takes seconds to run to completion. And I’ve found that changing the defines manually in the hxml is tedious and error prone (it’s painfully common to find out that I’ve forgot to change one of them only after the fact, meaning that I was working with the wrong assumptions. Me: :thinking: :open_mouth: :cry:).

Moreover, as I said, I’m interested in seeing if there’s a generic way to do this kind of things.

Experimenting a bit with @ibilon’s last suggestion (the one based on @nadako’s idea (can’t mention him as per forum rules… I know!?)), led me to an interesting discovery: you can kind of undefine a define by assigning it an empty string.

PS: By the way, I’m enjoying the discussion so far, thanks!

I’ve been experimenting with a mix of the suggestions here, a bit clunky but it works.

(Not sure I’ll use this in an actual project, as it’s probably too opaque from the user point of view, but it was fun exploring the possibilities)

One thing I cannot find a solution for, is how to avoid the duplication of asBool() and __asBool(). I’d like to have just one method, callable both from actual code (like from TestAll.hx) and from fixBoolDefines (so from an init macro). I’ve tried multiple combinations but haven’t been successful so far, any tips?