Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const std = @import("std");

pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});

const gpu_tests = b.option(bool, "gpu-tests", "Include Metal-dependent tests") orelse false;
const model_path = b.option([]const u8, "model", "Model path for integration tests");

// -- Module graph (compile-enforced) --
// Zig's module import table makes undeclared @import a compile error,
// so the dependency direction declared here is the only one that compiles.

// Leaf modules (no dependencies)
const core_mod = b.createModule(.{
.root_source_file = b.path("src/core/root.zig"),
.target = target,
.optimize = optimize,
});

const mlxbridge_mod = b.createModule(.{
.root_source_file = b.path("src/mlxbridge/root.zig"),
.target = target,
.optimize = optimize,
});

// chat -> core
const chat_mod = b.createModule(.{
.root_source_file = b.path("src/chat/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
},
});

// model -> core, mlxbridge
const model_mod = b.createModule(.{
.root_source_file = b.path("src/model/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
.{ .name = "mlxbridge", .module = mlxbridge_mod },
},
});

// engine -> core, mlxbridge, model (NOT chat)
const engine_mod = b.createModule(.{
.root_source_file = b.path("src/engine/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
.{ .name = "mlxbridge", .module = mlxbridge_mod },
.{ .name = "model", .module = model_mod },
},
});

// registry -> core (no mlx)
const registry_mod = b.createModule(.{
.root_source_file = b.path("src/registry/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
},
});

// http -> core, chat, model, engine, registry
const http_mod = b.createModule(.{
.root_source_file = b.path("src/http/root.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
.{ .name = "chat", .module = chat_mod },
.{ .name = "model", .module = model_mod },
.{ .name = "engine", .module = engine_mod },
.{ .name = "registry", .module = registry_mod },
},
});

// -- Executable --
const exe_mod = b.createModule(.{
.root_source_file = b.path("src/cli/main.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "core", .module = core_mod },
.{ .name = "mlxbridge", .module = mlxbridge_mod },
.{ .name = "chat", .module = chat_mod },
.{ .name = "model", .module = model_mod },
.{ .name = "engine", .module = engine_mod },
.{ .name = "registry", .module = registry_mod },
.{ .name = "http", .module = http_mod },
},
});

const exe = b.addExecutable(.{
.name = "mlxd",
.root_module = exe_mod,
});
b.installArtifact(exe);

// -- Run step --
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run mlxd");
run_step.dependOn(&run_cmd.step);

// -- Test step (per-module) --
const test_step = b.step("test", "Run all module tests");
Comment thread
tlkahn marked this conversation as resolved.

const build_opts = b.addOptions();
build_opts.addOption(bool, "gpu_tests", gpu_tests);
const build_opts_mod = build_opts.createModule();

const test_modules = [_]*std.Build.Module{
Comment thread
tlkahn marked this conversation as resolved.
core_mod,
mlxbridge_mod,
chat_mod,
model_mod,
engine_mod,
registry_mod,
http_mod,
exe_mod,
};

for (test_modules) |mod| {
mod.addImport("build_options", build_opts_mod);
const t = b.addTest(.{ .root_module = mod });
const run_t = b.addRunArtifact(t);
test_step.dependOn(&run_t.step);
}

// -- Integration test step --
const itest_opts = b.addOptions();
itest_opts.addOption(?[]const u8, "model_path", model_path);

const itest_mod = b.createModule(.{
.root_source_file = b.path("tests/itest.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "build_options", .module = itest_opts.createModule() },
},
});

const itest = b.addTest(.{ .root_module = itest_mod });
const run_itest = b.addRunArtifact(itest);
const itest_step = b.step("itest", "Run integration tests against a real model");
itest_step.dependOn(&run_itest.step);
}
11 changes: 11 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.{
.name = .mlxd,
.version = "0.1.0",
.minimum_zig_version = "0.16.0",
.fingerprint = 0x4fc94390cee4169,
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}
5 changes: 5 additions & 0 deletions src/chat/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
comptime {
_ = @import("core");
}

test "chat compiles" {}
Empty file added src/cli/cmd/.gitkeep
Empty file.
58 changes: 58 additions & 0 deletions src/cli/main.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const std = @import("std");

comptime {
_ = @import("core");
_ = @import("mlxbridge");
_ = @import("chat");
_ = @import("model");
_ = @import("engine");
_ = @import("registry");
_ = @import("http");
}

const usage =
\\Usage: mlxd <command>
\\
\\Commands:
\\ serve Start OpenAI-compatible HTTP server
\\ run One-shot or interactive text generation
\\ pull Download model from HuggingFace
\\ list List locally available models
\\
;

fn writeAll(fd: std.posix.fd_t, bytes: []const u8) error{WriteFailed}!void {
var written: usize = 0;
while (written < bytes.len) {
const rc = std.posix.system.write(fd, bytes[written..].ptr, bytes[written..].len);
switch (std.posix.errno(rc)) {
.SUCCESS => written += @intCast(@as(isize, @bitCast(rc))),
.INTR => continue,
else => return error.WriteFailed,
}
}
}

pub fn main(init: std.process.Init.Minimal) u8 {
var args = std.process.Args.Iterator.init(init.args);
_ = args.next();

const cmd = args.next() orelse {
writeAll(std.posix.STDERR_FILENO, usage) catch {};
return 1;
};

const commands = [_][]const u8{ "serve", "run", "pull", "list" };

for (commands) |name| {
if (std.mem.eql(u8, cmd, name)) {
writeAll(std.posix.STDERR_FILENO, "mlxd ") catch {};
writeAll(std.posix.STDERR_FILENO, name) catch {};
writeAll(std.posix.STDERR_FILENO, ": not yet implemented\n") catch {};
return 1;
}
}

writeAll(std.posix.STDERR_FILENO, usage) catch {};
return 1;
}
1 change: 1 addition & 0 deletions src/core/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test "core compiles" {}
Empty file added src/engine/constrain/.gitkeep
Empty file.
7 changes: 7 additions & 0 deletions src/engine/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
comptime {
_ = @import("core");
_ = @import("mlxbridge");
_ = @import("model");
}

test "engine compiles" {}
Empty file added src/engine/specdecode/.gitkeep
Empty file.
Empty file added src/http/handlers/.gitkeep
Empty file.
9 changes: 9 additions & 0 deletions src/http/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
comptime {
_ = @import("core");
_ = @import("chat");
_ = @import("model");
_ = @import("engine");
_ = @import("registry");
}

test "http compiles" {}
1 change: 1 addition & 0 deletions src/mlxbridge/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test "mlxbridge compiles" {}
6 changes: 6 additions & 0 deletions src/model/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
comptime {
_ = @import("core");
_ = @import("mlxbridge");
}

test "model compiles" {}
5 changes: 5 additions & 0 deletions src/registry/root.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
comptime {
_ = @import("core");
}

test "registry compiles" {}
Empty file added tests/fixtures/.gitkeep
Empty file.
8 changes: 8 additions & 0 deletions tests/itest.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const std = @import("std");
const build_options = @import("build_options");

test "integration: serve smoke test" {
if (build_options.model_path == null) return error.SkipZigTest;
// Real itest (spawn `mlxd serve`, drive via std.http.Client) not yet implemented.
return error.IntegrationTestsNotYetImplemented;
}