Repository navigation
Description
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>.cppandinclude/<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.mlderives them from the class path innew_source_file(writing src and include files),add_include(the#includelines),write_build_data(the<file>and<depend>entries in Build.xml) and the@:nativeGenheader include in the dependency collector. The development branch does the same insrc/generators/cpp/cppSourceWriter.ml. - No existing define shortens them.
-D vcprojflattens only the .cpp names tosrc/a-b-Class.cpp; headers keep the full path and Build.xml still listssrc/a/b/Class.cpp, so the build fails.-D include_prefixonly 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
#includelines 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@:includeor@:headerIncludewould 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
longPathAwareto 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