Skip to content
Open
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
6 changes: 3 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 @@ -40,6 +40,6 @@ jobs:
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
65 changes: 48 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,7 @@ pub fn build(b: *Build) !void {
.optimize = optimize,
.target = target,
.flags_module = flags_module,
.compat_tuple_module = compat_tuple_module,
});
}

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

// ========== Helper Functions ==========
Expand Down Expand Up @@ -164,6 +178,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 +197,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
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();
}
}
};
19 changes: 5 additions & 14 deletions examples/comprehensive/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub fn main() !void {
main_window.setRuntime(.NodeJS);

// Create public directory
std.fs.cwd().makeDir("examples/comprehensive/public") catch {};
compat.makeDir("examples/comprehensive/public") catch {};

// Show window
try main_window.show(html);
Expand Down Expand Up @@ -388,7 +388,7 @@ fn uploadFile(e: *webui.Event, filename: [:0]const u8, content: [:0]const u8) vo
}

// Ensure public directory exists
std.fs.cwd().makePath("examples/comprehensive/public") catch |err| {
compat.makePath("examples/comprehensive/public") catch |err| {
std.debug.print("Warning: Failed to create public directory: {}\n", .{err});
// Continue anyway, maybe directory already exists
};
Expand Down Expand Up @@ -458,19 +458,10 @@ fn uploadFile(e: *webui.Event, filename: [:0]const u8, content: [:0]const u8) vo

std.debug.print("Creating file at: {s}\n", .{file_path});

// Create file
const file = std.fs.cwd().createFile(file_path, .{}) catch |err| {
std.debug.print("Failed to create file {s}: {}\n", .{ file_path, err });
result = std.fmt.bufPrintZ(response[0..], "Error: Failed to create file '{s}' ({s})", .{ filename, @errorName(err) }) catch "Error";
e.returnString(result);
return;
};
defer file.close();

// Write content to file
file.writeAll(content) catch |err| {
std.debug.print("Failed to write to file {s}: {}\n", .{ file_path, err });
result = std.fmt.bufPrintZ(response[0..], "Error: Failed to write to file '{s}' ({s})", .{ filename, @errorName(err) }) catch "Error";
compat.writeFile(file_path, content) catch |err| {
std.debug.print("Failed to write file {s}: {}\n", .{ file_path, err });
result = std.fmt.bufPrintZ(response[0..], "Error: Failed to write file '{s}' ({s})", .{ filename, @errorName(err) }) catch "Error";
e.returnString(result);
return;
};
Expand Down
13 changes: 7 additions & 6 deletions examples/custom_spa_server_on_free_port/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Note: if you want to run this example, you nedd a python, zig will wrap a child process to launch python server
const std = @import("std");
const webui = @import("webui");
const compat = @import("compat");

var python_server_proc: std.process.Child = undefined;
var python_server_proc: compat.ChildProcess = undefined;
var python_running: bool = false;

var home_url: [:0]u8 = undefined;
Expand Down Expand Up @@ -38,10 +39,9 @@ pub fn main() !void {
const port_argument1: []u8 = try std.fmt.bufPrintZ(&buf1, "{d}", .{backend_port});
const port_argument2: []u8 = try std.fmt.bufPrintZ(&buf2, "{d}", .{webui_port});
const argv = [_][]const u8{ "python", "./free_port_web_server.py", port_argument1, port_argument2 };
python_server_proc = std.process.Child.init(&argv, std.heap.page_allocator);

// start the SPA web server:
startPythonWebServer();
startPythonWebServer(&argv);

// Show a new window served by our custom web server (spawned above):
var buf: [64]u8 = undefined;
Expand All @@ -58,11 +58,12 @@ pub fn main() !void {
killPythonWebServer();
}

fn startPythonWebServer() void {
fn startPythonWebServer(argv: []const []const u8) void {
if (python_running == false) { // a better check would be a test for the process itself
if (python_server_proc.spawn()) |_| {
if (compat.ChildProcess.spawn(argv, std.heap.page_allocator)) |child| {
python_server_proc = child;
python_running = true;
std.debug.print("Spawned python server process PID={}\n", .{python_server_proc.id});
std.debug.print("Spawned python server process PID={?}\n", .{python_server_proc.pid});
} else |err| {
std.debug.print("NOT Starting python server: {}\n", .{err});
}
Expand Down
Loading
Loading