Skip to content

Generated C++ file names for generic instances pass the Windows 260 character limit in deep folders #356

Open

Description

@dimensionscape

A C++ build on Windows fails when the project sits in a deep folder and uses a @:generic class instantiated with a type from another deep package. Haxe stops while writing its generated C++:

Sys_error("<output>/include/app/internal/system/timer/TimerTable_app_internal_system_timer_wheel__TimerWheel_WheelNode.h: No such file or directory")

This came up with a Graphaxe/Lime app using crossbyte (TimerTable<WheelNode> in crossbyte._internal.system.timer). LongPathsEnabled is 1 on that machine and does not help.

Cause

  • Haxe names the generic instance after the parameter's full path: TimerTable_crossbyte__internal_system_timer_wheel__TimerWheel_WheelNode.
  • The Haxe C++ generator writes each class to src/<package>/<Class>.cpp and include/<package>/<Class>.h, so the package appears twice, once as folders and once in the name. For crossbyte that part alone is 112 (src) and 114 (include) characters.
  • Generated file names are chosen in Haxe, not in libhxcpp. In Haxe 4.3.7, src/generators/gencpp.ml derives them from the class path in new_source_file (writing src and include files), add_include (the #include lines), write_build_data (the <file> and <depend> entries in Build.xml) and the @:nativeGen header include in the dependency collector. The development branch does the same in src/generators/cpp/cppSourceWriter.ml.
  • No existing define shortens them. -D vcproj flattens only the .cpp names to src/a-b-Class.cpp; headers keep the full path and Build.xml still lists src/a/b/Class.cpp, so the build fails. -D include_prefix only adds a folder.
  • The libhxcpp build tool is fine: object files are written flat under obj/ (obj/msvc1964/<hash>_<Class>.obj).

A long-path-aware haxe.exe is not enough

haxe.exe 4.3.7 has a manifest without longPathAware, so the registry setting does not apply to it. That part is HaxeFoundation/haxe#11714 (open). MSVC has the same limit, though. Generating at a short folder and copying the tree to a folder where paths reach 279 characters, cl.exe 14.44 fails:

fatal error C1083: Cannot open source file: './src/app/internal/system/timer/TimerTable_app_internal_system_timer_wheel__TimerWheel_WheelNode.cpp': No such file or directory
fatal error C1083: Cannot open include file: 'app/internal/system/timer/TimerTable_app_internal_system_timer_wheel__TimerWheel_WheelNode.h': No such file or directory

cl.exe, c1xx.dll and link.exe have no longPathAware manifest either, and cl.exe does not find any header through a \\?\ include directory. So libhxcpp cannot work around this with extended-length paths, and fixing haxe.exe alone would move the failure to the compile step.

Proposed fix

In the Haxe C++ generator: when a class's file base name is longer than a limit, write it as a truncated prefix plus a hash of the full class path, for example TimerTable_52e02d87.h (the first N characters, an underscore, 8 hex digits of MD5 of the dotted class path). Only file names change. C++ class names, include guards (INCLUDED_...), Haxe class names, reflection and stack traces stay the same.

  • Deterministic: the name depends only on the class path, so it is the same on every run and every machine, and incremental builds and the compile cache keep working.
  • Collision-free: the generator keeps a table of shortened names for the run and reports an error if two class paths map to the same file.
  • One helper used in the four places listed above, so the written files, the #include lines and Build.xml agree. Roughly 40 to 60 lines in gencpp.ml for 4.3.x and the same in cppSourceWriter.ml for development, plus a test.
  • Opt-in through a define at first (name to be agreed upstream, for example -D cpp_max_file_name=40), since user code that names a generated header in @:include or @:headerInclude would see a different file name. It could become the default later.

I checked the result by hand: after renaming the long instance's .cpp and .h to TimerTable_52e02d87 and updating the includes and Build.xml, the repro builds and runs with unchanged libhxcpp and MSVC from a 182 character output folder. Without the rename it fails from 159 characters.

Where each change lives:

  • Haxe: the patch above, as a PR to HaxeFoundation/haxe development, with a backport request for 4.3.x. Adding longPathAware to haxe.exe (#11714) still helps other targets but does not fix C++ builds with MSVC.
  • libhxcpp: no change to the build tool or runtime. Once a Haxe release has the define, libhxcpp could set it in extraParams.hxml so it is on by default (Haxe versions without it ignore the define). The tradeoff is that a long generated header would then have a different name under libhxcpp than under hxcpp.

Until then: build from a shorter folder, or give the generic parameter a shorter path in crossbyte (for example move WheelNode out of TimerWheel.hx into a shorter package), which shortens the instance name.

Repro

src/app/internal/system/timer/TimerTable.hx:

package app.internal.system.timer;

@:generic
class TimerTable<T> {
	var items:Array<T> = [];

	public function new() {}

	public function add(item:T):Int {
		items.push(item);
		return items.length - 1;
	}

	public function get(index:Int):T {
		return items[index];
	}
}

src/app/internal/system/timer/wheel/TimerWheel.hx:

package app.internal.system.timer.wheel;

import app.internal.system.timer.TimerTable;

class TimerWheel {
	final table = new TimerTable<WheelNode>();

	public function new() {}

	public function schedule(delay:Int):Int {
		return table.add(new WheelNode(delay));
	}

	public function delayOf(handle:Int):Int {
		return table.get(handle).delay;
	}
}

private class WheelNode {
	public final delay:Int;

	public function new(delay:Int) {
		this.delay = delay;
	}
}

src/Main.hx:

import app.internal.system.timer.wheel.TimerWheel;

class Main {
	static function main() {
		var wheel = new TimerWheel();
		Sys.println(wheel.delayOf(wheel.schedule(42)));
	}
}

Generate into a 160 character folder (C:/lp/ plus 150 x characters plus /cpp):

haxe -cp src -main Main -cpp C:/lp/xxxx...xxxx/cpp -D no-compilation

This fails with the Sys_error above. With 140 x characters it succeeds. The generated include path reaches 260 characters at an output folder of 159 characters. From 158 characters the full build with libhxcpp succeeds and prints 42.

Tested with Haxe 4.3.7, libhxcpp main, MSVC 14.44 (msvc1964), Windows 10 22H2 with LongPathsEnabled set to 1.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions