COMMUNITY

Haxe macro generates a file, but returns incomplete file content

I set up an auto build macro (buildTasks) to generate and store information on certain variables with in a text file at compile time. Then, there’s another macro (getCleanerInfo) that returns the contents of the file for a class to parse later.

The generated text file is 569 lines long, but the second macro only grabs the first 372 lines.

If I use generated file as is and skip calling the auto build macro, the second macro instead returns all 569 lines.

Not sure what I’m doing wrong.


class CleanerMacro
{
	private static var _destroyableIDs:Array<String> 	= ["destroy", "d", "destroyable"];
	private static var _nullableIDs:Array<String> 		= ["nullify", "n", "nullable"];
	private static var _resetableIDs:Array<String> 		= ["resetable", "r", "reset"];
	private static var _instanceIDs:Array<String> 		= ["instance"];
	private static var _first:Bool						= true;
	
	// -----------------------------------------------------------------------------------------------
	macro static public function buildTasks():Array<haxe.macro.Expr.Field>
	{
		var localClass = haxe.macro.Context.getLocalClass().get();
		
		// if we've already processed this class, skip it
		if ( localClass.meta.has(":processed") ) {
			return null;
		}
		
		// mark the class as processed
		localClass.meta.add(":processed", [], localClass.pos);
		
		var text:String = null;
		if ( _first ) {
			// first entry, use empty text
			text = "";
		}
		else if ( sys.FileSystem.exists("cleanerData.txt") ) {
			// subsequent entry, append text
			text = sys.io.File.getContent("cleanerData.txt") + "\n";
		}
		
		// clear flag
		_first = false;
		
		// get build fields
		var fields:Array<haxe.macro.Expr.Field> = haxe.macro.Context.getBuildFields();
		
		// set up arrays
		var r:Array<String> = [];
		var n:Array<String> = [];
		var d:Array<String> = [];
		var s:Array<String> = [];
		var md:haxe.macro.Expr.Metadata;
		var metaDataName:String;
		var fieldName:String;
		var textToAdd:String;
		var c:String = Std.string(haxe.macro.Context.Context.getLocalClass());
		
		for ( i in 0...fields.length ) {
			if ( fields[i].meta != null ) {
				md = fields[i].meta;
				fieldName = fields[i].name;
				
				// search the metadata for certain tags
				for ( x in 0...md.length ) {
					metaDataName = md[x].name.toLowerCase();
					
					if ( _resetableIDs.indexOf(metaDataName) > -1 ) {
						// function that takes a single boolean as an argument, deactivate on cleanup
						r.push(fieldName);
					}
					else {
						if ( _destroyableIDs.indexOf(metaDataName) > -1 ) {
							// object that needs to also be destroyed and set to null
							d.push(fieldName);
						}
						
						if ( _nullableIDs.indexOf(metaDataName) > -1 ) {
							// object doesn't need to be destroyed but needs to be set to null
							n.push(fieldName);
						}
						
						if ( _instanceIDs.indexOf(metaDataName) > -1 ) {
							// singleton that needs to be set to null
							s.push(fieldName);
						}
					}
				}
			}
		}
		
		textToAdd = "c=" + c;
		textToAdd += ";r=" + r.join(","); // resetables
		textToAdd += ";d=" + d.join(","); // destroyables
		textToAdd += ";n=" + n.join(","); // nullables
		textToAdd += ";i=" + s.join(","); // singletons
		
		text += textToAdd;
		sys.io.File.saveContent("cleanerData.txt", text);
		
		return null;
	}
	
	// -----------------------------------------------------------------------------------------------
	macro static public function getCleanerInfo():haxe.macro.Expr.ExprOf<String> {
		return macro $v{ sys.io.File.getContent("cleanerData.txt") };
	}
	
}

Could it be that the reading macro runs while the writing macro hasn’t finished yet? Thus the reading happens midst writing and that’s why it gets the content only partially?

Ah, yes, that appears to be it.

I just traced out the date each time the auto build macro was called and the date when my other macro was called. The first auto build call was at 2020-08-26 07:10:02 local time and the last was at 2020-08-26 07:10:04, but the other macro got called at 2020-08-26 07:10:03 before all the auto builds had finished. Interesting.

I wonder if there’s a way to prevent this from happening?

It seems macro execution order isn’t guaranteed, so I found a workaround – I attach the string data I wanted to store as a static field using the macro and retrieve it later using reflection on when it’s needed at runtime. I wanted to be able to process everything in one call to avoid as many runtime hiccups as possible, but this will have to do.

Maybe you can do the reading in Context.onAfterTyping, which will ensure reading happens after the writing.

Another way might be to use an initialization macro to do the writing, which is guaranteed to run before any build macros.

Oooh that could be useful! I’ll be sure to try this out next. Thanks for the info! :slight_smile: