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
8 changes: 5 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
version: [0.14.0, 0.15.1]
version: [0.14.0, 0.15.1, 0.16.0]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -27,7 +27,7 @@ jobs:
with:
xcode-version: latest-stable
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
uses: mlugg/setup-zig@v2
with:
version: ${{ matrix.version }}
- uses: actions/checkout@v4
Expand All @@ -36,10 +36,12 @@ jobs:
- name: Build examples dynamic
if: runner.os != 'Windows'
run: zig build examples -Dis_static=false
- name: Run unit tests
run: zig build test --summary all
lint:
runs-on: ubuntu-latest
steps:
- name: Setup Zig
uses: goto-bus-stop/setup-zig@v2
uses: mlugg/setup-zig@v2
- name: Verify formatting
run: zig fmt .
4 changes: 2 additions & 2 deletions .github/workflows/deploy_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0 # Not needed if lastUpdated is not enabled
- uses: goto-bus-stop/setup-zig@v2
- uses: mlugg/setup-zig@v2
with:
version: 0.15.1
version: 0.16.0
- name: remove ./src/examples
run: rm -rf ./src/examples
- name: Generate Docs
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
zig-cache
zig-out
zig-pkg
.direnv
.zig-cache
examples/comprehensive/examples
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ Like `zig build run_minimal`, this will build and run the `minimal` example.

## Installation

> note: for `0.13.0` and previous version, please use tag `2.5.0-beta.2`
> note: for `0.13.0` and previous versions, please use tag `2.5.0-beta.2`

### Zig `0.14.0` \ `0.15.1`
### Zig `0.14.0` / `0.15.1` / `0.16.0`

> To be honest, I don’t recommend using the nightly version because the API of the build system is not yet stable, which means that there may be problems with not being able to build after nightly is updated.
The same package builds against every supported stable Zig release. Nightly is
still not recommended — the build-system API can change between dev builds
and break the binding without warning.

1. Add to `build.zig.zon`

Expand Down
115 changes: 98 additions & 17 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ pub fn build(b: *Build) !void {

const flags_module = flags_options.createModule();

// Pick the tuple-synthesis helper that matches the running Zig version.
// 0.16 removed `@Type` and rejects it at parse time, so the legacy
// implementation must live in a sibling file that is never compiled
// on 0.16. See src/compat_tuple_{old,new}.zig.
const compat_tuple_path = if (current_zig.minor >= 16)
b.pathJoin(&.{ "src", "compat_tuple_new.zig" })
else
b.pathJoin(&.{ "src", "compat_tuple_old.zig" });
const compat_tuple_module = b.addModule("compat_tuple", .{
.root_source_file = b.path(compat_tuple_path),
});

const webui = b.dependency("webui", .{
.target = target,
.optimize = optimize,
Expand All @@ -53,10 +65,10 @@ pub fn build(b: *Build) !void {
});
const webui_module = b.addModule("webui", .{
.root_source_file = b.path(b.pathJoin(&.{ "src", "webui.zig" })),
.imports = &.{.{
.name = "flags",
.module = flags_module,
}},
.imports = &.{
.{ .name = "flags", .module = flags_module },
.{ .name = "compat_tuple", .module = compat_tuple_module },
},
});
webui_module.linkLibrary(webui.artifact("webui"));

Expand All @@ -78,6 +90,16 @@ pub fn build(b: *Build) !void {
.optimize = optimize,
.target = target,
.flags_module = flags_module,
.compat_tuple_module = compat_tuple_module,
});

buildTests(b, .{
.optimize = optimize,
.target = target,
.webui_module = webui_module,
.webui_artifact = webui.artifact("webui"),
.flags_module = flags_module,
.compat_tuple_module = compat_tuple_module,
});
}

Expand All @@ -94,6 +116,16 @@ const GenerateDocsOptions = struct {
optimize: OptimizeMode,
target: Build.ResolvedTarget,
flags_module: *Module,
compat_tuple_module: *Module,
};

const BuildTestsOptions = struct {
optimize: OptimizeMode,
target: Build.ResolvedTarget,
webui_module: *Module,
webui_artifact: *Compile,
flags_module: *Module,
compat_tuple_module: *Module,
};

// ========== Helper Functions ==========
Expand Down Expand Up @@ -152,6 +184,38 @@ fn createExecutable(
}
}

// ========== Tests ==========

fn buildTests(b: *Build, options: BuildTestsOptions) void {
const tests_path = b.path(b.pathJoin(&.{ "src", "tests.zig" }));

const tests = if (builtin.zig_version.minor == 14) b.addTest(.{
.name = "webui-tests",
.root_source_file = tests_path,
.target = options.target,
.optimize = options.optimize,
}) else b.addTest(.{
.name = "webui-tests",
.root_module = b.createModule(.{
.root_source_file = tests_path,
.target = options.target,
.optimize = options.optimize,
}),
});

tests.root_module.addImport("webui", options.webui_module);
tests.root_module.addImport("flags", options.flags_module);
tests.root_module.addImport("compat_tuple", options.compat_tuple_module);
// `linkLibrary` lives on Compile in 0.14/0.15 but was moved entirely to
// Module in 0.16 — go through `root_module` which exists on every
// supported version.
tests.root_module.linkLibrary(options.webui_artifact);

const run_tests = b.addRunArtifact(tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
}

// ========== Documentation Generation ==========

fn generateDocs(b: *Build, options: GenerateDocsOptions) void {
Expand All @@ -164,6 +228,7 @@ fn generateDocs(b: *Build, options: GenerateDocsOptions) void {
);

webui_lib.root_module.addImport("flags", options.flags_module);
webui_lib.root_module.addImport("compat_tuple", options.compat_tuple_module);

const docs_step = b.step("docs", "Generate docs");
const docs_install = b.addInstallDirectory(.{
Expand All @@ -182,21 +247,37 @@ fn buildExamples(b: *Build, options: BuildExamplesOptions) !void {
const build_all_step = b.step("examples", "build all examples");
const examples_path = lazy_path.getPath(b);

var examples_dir = b.build_root.handle.openDir(examples_path, .{ .iterate = true }) catch |err| {
switch (err) {
error.FileNotFound => return,
else => return err,
if (comptime builtin.zig_version.minor >= 16) {
// Zig 0.16+: build_root.handle is std.Io.Dir and requires an `io`.
const io = b.graph.io;
var examples_dir = b.build_root.handle.openDir(io, examples_path, .{ .iterate = true }) catch |err| {
switch (err) {
error.FileNotFound => return,
else => return err,
}
};
defer examples_dir.close(io);

var iter = examples_dir.iterate();
while (try iter.next(io)) |entry| {
if (entry.kind != .directory) continue;
try buildExample(b, entry.name, options, build_all_step);
}
};
defer examples_dir.close();

var iter = examples_dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind != .directory) {
continue;
} else {
// Zig 0.14/0.15: build_root.handle is std.fs.Dir.
var examples_dir = b.build_root.handle.openDir(examples_path, .{ .iterate = true }) catch |err| {
switch (err) {
error.FileNotFound => return,
else => return err,
}
};
defer examples_dir.close();

var iter = examples_dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind != .directory) continue;
try buildExample(b, entry.name, options, build_all_step);
}

try buildExample(b, entry.name, options, build_all_step);
}
}

Expand Down
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
.minimum_zig_version = "0.14.0",
.dependencies = .{
.webui = .{
.hash = "webui-2.5.0-beta.4-pxqD5a53OADNensyGsjVC_i_ksZ_G1enGSSsVD6p2dgg",
.url = "https://github.com/webui-dev/webui/archive/62bed203df456cb150bc088ed4f1b173526e9776.tar.gz",
.hash = "webui-2.5.0-beta.4-pxqD5TCWQABe1AyvdqsBEmQyFjbMMFb7yJHmMVAq_ZBZ",
.url = "https://github.com/webui-dev/webui/archive/dadf4175d6f2c4060b7a27a32e6e9e64e647116f.tar.gz",
},
},
.paths = .{
Expand Down
122 changes: 119 additions & 3 deletions examples/compat.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
//! Compatibility layer for Zig 0.15 and 0.16
//! Compatibility layer for Zig 0.14 / 0.15 / 0.16.
//!
//! Several stdlib APIs the examples rely on were renamed or removed in
//! Zig 0.16 (Writergate, `std.fs` -> `std.Io.Dir`, `GeneralPurposeAllocator`
//! -> `DebugAllocator`, etc). This module hides the version differences
//! so each example can stay short and readable.
const std = @import("std");
const builtin = @import("builtin");

Expand Down Expand Up @@ -44,8 +49,7 @@ pub fn fixedBufferStream(buffer: []u8) FixedBufferStream {
}

/// Type alias for FixedBufferStream that works in both versions
pub const FixedBufferStream = if (is_zig_0_16_or_later)
blk: {
pub const FixedBufferStream = if (is_zig_0_16_or_later) blk: {
// Zig 0.16: Define our own FixedBufferStream with custom Writer
break :blk struct {
buffer: []u8,
Expand Down Expand Up @@ -91,3 +95,115 @@ blk: {
// Zig 0.15: Use standard library type
break :blk @TypeOf(std.io.fixedBufferStream(@as([]u8, undefined)));
};

// ===== Allocator compat ======================================================

/// `std.heap.GeneralPurposeAllocator` was renamed to `std.heap.DebugAllocator`
/// in Zig 0.16. Use this alias to write code that works on all supported
/// versions.
pub const GeneralPurposeAllocator = if (is_zig_0_16_or_later)
std.heap.DebugAllocator
else
std.heap.GeneralPurposeAllocator;

// ===== Filesystem compat =====================================================
//
// `std.fs.cwd()` and the synchronous `std.fs.File` API were removed in 0.16.
// The new API lives under `std.Io.Dir` / `std.Io.File` and threads an `io`
// instance through every call. The helpers below give the examples a small,
// uniform surface that hides this difference.

/// Create directories as needed up to (and including) `path`. Equivalent to
/// `mkdir -p` on POSIX.
pub fn makePath(path: []const u8) !void {
if (comptime is_zig_0_16_or_later) {
// Zig 0.16 renamed `makePath` to `createDirPath`.
const io = ioInstance();
try std.Io.Dir.cwd().createDirPath(io, path);
} else {
try std.fs.cwd().makePath(path);
}
}

/// Create a single directory. Returns an error if `path` already exists.
pub fn makeDir(path: []const u8) !void {
if (comptime is_zig_0_16_or_later) {
// Zig 0.16 renamed `makeDir` to `createDir`; pass the platform default
// permissions so callers don't need to know the new shape.
const io = ioInstance();
try std.Io.Dir.cwd().createDir(io, path, .default_dir);
} else {
try std.fs.cwd().makeDir(path);
}
}

/// Delete a regular file relative to the current working directory.
pub fn deleteFile(path: []const u8) !void {
if (comptime is_zig_0_16_or_later) {
const io = ioInstance();
try std.Io.Dir.cwd().deleteFile(io, path);
} else {
try std.fs.cwd().deleteFile(path);
}
}

/// One-shot "create file and write everything to it" helper. Hides the
/// reader/writer plumbing differences between 0.15 and 0.16.
pub fn writeFile(path: []const u8, content: []const u8) !void {
if (comptime is_zig_0_16_or_later) {
const io = ioInstance();
var file = try std.Io.Dir.cwd().createFile(io, path, .{});
defer file.close(io);
try file.writeStreamingAll(io, content);
} else {
const file = try std.fs.cwd().createFile(path, .{});
defer file.close();
try file.writeAll(content);
}
}

/// Get a usable `std.Io` instance on 0.16. Cheap to call: returns the
/// process-global single-threaded implementation.
fn ioInstance() std.Io {
if (comptime !is_zig_0_16_or_later) @compileError("ioInstance is 0.16+ only");
return std.Io.Threaded.global_single_threaded.io();
}

// ===== Child process compat ==================================================
//
// Zig 0.16 removed `std.process.Child.init(argv, allocator)` and reworked
// child-process spawning around `std.process.spawn(io, options)`. The wrapper
// below is intentionally narrow — it exposes only what the examples need:
// spawn-with-argv, get the OS pid, and kill.

pub const ChildProcess = struct {
/// OS-level pid. Optional because 0.16 stores it as `?i32` (the value is
/// `null` after `kill`/`wait`); on 0.14/0.15 it is always populated.
pid: ?std.process.Child.Id,
child: std.process.Child,

/// Spawn a process with the given argv. `allocator` is used for argv
/// translation on 0.14/0.15; ignored on 0.16 (which routes through Io).
pub fn spawn(argv: []const []const u8, allocator: std.mem.Allocator) !ChildProcess {
if (comptime is_zig_0_16_or_later) {
const io = ioInstance();
const child = try std.process.spawn(io, .{ .argv = argv });
return .{ .pid = child.id, .child = child };
} else {
// The 0.14/0.15 Child.init signature requires an allocator; suppress
// the "unused parameter" warning by referencing it explicitly here.
var child = std.process.Child.init(argv, allocator);
try child.spawn();
return .{ .pid = child.id, .child = child };
}
}

pub fn kill(self: *ChildProcess) !void {
if (comptime is_zig_0_16_or_later) {
const io = ioInstance();
self.child.kill(io);
} else {
_ = try self.child.kill();
}
}
};
Loading
Loading