const std = @import("std"); // Clean build configuration for optimized CLI (Zig 0.15 std.Build API) pub fn build(b: *std.Build) void { // Standard target options const target = b.standardTargetOptions(.{}); const test_filter = b.option([]const u8, "test-filter", "Filter unit tests by name"); _ = test_filter; // Optimized release mode for size const optimize = b.standardOptimizeOption(.{ .preferred_optimize_mode = .ReleaseSmall }); const options = b.addOptions(); const arch = target.result.cpu.arch; const os_tag = target.result.os.tag; const arch_str: []const u8 = switch (arch) { .x86_64 => "x86_64", .aarch64 => "arm64", else => "unknown", }; const os_str: []const u8 = switch (os_tag) { .linux => "linux", .macos => "darwin", .windows => "windows", else => "unknown", }; const candidate_specific = b.fmt("src/assets/rsync_release_{s}_{s}.bin", .{ os_str, arch_str }); const candidate_default = "src/assets/rsync_release.bin"; var selected_candidate: []const u8 = ""; var has_rsync_release = false; // Prefer a platform-specific asset if available. if (std.fs.cwd().openFile(candidate_specific, .{}) catch null) |f| { f.close(); selected_candidate = candidate_specific; has_rsync_release = true; } else if (std.fs.cwd().openFile(candidate_default, .{}) catch null) |f| { f.close(); selected_candidate = candidate_default; has_rsync_release = true; } if ((optimize == .ReleaseSmall or optimize == .ReleaseFast) and !has_rsync_release) { std.debug.panic( "Release build requires an embedded rsync binary asset. Provide one of: '{s}' or '{s}'", .{ candidate_specific, candidate_default }, ); } // rsync_embedded_binary.zig calls @embedFile() from cli/src/utils, so the embed path // must be relative to that directory. const selected_embed_path = if (has_rsync_release) b.fmt("../assets/{s}", .{std.fs.path.basename(selected_candidate)}) else ""; options.addOption(bool, "has_rsync_release", has_rsync_release); options.addOption([]const u8, "rsync_release_path", selected_embed_path); // Check for SQLite assets (mirrors rsync pattern) const sqlite_dir_specific = b.fmt("src/assets/sqlite_release_{s}_{s}", .{ os_str, arch_str }); const sqlite_dir_default = "src/assets/sqlite_release"; var has_sqlite_release = false; var sqlite_release_path: []const u8 = ""; // Try platform-specific directory first if (std.fs.cwd().access(sqlite_dir_specific, .{})) |_| { has_sqlite_release = true; sqlite_release_path = sqlite_dir_specific; } else |_| { // Try default directory if (std.fs.cwd().access(sqlite_dir_default, .{})) |_| { has_sqlite_release = true; sqlite_release_path = sqlite_dir_default; } else |_| {} } if ((optimize == .ReleaseSmall or optimize == .ReleaseFast) and !has_sqlite_release) { std.debug.panic( "Release build requires SQLite amalgamation. Run: make build-sqlite", .{}, ); } options.addOption(bool, "has_sqlite_release", has_sqlite_release); options.addOption([]const u8, "sqlite_release_path", sqlite_release_path); // CLI executable - declared BEFORE SQLite setup so exe can be referenced const exe = b.addExecutable(.{ .name = "ml", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); exe.root_module.strip = true; exe.root_module.addOptions("build_options", options); // Link native dataset_hash library exe.linkLibC(); exe.addLibraryPath(b.path("../native/build")); exe.linkSystemLibrary("dataset_hash"); exe.addIncludePath(b.path("../native/dataset_hash")); // SQLite setup: embedded for release, system lib for dev if (has_sqlite_release) { // Release: compile SQLite from downloaded amalgamation const sqlite_c_path = b.fmt("{s}/sqlite3.c", .{sqlite_release_path}); exe.addCSourceFile(.{ .file = b.path(sqlite_c_path), .flags = &.{ "-DSQLITE_ENABLE_FTS5", "-DSQLITE_ENABLE_JSON1", "-DSQLITE_THREADSAFE=1", "-DSQLITE_USE_URI", } }); exe.addIncludePath(b.path(sqlite_release_path)); } else { // Dev: link against system SQLite exe.linkSystemLibrary("sqlite3"); } // Install the executable to zig-out/bin b.installArtifact(exe); // Default build: install optimized CLI (used by `zig build`) const prod_step = b.step("prod", "Build production CLI binary"); prod_step.dependOn(&exe.step); // Convenience run step const run_cmd = b.addRunArtifact(exe); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); // Standard Zig test discovery - find all test files automatically const test_step = b.step("test", "Run unit tests"); // Safety check for release builds const safety_check_step = b.step("safety-check", "Verify ReleaseSafe mode is used for production"); if (optimize != .ReleaseSafe and optimize != .Debug) { const warn_no_safe = b.addSystemCommand(&.{ "echo", "WARNING: Building without ReleaseSafe mode. Production builds should use -Doptimize=ReleaseSafe" }); safety_check_step.dependOn(&warn_no_safe.step); } // Test main executable const main_tests = b.addTest(.{ .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = .Debug, }), }); main_tests.root_module.addOptions("build_options", options); const run_main_tests = b.addRunArtifact(main_tests); test_step.dependOn(&run_main_tests.step); // Find all test files in tests/ directory automatically var test_dir = std.fs.cwd().openDir("tests", .{}) catch |err| { std.log.warn("Failed to open tests directory: {}", .{err}); return; }; defer test_dir.close(); // Create src module that tests can import from const src_module = b.createModule(.{ .root_source_file = b.path("src.zig"), .target = target, .optimize = .Debug, }); var iter = test_dir.iterate(); while (iter.next() catch |err| { std.log.warn("Error iterating test files: {}", .{err}); return; }) |entry| { if (entry.kind == .file and std.mem.endsWith(u8, entry.name, "_test.zig")) { const test_path = b.pathJoin(&.{ "tests", entry.name }); const test_module = b.createModule(.{ .root_source_file = b.path(test_path), .target = target, .optimize = .Debug, }); test_module.addOptions("build_options", options); // Make src module available to tests as "src" test_module.addImport("src", src_module); const test_exe = b.addTest(.{ .root_module = test_module, }); const run_test = b.addRunArtifact(test_exe); test_step.dependOn(&run_test.step); } } }