From 9346e13dff1160e439867fac30da0ef3c64c71c2 Mon Sep 17 00:00:00 2001 From: Simon Hartcher Date: Fri, 8 May 2026 12:56:12 +1000 Subject: [PATCH] Add Zig build for WhipLib (Phase 1) Bring the project under a single Zig-driven build system, mirroring the ROLLER repo's mise + build.zig pattern. WhipLib compiles cleanly for macOS arm64 (native), aarch64-linux-gnu (cross), and x86_64-windows-gnu (cross) from a Mac, replacing the per-platform Makefile / vcxproj split for this library. Build configuration: - mise.toml pins Zig 0.15.2 (matches ROLLER) with build/clean/test tasks - GLEW_NO_GLU is defined globally (codebase has zero GLU calls; avoids needing libGLU on cross-compiles) - WHIPLIB_LIB is defined so the static archive's WLFUNC declarations don't accidentally become __declspec(dllimport) on Windows - FBXExporter.cpp is gated behind -Dfbx=true. FBX SDK 2020.3.7 headers have known clang compatibility issues (deprecated vsprintf, internal typo mLefttChild); resolving those is Phase 2 work along with adding the CLI tools that link FBX Out of scope here: ModelExporter / TrackAnalyzer / CarPlansParser binaries, and TrackEditor (Qt5 GUI, needs moc/uic/rcc orchestration). --- .gitignore | 5 ++ build.zig | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++ build.zig.zon | 13 +++++ mise.toml | 14 +++++ 4 files changed, 180 insertions(+) create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 mise.toml diff --git a/.gitignore b/.gitignore index bf529666..3315889d 100644 --- a/.gitignore +++ b/.gitignore @@ -363,6 +363,11 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd + +# Zig build artifacts +zig-out/ +.zig-cache/ + /TrackEditor/resource.cpp /TrackEditor/null /TrackEditor/ui diff --git a/build.zig b/build.zig new file mode 100644 index 00000000..b10e41ca --- /dev/null +++ b/build.zig @@ -0,0 +1,148 @@ +// Zig build for ROLLER Track Editor. +// +// Phase 1 (current): WhipLib static library compiles on macOS, Linux, Windows. +// Phase 2: ModelExporter / TrackAnalyzer / CarPlansParser CLI tools (need FBX SDK). +// Phase 3: TrackEditor Qt5 GUI (needs moc/uic/rcc orchestration). + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // Build-time toggles. FBX SDK ships precompiled binaries that are not in + // the repo; until they're installed under external/FBX/lib/, the FBX + // exporter cannot compile. + const enable_fbx = b.option( + bool, + "fbx", + "Compile FBXExporter.cpp (requires FBX SDK installed at external/FBX/)", + ) orelse false; + + const whiplib = buildWhipLib(b, target, optimize, enable_fbx); + b.installArtifact(whiplib); +} + +fn buildWhipLib( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + enable_fbx: bool, +) *std.Build.Step.Compile { + const mod = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }); + + mod.addIncludePath(b.path("WhipLib")); + mod.addIncludePath(b.path("WhipLib/CarPlans")); + mod.addIncludePath(b.path("WhipLib/SignPlans")); + mod.addIncludePath(b.path("external/glm")); + mod.addIncludePath(b.path("external/stb")); + // The codebase mixes `#include ` and `#include "glew.h"` without a + // `GL/` prefix, so both directories must be on the search path. + mod.addIncludePath(b.path("external/glew/include")); + mod.addIncludePath(b.path("external/glew/include/GL")); + + mod.addCMacro("GLM_ENABLE_EXPERIMENTAL", "1"); + // GLEW pulls in by default. The GLU library is deprecated and + // unused by this codebase (verified by grep), and asking for it makes + // cross-compilation to Linux brittle (no glu headers in zig's sysroot). + mod.addCMacro("GLEW_NO_GLU", "1"); + // WhipLib.h's WLFUNC macro defaults to __declspec(dllimport) on Windows + // unless one of WHIPLIB_LIB / WHIPLIB_DLL is defined. We're building a + // static archive, so flag that to keep the implementations linkable. + mod.addCMacro("WHIPLIB_LIB", "1"); + + addPlatformIncludes(mod, target); + + if (enable_fbx) { + mod.addIncludePath(b.path("external/FBX/include")); + } + + const cpp_flags: []const []const u8 = &.{ + "-std=c++17", + "-fPIC", + // Match the upstream Linux Makefile defaults. + }; + + var sources = std.ArrayList([]const u8){}; + defer sources.deinit(b.allocator); + sources.appendSlice(b.allocator, &whiplib_core_sources) catch @panic("OOM"); + if (enable_fbx) { + sources.append(b.allocator, "FBXExporter.cpp") catch @panic("OOM"); + } + + mod.addCSourceFiles(.{ + .root = b.path("WhipLib"), + .files = sources.items, + .flags = cpp_flags, + }); + + return b.addLibrary(.{ + .name = "WhipLib", + .root_module = mod, + .linkage = .static, + }); +} + +// Hardcoded brewed prefixes are fine for now — once Phase 2 lands we'll +// resolve these via `brew --prefix` invoked from a build step or pkg-config. +fn addPlatformIncludes(mod: *std.Build.Module, target: std.Build.ResolvedTarget) void { + switch (target.result.os.tag) { + .macos => { + // GL_SILENCE_DEPRECATION mutes the wall of OpenGL warnings macOS + // emits since 10.14. The editor uses GLEW for GL bindings so the + // deprecated system headers aren't actually called. + mod.addCMacro("GL_SILENCE_DEPRECATION", "1"); + + // Apple Silicon Homebrew lives under /opt/homebrew; Intel under + // /usr/local. Add both — the missing one is harmless. + mod.addSystemIncludePath(.{ .cwd_relative = "/opt/homebrew/include" }); + mod.addSystemIncludePath(.{ .cwd_relative = "/opt/homebrew/opt/libxml2/include/libxml2" }); + mod.addSystemIncludePath(.{ .cwd_relative = "/usr/local/include" }); + mod.addSystemIncludePath(.{ .cwd_relative = "/usr/local/opt/libxml2/include/libxml2" }); + }, + .linux => { + mod.addSystemIncludePath(.{ .cwd_relative = "/usr/include/libxml2" }); + }, + else => {}, + } +} + +// Mirrors the SRC list in WhipLib/Makefile, minus FBXExporter.cpp (gated by +// -Dfbx). Keep alphabetised for diff-friendliness. +const whiplib_core_sources = [_][]const u8{ + "Camera.cpp", + "CarHelpers.cpp", + "Clock.cpp", + "DisasmHelpers.cpp", + "DriveComponent.cpp", + "Entity.cpp", + "GameClock.cpp", + "GameInput.cpp", + "IndexBuffer.cpp", + "Logging.cpp", + "MathHelpers.cpp", + "NoclipComponent.cpp", + "ObjExporter.cpp", + "ObjImporter.cpp", + "Palette.cpp", + "PhysicsComponent.cpp", + "Renderer.cpp", + "Scene.cpp", + "SceneManager.cpp", + "Shader.cpp", + "ShapeComponent.cpp", + "ShapeData.cpp", + "ShapeFactory.cpp", + "Texture.cpp", + "Track.cpp", + "TrackComponent.cpp", + "Unmangler.cpp", + "VertexArray.cpp", + "VertexBuffer.cpp", + "WhipLib.cpp", +}; + +const std = @import("std"); diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 00000000..2ffdcd32 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,13 @@ +.{ + .name = .roller_track_editor, + .version = "0.0.0", + .fingerprint = 0x14fcfb7861a850c3, + .minimum_zig_version = "0.15.2", + .dependencies = .{}, + .paths = .{ + "build.zig", + "build.zig.zon", + "WhipLib", + "external", + }, +} diff --git a/mise.toml b/mise.toml new file mode 100644 index 00000000..f5eea3fe --- /dev/null +++ b/mise.toml @@ -0,0 +1,14 @@ +[tools] +zig = "0.15.2" + +[tasks.build] +description = "Build all configured Zig targets (WhipLib + tools)" +run = "zig build" + +[tasks.clean] +description = "Clean Zig build artifacts" +run = "rm -rf zig-out .zig-cache" + +[tasks.test] +description = "Run zig build tests (none yet — placeholder)" +run = "zig build test"