const std = @import("std"); const bh = @import("bh"); pub fn build(b: *std.Build) void { const opts = bh.declareOptions(b); // am i good to always have enet as static? // const enet = if (true) b.addStaticLibrary(.{ // .name = "enet_c", // .target = target, // .optimize = optimize, // }) else b.addSharedLibrary(.{ // .name = "enet_c", // .target = target, // .optimize = optimize, // }); const enet = b.addLibrary(.{ .name = "enet_c", .linkage = .static, .root_module = b.createModule(.{ .target = opts.target, .optimize = opts.optimize, }), }); enet.linkLibC(); enet.addCSourceFiles(.{ .root = b.path("enet-1.3.18"), .files = &.{ "callbacks.c", "compress.c", "host.c", "list.c", "packet.c", "peer.c", "protocol.c", "win32.c", "unix.c", }, .flags = &.{"-DHAS_OFFSETOF=1"}, }); enet.addIncludePath(b.path("enet-1.3.18/include")); // Platform-specific configuration switch (opts.target.result.os.tag) { .windows => { enet.linkSystemLibrary("ws2_32"); enet.linkSystemLibrary("winmm"); // Use .def file to control exports and avoid CRT symbol conflicts }, .linux, .macos => { // Unix platforms - no additional libraries needed }, else => {}, } b.installArtifact(enet); const mod = b.addModule("enet", .{ .target = opts.target, .optimize = opts.optimize, .root_source_file = b.path("src/enet.zig"), .link_libc = true, }); mod.linkLibrary(enet); mod.addIncludePath(b.path("enet-1.3.18/include/")); const test_step = b.step("test", "run unit tests for enet"); const tests = b.addTest(.{ .root_module = b.createModule(.{ .link_libc = true, .target = opts.target, .optimize = opts.optimize, .root_source_file = b.path("src/tests.zig"), }), }); tests.root_module.addImport("enet", mod); tests.root_module.addIncludePath(b.path("enet-1.3.18/include/")); const runArtifact = b.addRunArtifact(tests); test_step.dependOn(&runArtifact.step); // Test server executable const test_server = b.addExecutable(.{ .name = "test-server", .root_module = b.createModule(.{ .root_source_file = b.path("src/test_server.zig"), .target = opts.target, .optimize = opts.optimize, }), }); test_server.root_module.addImport("enet", mod); test_server.linkLibC(); // Test client executable const test_client = b.addExecutable(.{ .name = "test-client", .root_module = b.createModule(.{ .root_source_file = b.path("src/test_client.zig"), .target = opts.target, .optimize = opts.optimize, }), }); test_client.root_module.addImport("enet", mod); test_client.linkLibC(); // Install test programs const install_test_server = b.addInstallArtifact(test_server, .{}); const install_test_client = b.addInstallArtifact(test_client, .{}); // Steps to run test programs individually const run_test_server = b.addRunArtifact(test_server); const run_test_client = b.addRunArtifact(test_client); const run_server_step = b.step("run-test-server", "Run the ENet test server"); run_server_step.dependOn(&run_test_server.step); const run_client_step = b.step("run-test-client", "Run the ENet test client"); run_client_step.dependOn(&run_test_client.step); // Loopback test that runs both server and client const run_loopback_step = b.step("run-test-loopback", "Run ENet loopback test (server + client)"); // Create a custom step for the loopback test const loopback_test = LoopbackTestStep.create(b, test_server, test_client); run_loopback_step.dependOn(&loopback_test.step); // Build step for test programs const build_tests_step = b.step("build-tests", "Build test server and client programs"); build_tests_step.dependOn(&install_test_server.step); build_tests_step.dependOn(&install_test_client.step); } // Custom step to run server and client concurrently for loopback testing const LoopbackTestStep = struct { step: std.Build.Step, builder: *std.Build, server_exe: *std.Build.Step.Compile, client_exe: *std.Build.Step.Compile, const Self = @This(); pub fn create(builder: *std.Build, server_exe: *std.Build.Step.Compile, client_exe: *std.Build.Step.Compile) *Self { const self = builder.allocator.create(Self) catch @panic("OOM"); self.* = Self{ .step = std.Build.Step.init(.{ .id = .custom, .name = "loopback-test", .owner = builder, .makeFn = make, }), .builder = builder, .server_exe = server_exe, .client_exe = client_exe, }; self.step.dependOn(&server_exe.step); self.step.dependOn(&client_exe.step); return self; } fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { const self: *Self = @fieldParentPtr("step", step); const allocator = self.builder.allocator; std.debug.print("Running ENet loopback test...\n", .{}); // Get the executable paths const server_path = self.server_exe.getEmittedBin().getPath(self.builder); const client_path = self.client_exe.getEmittedBin().getPath(self.builder); // Start the server process std.debug.print("Starting test server...\n", .{}); var server_process = std.process.Child.init(&[_][]const u8{server_path}, allocator); server_process.stdout_behavior = .Pipe; server_process.stderr_behavior = .Pipe; try server_process.spawn(); defer { _ = server_process.kill() catch {}; } // Give server time to start std.Thread.sleep(1 * std.time.ns_per_s); // Start the client process std.debug.print("Starting test client...\n", .{}); var client_process = std.process.Child.init(&[_][]const u8{client_path}, allocator); client_process.stdout_behavior = .Pipe; client_process.stderr_behavior = .Pipe; try client_process.spawn(); // Wait for client to complete const client_result = try client_process.wait(); // Give server a moment to process the quit message and shut down naturally std.Thread.sleep(2 * std.time.ns_per_s); // Check if server is still running and terminate if needed const server_result = server_process.wait() catch { std.debug.print("Server still running, terminating...\n", .{}); _ = server_process.kill() catch {}; return; }; // Read and display output if (client_process.stdout) |stdout| { const client_output = try stdout.readToEndAlloc(allocator, 8192); defer allocator.free(client_output); std.debug.print("\n=== CLIENT OUTPUT ===\n{s}\n", .{client_output}); } if (server_process.stdout) |stdout| { const output = try stdout.readToEndAlloc(allocator, 8192); // const server_output = try stdout.readToEndAlloc.read(allocator, 8192); defer allocator.free(output); std.debug.print("\n=== SERVER OUTPUT ===\n{s}\n", .{output}); } std.debug.print("\n=== LOOPBACK TEST RESULTS ===\n", .{}); std.debug.print("Client exit code: {}\n", .{client_result}); std.debug.print("Server exit code: {}\n", .{server_result}); if (client_result == .Exited and client_result.Exited == 0 and server_result == .Exited and server_result.Exited == 0) { std.debug.print("Loopback test PASSED!\n", .{}); } else { std.debug.print("Loopback test FAILED!\n", .{}); return error.TestFailed; } } };