├── .envrc
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── build.zig
├── flake.lock
├── flake.nix
├── shell.nix
└── src
├── Async.zig
├── Cond.zig
├── Idle.zig
├── Loop.zig
├── Mutex.zig
├── Pipe.zig
├── Prepare.zig
├── Sem.zig
├── Thread.zig
├── Timer.zig
├── Tty.zig
├── c.zig
├── cimport_linux.zig
├── cimport_macos.zig
├── error.zig
├── handle.zig
├── main.zig
├── stream.zig
└── tests.zig
/.envrc:
--------------------------------------------------------------------------------
1 | # If we are a computer with nix-shell available, then use that to setup
2 | # the build environment with exactly what we need.
3 | if has nix; then
4 | use nix
5 | fi
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | zig-cache/
2 | zig-out/
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/libuv"]
2 | path = vendor/libuv
3 | url = https://github.com/libuv/libuv.git
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Mitchell Hashimoto
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | **⚠️ Project Archived ⚠️.** This project is archived. I used this for a few
4 | real projects since 2021 and it was stable, but I've since moved onto using
5 | [libxev](https://github.com/mitchellh/libxev) so I'm no longer maintaining
6 | the libuv bindings.
7 |
8 |
9 |
10 | # zig-libuv
11 |
12 | zig-libuv contains a `build.zig` that can build libuv using Zig and also
13 | contains a package with Zig bindings. Both can be used together or separately.
14 | Building libuv with Zig enables easy cross-compilation. The bindings allow
15 | you to consume libuv easily whether it is built with Zig or not.
16 |
17 | ## Example
18 |
19 | There are lots of examples in the tests for each individual handle type.
20 | Below is an example of using a timer, copied exactly from the tests:
21 |
22 | ```zig
23 | var loop = try Loop.init(testing.allocator);
24 | defer loop.deinit(testing.allocator);
25 |
26 | var timer = try init(testing.allocator, loop);
27 | defer timer.deinit(testing.allocator);
28 |
29 | var called: bool = false;
30 | timer.setData(&called);
31 | try timer.start((struct {
32 | fn callback(t: *Timer) void {
33 | t.getData(bool).?.* = true;
34 | t.close(null);
35 | }
36 | }).callback, 10, 1000);
37 |
38 | _ = try loop.run(.default);
39 |
40 | try testing.expect(called);
41 | ```
42 |
43 | ## Usage
44 |
45 | To **build libuv:**
46 |
47 | ```zig
48 | const libuv = @import("path/to/zig-libuv/build.zig");
49 |
50 | pub fn build(b: *std.build.Builder) !void {
51 | // ...
52 |
53 | const exe = b.addExecutable("my-program", "src/main.zig");
54 | _ = libuv.link(b, exe);
55 | }
56 | ```
57 |
58 | To **use the Zig bindings**, add the package:
59 |
60 | ```zig
61 | exe.addPackage(libuv.pkg);
62 | ```
63 |
--------------------------------------------------------------------------------
/build.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 |
3 | /// Directories with our includes.
4 | const root = thisDir() ++ "/vendor/libuv/";
5 | const include_path = root ++ "include";
6 |
7 | pub const pkg = std.build.Pkg{
8 | .name = "libuv",
9 | .source = .{ .path = thisDir() ++ "/src/main.zig" },
10 | };
11 |
12 | fn thisDir() []const u8 {
13 | return std.fs.path.dirname(@src().file) orelse ".";
14 | }
15 |
16 | pub fn build(b: *std.build.Builder) !void {
17 | const target = b.standardTargetOptions(.{});
18 | const optimize = b.standardOptimizeOption(.{});
19 |
20 | const tests = b.addTest(.{
21 | .name = "pixman-test",
22 | .kind = .test_exe,
23 | .root_source_file = .{ .path = "src/main.zig" },
24 | .target = target,
25 | .optimize = optimize,
26 | });
27 | _ = try link(b, tests);
28 | tests.install();
29 |
30 | const test_step = b.step("test", "Run tests");
31 | const tests_run = tests.run();
32 | test_step.dependOn(&tests_run.step);
33 | }
34 |
35 | pub fn link(b: *std.build.Builder, step: *std.build.LibExeObjStep) !*std.build.LibExeObjStep {
36 | const libuv = try buildLibuv(b, step);
37 | step.linkLibrary(libuv);
38 | step.addIncludePath(include_path);
39 | return libuv;
40 | }
41 |
42 | pub fn buildLibuv(
43 | b: *std.build.Builder,
44 | step: *std.build.LibExeObjStep,
45 | ) !*std.build.LibExeObjStep {
46 | const target = step.target;
47 | const lib = b.addStaticLibrary(.{
48 | .name = "uv",
49 | .target = target,
50 | .optimize = step.optimize,
51 | });
52 |
53 | // Include dirs
54 | lib.addIncludePath(include_path);
55 | lib.addIncludePath(root ++ "src");
56 |
57 | // Links
58 | if (target.isWindows()) {
59 | lib.linkSystemLibrary("psapi");
60 | lib.linkSystemLibrary("user32");
61 | lib.linkSystemLibrary("advapi32");
62 | lib.linkSystemLibrary("iphlpapi");
63 | lib.linkSystemLibrary("userenv");
64 | lib.linkSystemLibrary("ws2_32");
65 | }
66 | if (target.isLinux()) {
67 | lib.linkSystemLibrary("pthread");
68 | }
69 | lib.linkLibC();
70 |
71 | // Compilation
72 | var flags = std.ArrayList([]const u8).init(b.allocator);
73 | defer flags.deinit();
74 | // try flags.appendSlice(&.{});
75 |
76 | if (!target.isWindows()) {
77 | try flags.appendSlice(&.{
78 | "-D_FILE_OFFSET_BITS=64",
79 | "-D_LARGEFILE_SOURCE",
80 | });
81 | }
82 |
83 | if (target.isLinux()) {
84 | try flags.appendSlice(&.{
85 | "-D_GNU_SOURCE",
86 | "-D_POSIX_C_SOURCE=200112",
87 | });
88 | }
89 |
90 | if (target.isDarwin()) {
91 | try flags.appendSlice(&.{
92 | "-D_DARWIN_UNLIMITED_SELECT=1",
93 | "-D_DARWIN_USE_64_BIT_INODE=1",
94 | });
95 | }
96 |
97 | // C files common to all platforms
98 | lib.addCSourceFiles(&.{
99 | root ++ "src/fs-poll.c",
100 | root ++ "src/idna.c",
101 | root ++ "src/inet.c",
102 | root ++ "src/random.c",
103 | root ++ "src/strscpy.c",
104 | root ++ "src/strtok.c",
105 | root ++ "src/threadpool.c",
106 | root ++ "src/timer.c",
107 | root ++ "src/uv-common.c",
108 | root ++ "src/uv-data-getter-setters.c",
109 | root ++ "src/version.c",
110 | }, flags.items);
111 |
112 | if (!target.isWindows()) {
113 | lib.addCSourceFiles(&.{
114 | root ++ "src/unix/async.c",
115 | root ++ "src/unix/core.c",
116 | root ++ "src/unix/dl.c",
117 | root ++ "src/unix/fs.c",
118 | root ++ "src/unix/getaddrinfo.c",
119 | root ++ "src/unix/getnameinfo.c",
120 | root ++ "src/unix/loop-watcher.c",
121 | root ++ "src/unix/loop.c",
122 | root ++ "src/unix/pipe.c",
123 | root ++ "src/unix/poll.c",
124 | root ++ "src/unix/process.c",
125 | root ++ "src/unix/random-devurandom.c",
126 | root ++ "src/unix/signal.c",
127 | root ++ "src/unix/stream.c",
128 | root ++ "src/unix/tcp.c",
129 | root ++ "src/unix/thread.c",
130 | root ++ "src/unix/tty.c",
131 | root ++ "src/unix/udp.c",
132 | }, flags.items);
133 | }
134 |
135 | if (target.isLinux() or target.isDarwin()) {
136 | lib.addCSourceFiles(&.{
137 | root ++ "src/unix/proctitle.c",
138 | }, flags.items);
139 | }
140 |
141 | if (target.isLinux()) {
142 | lib.addCSourceFiles(&.{
143 | root ++ "src/unix/linux.c",
144 | root ++ "src/unix/procfs-exepath.c",
145 | root ++ "src/unix/random-getrandom.c",
146 | root ++ "src/unix/random-sysctl-linux.c",
147 | }, flags.items);
148 | }
149 |
150 | if (target.isDarwin() or
151 | target.isOpenBSD() or
152 | target.isNetBSD() or
153 | target.isFreeBSD() or
154 | target.isDragonFlyBSD())
155 | {
156 | lib.addCSourceFiles(&.{
157 | root ++ "src/unix/bsd-ifaddrs.c",
158 | root ++ "src/unix/kqueue.c",
159 | }, flags.items);
160 | }
161 |
162 | if (target.isDarwin() or target.isOpenBSD()) {
163 | lib.addCSourceFiles(&.{
164 | root ++ "src/unix/random-getentropy.c",
165 | }, flags.items);
166 | }
167 |
168 | if (target.isDarwin()) {
169 | lib.addCSourceFiles(&.{
170 | root ++ "src/unix/darwin-proctitle.c",
171 | root ++ "src/unix/darwin.c",
172 | root ++ "src/unix/fsevents.c",
173 | }, flags.items);
174 | }
175 |
176 | return lib;
177 | }
178 |
--------------------------------------------------------------------------------
/flake.lock:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": {
3 | "flake-compat": {
4 | "flake": false,
5 | "locked": {
6 | "lastModified": 1673956053,
7 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
8 | "owner": "edolstra",
9 | "repo": "flake-compat",
10 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
11 | "type": "github"
12 | },
13 | "original": {
14 | "owner": "edolstra",
15 | "repo": "flake-compat",
16 | "type": "github"
17 | }
18 | },
19 | "flake-compat_2": {
20 | "flake": false,
21 | "locked": {
22 | "lastModified": 1673956053,
23 | "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
24 | "owner": "edolstra",
25 | "repo": "flake-compat",
26 | "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
27 | "type": "github"
28 | },
29 | "original": {
30 | "owner": "edolstra",
31 | "repo": "flake-compat",
32 | "type": "github"
33 | }
34 | },
35 | "flake-utils": {
36 | "locked": {
37 | "lastModified": 1667395993,
38 | "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
39 | "owner": "numtide",
40 | "repo": "flake-utils",
41 | "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
42 | "type": "github"
43 | },
44 | "original": {
45 | "owner": "numtide",
46 | "repo": "flake-utils",
47 | "type": "github"
48 | }
49 | },
50 | "flake-utils_2": {
51 | "locked": {
52 | "lastModified": 1659877975,
53 | "narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
54 | "owner": "numtide",
55 | "repo": "flake-utils",
56 | "rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
57 | "type": "github"
58 | },
59 | "original": {
60 | "owner": "numtide",
61 | "repo": "flake-utils",
62 | "type": "github"
63 | }
64 | },
65 | "nixpkgs": {
66 | "locked": {
67 | "lastModified": 1672580127,
68 | "narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=",
69 | "owner": "nixos",
70 | "repo": "nixpkgs",
71 | "rev": "0874168639713f547c05947c76124f78441ea46c",
72 | "type": "github"
73 | },
74 | "original": {
75 | "owner": "nixos",
76 | "ref": "release-22.05",
77 | "repo": "nixpkgs",
78 | "type": "github"
79 | }
80 | },
81 | "nixpkgs_2": {
82 | "locked": {
83 | "lastModified": 1661151577,
84 | "narHash": "sha256-++S0TuJtuz9IpqP8rKktWyHZKpgdyrzDFUXVY07MTRI=",
85 | "owner": "NixOS",
86 | "repo": "nixpkgs",
87 | "rev": "54060e816971276da05970a983487a25810c38a7",
88 | "type": "github"
89 | },
90 | "original": {
91 | "owner": "NixOS",
92 | "ref": "nixpkgs-unstable",
93 | "repo": "nixpkgs",
94 | "type": "github"
95 | }
96 | },
97 | "root": {
98 | "inputs": {
99 | "flake-compat": "flake-compat",
100 | "flake-utils": "flake-utils",
101 | "nixpkgs": "nixpkgs",
102 | "zig": "zig"
103 | }
104 | },
105 | "zig": {
106 | "inputs": {
107 | "flake-compat": "flake-compat_2",
108 | "flake-utils": "flake-utils_2",
109 | "nixpkgs": "nixpkgs_2"
110 | },
111 | "locked": {
112 | "lastModified": 1675685322,
113 | "narHash": "sha256-iQLyJcfDd5+mRF8b9sT4R/tLhwkTYOxKoaqgoAovlKo=",
114 | "owner": "mitchellh",
115 | "repo": "zig-overlay",
116 | "rev": "2c7f714a6c7fdc85289d3dc201510ea7396656b5",
117 | "type": "github"
118 | },
119 | "original": {
120 | "owner": "mitchellh",
121 | "repo": "zig-overlay",
122 | "type": "github"
123 | }
124 | }
125 | },
126 | "root": "root",
127 | "version": 7
128 | }
129 |
--------------------------------------------------------------------------------
/flake.nix:
--------------------------------------------------------------------------------
1 | {
2 | description = "An empty project that uses Zig.";
3 |
4 | inputs = {
5 | nixpkgs.url = "github:nixos/nixpkgs/release-22.05";
6 | flake-utils.url = "github:numtide/flake-utils";
7 | zig.url = "github:mitchellh/zig-overlay";
8 |
9 | # Used for shell.nix
10 | flake-compat = {
11 | url = github:edolstra/flake-compat;
12 | flake = false;
13 | };
14 | };
15 |
16 | outputs = {
17 | self,
18 | nixpkgs,
19 | flake-utils,
20 | ...
21 | } @ inputs: let
22 | overlays = [
23 | # Other overlays
24 | (final: prev: {
25 | zigpkgs = inputs.zig.packages.${prev.system};
26 | })
27 | ];
28 |
29 | # Our supported systems are the same supported systems as the Zig binaries
30 | systems = builtins.attrNames inputs.zig.packages;
31 | in
32 | flake-utils.lib.eachSystem systems (
33 | system: let
34 | pkgs = import nixpkgs {inherit overlays system;};
35 | in rec {
36 | devShells.default = pkgs.mkShell {
37 | nativeBuildInputs = with pkgs; [
38 | zigpkgs.master
39 | ];
40 | };
41 |
42 | # For compatibility with older versions of the `nix` binary
43 | devShell = self.devShells.${system}.default;
44 | }
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | (import
2 | (
3 | let
4 | flake-compat = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.flake-compat;
5 | in
6 | fetchTarball {
7 | url = "https://github.com/edolstra/flake-compat/archive/${flake-compat.locked.rev}.tar.gz";
8 | sha256 = flake-compat.locked.narHash;
9 | }
10 | )
11 | {src = ./.;})
12 | .shellNix
13 |
--------------------------------------------------------------------------------
/src/Async.zig:
--------------------------------------------------------------------------------
1 | //! Async handles allow the user to “wakeup” the event loop and get a callback
2 | //! called from another thread.
3 | const Async = @This();
4 |
5 | const std = @import("std");
6 | const Allocator = std.mem.Allocator;
7 | const testing = std.testing;
8 | const c = @import("c.zig");
9 | const errors = @import("error.zig");
10 | const Loop = @import("Loop.zig");
11 | const Handle = @import("handle.zig").Handle;
12 |
13 | handle: *c.uv_async_t,
14 |
15 | pub usingnamespace Handle(Async);
16 |
17 | pub fn init(alloc: Allocator, loop: Loop, comptime cb: fn (*Async) void) !Async {
18 | var handle = try alloc.create(c.uv_async_t);
19 | errdefer alloc.destroy(handle);
20 |
21 | const Wrapper = struct {
22 | pub fn callback(arg: [*c]c.uv_async_t) callconv(.C) void {
23 | var newSelf: Async = .{ .handle = arg };
24 | @call(.always_inline, cb, .{&newSelf});
25 | }
26 | };
27 |
28 | try errors.convertError(c.uv_async_init(loop.loop, handle, Wrapper.callback));
29 | return Async{ .handle = handle };
30 | }
31 |
32 | pub fn deinit(self: *Async, alloc: Allocator) void {
33 | alloc.destroy(self.handle);
34 | self.* = undefined;
35 | }
36 |
37 | /// Wake up the event loop and call the async handle’s callback.
38 | pub fn send(self: Async) !void {
39 | try errors.convertError(c.uv_async_send(self.handle));
40 | }
41 |
42 | test "Async" {
43 | var loop = try Loop.init(testing.allocator);
44 | defer loop.deinit(testing.allocator);
45 | var h = try init(testing.allocator, loop, (struct {
46 | fn callback(v: *Async) void {
47 | v.close(null);
48 | }
49 | }).callback);
50 | defer h.deinit(testing.allocator);
51 |
52 | try h.send();
53 | _ = try loop.run(.default);
54 | }
55 |
--------------------------------------------------------------------------------
/src/Cond.zig:
--------------------------------------------------------------------------------
1 | //! Condition variables implemented via libuv.
2 | const Cond = @This();
3 |
4 | const std = @import("std");
5 | const Allocator = std.mem.Allocator;
6 | const testing = std.testing;
7 | const c = @import("c.zig");
8 | const errors = @import("error.zig");
9 | const Mutex = @import("Mutex.zig");
10 |
11 | cond: *c.uv_cond_t,
12 |
13 | pub fn init(alloc: Allocator) !Cond {
14 | const cond = try alloc.create(c.uv_cond_t);
15 | try errors.convertError(c.uv_cond_init(cond));
16 | return Cond{ .cond = cond };
17 | }
18 |
19 | pub fn deinit(self: *Cond, alloc: Allocator) void {
20 | c.uv_cond_destroy(self.cond);
21 | alloc.destroy(self.cond);
22 | self.* = undefined;
23 | }
24 |
25 | pub fn signal(self: Cond) void {
26 | c.uv_cond_signal(self.cond);
27 | }
28 |
29 | pub fn broadcast(self: Cond) void {
30 | c.uv_cond_broadcast(self.cond);
31 | }
32 |
33 | pub fn wait(self: Cond, mutex: Mutex) void {
34 | c.uv_cond_wait(self.cond, mutex.mutex);
35 | }
36 |
37 | pub fn timedwait(self: Cond, mutex: Mutex, timeout: u64) c_int {
38 | return c.uv_cond_timedwait(self.cond, mutex.mutex, timeout);
39 | }
40 |
41 | test {
42 | var cond = try init(testing.allocator);
43 | defer cond.deinit(testing.allocator);
44 | }
45 |
--------------------------------------------------------------------------------
/src/Idle.zig:
--------------------------------------------------------------------------------
1 | //! Idle handles will run the given callback once per loop iteration, right
2 | //! before the uv_prepare_t handles.
3 | const Idle = @This();
4 |
5 | const std = @import("std");
6 | const Allocator = std.mem.Allocator;
7 | const testing = std.testing;
8 | const c = @import("c.zig");
9 | const errors = @import("error.zig");
10 | const Loop = @import("Loop.zig");
11 | const Handle = @import("handle.zig").Handle;
12 |
13 | handle: *c.uv_idle_t,
14 |
15 | pub usingnamespace Handle(Idle);
16 |
17 | pub fn init(alloc: Allocator, loop: Loop) !Idle {
18 | var handle = try alloc.create(c.uv_idle_t);
19 | errdefer alloc.destroy(handle);
20 |
21 | try errors.convertError(c.uv_idle_init(loop.loop, handle));
22 | return Idle{ .handle = handle };
23 | }
24 |
25 | pub fn deinit(self: *Idle, alloc: Allocator) void {
26 | alloc.destroy(self.handle);
27 | self.* = undefined;
28 | }
29 |
30 | /// Start the handle with the given callback. This function always succeeds,
31 | /// except when cb is NULL.
32 | pub fn start(self: Idle, comptime cb: fn (*Idle) void) !void {
33 | const Wrapper = struct {
34 | pub fn callback(arg: [*c]c.uv_idle_t) callconv(.C) void {
35 | var newSelf: Idle = .{ .handle = arg };
36 | @call(.always_inline, cb, .{&newSelf});
37 | }
38 | };
39 |
40 | try errors.convertError(c.uv_idle_start(self.handle, Wrapper.callback));
41 | }
42 |
43 | /// Stop the handle, the callback will no longer be called.
44 | pub fn stop(self: Idle) !void {
45 | try errors.convertError(c.uv_idle_stop(self.handle));
46 | }
47 |
48 | test "Idle" {
49 | var loop = try Loop.init(testing.allocator);
50 | defer loop.deinit(testing.allocator);
51 | var h = try init(testing.allocator, loop);
52 | defer h.deinit(testing.allocator);
53 |
54 | var called: bool = false;
55 | h.setData(&called);
56 | try h.start((struct {
57 | fn callback(t: *Idle) void {
58 | t.getData(bool).?.* = true;
59 | t.close(null);
60 | }
61 | }).callback);
62 |
63 | _ = try loop.run(.default);
64 |
65 | try testing.expect(called);
66 | }
67 |
--------------------------------------------------------------------------------
/src/Loop.zig:
--------------------------------------------------------------------------------
1 | const Loop = @This();
2 |
3 | const std = @import("std");
4 | const Allocator = std.mem.Allocator;
5 | const testing = std.testing;
6 | const c = @import("c.zig");
7 | const errors = @import("error.zig");
8 |
9 | loop: *c.uv_loop_t,
10 |
11 | /// Initialize a new uv_loop.
12 | pub fn init(alloc: Allocator) !Loop {
13 | // The uv_loop_t type MUST be heap allocated and must not be copied.
14 | // I can't find a definitive source on this, but the test suite starts
15 | // hanging in weird places and doing bad things when it is copied.
16 | const loop = try alloc.create(c.uv_loop_t);
17 | try errors.convertError(c.uv_loop_init(loop));
18 | return Loop{ .loop = loop };
19 | }
20 |
21 | /// Releases all internal loop resources. Call this function only when the
22 | /// loop has finished executing and all open handles and requests have been
23 | /// closed, or this will silently fail (in debug mode it will panic).
24 | pub fn deinit(self: *Loop, alloc: Allocator) void {
25 | // deinit functions idiomatically cannot fail in Zig, so we do the
26 | // next best thing here and assert so that in debug mode you'll get
27 | // a crash.
28 | std.debug.assert(c.uv_loop_close(self.loop) >= 0);
29 | alloc.destroy(self.loop);
30 | self.* = undefined;
31 | }
32 |
33 | /// Returns true if the loop is still alive.
34 | pub fn alive(self: Loop) !bool {
35 | const res = c.uv_loop_alive(self.loop);
36 | try errors.convertError(res);
37 | return res > 0;
38 | }
39 |
40 | /// This function runs the event loop. See RunMode for mode documentation.
41 | ///
42 | /// This is not reentrant. It must not be called from a callback.
43 | pub fn run(self: Loop, mode: RunMode) !u32 {
44 | const res = c.uv_run(self.loop, @enumToInt(mode));
45 | try errors.convertError(res);
46 | return @intCast(u32, res);
47 | }
48 |
49 | /// Stop the event loop, causing uv_run() to end as soon as possible. This
50 | /// will happen not sooner than the next loop iteration. If this function was
51 | /// called before blocking for i/o, the loop won’t block for i/o on this iteration.
52 | pub fn stop(self: Loop) void {
53 | c.uv_stop(self.loop);
54 | }
55 |
56 | /// Get backend file descriptor. Only kqueue, epoll and event ports are supported.
57 | ///
58 | /// This can be used in conjunction with uv_run(loop, UV_RUN_NOWAIT) to poll
59 | /// in one thread and run the event loop’s callbacks in another see
60 | /// test/test-embed.c for an example.
61 | pub fn backendFd(self: Loop) !c_int {
62 | const res = c.uv_backend_fd(self.loop);
63 | try errors.convertError(res);
64 | return res;
65 | }
66 |
67 | /// Get the poll timeout. The return value is in milliseconds, or -1 for no
68 | /// timeout.
69 | pub fn backendTimeout(self: Loop) c_int {
70 | return c.uv_backend_timeout(self.loop);
71 | }
72 |
73 | /// Return the current timestamp in milliseconds. The timestamp is cached at
74 | /// the start of the event loop tick, see uv_update_time() for details and rationale.
75 | ///
76 | /// The timestamp increases monotonically from some arbitrary point in time.
77 | /// Don’t make assumptions about the starting point, you will only get disappointed.
78 | pub fn now(self: Loop) u64 {
79 | return c.uv_now(self.loop);
80 | }
81 |
82 | /// Update the event loop’s concept of “now”. Libuv caches the current time at
83 | /// the start of the event loop tick in order to reduce the number of time-related
84 | /// system calls.
85 | ///
86 | /// You won’t normally need to call this function unless you have callbacks
87 | /// that block the event loop for longer periods of time, where “longer” is
88 | /// somewhat subjective but probably on the order of a millisecond or more.
89 | pub fn updateTime(self: Loop) void {
90 | return c.uv_update_time(self.loop);
91 | }
92 |
93 | /// Sets loop->data to data.
94 | pub fn setData(self: Loop, pointer: ?*anyopaque) void {
95 | c.uv_loop_set_data(self.loop, pointer);
96 | }
97 |
98 | /// Returns loop->data.
99 | pub fn getData(self: Loop, comptime DT: type) ?*DT {
100 | return if (c.uv_loop_get_data(self.loop)) |ptr|
101 | @ptrCast(?*DT, @alignCast(@alignOf(DT), ptr))
102 | else
103 | null;
104 | }
105 |
106 | /// Mode used to run the loop with uv_run().
107 | pub const RunMode = enum(c.uv_run_mode) {
108 | default = c.UV_RUN_DEFAULT,
109 | once = c.UV_RUN_ONCE,
110 | nowait = c.UV_RUN_NOWAIT,
111 | };
112 |
113 | test {
114 | var loop = try init(testing.allocator);
115 | defer loop.deinit(testing.allocator);
116 |
117 | var data: u8 = 42;
118 | loop.setData(&data);
119 | try testing.expect(loop.getData(u8).?.* == 42);
120 |
121 | try testing.expect((try loop.backendFd()) > 0);
122 | try testing.expectEqual(@as(u32, 0), try loop.run(.nowait));
123 | }
124 |
--------------------------------------------------------------------------------
/src/Mutex.zig:
--------------------------------------------------------------------------------
1 | //! Mutexes implemented via libuv.
2 | const Mutex = @This();
3 |
4 | const std = @import("std");
5 | const Allocator = std.mem.Allocator;
6 | const testing = std.testing;
7 | const c = @import("c.zig");
8 | const errors = @import("error.zig");
9 |
10 | mutex: *c.uv_mutex_t,
11 |
12 | pub fn init(alloc: Allocator) !Mutex {
13 | const mutex = try alloc.create(c.uv_mutex_t);
14 | try errors.convertError(c.uv_mutex_init(mutex));
15 | return Mutex{ .mutex = mutex };
16 | }
17 |
18 | pub fn deinit(self: *Mutex, alloc: Allocator) void {
19 | c.uv_mutex_destroy(self.mutex);
20 | alloc.destroy(self.mutex);
21 | self.* = undefined;
22 | }
23 |
24 | pub fn lock(self: Mutex) void {
25 | c.uv_mutex_lock(self.mutex);
26 | }
27 |
28 | pub fn unlock(self: Mutex) void {
29 | c.uv_mutex_unlock(self.mutex);
30 | }
31 |
32 | test {
33 | var mutex = try init(testing.allocator);
34 | defer mutex.deinit(testing.allocator);
35 | }
36 |
--------------------------------------------------------------------------------
/src/Pipe.zig:
--------------------------------------------------------------------------------
1 | //! Pipe handles provide an abstraction over streaming files on Unix
2 | //! (including local domain sockets, pipes, and FIFOs) and named pipes on
3 | //! Windows.
4 | const Pipe = @This();
5 |
6 | const std = @import("std");
7 | const Allocator = std.mem.Allocator;
8 | const testing = std.testing;
9 | const c = @import("c.zig");
10 | const errors = @import("error.zig");
11 | const Loop = @import("Loop.zig");
12 | const Handle = @import("handle.zig").Handle;
13 | const stream = @import("stream.zig");
14 | const Stream = stream.Stream;
15 | const WriteReq = stream.WriteReq;
16 |
17 | handle: *c.uv_pipe_t,
18 |
19 | pub usingnamespace Handle(Pipe);
20 | pub usingnamespace Stream(Pipe);
21 |
22 | /// Valid flags for pipe.
23 | pub const Flags = packed struct {
24 | _ignore: u6 = 0,
25 | nonblock: bool = false, // UV_NONBLOCK_PIPE = 0x40
26 | _ignore_high: u1 = 0,
27 |
28 | pub inline fn toInt(self: Flags, comptime IntType: type) IntType {
29 | return @intCast(IntType, @bitCast(u8, self));
30 | }
31 |
32 | test "Flags: expected value" {
33 | const f: Flags = .{ .nonblock = true };
34 | try testing.expectEqual(c.UV_NONBLOCK_PIPE, f.toInt(c_int));
35 | }
36 | };
37 |
38 | /// Pair is a pair of ends to a single pipe.
39 | pub const Pair = struct {
40 | read: c.uv_file,
41 | write: c.uv_file,
42 | };
43 |
44 | /// Create a pair of connected pipe handles. Data may be written to fds[1] and
45 | /// read from fds[0]. The resulting handles can be passed to uv_pipe_open,
46 | /// used with uv_spawn, or for any other purpose.
47 | pub fn pipe(read_flags: Flags, write_flags: Flags) !Pair {
48 | var res: [2]c.uv_file = undefined;
49 | try errors.convertError(c.uv_pipe(
50 | &res,
51 | read_flags.toInt(c_int),
52 | write_flags.toInt(c_int),
53 | ));
54 | return Pair{ .read = res[0], .write = res[1] };
55 | }
56 |
57 | pub fn init(alloc: Allocator, loop: Loop, ipc: bool) !Pipe {
58 | var handle = try alloc.create(c.uv_pipe_t);
59 | errdefer alloc.destroy(handle);
60 | try errors.convertError(c.uv_pipe_init(loop.loop, handle, @boolToInt(ipc)));
61 | return Pipe{ .handle = handle };
62 | }
63 |
64 | pub fn deinit(self: *Pipe, alloc: Allocator) void {
65 | alloc.destroy(self.handle);
66 | self.* = undefined;
67 | }
68 |
69 | /// Open an existing file descriptor or HANDLE as a pipe.
70 | pub fn open(self: Pipe, file: c.uv_file) !void {
71 | try errors.convertError(c.uv_pipe_open(self.handle, file));
72 | }
73 |
74 | test {
75 | _ = Flags;
76 | }
77 |
78 | test "Pipe" {
79 | const pair = try pipe(.{ .nonblock = true }, .{ .nonblock = true });
80 |
81 | var loop = try Loop.init(testing.allocator);
82 | defer loop.deinit(testing.allocator);
83 |
84 | // Read side
85 | var reader = try init(testing.allocator, loop, false);
86 | defer reader.deinit(testing.allocator);
87 |
88 | try reader.open(pair.read);
89 | try testing.expect(try reader.isReadable());
90 | try testing.expect(!try reader.isWritable());
91 |
92 | // Write side
93 | var writer = try init(testing.allocator, loop, false);
94 | defer writer.deinit(testing.allocator);
95 |
96 | try writer.open(pair.write);
97 | try testing.expect(!try writer.isReadable());
98 | try testing.expect(try writer.isWritable());
99 |
100 | // Set our data that we'll use to assert
101 | var data: TestData = .{};
102 | defer data.deinit();
103 | writer.setData(&data);
104 |
105 | // Write
106 | var writeReq = try WriteReq.init(testing.allocator);
107 | defer writeReq.deinit(testing.allocator);
108 |
109 | try writer.write(
110 | writeReq,
111 | &[_][]const u8{
112 | "hello",
113 | },
114 | TestData.write,
115 | );
116 |
117 | // Run write and verify success
118 | _ = try loop.run(.once);
119 | try testing.expectEqual(@as(u8, 1), data.count);
120 | try testing.expectEqual(@as(i32, 0), data.status);
121 |
122 | // Read
123 | try reader.readStart(TestData.alloc, TestData.read);
124 | reader.setData(&data);
125 | _ = try loop.run(.once);
126 |
127 | // Check our data
128 | try testing.expectEqual(@as(usize, 5), data.data.items.len);
129 | try testing.expectEqualStrings("hello", data.data.items);
130 | data.data.clearRetainingCapacity();
131 |
132 | // Try writing directly
133 | _ = try writer.tryWrite(&[_][]const u8{"world"});
134 | _ = try loop.run(.once);
135 | try testing.expectEqual(@as(usize, 5), data.data.items.len);
136 | try testing.expectEqualStrings("world", data.data.items);
137 |
138 | // End
139 | reader.readStop();
140 | reader.close(null);
141 | writer.close(null);
142 | _ = try loop.run(.default);
143 | }
144 |
145 | /// Logic for testing read/write of pipes.
146 | const TestData = struct {
147 | count: u8 = 0,
148 | status: i32 = 0,
149 | data: std.ArrayListUnmanaged(u8) = .{},
150 |
151 | fn deinit(self: *TestData) void {
152 | self.data.deinit(testing.allocator);
153 | self.* = undefined;
154 | }
155 |
156 | fn write(req: *WriteReq, status: i32) void {
157 | var data = req.handle(Pipe).?.getData(TestData).?;
158 | data.count += 1;
159 | data.status = status;
160 | }
161 |
162 | fn alloc(_: *Pipe, size: usize) ?[]u8 {
163 | return testing.allocator.alloc(u8, size) catch null;
164 | }
165 |
166 | fn read(h: *Pipe, n: isize, buf: []const u8) void {
167 | var data = h.getData(TestData).?;
168 | data.data.appendSlice(
169 | testing.allocator,
170 | buf[0..@intCast(usize, n)],
171 | ) catch unreachable;
172 | testing.allocator.free(buf);
173 | }
174 | };
175 |
--------------------------------------------------------------------------------
/src/Prepare.zig:
--------------------------------------------------------------------------------
1 | //! Prepare handles will run the given callback once per loop iteration, right
2 | //! before polling for i/o.
3 | const Prepare = @This();
4 |
5 | const std = @import("std");
6 | const Allocator = std.mem.Allocator;
7 | const testing = std.testing;
8 | const c = @import("c.zig");
9 | const errors = @import("error.zig");
10 | const Loop = @import("Loop.zig");
11 | const Handle = @import("handle.zig").Handle;
12 |
13 | handle: *c.uv_prepare_t,
14 |
15 | pub usingnamespace Handle(Prepare);
16 |
17 | pub fn init(alloc: Allocator, loop: Loop) !Prepare {
18 | var handle = try alloc.create(c.uv_prepare_t);
19 | errdefer alloc.destroy(handle);
20 |
21 | try errors.convertError(c.uv_prepare_init(loop.loop, handle));
22 | return Prepare{ .handle = handle };
23 | }
24 |
25 | pub fn deinit(self: *Prepare, alloc: Allocator) void {
26 | alloc.destroy(self.handle);
27 | self.* = undefined;
28 | }
29 |
30 | /// Start the handle with the given callback. This function always succeeds,
31 | /// except when cb is NULL.
32 | pub fn start(self: Prepare, comptime cb: fn (*Prepare) void) !void {
33 | const Wrapper = struct {
34 | pub fn callback(arg: [*c]c.uv_prepare_t) callconv(.C) void {
35 | var newSelf: Prepare = .{ .handle = arg };
36 | @call(.always_inline, cb, .{&newSelf});
37 | }
38 | };
39 |
40 | try errors.convertError(c.uv_prepare_start(self.handle, Wrapper.callback));
41 | }
42 |
43 | /// Stop the handle, the callback will no longer be called.
44 | pub fn stop(self: Prepare) !void {
45 | try errors.convertError(c.uv_prepare_stop(self.handle));
46 | }
47 |
48 | test "Prepare" {
49 | var loop = try Loop.init(testing.allocator);
50 | defer loop.deinit(testing.allocator);
51 | var h = try init(testing.allocator, loop);
52 | defer h.deinit(testing.allocator);
53 |
54 | var called: bool = false;
55 | h.setData(&called);
56 | try h.start((struct {
57 | fn callback(t: *Prepare) void {
58 | t.getData(bool).?.* = true;
59 | t.close(null);
60 | }
61 | }).callback);
62 |
63 | _ = try loop.run(.default);
64 |
65 | try testing.expect(called);
66 | }
67 |
--------------------------------------------------------------------------------
/src/Sem.zig:
--------------------------------------------------------------------------------
1 | //! Semaphores implemented via libuv.
2 | const Sem = @This();
3 |
4 | const std = @import("std");
5 | const Allocator = std.mem.Allocator;
6 | const testing = std.testing;
7 | const c = @import("c.zig");
8 | const errors = @import("error.zig");
9 |
10 | sem: *c.uv_sem_t,
11 |
12 | pub fn init(alloc: Allocator, value: u32) !Sem {
13 | const sem = try alloc.create(c.uv_sem_t);
14 | try errors.convertError(c.uv_sem_init(sem, value));
15 | return Sem{ .sem = sem };
16 | }
17 |
18 | pub fn deinit(self: *Sem, alloc: Allocator) void {
19 | c.uv_sem_destroy(self.sem);
20 | alloc.destroy(self.sem);
21 | self.* = undefined;
22 | }
23 |
24 | pub fn post(self: Sem) void {
25 | c.uv_sem_post(self.sem);
26 | }
27 |
28 | pub fn wait(self: Sem) void {
29 | c.uv_sem_wait(self.sem);
30 | }
31 |
32 | test {
33 | var sem = try init(testing.allocator, 0);
34 | defer sem.deinit(testing.allocator);
35 | }
36 |
--------------------------------------------------------------------------------
/src/Thread.zig:
--------------------------------------------------------------------------------
1 | //! Threading implemented by libuv.
2 | const Thread = @This();
3 |
4 | const std = @import("std");
5 | const Allocator = std.mem.Allocator;
6 | const testing = std.testing;
7 | const c = @import("c.zig");
8 | const errors = @import("error.zig");
9 |
10 | thread: c.uv_thread_t,
11 |
12 | /// Get the current thread
13 | pub fn self() Thread {
14 | return .{ .thread = c.uv_thread_self() };
15 | }
16 |
17 | /// Initialize a new thread.
18 | pub fn init(
19 | comptime callback: fn () void,
20 | ) !Thread {
21 | const CWrapper = struct {
22 | pub fn wrapper(_: ?*const anyopaque) callconv(.C) void {
23 | @call(.always_inline, callback, .{});
24 | }
25 | };
26 |
27 | var res = Thread{ .thread = undefined };
28 | try errors.convertError(c.uv_thread_create(&res.thread, CWrapper.wrapper, null));
29 | return res;
30 | }
31 |
32 | /// Initialize a new thread with user data attached.
33 | pub fn initData(
34 | data: anytype,
35 | comptime callback: fn (arg: @TypeOf(data)) void,
36 | ) !Thread {
37 | // Comptime stuff to learn more about our data parameter. This is used
38 | // to do the proper casts for the callback.
39 | const Data = @TypeOf(data);
40 | const dataInfo = @typeInfo(Data);
41 | if (dataInfo != .Pointer) @compileError("data must be a pointer type");
42 |
43 | const CWrapper = struct {
44 | pub fn wrapper(arg: ?*anyopaque) callconv(.C) void {
45 | @call(.always_inline, callback, .{
46 | @ptrCast(Data, @alignCast(@alignOf(dataInfo.Pointer.child), arg)),
47 | });
48 | }
49 | };
50 |
51 | var res: Thread = .{ .thread = undefined };
52 | try errors.convertError(c.uv_thread_create(
53 | &res.thread,
54 | CWrapper.wrapper,
55 | data,
56 | ));
57 | return res;
58 | }
59 |
60 | pub fn join(t: *Thread) !void {
61 | try errors.convertError(c.uv_thread_join(&t.thread));
62 | }
63 |
64 | test "Thread: no data argument" {
65 | count = 0;
66 | var thread = try init(incr);
67 | try thread.join();
68 | try testing.expectEqual(@as(u8, 1), count);
69 | }
70 |
71 | test "Thread: with data argument" {
72 | count = 0;
73 | var data: u8 = 2;
74 | var thread = try initData(&data, incrBy);
75 | try thread.join();
76 | try testing.expectEqual(@as(u8, 2), count);
77 | }
78 |
79 | var count: u8 = 0;
80 |
81 | fn incr() void {
82 | count += 1;
83 | }
84 |
85 | fn incrBy(v: *u8) void {
86 | count += v.*;
87 | }
88 |
--------------------------------------------------------------------------------
/src/Timer.zig:
--------------------------------------------------------------------------------
1 | //! Timer handles are used to schedule callbacks to be called in the future.
2 | const Timer = @This();
3 |
4 | const std = @import("std");
5 | const Allocator = std.mem.Allocator;
6 | const testing = std.testing;
7 | const c = @import("c.zig");
8 | const errors = @import("error.zig");
9 | const Loop = @import("Loop.zig");
10 | const Handle = @import("handle.zig").Handle;
11 |
12 | handle: *c.uv_timer_t,
13 |
14 | pub usingnamespace Handle(Timer);
15 |
16 | pub fn init(alloc: Allocator, loop: Loop) !Timer {
17 | var timer = try alloc.create(c.uv_timer_t);
18 | errdefer alloc.destroy(timer);
19 | try errors.convertError(c.uv_timer_init(loop.loop, timer));
20 | return Timer{ .handle = timer };
21 | }
22 |
23 | pub fn deinit(self: *Timer, alloc: Allocator) void {
24 | alloc.destroy(self.handle);
25 | self.* = undefined;
26 | }
27 |
28 | /// Start the timer. timeout and repeat are in milliseconds.
29 | ///
30 | /// If timeout is zero, the callback fires on the next event loop iteration.
31 | /// If repeat is non-zero, the callback fires first after timeout milliseconds
32 | /// and then repeatedly after repeat milliseconds.
33 | pub fn start(
34 | self: Timer,
35 | comptime cb: fn (*Timer) void,
36 | timeout: u64,
37 | repeat: u64,
38 | ) !void {
39 | const Wrapper = struct {
40 | pub fn callback(handle: [*c]c.uv_timer_t) callconv(.C) void {
41 | var newSelf: Timer = .{ .handle = handle };
42 | @call(.always_inline, cb, .{&newSelf});
43 | }
44 | };
45 |
46 | try errors.convertError(c.uv_timer_start(
47 | self.handle,
48 | Wrapper.callback,
49 | timeout,
50 | repeat,
51 | ));
52 | }
53 |
54 | /// Stop the timer, the callback will not be called anymore.
55 | pub fn stop(self: Timer) !void {
56 | try errors.convertError(c.uv_timer_stop(self.handle));
57 | }
58 |
59 | /// Stop the timer, and if it is repeating restart it using the repeat value
60 | /// as the timeout. If the timer has never been started before it returns UV_EINVAL.
61 | pub fn again(self: Timer) !void {
62 | try errors.convertError(c.uv_timer_again(self.handle));
63 | }
64 |
65 | /// Get the timer repeat value.
66 | pub fn getRepeat(self: Timer) u64 {
67 | return c.uv_timer_get_repeat(self.handle);
68 | }
69 |
70 | /// Set the repeat interval value in milliseconds. The timer will be scheduled
71 | /// to run on the given interval, regardless of the callback execution duration,
72 | /// and will follow normal timer semantics in the case of a time-slice overrun.
73 | pub fn setRepeat(self: Timer, repeat: u64) void {
74 | c.uv_timer_set_repeat(self.handle, repeat);
75 | }
76 |
77 | test "Timer" {
78 | var loop = try Loop.init(testing.allocator);
79 | defer loop.deinit(testing.allocator);
80 | var timer = try init(testing.allocator, loop);
81 | defer timer.deinit(testing.allocator);
82 |
83 | var called: bool = false;
84 | timer.setData(&called);
85 | try timer.start((struct {
86 | fn callback(t: *Timer) void {
87 | t.getData(bool).?.* = true;
88 | t.close(null);
89 | }
90 | }).callback, 10, 1000);
91 |
92 | _ = try loop.run(.default);
93 |
94 | try testing.expect(called);
95 | }
96 |
97 | test "Timer: close callback" {
98 | var loop = try Loop.init(testing.allocator);
99 | defer loop.deinit(testing.allocator);
100 | var timer = try init(testing.allocator, loop);
101 | defer timer.deinit(testing.allocator);
102 |
103 | var data: u8 = 42;
104 | timer.setData(&data);
105 | timer.close((struct {
106 | fn callback(v: *Timer) void {
107 | var dataPtr = v.getData(u8).?;
108 | dataPtr.* = 24;
109 | }
110 | }).callback);
111 | _ = try loop.run(.default);
112 |
113 | try testing.expectEqual(@as(u8, 24), data);
114 | }
115 |
--------------------------------------------------------------------------------
/src/Tty.zig:
--------------------------------------------------------------------------------
1 | //! Tty handles represent a stream for the console.
2 | const Tty = @This();
3 |
4 | const std = @import("std");
5 | const fd_t = std.os.fd_t;
6 | const Allocator = std.mem.Allocator;
7 | const testing = std.testing;
8 | const c = @import("c.zig");
9 | const errors = @import("error.zig");
10 | const Loop = @import("Loop.zig");
11 | const Handle = @import("handle.zig").Handle;
12 | const Stream = @import("stream.zig").Stream;
13 |
14 | handle: *c.uv_tty_t,
15 |
16 | pub usingnamespace Handle(Tty);
17 | pub usingnamespace Stream(Tty);
18 |
19 | pub fn init(alloc: Allocator, loop: Loop, fd: fd_t) !Tty {
20 | var tty = try alloc.create(c.uv_tty_t);
21 | errdefer alloc.destroy(tty);
22 | try errors.convertError(c.uv_tty_init(loop.loop, tty, fd, 0));
23 | return Tty{ .handle = tty };
24 | }
25 |
26 | pub fn deinit(self: *Tty, alloc: Allocator) void {
27 | alloc.destroy(self.handle);
28 | self.* = undefined;
29 | }
30 |
--------------------------------------------------------------------------------
/src/c.zig:
--------------------------------------------------------------------------------
1 | const builtin = @import("builtin");
2 |
3 | pub usingnamespace switch (builtin.zig_backend) {
4 | .stage1 => @cImport({
5 | @cInclude("uv.h");
6 | }),
7 |
8 | // Workaround for:
9 | // https://github.com/ziglang/zig/issues/12325
10 | //
11 | // Generated by:
12 | // zig translate-c -target aarch64-macos -lc -Ivendor/libuv/include vendor/libuv/include/uv.h
13 | // (and then manually modified)
14 | else => switch (builtin.os.tag) {
15 | .macos => @import("cimport_macos.zig"),
16 | .linux => @import("cimport_linux.zig"),
17 | else => @compileError("unsupported OS for now, see this line"),
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/src/error.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const testing = std.testing;
3 | const c = @import("c.zig");
4 |
5 | /// Enum mapping for errors.
6 | pub const Errno = enum(i32) {
7 | E2BIG = c.UV_E2BIG,
8 | EACCES = c.UV_EACCES,
9 | EADDRINUSE = c.UV_EADDRINUSE,
10 | EADDRNOTAVAIL = c.UV_EADDRNOTAVAIL,
11 | EAFNOSUPPORT = c.UV_EAFNOSUPPORT,
12 | EAGAIN = c.UV_EAGAIN,
13 | EAI_ADDRFAMILY = c.UV_EAI_ADDRFAMILY,
14 | EAI_AGAIN = c.UV_EAI_AGAIN,
15 | EAI_BADFLAGS = c.UV_EAI_BADFLAGS,
16 | EAI_BADHINTS = c.UV_EAI_BADHINTS,
17 | EAI_CANCELED = c.UV_EAI_CANCELED,
18 | EAI_FAIL = c.UV_EAI_FAIL,
19 | EAI_FAMILY = c.UV_EAI_FAMILY,
20 | EAI_MEMORY = c.UV_EAI_MEMORY,
21 | EAI_NODATA = c.UV_EAI_NODATA,
22 | EAI_NONAME = c.UV_EAI_NONAME,
23 | EAI_OVERFLOW = c.UV_EAI_OVERFLOW,
24 | EAI_PROTOCOL = c.UV_EAI_PROTOCOL,
25 | EAI_SERVICE = c.UV_EAI_SERVICE,
26 | EAI_SOCKTYPE = c.UV_EAI_SOCKTYPE,
27 | EALREADY = c.UV_EALREADY,
28 | EBADF = c.UV_EBADF,
29 | EBUSY = c.UV_EBUSY,
30 | ECANCELED = c.UV_ECANCELED,
31 | ECHARSET = c.UV_ECHARSET,
32 | ECONNABORTED = c.UV_ECONNABORTED,
33 | ECONNREFUSED = c.UV_ECONNREFUSED,
34 | ECONNRESET = c.UV_ECONNRESET,
35 | EDESTADDRREQ = c.UV_EDESTADDRREQ,
36 | EEXIST = c.UV_EEXIST,
37 | EFAULT = c.UV_EFAULT,
38 | EFBIG = c.UV_EFBIG,
39 | EHOSTUNREACH = c.UV_EHOSTUNREACH,
40 | EINTR = c.UV_EINTR,
41 | EINVAL = c.UV_EINVAL,
42 | EIO = c.UV_EIO,
43 | EISCONN = c.UV_EISCONN,
44 | EISDIR = c.UV_EISDIR,
45 | ELOOP = c.UV_ELOOP,
46 | EMFILE = c.UV_EMFILE,
47 | EMSGSIZE = c.UV_EMSGSIZE,
48 | ENAMETOOLONG = c.UV_ENAMETOOLONG,
49 | ENETDOWN = c.UV_ENETDOWN,
50 | ENETUNREACH = c.UV_ENETUNREACH,
51 | ENFILE = c.UV_ENFILE,
52 | ENOBUFS = c.UV_ENOBUFS,
53 | ENODEV = c.UV_ENODEV,
54 | ENOENT = c.UV_ENOENT,
55 | ENOMEM = c.UV_ENOMEM,
56 | ENONET = c.UV_ENONET,
57 | ENOPROTOOPT = c.UV_ENOPROTOOPT,
58 | ENOSPC = c.UV_ENOSPC,
59 | ENOSYS = c.UV_ENOSYS,
60 | ENOTCONN = c.UV_ENOTCONN,
61 | ENOTDIR = c.UV_ENOTDIR,
62 | ENOTEMPTY = c.UV_ENOTEMPTY,
63 | ENOTSOCK = c.UV_ENOTSOCK,
64 | ENOTSUP = c.UV_ENOTSUP,
65 | EPERM = c.UV_EPERM,
66 | EPIPE = c.UV_EPIPE,
67 | EPROTO = c.UV_EPROTO,
68 | EPROTONOSUPPORT = c.UV_EPROTONOSUPPORT,
69 | EPROTOTYPE = c.UV_EPROTOTYPE,
70 | ERANGE = c.UV_ERANGE,
71 | EROFS = c.UV_EROFS,
72 | ESHUTDOWN = c.UV_ESHUTDOWN,
73 | ESPIPE = c.UV_ESPIPE,
74 | ESRCH = c.UV_ESRCH,
75 | ETIMEDOUT = c.UV_ETIMEDOUT,
76 | ETXTBSY = c.UV_ETXTBSY,
77 | EXDEV = c.UV_EXDEV,
78 | UNKNOWN = c.UV_UNKNOWN,
79 | EOF = c.UV_EOF,
80 | ENXIO = c.UV_ENXIO,
81 | EHOSTDOWN = c.UV_EHOSTDOWN,
82 | EREMOTEIO = c.UV_EREMOTEIO,
83 | ENOTTY = c.UV_ENOTTY,
84 | EFTYPE = c.UV_EFTYPE,
85 | EILSEQ = c.UV_EILSEQ,
86 | ESOCKTNOSUPPORT = c.UV_ESOCKTNOSUPPORT,
87 | };
88 |
89 | /// Errors that libuv can produce.
90 | ///
91 | /// http://docs.libuv.org/en/v1.x/errors.html
92 | pub const Error = blk: {
93 | // We produce these from the Errno enum so that we can easily
94 | // keep it synced.
95 | const info = @typeInfo(Errno).Enum;
96 | var errors: [info.fields.len]std.builtin.Type.Error = undefined;
97 | for (info.fields) |field, i| {
98 | errors[i] = .{ .name = field.name };
99 | }
100 |
101 | break :blk @Type(.{ .ErrorSet = &errors });
102 | };
103 |
104 | /// Convert the result of a libuv API call to an error (or no error).
105 | pub fn convertError(r: i32) !void {
106 | if (r >= 0) return;
107 |
108 | return switch (@intToEnum(Errno, r)) {
109 | .E2BIG => Error.E2BIG,
110 | .EACCES => Error.EACCES,
111 | .EADDRINUSE => Error.EADDRINUSE,
112 | .EADDRNOTAVAIL => Error.EADDRNOTAVAIL,
113 | .EAFNOSUPPORT => Error.EAFNOSUPPORT,
114 | .EAGAIN => Error.EAGAIN,
115 | .EAI_ADDRFAMILY => Error.EAI_ADDRFAMILY,
116 | .EAI_AGAIN => Error.EAI_AGAIN,
117 | .EAI_BADFLAGS => Error.EAI_BADFLAGS,
118 | .EAI_BADHINTS => Error.EAI_BADHINTS,
119 | .EAI_CANCELED => Error.EAI_CANCELED,
120 | .EAI_FAIL => Error.EAI_FAIL,
121 | .EAI_FAMILY => Error.EAI_FAMILY,
122 | .EAI_MEMORY => Error.EAI_MEMORY,
123 | .EAI_NODATA => Error.EAI_NODATA,
124 | .EAI_NONAME => Error.EAI_NONAME,
125 | .EAI_OVERFLOW => Error.EAI_OVERFLOW,
126 | .EAI_PROTOCOL => Error.EAI_PROTOCOL,
127 | .EAI_SERVICE => Error.EAI_SERVICE,
128 | .EAI_SOCKTYPE => Error.EAI_SOCKTYPE,
129 | .EALREADY => Error.EALREADY,
130 | .EBADF => Error.EBADF,
131 | .EBUSY => Error.EBUSY,
132 | .ECANCELED => Error.ECANCELED,
133 | .ECHARSET => Error.ECHARSET,
134 | .ECONNABORTED => Error.ECONNABORTED,
135 | .ECONNREFUSED => Error.ECONNREFUSED,
136 | .ECONNRESET => Error.ECONNRESET,
137 | .EDESTADDRREQ => Error.EDESTADDRREQ,
138 | .EEXIST => Error.EEXIST,
139 | .EFAULT => Error.EFAULT,
140 | .EFBIG => Error.EFBIG,
141 | .EHOSTUNREACH => Error.EHOSTUNREACH,
142 | .EINTR => Error.EINTR,
143 | .EINVAL => Error.EINVAL,
144 | .EIO => Error.EIO,
145 | .EISCONN => Error.EISCONN,
146 | .EISDIR => Error.EISDIR,
147 | .ELOOP => Error.ELOOP,
148 | .EMFILE => Error.EMFILE,
149 | .EMSGSIZE => Error.EMSGSIZE,
150 | .ENAMETOOLONG => Error.ENAMETOOLONG,
151 | .ENETDOWN => Error.ENETDOWN,
152 | .ENETUNREACH => Error.ENETUNREACH,
153 | .ENFILE => Error.ENFILE,
154 | .ENOBUFS => Error.ENOBUFS,
155 | .ENODEV => Error.ENODEV,
156 | .ENOENT => Error.ENOENT,
157 | .ENOMEM => Error.ENOMEM,
158 | .ENONET => Error.ENONET,
159 | .ENOPROTOOPT => Error.ENOPROTOOPT,
160 | .ENOSPC => Error.ENOSPC,
161 | .ENOSYS => Error.ENOSYS,
162 | .ENOTCONN => Error.ENOTCONN,
163 | .ENOTDIR => Error.ENOTDIR,
164 | .ENOTEMPTY => Error.ENOTEMPTY,
165 | .ENOTSOCK => Error.ENOTSOCK,
166 | .ENOTSUP => Error.ENOTSUP,
167 | .EPERM => Error.EPERM,
168 | .EPIPE => Error.EPIPE,
169 | .EPROTO => Error.EPROTO,
170 | .EPROTONOSUPPORT => Error.EPROTONOSUPPORT,
171 | .EPROTOTYPE => Error.EPROTOTYPE,
172 | .ERANGE => Error.ERANGE,
173 | .EROFS => Error.EROFS,
174 | .ESHUTDOWN => Error.ESHUTDOWN,
175 | .ESPIPE => Error.ESPIPE,
176 | .ESRCH => Error.ESRCH,
177 | .ETIMEDOUT => Error.ETIMEDOUT,
178 | .ETXTBSY => Error.ETXTBSY,
179 | .EXDEV => Error.EXDEV,
180 | .UNKNOWN => Error.UNKNOWN,
181 | .EOF => Error.EOF,
182 | .ENXIO => Error.ENXIO,
183 | .EHOSTDOWN => Error.EHOSTDOWN,
184 | .EREMOTEIO => Error.EREMOTEIO,
185 | .ENOTTY => Error.ENOTTY,
186 | .EFTYPE => Error.EFTYPE,
187 | .EILSEQ => Error.EILSEQ,
188 | .ESOCKTNOSUPPORT => Error.ESOCKTNOSUPPORT,
189 | };
190 | }
191 |
192 | test {
193 | // This is mostly just forcing our error type and function to be
194 | // codegenned and run once to ensure we have all the types.
195 | try testing.expectError(Error.EFTYPE, convertError(c.UV_EFTYPE));
196 | }
197 |
--------------------------------------------------------------------------------
/src/handle.zig:
--------------------------------------------------------------------------------
1 | const c = @import("c.zig");
2 |
3 | const Loop = @import("Loop.zig");
4 | const errors = @import("error.zig");
5 |
6 | /// Returns a struct that has all the shared handle functions for the
7 | /// given handle type T. The type T must have a field named "handle".
8 | /// This is expected to be used with usingnamespace to add the shared
9 | /// handler functions to other handle types.
10 | pub fn Handle(comptime T: type) type {
11 | // 1. T should be a struct
12 | // 2. First field should be the handle pointer
13 |
14 | return struct {
15 | // note: this has to be here: https://github.com/ziglang/zig/issues/11367
16 | const tInfo = @typeInfo(T).Struct;
17 | const HandleType = tInfo.fields[0].type;
18 |
19 | // Request handle to be closed. close_cb will be called asynchronously
20 | // after this call. This MUST be called on each handle before memory
21 | // is released. Moreover, the memory can only be released in close_cb
22 | // or after it has returned.
23 | //
24 | // Handles that wrap file descriptors are closed immediately but
25 | // close_cb will still be deferred to the next iteration of the event
26 | // loop. It gives you a chance to free up any resources associated with
27 | // the handle.
28 | //
29 | // In-progress requests, like uv_connect_t or uv_write_t, are cancelled
30 | // and have their callbacks called asynchronously with status=UV_ECANCELED.
31 | pub fn close(self: T, comptime cb: ?fn (*T) void) void {
32 | const cbParam = if (cb) |f|
33 | (struct {
34 | pub fn callback(handle: [*c]c.uv_handle_t) callconv(.C) void {
35 | // We get the raw handle, so we need to reconstruct
36 | // the T. This is mutable because a lot of the libuv APIs
37 | // are non-const but modifying it makes no sense.
38 | var param: T = .{ .handle = @ptrCast(HandleType, handle) };
39 | @call(.always_inline, f, .{¶m});
40 | }
41 | }).callback
42 | else
43 | null;
44 |
45 | c.uv_close(@ptrCast(*c.uv_handle_t, self.handle), cbParam);
46 | }
47 |
48 | /// Loop returns the loop that this handle is a part of.
49 | pub fn loop(self: T) Loop {
50 | const handle = @ptrCast(*c.uv_handle_t, self.handle);
51 | return .{ .loop = c.uv_handle_get_loop(handle) };
52 | }
53 |
54 | /// Returns non-zero if the handle is active, zero if it’s inactive.
55 | /// Rule of thumb: if a handle of type uv_foo_t has a uv_foo_start()
56 | /// function, then it’s active from the moment that function is called.
57 | /// Likewise, uv_foo_stop() deactivates the handle again.
58 | pub fn isActive(self: T) !bool {
59 | const res = c.uv_is_active(@ptrCast(*c.uv_handle_t, self.handle));
60 | try errors.convertError(res);
61 | return res > 0;
62 | }
63 |
64 | /// Sets handle->data to data.
65 | pub fn setData(self: T, pointer: ?*anyopaque) void {
66 | c.uv_handle_set_data(
67 | @ptrCast(*c.uv_handle_t, self.handle),
68 | pointer,
69 | );
70 | }
71 |
72 | /// Returns handle->data.
73 | pub fn getData(self: T, comptime DT: type) ?*DT {
74 | return if (c.uv_handle_get_data(@ptrCast(*c.uv_handle_t, self.handle))) |ptr|
75 | @ptrCast(?*DT, @alignCast(@alignOf(DT), ptr))
76 | else
77 | null;
78 | }
79 | };
80 | }
81 |
--------------------------------------------------------------------------------
/src/main.zig:
--------------------------------------------------------------------------------
1 | const std = @import("std");
2 | const stream = @import("stream.zig");
3 |
4 | pub const c = @import("c.zig");
5 | pub const Loop = @import("Loop.zig");
6 | pub const Async = @import("Async.zig");
7 | pub const Idle = @import("Idle.zig");
8 | pub const Pipe = @import("Pipe.zig");
9 | pub const Prepare = @import("Prepare.zig");
10 | pub const Timer = @import("Timer.zig");
11 | pub const Tty = @import("Tty.zig");
12 | pub const Cond = @import("Cond.zig");
13 | pub const Mutex = @import("Mutex.zig");
14 | pub const Sem = @import("Sem.zig");
15 | pub const Thread = @import("Thread.zig");
16 | pub const WriteReq = stream.WriteReq;
17 |
18 | pub usingnamespace @import("error.zig");
19 |
20 | test {
21 | _ = @import("tests.zig");
22 | _ = stream;
23 |
24 | _ = Loop;
25 | _ = Async;
26 | _ = Idle;
27 | _ = Prepare;
28 | _ = Pipe;
29 | _ = Timer;
30 | _ = Tty;
31 | _ = Cond;
32 | _ = Mutex;
33 | _ = Sem;
34 | _ = Thread;
35 | }
36 |
--------------------------------------------------------------------------------
/src/stream.zig:
--------------------------------------------------------------------------------
1 | const c = @import("c.zig");
2 |
3 | const std = @import("std");
4 | const Allocator = std.mem.Allocator;
5 | const testing = std.testing;
6 | const Loop = @import("Loop.zig");
7 | const errors = @import("error.zig");
8 | const Error = errors.Error;
9 |
10 | /// Returns a struct that has all the shared stream functions for the
11 | /// given stream type T. The type T must have a field named "handle".
12 | /// This is expected to be used with usingnamespace to add the shared
13 | /// stream functions to other handle types.
14 | pub fn Stream(comptime T: type) type {
15 | // 1. T should be a struct
16 | // 2. First field should be the handle pointer
17 |
18 | return struct {
19 | // note: this has to be here: https://github.com/ziglang/zig/issues/11367
20 | const tInfo = @typeInfo(T).Struct;
21 | const HandleType = tInfo.fields[0].type;
22 |
23 | /// Returns 1 if the stream is readable, 0 otherwise.
24 | pub fn isReadable(self: T) !bool {
25 | const res = c.uv_is_readable(@ptrCast(*c.uv_stream_t, self.handle));
26 | try errors.convertError(res);
27 | return res > 0;
28 | }
29 |
30 | /// Returns 1 if the stream is writable, 0 otherwise.
31 | pub fn isWritable(self: T) !bool {
32 | const res = c.uv_is_writable(@ptrCast(*c.uv_stream_t, self.handle));
33 | try errors.convertError(res);
34 | return res > 0;
35 | }
36 |
37 | /// Write data to stream. Buffers are written in order.
38 | pub fn write(
39 | self: T,
40 | req: WriteReq,
41 | bufs: []const []const u8,
42 | comptime cb: fn (req: *WriteReq, status: i32) void,
43 | ) !void {
44 | const Wrapper = struct {
45 | fn callback(cbreq: [*c]c.uv_write_t, status: c_int) callconv(.C) void {
46 | var newreq: WriteReq = .{ .req = cbreq };
47 | @call(.always_inline, cb, .{
48 | &newreq,
49 | @intCast(i32, status),
50 | });
51 | }
52 | };
53 |
54 | // We can directly ptrCast bufs.ptr to a C pointer of uv_buf_t
55 | // because they have the exact same layout in memory. We have a
56 | // unit test below that keeps this true.
57 | try errors.convertError(c.uv_write(
58 | req.req,
59 | @ptrCast(*c.uv_stream_t, self.handle),
60 | @ptrCast([*c]const c.uv_buf_t, bufs.ptr),
61 | @intCast(c_uint, bufs.len),
62 | Wrapper.callback,
63 | ));
64 | }
65 |
66 | /// Same as uv_write(), but won’t queue a write request if it can’t
67 | /// be completed immediately.
68 | pub fn tryWrite(self: T, bufs: []const []const u8) !usize {
69 | const res = c.uv_try_write(
70 | @ptrCast(*c.uv_stream_t, self.handle),
71 | @ptrCast([*c]const c.uv_buf_t, bufs.ptr),
72 | @intCast(c_uint, bufs.len),
73 | );
74 | try errors.convertError(res);
75 | return @intCast(usize, res);
76 | }
77 |
78 | /// Read data from an incoming stream. The uv_read_cb callback will
79 | /// be made several times until there is no more data to read or
80 | /// uv_read_stop() is called.
81 | pub fn readStart(
82 | self: T,
83 | comptime alloc_cb: fn (self: *T, size: usize) ?[]u8,
84 | comptime read_cb: fn (self: *T, nread: isize, buf: []const u8) void,
85 | ) !void {
86 | const Wrapper = struct {
87 | fn alloc(
88 | cbhandle: [*c]c.uv_handle_t,
89 | cbsize: usize,
90 | buf: [*c]c.uv_buf_t,
91 | ) callconv(.C) void {
92 | var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) };
93 | const result = @call(.always_inline, alloc_cb, .{
94 | ¶m,
95 | cbsize,
96 | });
97 |
98 | if (result) |slice| {
99 | buf.* = .{
100 | .base = slice.ptr,
101 | .len = slice.len,
102 | };
103 | } else {
104 | buf.* = .{ .base = null, .len = 0 };
105 | }
106 | }
107 |
108 | fn read(
109 | cbhandle: [*c]c.uv_stream_t,
110 | cbnread: isize,
111 | cbbuf: [*c]const c.uv_buf_t,
112 | ) callconv(.C) void {
113 | var param: T = .{ .handle = @ptrCast(HandleType, cbhandle) };
114 | @call(.always_inline, read_cb, .{
115 | ¶m,
116 | cbnread,
117 | cbbuf.*.base[0..cbbuf.*.len],
118 | });
119 | }
120 | };
121 |
122 | try errors.convertError(c.uv_read_start(
123 | @ptrCast(*c.uv_stream_t, self.handle),
124 | Wrapper.alloc,
125 | Wrapper.read,
126 | ));
127 | }
128 |
129 | /// Stop reading data from the stream. The uv_read_cb callback will
130 | /// no longer be called.
131 | ///
132 | /// This function is idempotent and may be safely called on a stopped
133 | /// stream.
134 | pub fn readStop(self: T) void {
135 | // Docs say we can ignore this result.
136 | _ = c.uv_read_stop(@ptrCast(*c.uv_stream_t, self.handle));
137 | }
138 | };
139 | }
140 |
141 | /// Write request type. Careful attention must be paid when reusing objects
142 | /// of this type. When a stream is in non-blocking mode, write requests sent
143 | /// with uv_write will be queued. Reusing objects at this point is undefined
144 | /// behaviour. It is safe to reuse the uv_write_t object only after the
145 | /// callback passed to uv_write is fired.
146 | pub const WriteReq = struct {
147 | /// This is the underlying type that WriteReq wraps. This is exposed
148 | /// so that you can pre-allocate the type and wrap it in a WrapReq.
149 | pub const T = c.uv_write_t;
150 |
151 | req: *T,
152 |
153 | pub fn init(alloc: Allocator) !WriteReq {
154 | var req = try alloc.create(c.uv_write_t);
155 | errdefer alloc.destroy(req);
156 | return WriteReq{ .req = req };
157 | }
158 |
159 | pub fn deinit(self: *WriteReq, alloc: Allocator) void {
160 | alloc.destroy(self.req);
161 | self.* = undefined;
162 | }
163 |
164 | /// Pointer to the stream where this write request is running.
165 | /// T should be a high-level handle type such as "Pipe".
166 | pub fn handle(self: WriteReq, comptime HT: type) ?HT {
167 | const tInfo = @typeInfo(HT).Struct;
168 | const HandleType = tInfo.fields[0].type;
169 |
170 | return if (self.req.handle) |ptr|
171 | return HT{ .handle = @ptrCast(HandleType, ptr) }
172 | else
173 | null;
174 | }
175 |
176 | test "Write: create and destroy" {
177 | var h = try init(testing.allocator);
178 | defer h.deinit(testing.allocator);
179 | }
180 | };
181 |
182 | test {
183 | _ = WriteReq;
184 | }
185 |
--------------------------------------------------------------------------------
/src/tests.zig:
--------------------------------------------------------------------------------
1 | //! This file contains other behavior tests for the libuv integration.
2 | //! We trust that libuv works, but still test some behaviors to ensure
3 | //! that our wrappers around libuv are working as expected.
4 |
5 | const std = @import("std");
6 | const testing = std.testing;
7 | const libuv = @import("main.zig");
8 |
9 | test "Async: cancel timer" {
10 | const alloc = testing.allocator;
11 | var loop = try libuv.Loop.init(alloc);
12 | defer loop.deinit(alloc);
13 |
14 | var timer = try libuv.Timer.init(alloc, loop);
15 | defer timer.deinit(alloc);
16 |
17 | // Start a timer with a long timeout. This will block our loop.
18 | try timer.start((struct {
19 | fn callback(_: *libuv.Timer) void {}
20 | }).callback, 5000, 5000);
21 |
22 | var async_handle = try libuv.Async.init(testing.allocator, loop, (struct {
23 | fn callback(v: *libuv.Async) void {
24 | v.loop().stop();
25 | v.close(null);
26 | }
27 | }).callback);
28 | defer async_handle.deinit(testing.allocator);
29 | try async_handle.send();
30 |
31 | // This run through the loop should exit because we called loop stop.
32 | _ = try loop.run(.default);
33 |
34 | // We need to run the loop one more time to handle all our close callbacks.
35 | timer.close(null);
36 | _ = try loop.run(.default);
37 | }
38 |
--------------------------------------------------------------------------------