diff --git a/.gitmodules b/.gitmodules index 28095252a..eee2a129c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "vendor/cglm"] path = vendor/cglm url = https://github.com/recp/cglm.git +[submodule "vendor/libuv"] + path = vendor/libuv + url = https://github.com/libuv/libuv.git diff --git a/build.zig b/build.zig index 18ea397f5..2de72a81e 100644 --- a/build.zig +++ b/build.zig @@ -3,6 +3,7 @@ const Builder = std.build.Builder; const LibExeObjStep = std.build.LibExeObjStep; const glfw = @import("vendor/mach/glfw/build.zig"); const ft = @import("src/freetype/build.zig"); +const uv = @import("src/libuv/build.zig"); pub fn build(b: *std.build.Builder) !void { const target = b.standardTargetOptions(.{}); @@ -23,11 +24,8 @@ pub fn build(b: *std.build.Builder) !void { const ftlib = try ft.create(b, target, mode, .{}); ftlib.link(exe); - // to link to system: - // exe.linkSystemLibrary("freetype2"); - // exe.linkSystemLibrary("libpng"); - // exe.linkSystemLibrary("bzip2"); - // ftlib.addIncludeDirs(exe); + + const libuv = try uv.create(b, target, mode); // stb if we need it // exe.addIncludeDir("vendor/stb"); @@ -46,6 +44,7 @@ pub fn build(b: *std.build.Builder) !void { const test_step = b.step("test", "Run all tests"); const lib_tests = b.addTest("src/main.zig"); ftlib.link(lib_tests); + libuv.link(lib_tests); lib_tests.addIncludeDir("vendor/glad/include/"); lib_tests.addCSourceFile("vendor/glad/src/gl.c", &.{}); test_step.dependOn(&lib_tests.step); diff --git a/src/libuv/Loop.zig b/src/libuv/Loop.zig new file mode 100644 index 000000000..b748e9899 --- /dev/null +++ b/src/libuv/Loop.zig @@ -0,0 +1,53 @@ +const Loop = @This(); + +const std = @import("std"); +const Allocator = std.mem.Allocator; +const testing = std.testing; +const c = @import("c.zig"); +const errors = @import("error.zig"); + +loop: *c.uv_loop_t, + +/// Initialize a new uv_loop. +pub fn init(alloc: Allocator) !Loop { + // The uv_loop_t type MUST be heap allocated and must not be copied. + // I can't find a definitive source on this, but the test suite starts + // hanging in weird places and doing bad things when it is copied. + const loop = try alloc.create(c.uv_loop_t); + try errors.convertError(c.uv_loop_init(loop)); + return Loop{ .loop = loop }; +} + +/// Releases all internal loop resources. Call this function only when the +/// loop has finished executing and all open handles and requests have been +/// closed, or this will silently fail (in debug mode it will panic). +pub fn deinit(self: *Loop, alloc: Allocator) void { + // deinit functions idiomatically cannot fail in Zig, so we do the + // next best thing here and assert so that in debug mode you'll get + // a crash. + std.debug.assert(c.uv_loop_close(self.loop) >= 0); + alloc.destroy(self.loop); + self.* = undefined; +} + +/// This function runs the event loop. See RunMode for mode documentation. +/// +/// This is not reentrant. It must not be called from a callback. +pub fn run(self: *Loop, mode: RunMode) !u32 { + const res = c.uv_run(self.loop, @enumToInt(mode)); + try errors.convertError(res); + return @intCast(u32, res); +} + +/// Mode used to run the loop with uv_run(). +pub const RunMode = enum(c.uv_run_mode) { + default = c.UV_RUN_DEFAULT, + once = c.UV_RUN_ONCE, + nowait = c.UV_RUN_NOWAIT, +}; + +test { + var loop = try init(testing.allocator); + defer loop.deinit(testing.allocator); + try testing.expectEqual(@as(u32, 0), try loop.run(.nowait)); +} diff --git a/src/libuv/build.zig b/src/libuv/build.zig new file mode 100644 index 000000000..c2d4411e8 --- /dev/null +++ b/src/libuv/build.zig @@ -0,0 +1,132 @@ +const std = @import("std"); + +/// This is the type returned by create. +pub const Library = struct { + step: *std.build.LibExeObjStep, + + /// statically link this library into the given step + pub fn link(self: Library, other: *std.build.LibExeObjStep) void { + self.addIncludeDirs(other); + other.linkLibrary(self.step); + } + + /// only add the include dirs to the given step. This is useful if building + /// a static library that you don't want to fully link in the code of this + /// library. + pub fn addIncludeDirs(self: Library, other: *std.build.LibExeObjStep) void { + _ = self; + other.addIncludeDir(include_dir); + } +}; + +/// Create this library. This is the primary API users of build.zig should +/// use to link this library to their application. On the resulting Library, +/// call the link function and given your own application step. +pub fn create( + b: *std.build.Builder, + target: std.zig.CrossTarget, + mode: std.builtin.Mode, +) !Library { + const ret = b.addStaticLibrary("uv", null); + ret.setTarget(target); + ret.setBuildMode(mode); + + var flags = std.ArrayList([]const u8).init(b.allocator); + defer flags.deinit(); + + // try flags.appendSlice(&.{}); + + if (!target.isWindows()) { + try flags.appendSlice(&.{ + "-D_FILE_OFFSET_BITS=64", + "-D_LARGEFILE_SOURCE", + }); + } + + if (target.isLinux()) { + try flags.appendSlice(&.{ + "-D_GNU_SOURCE", + "-D_POSIX_C_SOURCE=200112", + }); + } + + // C files common to all platforms + ret.addCSourceFiles(&.{ + root() ++ "src/fs-poll.c", + root() ++ "src/idna.c", + root() ++ "src/inet.c", + root() ++ "src/random.c", + root() ++ "src/strscpy.c", + root() ++ "src/strtok.c", + root() ++ "src/threadpool.c", + root() ++ "src/timer.c", + root() ++ "src/uv-common.c", + root() ++ "src/uv-data-getter-setters.c", + root() ++ "src/version.c", + }, flags.items); + + if (!target.isWindows()) { + ret.addCSourceFiles(&.{ + root() ++ "src/unix/async.c", + root() ++ "src/unix/core.c", + root() ++ "src/unix/dl.c", + root() ++ "src/unix/fs.c", + root() ++ "src/unix/getaddrinfo.c", + root() ++ "src/unix/getnameinfo.c", + root() ++ "src/unix/loop-watcher.c", + root() ++ "src/unix/loop.c", + root() ++ "src/unix/pipe.c", + root() ++ "src/unix/poll.c", + root() ++ "src/unix/process.c", + root() ++ "src/unix/random-devurandom.c", + root() ++ "src/unix/signal.c", + root() ++ "src/unix/stream.c", + root() ++ "src/unix/tcp.c", + root() ++ "src/unix/thread.c", + root() ++ "src/unix/tty.c", + root() ++ "src/unix/udp.c", + }, flags.items); + } + + if (target.isLinux() or target.isDarwin()) { + ret.addCSourceFiles(&.{ + root() ++ "src/unix/proctitle.c", + }, flags.items); + } + + if (target.isLinux()) { + ret.addCSourceFiles(&.{ + root() ++ "src/unix/linux-core.c", + root() ++ "src/unix/linux-inotify.c", + root() ++ "src/unix/linux-syscalls.c", + root() ++ "src/unix/procfs-exepath.c", + root() ++ "src/unix/random-getrandom.c", + root() ++ "src/unix/random-sysctl-linux.c", + root() ++ "src/unix/epoll.c", + }, flags.items); + } + + ret.addIncludeDir(include_dir); + ret.addIncludeDir(root() ++ "src"); + if (target.isWindows()) { + ret.linkSystemLibrary("psapi"); + ret.linkSystemLibrary("user32"); + ret.linkSystemLibrary("advapi32"); + ret.linkSystemLibrary("iphlpapi"); + ret.linkSystemLibrary("userenv"); + ret.linkSystemLibrary("ws2_32"); + } + if (target.isLinux()) { + ret.linkSystemLibrary("pthread"); + } + ret.linkLibC(); + + return Library{ .step = ret }; +} + +fn root() []const u8 { + return (std.fs.path.dirname(@src().file) orelse unreachable) ++ "/../../vendor/libuv/"; +} + +/// Directories with our includes. +const include_dir = root() ++ "include"; diff --git a/src/libuv/c.zig b/src/libuv/c.zig new file mode 100644 index 000000000..b66f023bf --- /dev/null +++ b/src/libuv/c.zig @@ -0,0 +1,3 @@ +pub usingnamespace @cImport({ + @cInclude("uv.h"); +}); diff --git a/src/libuv/error.zig b/src/libuv/error.zig new file mode 100644 index 000000000..1bab2d6fc --- /dev/null +++ b/src/libuv/error.zig @@ -0,0 +1,185 @@ +const std = @import("std"); +const testing = std.testing; +const c = @import("c.zig"); + +/// Errors that libuv can produce +/// +/// http://docs.libuv.org/en/v1.x/errors.html +pub const Error = error{ + E2BIG, + EACCES, + EADDRINUSE, + EADDRNOTAVAIL, + EAFNOSUPPORT, + EAGAIN, + EAI_ADDRFAMILY, + EAI_AGAIN, + EAI_BADFLAGS, + EAI_BADHINTS, + EAI_CANCELED, + EAI_FAIL, + EAI_FAMILY, + EAI_MEMORY, + EAI_NODATA, + EAI_NONAME, + EAI_OVERFLOW, + EAI_PROTOCOL, + EAI_SERVICE, + EAI_SOCKTYPE, + EALREADY, + EBADF, + EBUSY, + ECANCELED, + ECHARSET, + ECONNABORTED, + ECONNREFUSED, + ECONNRESET, + EDESTADDRREQ, + EEXIST, + EFAULT, + EFBIG, + EHOSTUNREACH, + EINTR, + EINVAL, + EIO, + EISCONN, + EISDIR, + ELOOP, + EMFILE, + EMSGSIZE, + ENAMETOOLONG, + ENETDOWN, + ENETUNREACH, + ENFILE, + ENOBUFS, + ENODEV, + ENOENT, + ENOMEM, + ENONET, + ENOPROTOOPT, + ENOSPC, + ENOSYS, + ENOTCONN, + ENOTDIR, + ENOTEMPTY, + ENOTSOCK, + ENOTSUP, + EPERM, + EPIPE, + EPROTO, + EPROTONOSUPPORT, + EPROTOTYPE, + ERANGE, + EROFS, + ESHUTDOWN, + ESPIPE, + ESRCH, + ETIMEDOUT, + ETXTBSY, + EXDEV, + UNKNOWN, + EOF, + ENXIO, + EMLINK, + EHOSTDOWN, + EREMOTEIO, + ENOTTY, + EFTYPE, + EILSEQ, + ESOCKTNOSUPPORT, +}; + +/// Convert the result of a libuv API call to an error (or no error). +pub fn convertError(r: i32) !void { + if (r >= 0) return; + + return switch (r) { + c.UV_E2BIG => Error.E2BIG, + c.UV_EACCES => Error.EACCES, + c.UV_EADDRINUSE => Error.EADDRINUSE, + c.UV_EADDRNOTAVAIL => Error.EADDRNOTAVAIL, + c.UV_EAFNOSUPPORT => Error.EAFNOSUPPORT, + c.UV_EAGAIN => Error.EAGAIN, + c.UV_EAI_ADDRFAMILY => Error.EAI_ADDRFAMILY, + c.UV_EAI_AGAIN => Error.EAI_AGAIN, + c.UV_EAI_BADFLAGS => Error.EAI_BADFLAGS, + c.UV_EAI_BADHINTS => Error.EAI_BADHINTS, + c.UV_EAI_CANCELED => Error.EAI_CANCELED, + c.UV_EAI_FAIL => Error.EAI_FAIL, + c.UV_EAI_FAMILY => Error.EAI_FAMILY, + c.UV_EAI_MEMORY => Error.EAI_MEMORY, + c.UV_EAI_NODATA => Error.EAI_NODATA, + c.UV_EAI_NONAME => Error.EAI_NONAME, + c.UV_EAI_OVERFLOW => Error.EAI_OVERFLOW, + c.UV_EAI_PROTOCOL => Error.EAI_PROTOCOL, + c.UV_EAI_SERVICE => Error.EAI_SERVICE, + c.UV_EAI_SOCKTYPE => Error.EAI_SOCKTYPE, + c.UV_EALREADY => Error.EALREADY, + c.UV_EBADF => Error.EBADF, + c.UV_EBUSY => Error.EBUSY, + c.UV_ECANCELED => Error.ECANCELED, + c.UV_ECHARSET => Error.ECHARSET, + c.UV_ECONNABORTED => Error.ECONNABORTED, + c.UV_ECONNREFUSED => Error.ECONNREFUSED, + c.UV_ECONNRESET => Error.ECONNRESET, + c.UV_EDESTADDRREQ => Error.EDESTADDRREQ, + c.UV_EEXIST => Error.EEXIST, + c.UV_EFAULT => Error.EFAULT, + c.UV_EFBIG => Error.EFBIG, + c.UV_EHOSTUNREACH => Error.EHOSTUNREACH, + c.UV_EINTR => Error.EINTR, + c.UV_EINVAL => Error.EINVAL, + c.UV_EIO => Error.EIO, + c.UV_EISCONN => Error.EISCONN, + c.UV_EISDIR => Error.EISDIR, + c.UV_ELOOP => Error.ELOOP, + c.UV_EMFILE => Error.EMFILE, + c.UV_EMSGSIZE => Error.EMSGSIZE, + c.UV_ENAMETOOLONG => Error.ENAMETOOLONG, + c.UV_ENETDOWN => Error.ENETDOWN, + c.UV_ENETUNREACH => Error.ENETUNREACH, + c.UV_ENFILE => Error.ENFILE, + c.UV_ENOBUFS => Error.ENOBUFS, + c.UV_ENODEV => Error.ENODEV, + c.UV_ENOENT => Error.ENOENT, + c.UV_ENOMEM => Error.ENOMEM, + c.UV_ENONET => Error.ENONET, + c.UV_ENOPROTOOPT => Error.ENOPROTOOPT, + c.UV_ENOSPC => Error.ENOSPC, + c.UV_ENOSYS => Error.ENOSYS, + c.UV_ENOTCONN => Error.ENOTCONN, + c.UV_ENOTDIR => Error.ENOTDIR, + c.UV_ENOTEMPTY => Error.ENOTEMPTY, + c.UV_ENOTSOCK => Error.ENOTSOCK, + c.UV_ENOTSUP => Error.ENOTSUP, + c.UV_EPERM => Error.EPERM, + c.UV_EPIPE => Error.EPIPE, + c.UV_EPROTO => Error.EPROTO, + c.UV_EPROTONOSUPPORT => Error.EPROTONOSUPPORT, + c.UV_EPROTOTYPE => Error.EPROTOTYPE, + c.UV_ERANGE => Error.ERANGE, + c.UV_EROFS => Error.EROFS, + c.UV_ESHUTDOWN => Error.ESHUTDOWN, + c.UV_ESPIPE => Error.ESPIPE, + c.UV_ESRCH => Error.ESRCH, + c.UV_ETIMEDOUT => Error.ETIMEDOUT, + c.UV_ETXTBSY => Error.ETXTBSY, + c.UV_EXDEV => Error.EXDEV, + c.UV_UNKNOWN => Error.UNKNOWN, + c.UV_EOF => Error.EOF, + c.UV_ENXIO => Error.ENXIO, + c.UV_EHOSTDOWN => Error.EHOSTDOWN, + c.UV_EREMOTEIO => Error.EREMOTEIO, + c.UV_ENOTTY => Error.ENOTTY, + c.UV_EFTYPE => Error.EFTYPE, + c.UV_EILSEQ => Error.EILSEQ, + c.UV_ESOCKTNOSUPPORT => Error.ESOCKTNOSUPPORT, + else => unreachable, + }; +} + +test { + // This is mostly just forcing our error type and function to be + // codegenned and run once to ensure we have all the types. + try testing.expectError(Error.EFTYPE, convertError(c.UV_EFTYPE)); +} diff --git a/src/libuv/main.zig b/src/libuv/main.zig new file mode 100644 index 000000000..d012ab5d3 --- /dev/null +++ b/src/libuv/main.zig @@ -0,0 +1,7 @@ +const Loop = @import("Loop.zig"); +const Error = @import("error.zig").Error; + +test { + _ = Loop; + _ = Error; +} diff --git a/src/main.zig b/src/main.zig index c7f947619..36c492f2e 100644 --- a/src/main.zig +++ b/src/main.zig @@ -26,4 +26,7 @@ test { _ = @import("Command.zig"); _ = @import("TempDir.zig"); _ = @import("terminal/Terminal.zig"); + + // TEMP + _ = @import("libuv/main.zig"); } diff --git a/vendor/libuv b/vendor/libuv new file mode 160000 index 000000000..9e59aa1bc --- /dev/null +++ b/vendor/libuv @@ -0,0 +1 @@ +Subproject commit 9e59aa1bc8c4d215ea3e05eafec7181747206f67