diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 328c3d322..6bcd15d22 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "vpk": { - "version": "0.0.1298", + "version": "1.2.161", "commands": [ "vpk" ], diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9a12ca8f3..16f427006 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -114,7 +114,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0" + dotnet-version: "10.0" # vpk 1.2 targets .NET 10 # Prefetch before msvcup/package so lazy deps (e.g. zigwin32 from GitHub) get retry # backoff; zig build msvcup-setup alone can hit HttpConnectionClosing with no retries. @@ -278,7 +278,7 @@ jobs: - uses: actions/setup-dotnet@v4 with: - dotnet-version: "8.0" + dotnet-version: "10.0" # vpk 1.2 targets .NET 10 - name: Fetch deps with retries shell: bash @@ -349,7 +349,13 @@ jobs: log="$RUNNER_TEMP/package-${{ matrix.channel }}.log" n=0 until [ "$n" -ge 3 ]; do - if zig build package -Doptimize=ReleaseFast "-Dtarget=${{ matrix.target }}" 2>&1 | tee "$log"; then + # Signing as build options: zig build caches its configuration without regard to the + # environment, so build.zig does not read the FIZZY_MACOS_* variables itself. + sign=() + [ -n "${FIZZY_MACOS_SIGN_APP:-}" ] && sign+=("-Dmacos-sign-app=$FIZZY_MACOS_SIGN_APP") + [ -n "${FIZZY_MACOS_SIGN_INSTALLER:-}" ] && sign+=("-Dmacos-sign-installer=$FIZZY_MACOS_SIGN_INSTALLER") + [ -n "${FIZZY_MACOS_NOTARY_PROFILE:-}" ] && sign+=("-Dmacos-notary-profile=$FIZZY_MACOS_NOTARY_PROFILE") + if zig build package -Doptimize=ReleaseFast "-Dtarget=${{ matrix.target }}" ${sign[@]+"${sign[@]}"} 2>&1 | tee "$log"; then exit 0 fi if ! grep -Eq 'HttpConnectionClosing|invalid HTTP response|TlsInitializationFailed|ConnectionResetByPeer|TemporaryNameServerFailure|unable to fetch|connection reset' "$log"; then diff --git a/app/Recents.zig b/app/Recents.zig index 07133b1cf..4b38e6b12 100644 --- a/app/Recents.zig +++ b/app/Recents.zig @@ -49,15 +49,10 @@ pub fn load(allocator: std.mem.Allocator, path: []const u8) !Recents { if (core.fs.readZ(allocator, dvui.io, path) catch null) |data| { defer allocator.free(data); - if ((zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Disk, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Disk, allocator, zig017_v) else |zig017_e| zig017_e; - }) catch null) |disk| { - defer zig017ZonFree(allocator, disk); - + var disk_arena: std.heap.ArenaAllocator = .init(allocator); + defer disk_arena.deinit(); + var disk_diag: std.zon.parse.Diagnostics = undefined; + if (std.zon.parse.fromSlice(Disk, .{ .gpa = allocator, .arena = disk_arena.allocator(), .source = data, .diagnostics = &disk_diag, .ignore_unknown_fields = true }) catch null) |disk| { for (disk.folders) |folder| { // A folder on a mount (`gdrive://…`) is not the disk's to check: it exists // whenever its plugin is signed in, which is decided at open time. @@ -172,64 +167,3 @@ pub fn deinit(recents: *Recents, allocator: std.mem.Allocator) void { recents.commands.deinit(allocator); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/app/settings/Settings.zig b/app/settings/Settings.zig index c4c7ffae3..5ede92e37 100644 --- a/app/settings/Settings.zig +++ b/app/settings/Settings.zig @@ -175,47 +175,36 @@ pub fn load(allocator: std.mem.Allocator, path: []const u8, plugins_dir: ?[]cons const data = core.fs.readZ(allocator, dvui.io, path) catch return default(allocator); defer allocator.free(data); - const parsed = parseOnly(allocator, data) catch |err| { + var arena: std.heap.ArenaAllocator = .init(allocator); + defer arena.deinit(); + const parsed = parseOnly(allocator, arena.allocator(), data) catch |err| { dvui.log.warn("Could not parse settings.zon ({s}); using defaults.", .{@errorName(err)}); return default(allocator); }; - defer freeParsed(allocator, parsed); - var result = parsed; // Own `theme` for the process lifetime (freed in `deinit`); every other field is scalar. result.theme = try allocator.dupe(u8, parsed.theme); return result; } -/// Frees a `parseOnly`/`load` result. Not `std.zon.parse.free` directly: since R12 the file only -/// records non-default fields, so a missing `theme` is filled by `std.zon.parse` with *this -/// struct's declared default* — a comptime string literal, not an allocation — and handing that -/// pointer to the allocator would be an invalid free. Zeroing it first makes the whole-struct -/// free a no-op for that field (`Allocator.free` returns early on an empty slice) while still -/// covering any other allocated field. -pub fn freeParsed(allocator: std.mem.Allocator, parsed: Settings) void { - var value = parsed; - if (value.theme.ptr == default_theme.ptr) value.theme = ""; - zig017ZonFree(allocator, value); -} - /// Parses `data` (already-read `settings.zon` bytes) into a `Settings` value, ignoring unknown /// fields (forward-compat + `.plugins` is handled separately — see `load`'s doc comment above). /// Unlike `load`, this returns the raw parse error on failure instead of falling back to /// defaults — defaulting is only correct when there's no existing live state to fall back *to* /// (startup); `Editor.reconcileExternalSettingsChange` (external hand-edit reconciliation, R11) /// has existing state and must not let a transient torn-write read reset every fizzy field and -/// every plugin's settings. Caller frees the result with `freeParsed` (not `Settings.deinit`, -/// which owns the live value's long-lived `theme`). -pub fn parseOnly(allocator: std.mem.Allocator, data: [:0]const u8) !Settings { +/// every plugin's settings. The result's strings live in `arena` (`allocator` is the parse's +/// scratch); `load` copies out the one it keeps. +pub fn parseOnly(allocator: std.mem.Allocator, arena: std.mem.Allocator, data: [:0]const u8) !Settings { @setEvalBranchQuota(10_000); - var parsed = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Settings, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Settings, allocator, zig017_v) else |zig017_e| zig017_e; + var diag: std.zon.parse.Diagnostics = undefined; + var parsed = try std.zon.parse.fromSlice(Settings, .{ + .gpa = allocator, + .arena = arena, + .source = data, + .diagnostics = &diag, + .ignore_unknown_fields = true, }); SettingsMigration.windowGlass(allocator, data, &parsed); return parsed; @@ -238,7 +227,7 @@ fn fieldEqual(comptime FT: type, a: FT, b: FT) bool { /// halves of `settings.zon` record just what the user actually changed. An untouched fizzy /// serializes to `.{}`; a field hand-written back at its default drops out on the next write. /// Reading back is unaffected: `load`/`parseOnly` fill every missing field from these same -/// defaults (see `freeParsed` for the one ownership wrinkle that creates). +/// defaults (they are the declared defaults themselves: `load` copies `theme` before keeping it). /// /// This is *not* the whole `settings.zon` file — plugin settings are a separate /// `.plugins = .{ . = .{...}, ... }` field spliced on afterward by @@ -269,64 +258,3 @@ pub fn serialize(settings: *const Settings, allocator: std.mem.Allocator) ![]u8 pub fn deinit(settings: *Settings, allocator: std.mem.Allocator) void { allocator.free(settings.theme); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/app/settings/SettingsMigration.zig b/app/settings/SettingsMigration.zig index 4592718a3..ae99eaa8f 100644 --- a/app/settings/SettingsMigration.zig +++ b/app/settings/SettingsMigration.zig @@ -75,17 +75,13 @@ pub fn migrateToPerPluginEnabled(allocator: std.mem.Allocator, settings_zon_path defer allocator.free(data); @setEvalBranchQuota(10_000); - const legacy = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(LegacyDisk, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(LegacyDisk, allocator, zig017_v) else |zig017_e| zig017_e; - }) catch |err| { + var legacy_arena: std.heap.ArenaAllocator = .init(allocator); + defer legacy_arena.deinit(); + var legacy_diag: std.zon.parse.Diagnostics = undefined; + const legacy = std.zon.parse.fromSlice(LegacyDisk, .{ .gpa = allocator, .arena = legacy_arena.allocator(), .source = data, .diagnostics = &legacy_diag, .ignore_unknown_fields = true }) catch |err| { dvui.log.warn("settings: could not parse settings.zon for R12 enabled migration ({s}); skipping", .{@errorName(err)}); return; }; - defer zig017ZonFree(allocator, legacy); const blocks = SettingsPluginsZon.listPluginBlocks(allocator, data) catch return; defer SettingsPluginsZon.freeEntries(allocator, blocks); @@ -161,11 +157,12 @@ pub fn migrateToPerPluginEnabled(allocator: std.mem.Allocator, settings_zon_path // Regenerate fizzy's own fields from a Settings parse (unknown fields — including the legacy // `disabled_plugins` list — are ignored), so the rewritten file drops that list for free. - const parsed = Settings.parseOnly(allocator, data) catch |err| { + var parsed_arena: std.heap.ArenaAllocator = .init(allocator); + defer parsed_arena.deinit(); + const parsed = Settings.parseOnly(allocator, parsed_arena.allocator(), data) catch |err| { dvui.log.warn("settings: could not re-serialize fizzy's own fields for R12 migration ({s}); skipping", .{@errorName(err)}); return; }; - defer Settings.freeParsed(allocator, parsed); const fizzy_text = Settings.serialize(&parsed, allocator) catch |err| { dvui.log.warn("settings: could not serialize fizzy's own fields for R12 migration ({s}); skipping", .{@errorName(err)}); @@ -258,14 +255,10 @@ const LegacyGlass = struct { /// dialogs' blur goes with them, as the roughness that followed it does. pub fn windowGlass(allocator: std.mem.Allocator, data: [:0]const u8, settings: *Settings) void { @setEvalBranchQuota(10_000); - const legacy = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(LegacyGlass, .{ .gpa = allocator, .arena = zig017_arena.allocator(), .source = data, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(LegacyGlass, allocator, zig017_v) else |zig017_e| zig017_e; - }) catch return; - defer zig017ZonFree(allocator, legacy); + var legacy_arena: std.heap.ArenaAllocator = .init(allocator); + defer legacy_arena.deinit(); + var legacy_diag: std.zon.parse.Diagnostics = undefined; + const legacy = std.zon.parse.fromSlice(LegacyGlass, .{ .gpa = allocator, .arena = legacy_arena.allocator(), .source = data, .diagnostics = &legacy_diag, .ignore_unknown_fields = true }) catch return; if (legacy.dialog_opacity == null and legacy.dialog_blur == null and legacy.dialog_lift == null and legacy.dialog_detail == null and legacy.dialog_refraction == null) return; const opaque_window = comptime builtin.target.cpu.arch == .wasm32 or (builtin.target.os.tag != .macos and builtin.target.os.tag != .windows); @@ -274,64 +267,3 @@ pub fn windowGlass(allocator: std.mem.Allocator, data: [:0]const u8, settings: * settings.window_opacity_light = std.math.clamp(o, 0, 1); }; } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/app/store/PluginLoader.zig b/app/store/PluginLoader.zig index fab9e8e04..6f1f12e23 100644 --- a/app/store/PluginLoader.zig +++ b/app/store/PluginLoader.zig @@ -532,8 +532,9 @@ pub fn probeName(allocator: std.mem.Allocator, path: []const u8) ?[]u8 { if (lib.lookup(dylib_api.GetManifestZonFn, dylib_api.symbol_manifest_zon)) |get_zon| { const zon = std.mem.span(get_zon()); if (zon.len > 0) { - if (sdk.Manifest.parse(allocator, zon)) |parsed| { - defer sdk.Manifest.free(allocator, parsed); + var arena: std.heap.ArenaAllocator = .init(allocator); + defer arena.deinit(); + if (sdk.Manifest.parse(allocator, arena.allocator(), zon)) |parsed| { if (parsed.name.len > 0) return allocator.dupe(u8, parsed.name) catch null; } else |_| {} } @@ -570,8 +571,9 @@ pub fn probeManifestInfo(allocator: std.mem.Allocator, path: []const u8) ?Probed const get_zon = lib.lookup(dylib_api.GetManifestZonFn, dylib_api.symbol_manifest_zon) orelse return null; const zon = std.mem.span(get_zon()); if (zon.len == 0) return null; - const parsed = sdk.Manifest.parse(allocator, zon) catch return null; - defer sdk.Manifest.free(allocator, parsed); + var arena: std.heap.ArenaAllocator = .init(allocator); + defer arena.deinit(); + const parsed = sdk.Manifest.parse(allocator, arena.allocator(), zon) catch return null; var out: ProbedManifest = .{}; errdefer freeProbedManifest(allocator, out); @@ -614,10 +616,10 @@ pub fn probeVersionInfo(path: []const u8) ?PluginVersionInfo { if (lib.lookup(dylib_api.GetManifestZonFn, dylib_api.symbol_manifest_zon)) |get_zon| { const zon = std.mem.span(get_zon()); if (zon.len > 0) { - // Stack/arena-free parse: use page allocator for a short-lived Manifest. - const gpa = std.heap.page_allocator; - if (sdk.Manifest.parse(gpa, zon)) |parsed| { - defer sdk.Manifest.free(gpa, parsed); + // A short-lived Manifest, in an arena over the page allocator. + var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + defer arena.deinit(); + if (sdk.Manifest.parse(std.heap.page_allocator, arena.allocator(), zon)) |parsed| { if (std.SemanticVersion.parse(parsed.version)) |sv| { plugin_version = .{ .major = @intCast(sv.major), .minor = @intCast(sv.minor), .patch = @intCast(sv.patch) }; } else |_| {} diff --git a/app/update/auto_update.zig b/app/update/auto_update.zig index 1163ba9a6..fe2f7420c 100644 --- a/app/update/auto_update.zig +++ b/app/update/auto_update.zig @@ -18,7 +18,7 @@ pub var hooks: Hooks = .{}; pub const impl: bool = build_opts.velopack_enabled and builtin.target.cpu.arch != .wasm32; -const Vpk = struct {}; // ZIG017: @cImport is gone; Velopack.h becomes a translate-c module +const Vpk = if (impl) @import("velopack_c") else struct {}; pub fn logVpkError(prefix: []const u8) void { if (!impl) return; diff --git a/app/update/update_install.zig b/app/update/update_install.zig index b7d9565b1..235ed8da0 100644 --- a/app/update/update_install.zig +++ b/app/update/update_install.zig @@ -19,7 +19,7 @@ const dvui = @import("dvui"); const auto_update = @import("auto_update.zig"); const build_opts = @import("build_opts"); -const Vpk = struct {}; // ZIG017: @cImport is gone +const Vpk = if (auto_update.impl) @import("velopack_c") else struct {}; const UpdateJob = @This(); diff --git a/backend/build.zig.zon b/backend/build.zig.zon index 2fdbf3fd6..5674be36a 100644 --- a/backend/build.zig.zon +++ b/backend/build.zig.zon @@ -9,8 +9,8 @@ // takes the patched tree (`-Dsdl_source`). How the patches are made and how SDL is updated: // fizzy's docs/DEPENDENCIES.md. Pinned here, by the package that uses them, and nowhere else. .sdl = .{ - .url = "https://github.com/fizzyedit/sdl_zig/archive/404f499d42156bdf428497ab08a82467c5a914c5.tar.gz", - .hash = "sdl-1.0.3+3.4.16-i4QD0XwKqgDQGV9OomCmHzkEF4hF1jVodwd7AUzla1LW", + .url = "https://github.com/fizzyedit/sdl_zig/archive/ef7eb36b0b28aeac933d1bcdc152c56654891780.tar.gz", + .hash = "sdl-1.0.3+3.4.16-i4QD0fEMqgARU41cFUViN07xvtpE9OeVQ6bDHadi1OfP", .lazy = true, }, // The release sdl_zig's own `.sdl` pins: the same tarball, fetched once. diff --git a/backend/src/Health.zig b/backend/src/Health.zig index 9b9c806cd..eeed51b86 100644 --- a/backend/src/Health.zig +++ b/backend/src/Health.zig @@ -314,14 +314,10 @@ test "a snapshot round-trips through ZON" { try std.zon.stringify.serialize(s, .{}, &out.writer); const text = try std.testing.allocator.dupeSentinel(u8, out.written(), 0); defer std.testing.allocator.free(text); - const back = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(std.testing.allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Snapshot, .{ .gpa = std.testing.allocator, .arena = zig017_arena.allocator(), .source = text, .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Snapshot, std.testing.allocator, zig017_v) else |zig017_e| zig017_e; - }); - defer zig017ZonFree(std.testing.allocator, back); + var back_arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer back_arena.deinit(); + var back_diag: std.zon.parse.Diagnostics = undefined; + const back = try std.zon.parse.fromSlice(Snapshot, .{ .gpa = std.testing.allocator, .arena = back_arena.allocator(), .source = text, .diagnostics = &back_diag }); try std.testing.expectEqual(s.presents, back.presents); try std.testing.expectEqual(s.os_windows, back.os_windows); try std.testing.expectEqual(s.ns_windows, back.ns_windows); @@ -330,64 +326,3 @@ test "a snapshot round-trips through ZON" { try std.testing.expectEqual(s.last_frame, back.last_frame); try std.testing.expectEqualStrings(s.first_sdl_error, back.first_sdl_error); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/backend/src/SDLBackend.zig b/backend/src/SDLBackend.zig index a5f5bdd3c..6e8beb16c 100644 --- a/backend/src/SDLBackend.zig +++ b/backend/src/SDLBackend.zig @@ -700,13 +700,10 @@ pub const WindowGeometry = struct { if (data.len >= nul_buf.len) return null; @memcpy(nul_buf[0..data.len], data); nul_buf[data.len] = 0; - const saved = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Saved, .{ .gpa = std.heap.page_allocator, .arena = zig017_arena.allocator(), .source = nul_buf[0..data.len :0], .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Saved, std.heap.page_allocator, zig017_v) else |zig017_e| zig017_e; - }) catch return null; + var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + defer arena.deinit(); + var diag: std.zon.parse.Diagnostics = undefined; + const saved = std.zon.parse.fromSliceNoAlloc(Saved, .{ .gpa = std.heap.page_allocator, .arena = arena.allocator(), .source = nul_buf[0..data.len :0], .diagnostics = &diag, .ignore_unknown_fields = true }) catch return null; return fromSaved(saved); } @@ -3759,40 +3756,3 @@ const live_resize_trace = struct { (dvui.Rect.Physical{ .x = 200, .y = r.h - 6, .w = r.w - 400, .h = 6 }).fill(.all(0), .{ .color = .{ .color = yellow } }); } }; - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/backend/src/platform/geometry.zig b/backend/src/platform/geometry.zig index f8fb06aa7..e40da2221 100644 --- a/backend/src/platform/geometry.zig +++ b/backend/src/platform/geometry.zig @@ -75,13 +75,10 @@ fn fileLoad(_: ?*anyopaque) ?Geometry { defer gpa.free(data); const z = gpa.dupeSentinel(u8, data, 0) catch return null; defer gpa.free(z); - const g = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Geometry, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = z, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Geometry, gpa, zig017_v) else |zig017_e| zig017_e; - }) catch return null; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var diag: std.zon.parse.Diagnostics = undefined; + const g = std.zon.parse.fromSliceNoAlloc(Geometry, .{ .gpa = gpa, .arena = arena.allocator(), .source = z, .diagnostics = &diag, .ignore_unknown_fields = true }) catch return null; return if (g.valid()) g else null; } @@ -265,40 +262,3 @@ fn trackLinux() void { if (w < 1 or h < 1) return; linux_normal = .{ .x = @floatFromInt(x), .y = @floatFromInt(y), .w = @floatFromInt(w), .h = @floatFromInt(h) }; } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/build.zig b/build.zig index 998ac705f..ab2046228 100644 --- a/build.zig +++ b/build.zig @@ -12,15 +12,13 @@ pub fn build(b: *std.Build) !void { const windows_msvc_libc_opt = b.option([]const u8, "windows-msvc-libc", "zig libc manifest for *-windows-msvc when cross-compiling; forwarded by packageall for Windows children") orelse null; const fetch_msvc_opt = b.option(bool, "fetch-msvc", "If *-windows-msvc libc is missing under .velopack-msvc/, run msvcup-setup first (downloads MSVC+SDK; requires network). Defaults to true on Windows hosts targeting *-windows-msvc.") orelse null; - // ZIG017 (release packaging PR): the FIZZY_MACOS_* fallbacks below are read at configure, and - // 0.17 caches the configuration without regard to the environment; scripts/release.sh and - // release.yml pass the -D options instead. - const macos_sign_app_identity = b.option([]const u8, "macos-sign-app", "macOS codesign identity for the app bundle (e.g. 'Developer ID Application: NAME (TEAMID)')") orelse - b.graph.environ_map.get("FIZZY_MACOS_SIGN_APP"); - const macos_sign_install_identity = b.option([]const u8, "macos-sign-installer", "macOS codesign identity for the installer pkg (e.g. 'Developer ID Installer: NAME (TEAMID)')") orelse - b.graph.environ_map.get("FIZZY_MACOS_SIGN_INSTALLER"); - const macos_notary_profile = b.option([]const u8, "macos-notary-profile", "notarytool keychain profile name (run `xcrun notarytool store-credentials ` first)") orelse - b.graph.environ_map.get("FIZZY_MACOS_NOTARY_PROFILE"); + // Options only, never the environment: Zig 0.17 caches the build's configuration without + // regard to it, so a variable read here would be seen once and then ignored. + // `scripts/release.sh` and `release.yml` turn the FIZZY_MACOS_* variables into these. + const macos_sign_app_identity = b.option([]const u8, "macos-sign-app", "macOS codesign identity for the app bundle (e.g. 'Developer ID Application: NAME (TEAMID)')"); + const macos_sign_install_identity = b.option([]const u8, "macos-sign-installer", "macOS codesign identity for the installer pkg (e.g. 'Developer ID Installer: NAME (TEAMID)')"); + const macos_notary_profile = b.option([]const u8, "macos-notary-profile", "notarytool keychain profile name (run `xcrun notarytool store-credentials ` first)"); + const macos_sdk = b.option([]const u8, "macos-sdk", "macOS SDK path for an explicit -Dtarget=*-macos (default: `xcrun --sdk macosx --show-sdk-path`)"); const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -31,6 +29,7 @@ pub fn build(b: *std.Build) !void { .macos_sign_app_identity = macos_sign_app_identity, .macos_sign_install_identity = macos_sign_install_identity, .macos_notary_profile = macos_notary_profile, + .macos_sdk = macos_sdk, // What fizzy-the-app ships in its web build beyond the built-ins. The browser cannot // load a plugin at runtime, so this is the one place a third-party plugin is named // in this repo — as build data, the way an app built on fizzy lists its own. By @@ -113,7 +112,7 @@ pub fn addDvui(fizzy_dep: *std.Build.Dependency, root_module: *std.Build.Module, const exe = @import("build/exe.zig"); const target = root_module.resolved_target orelse @panic("fizzy.addDvui: give the root module its target first"); const optimize = root_module.optimize orelse .debug; - const macos_sdl_paths = try @import("build/common.zig").macosSdlPathsForExplicitTarget(b, target); + const macos_sdl_paths = try @import("build/common.zig").macosSdlPathsForExplicitTarget(b, target, null); const native = exe.nativeDvui(b, backend, target, optimize, .off, macos_sdl_paths); root_module.addImport("dvui", native.dvui); root_module.addImport("backend", native.backend); diff --git a/build.zig.zon b/build.zig.zon index 07de075c6..77e8a680f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -39,8 +39,8 @@ // App-only. Plugins depend on `sdk/` (not this root package) so they never see // Velopack — see AGENTS.md. .velopack_zig = .{ - .url = "https://github.com/graphl-tech/velopack-zig/archive/0c2f20635a97fde38cc8000e9fb1a75f891cf37d.tar.gz", - .hash = "velopack_zig-0.0.1-LzeGcengAACU1f33uDAuLKlWXtek0KCC6i_b3XWeJMjd", + .url = "https://github.com/graphl-tech/velopack-zig/archive/3d553b7efb3b9443ac4ad2bffdf85d5ae328177e.tar.gz", + .hash = "velopack_zig-0.1.0-bbBwERkwAQBxx5LimM-B5UQrwoqltUUvrthOlyuDbscm", .lazy = true, }, .dvui_singleton_app = .{ diff --git a/build/app.zig b/build/app.zig index 753050182..8dad6a27e 100644 --- a/build/app.zig +++ b/build/app.zig @@ -46,6 +46,8 @@ pub const Options = struct { macos_sign_app_identity: ?[]const u8 = null, macos_sign_install_identity: ?[]const u8 = null, macos_notary_profile: ?[]const u8 = null, + /// `-Dmacos-sdk`: the macOS SDK for an explicit `-Dtarget=*-macos`; null asks `xcrun`. + macos_sdk: ?[]const u8 = null, }; pub fn build(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, opts: Options) !void { @@ -120,7 +122,7 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // package dependency to resolve here. Their canonical `build.zig` is only for the // standalone (`cd plugins/ && zig build`) third-party-shape build. - const macos_sdl_paths = try common.macosSdlPathsForExplicitTarget(b, target); + const macos_sdl_paths = try common.macosSdlPathsForExplicitTarget(b, target, opts.macos_sdk); const zig_out_subdir = common.zigOutSubdirForTarget(b, target); const zig_out_install_dir: std.Build.InstallDir = .{ .custom = zig_out_subdir }; @@ -142,10 +144,8 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options .fetch_if_missing = fetch_msvc, }); - var effective_win_libc: ?[]const u8 = win_libc.libc_path; - if (effective_win_libc == null) { - if (cross_win_msvc) effective_win_libc = null; // ZIG017 (release packaging PR): 0.17 has no global --libc; becomes -Dwindows-libc - } + // Only `-Dwindows-msvc-libc` or the managed `.velopack-msvc/` tree: Zig 0.17 has no global `--libc`. + const effective_win_libc: ?[]const u8 = win_libc.libc_path; // Velopack in the dev/install exe is opt-in (`-Dvelopack=true`). Release // packaging (`zig build package`) still links Velopack when the ABI supports @@ -334,8 +334,6 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. const zig_out_subdir = cfg.zig_out_subdir; const zig_out_install_dir = cfg.zig_out_install_dir; const target_is_windows_msvc = cfg.target_is_windows_msvc; - const cross_win_msvc = cfg.cross_win_msvc; - _ = cross_win_msvc; // ZIG017 (release packaging PR) const effective_win_libc = cfg.effective_win_libc; const velopack_supported_for_target = cfg.velopack_supported_for_target; const velopack_enabled = cfg.velopack_enabled; @@ -1043,7 +1041,7 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. // off for the msvc ABI regardless of host (cross or native Windows). integration_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, integration_tests, .{ .target = target, .optimize = optimize }); + try velopack.linkVelopack(b, v, integration_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); }; test_integration_step.dependOn(&b.addRunArtifact(integration_tests).step); @@ -1071,7 +1069,7 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. if (target.result.os.tag == .windows) demo_tests.root_module.linkSystemLibrary("comctl32", .{}); demo_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, demo_tests, .{ .target = target, .optimize = optimize }); + try velopack.linkVelopack(b, v, demo_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); }; test_integration_step.dependOn(&b.addRunArtifact(demo_tests).step); check_integration_step.dependOn(&demo_tests.step); @@ -1223,7 +1221,7 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. bench_replay.root_module.linkSystemLibrary("comctl32", .{}); } if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, bench_replay, .{ .target = target, .optimize = optimize }); + try velopack.linkVelopack(b, v, bench_replay, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); }; const check_bench_replay_step = b.step("check-bench-replay", "Compile fizzy bench-replay without running it"); @@ -1403,12 +1401,13 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. }; if (effective_win_libc) |ini| { - // ZIG017 (release packaging PR): no global --libc in 0.17 const libc_lp: std.Build.LazyPath = .{ .cwd_relative = ini }; velopack.applyWindowsMsvcLibcRecursive(b, roots[0..n], libc_lp); + // Absolute, and already declared to the configure cache where it was resolved + // (`velopack.resolveWindowsMsvcLibc`). const ini_exists = blk: { - b.root.root_dir.handle.access(b.graph.io, ini, .{}) catch break :blk false; + std.Io.Dir.cwd().access(b.graph.io, ini, .{}) catch break :blk false; break :blk true; }; if (ini_exists) { diff --git a/build/common.zig b/build/common.zig index a4d071ac9..763b495eb 100644 --- a/build/common.zig +++ b/build/common.zig @@ -57,14 +57,12 @@ pub const MacosSdlPaths = struct { lib: std.Build.LazyPath, }; -fn resolveMacosSdkPath(b: *std.Build) ![]const u8 { - // ZIG017 (release packaging PR): `SDKROOT` is read at configure, which 0.17 caches without - // regard to the environment; becomes a -D option. - if (b.graph.environ_map.get("SDKROOT")) |sdk| { +/// `-Dmacos-sdk`, else `xcrun`. Not `SDKROOT`: Zig 0.17 caches the build's configuration without +/// regard to the environment. +fn resolveMacosSdkPath(b: *std.Build, explicit: ?[]const u8) ![]const u8 { + if (explicit) |sdk| { const trimmed = std.mem.trim(u8, sdk, " \t\r\n"); - if (trimmed.len > 0) { - return b.dupePath(trimmed); - } + if (trimmed.len > 0) return b.dupePath(trimmed); } const argv: []const []const u8 = &.{ @@ -84,7 +82,7 @@ fn resolveMacosSdkPath(b: *std.Build) ![]const u8 { } switch (run.term) { .exited => |code| if (code != 0) { - std.log.err("SDL on macOS: explicit -Dtarget=*-macos needs an SDK path. xcrun exited with code {d}. Install Xcode Command Line Tools or set SDKROOT.", .{code}); + std.log.err("SDL on macOS: explicit -Dtarget=*-macos needs an SDK path. xcrun exited with code {d}. Install Xcode Command Line Tools or pass -Dmacos-sdk=.", .{code}); return error.MacosSdkPath; }, else => { @@ -97,15 +95,31 @@ fn resolveMacosSdkPath(b: *std.Build) ![]const u8 { return b.dupePath(path); } -pub fn macosSdlPathsForExplicitTarget(b: *std.Build, target: std.Build.ResolvedTarget) !?MacosSdlPaths { +pub fn macosSdlPathsForExplicitTarget(b: *std.Build, target: std.Build.ResolvedTarget, macos_sdk: ?[]const u8) !?MacosSdlPaths { if (target.result.os.tag != .macos) return null; if (b.graph.host.result.os.tag != .macos) return null; if (target.query.isNative()) return null; - const sdk = try resolveMacosSdkPath(b); + const sdk = try resolveMacosSdkPath(b, macos_sdk); return MacosSdlPaths{ .include = .{ .cwd_relative = b.pathJoin(&.{ sdk, "usr/include" }) }, .framework = .{ .cwd_relative = b.pathJoin(&.{ sdk, "System/Library/Frameworks" }) }, .lib = .{ .cwd_relative = b.pathJoin(&.{ sdk, "usr/lib" }) }, }; } + +/// Declare to Zig 0.17's configure cache that the build read whether `rel` exists: the file itself +/// when it does, and otherwise the nearest directory that exists, since the maker cannot load a +/// configuration that depends on a path inside a missing directory ("configuration failed: +/// FileSystemFailure"). Making the file changes that directory, so the build still reconfigures. +pub fn dependOnPathExistence(b: *std.Build, rel: []const u8) void { + b.root.access(b.graph.io, rel, .{}) catch { + var dir = std.fs.path.dirname(rel); + while (dir) |d| : (dir = std.fs.path.dirname(d)) { + b.root.access(b.graph.io, d, .{}) catch continue; + return b.dependOnDirectoryMetadata(b.path(d)); + } + return b.dependOnDirectoryMetadata(b.path(".")); + }; + b.dependOnFileMetadata(b.path(rel)); +} diff --git a/build/exe.zig b/build/exe.zig index a64bd3104..7219a3b1f 100644 --- a/build/exe.zig +++ b/build/exe.zig @@ -465,6 +465,10 @@ pub fn addFizzyExecutableForTarget( app_module.addImport("win32", win32); } exe.root_module.linkSystemLibrary("comctl32", .{}); + // MSVC's C runtime names the POSIX calls `_getcwd`, `_chdir`, …; `oldnames.lib` supplies + // the plain names, which dvui's tinyfiledialogs and fizzy's own startup use. Zig 0.16 + // linked it for every *-windows-msvc exe; 0.17 does not. + if (resolved_target.result.abi == .msvc) exe.root_module.linkSystemLibrary("oldnames", .{}); // Embed assets/windows/fizzy.rc -> fizzy.ico into the exe so Explorer, // Taskbar, Alt-Tab and the Velopack-generated Start Menu shortcut all @@ -485,7 +489,7 @@ pub fn addFizzyExecutableForTarget( resolved_target.result.abi == .msvc; exe.root_module.link_libcpp = !exe_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, exe, .{ .target = resolved_target, .optimize = optimize, .include_modules = &.{app_module} }); + try velopack.linkVelopack(b, v, exe, .{ .target = resolved_target, .optimize = optimize, .c_modules = &.{app_module} }); }; return .{ diff --git a/build/msvc.zig b/build/msvc.zig index 092250e98..d2407dedd 100644 --- a/build/msvc.zig +++ b/build/msvc.zig @@ -1,9 +1,25 @@ const std = @import("std"); -pub fn applyMsvcTranslateCShim(b: *std.Build, roots: []const *std.Build.Step.Compile) !void { - if (true) return; // ZIG017 (release packaging PR): Step.id and generated-file steps are gone in 0.17; the MSVC shim moves to where each translate-c step is made - var seen = std.AutoHashMap(*std.Build.Step.TranslateC, void).init(b.allocator); - defer seen.deinit(); +/// A C translation that some module's root comes from, for a `*-windows-msvc` target: Zig's own +/// `Step.TranslateC`, or a Run of the `translate-c` package's tool, which is how dvui translates +/// its headers on Zig 0.17. Found through the module graph: a module's generated root names the +/// step that makes it, by index, as `std.Build` itself finds it. +const Translation = union(enum) { + step: *std.Build.Step.TranslateC, + tool: *std.Build.Step.Run, + + fn ptrBits(t: Translation) u16 { + return switch (t) { + .step => |tc| tc.target.result.ptrBitWidth(), + // `--target=-windows-msvc`; every MSVC arch but x86 is 64-bit. + .tool => |run| if (std.mem.startsWith(u8, toolTarget(run).?, "x86-")) 32 else 64, + }; + } +}; + +fn msvcTranslations(b: *std.Build, roots: []const *std.Build.Step.Compile) ![]Translation { + var seen: std.AutoHashMapUnmanaged(*std.Build.Step, void) = .empty; + var out: std.ArrayList(Translation) = .empty; for (roots) |root_compile| { const graph = root_compile.root_module.getGraph(); for (graph.modules) |mod| { @@ -12,24 +28,57 @@ pub fn applyMsvcTranslateCShim(b: *std.Build, roots: []const *std.Build.Step.Com .generated => |g| g, else => continue, }; - const dep_step = b.graph.generated_files.items[@backingInt(gen.index)]; // ZIG017: guessed - if (true) continue; // ZIG017 - const tc: *std.Build.Step.TranslateC = @fieldParentPtr("step", dep_step); - const gop = try seen.getOrPut(tc); - if (gop.found_existing) continue; - const rt = tc.target.result; - if (rt.os.tag != .windows or rt.abi != .msvc) continue; - // `-I` searches before `-isystem`, so this shim wins over MSVC's . - tc.addIncludePath(b.path("src/backend/msvc_translatec_shim")); - // Pre-define SIZE_MAX so MSVC's stdint.h `#ifndef SIZE_MAX` block — which would - // otherwise install a `0xff…ui64` literal — skips itself. Belt-and-suspenders - // to the shim: covers the case where another header includes through - // a path that bypasses our shim. - tc.defineCMacro("SIZE_MAX", switch (rt.ptrBitWidth()) { - 32 => "4294967295U", - 64 => "18446744073709551615ULL", - else => "UINT_MAX", - }); + const owner = b.graph.generated_files.items[@backingInt(gen.index)]; + if ((try seen.getOrPut(b.allocator, owner)).found_existing) continue; + if (owner.cast(std.Build.Step.TranslateC)) |tc| { + const rt = tc.target.result; + if (rt.os.tag == .windows and rt.abi == .msvc) try out.append(b.allocator, .{ .step = tc }); + } else if (owner.cast(std.Build.Step.Run)) |run| { + const target = toolTarget(run) orelse continue; + if (std.mem.endsWith(u8, target, "-windows-msvc")) try out.append(b.allocator, .{ .tool = run }); + } + } + } + return out.items; +} + +/// The `--target=` a Run of the `translate-c` tool was given, or null for any other Run. +fn toolTarget(run: *std.Build.Step.Run) ?[]const u8 { + if (run.argv.items.len == 0) return null; + switch (run.argv.items[0]) { + .artifact => |a| if (!std.mem.eql(u8, a.artifact.name, "translate-c")) return null, + else => return null, + } + for (run.argv.items) |arg| switch (arg) { + .bytes => |bytes| if (std.mem.startsWith(u8, bytes, "--target=")) return bytes["--target=".len..], + else => {}, + }; + return null; +} + +pub fn applyMsvcTranslateCShim(b: *std.Build, roots: []const *std.Build.Step.Compile) !void { + const shim = b.path("src/backend/msvc_translatec_shim"); + for (try msvcTranslations(b, roots)) |t| { + // Pre-define SIZE_MAX so MSVC's stdint.h `#ifndef SIZE_MAX` block — which would + // otherwise install a `0xff…ui64` literal — skips itself. Belt-and-suspenders + // to the shim: covers the case where another header includes through + // a path that bypasses our shim. + const size_max = switch (t.ptrBits()) { + 32 => "4294967295U", + 64 => "18446744073709551615ULL", + else => "UINT_MAX", + }; + switch (t) { + .step => |tc| { + // `-I` searches before `-isystem`, so this shim wins over MSVC's . + tc.addIncludePath(shim); + tc.defineCMacro("SIZE_MAX", size_max); + }, + // The tool passes what it does not know to the C front end, after its own `--` too. + .tool => |run| { + run.addPrefixedDirectoryArg("-I", shim); + run.addArg(b.fmt("-DSIZE_MAX={s}", .{size_max})); + }, } } } @@ -44,8 +93,9 @@ pub fn applyMsvcIncludesToReachableTranslateC( roots: []const *std.Build.Step.Compile, libc_ini_path: []const u8, ) !void { - // `libc_ini_path` is absolute (resolved via `b.pathFromRoot`), so any Dir works as the base. - const data = try b.root.root_dir.handle.readFileAlloc(b.graph.io, libc_ini_path, b.allocator, .unlimited); + // `libc_ini_path` is absolute (resolved against the build root). + b.dependOnFileContents(b.graph.cwdRelativePath(libc_ini_path)); + const data = try std.Io.Dir.cwd().readFileAlloc(b.graph.io, libc_ini_path, b.allocator, .unlimited); var include_dir: ?[]const u8 = null; var sys_include_dir: ?[]const u8 = null; @@ -67,42 +117,33 @@ pub fn applyMsvcIncludesToReachableTranslateC( const shared_dir = try std.fs.path.join(b.allocator, &.{ sdk_inc_root, "shared" }); const winrt_dir = try std.fs.path.join(b.allocator, &.{ sdk_inc_root, "winrt" }); - var seen_translate_c = std.AutoHashMap(*std.Build.Step.TranslateC, void).init(b.allocator); - defer seen_translate_c.deinit(); - - for (roots) |root_compile| { - const graph = root_compile.root_module.getGraph(); - for (graph.modules) |mod| { - const root_src = mod.root_source_file orelse continue; - const gen = switch (root_src) { - .generated => |g| g, - else => continue, - }; - const dep_step = b.graph.generated_files.items[@backingInt(gen.index)]; // ZIG017: guessed - if (true) continue; // ZIG017 - - const tc: *std.Build.Step.TranslateC = @fieldParentPtr("step", dep_step); - const gop = try seen_translate_c.getOrPut(tc); - if (gop.found_existing) continue; - - const rt = tc.target.result; - if (rt.os.tag == .windows and rt.abi == .msvc) { - // `translate-c` has no API to pass `--libc `, so `-lc` makes Zig - // auto-detect a system MSVC/SDK install — which fails on a Windows host - // that has no Visual Studio (we use the .velopack-msvc/ tree instead) with - // `WindowsSdkNotFound`. Drop `-lc` here: every MSVC/UCRT/SDK include dir is - // added explicitly below, so the headers still resolve, and the consuming - // exe links libc itself — the translated bindings don't need their own. - tc.link_libc = false; - // Shim + SIZE_MAX define are applied separately by `applyMsvcTranslateCShim`. - // Order matters: MSVC's own headers first (override Windows SDK declarations - // when both exist), then UCRT, then the Windows SDK trio. - tc.addSystemIncludePath(.{ .cwd_relative = sys_include_dir.? }); - tc.addSystemIncludePath(.{ .cwd_relative = include_dir.? }); - tc.addSystemIncludePath(.{ .cwd_relative = um_dir }); - tc.addSystemIncludePath(.{ .cwd_relative = shared_dir }); - tc.addSystemIncludePath(.{ .cwd_relative = winrt_dir }); - } - } - } + for (try msvcTranslations(b, roots)) |t| switch (t) { + .step => |tc| { + // `translate-c` has no API to pass `--libc `, so `-lc` makes Zig + // auto-detect a system MSVC/SDK install — which fails on a Windows host + // that has no Visual Studio (we use the .velopack-msvc/ tree instead) with + // `WindowsSdkNotFound`. Drop `-lc` here: every MSVC/UCRT/SDK include dir is + // added explicitly below, so the headers still resolve, and the consuming + // exe links libc itself — the translated bindings don't need their own. + tc.link_libc = false; + // Shim + SIZE_MAX define are applied separately by `applyMsvcTranslateCShim`. + // Order matters: MSVC's own headers first (override Windows SDK declarations + // when both exist), then UCRT, then the Windows SDK trio. + tc.addSystemIncludePath(.{ .cwd_relative = sys_include_dir.? }); + tc.addSystemIncludePath(.{ .cwd_relative = include_dir.? }); + tc.addSystemIncludePath(.{ .cwd_relative = um_dir }); + tc.addSystemIncludePath(.{ .cwd_relative = shared_dir }); + tc.addSystemIncludePath(.{ .cwd_relative = winrt_dir }); + }, + // The `translate-c` tool takes the libc file itself and finds MSVC's include dirs in + // it. Its own flag, so it goes before the `--` that hands the rest to the C front end. + .tool => |run| { + const libc_arg: std.Build.Step.Run.Arg = .{ .bytes = b.fmt("--libc={s}", .{libc_ini_path}) }; + const dashdash = for (run.argv.items, 0..) |arg, i| switch (arg) { + .bytes => |bytes| if (std.mem.eql(u8, bytes, "--")) break i, + else => {}, + } else run.argv.items.len; + try run.argv.insert(b.graph.arena, dashdash, libc_arg); + }, + }; } diff --git a/build/package.zig b/build/package.zig index 2ace1d483..80bc23064 100644 --- a/build/package.zig +++ b/build/package.zig @@ -86,12 +86,7 @@ pub fn addSteps(opts: Options) *std.Build.Step { break :blk sh; }; - //const dotnet_tool_restore = velopack.addDotnetToolRestoreStep(b); - //const vpk_vendor_repair = velopack.addVpkVendorRepairStep(b); - //vpk_vendor_repair.step.dependOn(&dotnet_tool_restore.step); - - const vpk_pkg_sh = b.addSystemCommand(&.{"dotnet"}); - vpk_pkg_sh.addArg("vpk"); + const vpk_pkg_sh = try velopack.addVpkRun(b, vz, target); // When packaging a foreign-OS bundle, vpk needs an OS directive (e.g. `vpk [win] pack ...`) // because by default it auto-detects from the host OS. if (cross_os) { @@ -189,15 +184,11 @@ pub fn addSteps(opts: Options) *std.Build.Step { }, else => {}, } - vpk_pkg_sh.setEnvironmentVariable("DOTNET_ROLL_FORWARD", "Major"); // Stream vpk's stdout/stderr live so failures surface their actual // diagnostic instead of just an exit-code-N message from the build // runner. With `addOutputDirectoryArg` in play, `infer_from_args` // can otherwise capture+drop stdio on certain runner configs. vpk_pkg_sh.stdio = .inherit; - try velopack.attachMksquashfsToVpkRun(b, vz, vpk_pkg_sh, target); - - //vpk_pkg_sh.step.dependOn(&vpk_vendor_repair.step); vpk_pkg_sh.step.dependOn(pack_stage_tail); const build_package_install = b.addInstallDirectory(.{ @@ -252,6 +243,13 @@ pub fn addSteps(opts: Options) *std.Build.Step { } if (fetch_msvc) zig_pkg_run.addArg("-Dfetch-msvc"); } + // Signing reaches the macOS children as options: each child's own configuration is + // cached without regard to the environment. + if (std.mem.endsWith(u8, triple, "-macos")) { + if (macos_sign_app_identity) |id| zig_pkg_run.addArg(b.fmt("-Dmacos-sign-app={s}", .{id})); + if (macos_sign_install_identity) |id| zig_pkg_run.addArg(b.fmt("-Dmacos-sign-installer={s}", .{id})); + if (macos_notary_profile) |p| zig_pkg_run.addArg(b.fmt("-Dmacos-notary-profile={s}", .{p})); + } zig_pkg_run.setCwd(b.path(".")); if (prev_step) |p| { zig_pkg_run.step.dependOn(p); diff --git a/build/velopack.zig b/build/velopack.zig index 6b9a39c1b..4fbe629dc 100644 --- a/build/velopack.zig +++ b/build/velopack.zig @@ -1,8 +1,11 @@ //! Vendored Velopack build glue for the fizzy app build. //! -//! Mirrors `~/dev/velopack-zig/build.zig`; keep in sync if that repo's layout changes. +//! Mirrors graphl-tech/velopack-zig's `build.zig`; keep in sync if that repo's layout changes. +//! Vendored rather than `@import("velopack_zig")`, which would make the lazy dependency eager in +//! every consumer (AGENTS.md, "Keep the plugin build free of app-only dependencies"). const std = @import("std"); const builtin = @import("builtin"); +const common = @import("common.zig"); /// The resolved `velopack_zig` dependency (from `b.lazyDependency("velopack_zig", .{})`). /// `vz.builder` is velopack-zig's own builder — the equivalent of velopack-zig's internal @@ -10,6 +13,8 @@ const builtin = @import("builtin"); pub const Dep = *std.Build.Dependency; var trim_velopack_tool: ?*std.Build.Step.Compile = null; +var run_env_tool: ?*std.Build.Step.Compile = null; +var c_modules_given: std.AutoHashMapUnmanaged(*std.Build.Module, void) = .empty; var dotnet_restore_cache: std.AutoHashMapUnmanaged(*std.Build, *std.Build.Step.Run) = .{}; fn cachedDotnetToolRestore(b: *std.Build) *std.Build.Step.Run { @@ -38,12 +43,56 @@ fn trimVelopackLibTool(vz: Dep) *std.Build.Step.Compile { return t; } +/// velopack-zig's `tools/run_env.zig`: runs a command with environment changes applied when it +/// runs. Zig 0.17's `Run.setEnvironmentVariable` copies the whole configure-time environment into +/// the cached configuration, so a variable set later never reaches the command; and +/// `Run.addPathDir` is gone. Every command here that needs its environment changed goes through it. +fn runEnvTool(vz: Dep) *std.Build.Step.Compile { + if (run_env_tool) |t| return t; + const own = vz.builder; + const t = own.addExecutable(.{ + .name = "run-env", + .root_module = own.createModule(.{ + .root_source_file = vz.path("tools/run_env.zig"), + .target = own.graph.host, + .optimize = .debug, + }), + }); + run_env_tool = t; + return t; +} + +/// `dotnet vpk`, for `vpk pack` and the arguments the caller adds: with `DOTNET_ROLL_FORWARD` +/// (vpk targets an older .NET than is typically installed) and, for a Linux target, the bundled +/// `mksquashfs` on its PATH (for the AppImage). +pub fn addVpkRun(b: *std.Build, vz: Dep, target: std.Build.ResolvedTarget) !*std.Build.Step.Run { + const run = b.addRunArtifact(runEnvTool(vz)); + run.addArgs(&.{ "--env", "DOTNET_ROLL_FORWARD=Major" }); + if (target.result.os.tag == .linux) if (try buildMksquashfs(vz)) |m| { + run.addArg("--path"); + run.addDirectoryArg(m.bin_dir); + }; + run.addArgs(&.{ "--", "dotnet", "vpk" }); + return run; +} + +/// `Velopack.h` as a Zig module, through translate-c: Zig 0.17 removed `@cImport`. Imported as +/// `velopack_c` by the modules `linkVelopack` is given. +fn velopackCModule(b: *std.Build, vz: Dep, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { + const tc = b.addTranslateC(.{ + .root_source_file = vz.builder.dependency("velopack", .{}).path("include/Velopack.h"), + .target = target, + .optimize = optimize, + }); + return tc.createModule(); +} + pub const LinkVelopackOptions = struct { target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, - /// Other modules that `@cImport` `Velopack.h` — the `app` module's updater does, and an - /// include path on the exe's root module does not reach another module's C imports. - include_modules: []const *std.Build.Module = &.{}, + /// Modules that call Velopack's C API (the `app` module's updater does): each gets + /// `Velopack.h` as the `velopack_c` import. + c_modules: []const *std.Build.Module = &.{}, }; /// Add include path + the correct prebuilt static lib + Windows ws2_32/bcrypt + @@ -57,8 +106,11 @@ pub fn linkVelopack( const velopack_dep = vz.builder.dependency("velopack", .{}); const target = opts.target; - compile.root_module.addIncludePath(velopack_dep.path("include")); - for (opts.include_modules) |m| m.addIncludePath(velopack_dep.path("include")); + // Once per module: the integration, demo and bench tests share one `app` module. + for (opts.c_modules) |m| { + const gop = c_modules_given.getOrPut(b.allocator, m) catch @panic("OOM"); + if (!gop.found_existing) m.addImport("velopack_c", velopackCModule(b, vz, target, opts.optimize)); + } const lib_name = switch (target.result.os.tag) { .linux => switch (target.result.cpu.arch) { @@ -114,43 +166,20 @@ pub fn linkVelopack( } pub const MksquashfsBuild = struct { - step: *std.Build.Step, - bin_dir: []const u8, + /// The directory holding `mksquashfs`, resolved when the build runs. + bin_dir: std.Build.LazyPath, }; -/// Build the bundled mksquashfs for Linux AppImage packaging. Returns null on -/// non-Linux hosts and on the first build before the lazy `squashfs` dep is fetched. -pub fn buildMksquashfs(b: *std.Build, vz: Dep) !?MksquashfsBuild { +/// Build the bundled mksquashfs for Linux AppImage packaging. Returns null on Windows hosts and +/// on the first build before the lazy `squashfs` dep is fetched. +pub fn buildMksquashfs(vz: Dep) !?MksquashfsBuild { const own = vz.builder; if (own.graph.host.result.os.tag == .windows) return null; - const dep = own.lazyDependency("squashfs", .{ .target = own.graph.host, .optimize = .fast, }) orelse return null; - - const mksquashfs_art = dep.artifact("mksquashfs"); - const install_mksquashfs = b.addInstallArtifact(mksquashfs_art, .{ - .dest_dir = .{ .override = .{ .custom = "velopack-mksquashfs" } }, - }); - - return MksquashfsBuild{ - .step = &install_mksquashfs.step, - .bin_dir = "zig-out/ZIG017", - }; -} - -/// Wire the bundled mksquashfs into a `vpk pack` Run for Linux targets (no-op otherwise). -pub fn attachMksquashfsToVpkRun( - b: *std.Build, - vz: Dep, - run: *std.Build.Step.Run, - target: std.Build.ResolvedTarget, -) !void { - if (target.result.os.tag != .linux) return; - const built = (try buildMksquashfs(b, vz)) orelse return; - _ = built.bin_dir; // ZIG017 (release packaging PR): Run.addPathDir is gone in 0.17 - run.step.dependOn(built.step); + return .{ .bin_dir = dep.artifact("mksquashfs").getEmittedBin().dirname() }; } /// Install MSVC + Windows SDK into `/` and emit zig-libc-*.ini, @@ -170,24 +199,29 @@ pub fn addMsvcupSetupStep( const gen_path = vz.builder.root.joinString(b.allocator, "tools/gen_zig_libc_msvc.zig") catch |e| std.debug.panic("velopack: resolve gen_zig_libc_msvc.zig: {}", .{e}); - const run: *std.Build.Step.Run = switch (builtin.target.os.tag) { - .windows => blk: { + // Through `run_env`, not `setEnvironmentVariable` (see `runEnvTool`). + const run = b.addRunArtifact(runEnvTool(vz)); + run.addArgs(&.{ + "--env", b.fmt("VELOPACK_ZIG_ENV_FILE={s}", .{env_path}), + "--env", b.fmt("VELOPACK_ZIG_GEN_SCRIPT={s}", .{gen_path}), + "--env", b.fmt("VELOPACK_ZIG_ZIG={s}", .{b.graph.zig_exe}), + "--", + }); + switch (builtin.target.os.tag) { + .windows => { const script_path = vz.builder.root.joinString(b.allocator, "tools/setup-msvc.ps1") catch |e| std.debug.panic("velopack: resolve setup-msvc.ps1: {}", .{e}); - break :blk b.addSystemCommand(&.{ + run.addArgs(&.{ "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", script_path, resolved_install_dir, }); }, - else => blk: { + else => { const script_path = vz.builder.root.joinString(b.allocator, "tools/setup-msvc.sh") catch |e| std.debug.panic("velopack: resolve setup-msvc.sh: {}", .{e}); - break :blk b.addSystemCommand(&.{ "bash", script_path, resolved_install_dir }); + run.addArgs(&.{ "bash", script_path, resolved_install_dir }); }, - }; - run.setEnvironmentVariable("VELOPACK_ZIG_ENV_FILE", env_path); - run.setEnvironmentVariable("VELOPACK_ZIG_GEN_SCRIPT", gen_path); - run.setEnvironmentVariable("VELOPACK_ZIG_ZIG", b.graph.zig_exe); + } return run; } @@ -214,6 +248,7 @@ pub fn resolveWindowsMsvcLibc( if (opts.explicit_path) |p| { const abs = if (std.fs.path.isAbsolute(p)) b.dupePath(p) else (b.root.joinString(b.allocator, p) catch @panic("OOM")); + b.dependOnFileContents(b.graph.cwdRelativePath(abs)); return .{ .libc_path = abs, .needs_setup = false }; } @@ -224,8 +259,9 @@ pub fn resolveWindowsMsvcLibc( }; const rel = b.fmt("{s}/zig-libc-{s}.ini", .{ opts.install_dir_name, arch_suffix }); + common.dependOnPathExistence(b, rel); const exists = blk: { - b.root.root_dir.handle.access(b.graph.io, rel, .{}) catch break :blk false; + b.root.access(b.graph.io, rel, .{}) catch break :blk false; break :blk true; }; diff --git a/build/web.zig b/build/web.zig index ef7a0db8f..bf6109579 100644 --- a/build/web.zig +++ b/build/web.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const common = @import("common.zig"); const helpers = @import("../plugins/shared/build/helpers.zig"); const core_mod = @import("fizzy_sdk").core_module; const plugins = @import("plugins.zig"); @@ -234,7 +235,7 @@ pub fn addSteps( for (web_plugin_dirs) |wp| { const dir = wp.dir; const zon_path = b.pathJoin(&.{ dir, "plugin.zig.zon" }); - dependOnPathExistence(b, zon_path); + common.dependOnPathExistence(b, zon_path); b.root.access(b.graph.io, zon_path, .{}) catch { std.debug.print("fizzy web: plugin checkout '{s}' not found; the web build goes without it\n", .{dir}); continue; @@ -331,7 +332,7 @@ pub fn addSteps( for (web_pages.items) |page| { const web_dir = b.pathJoin(&.{ page.dir, "web" }); var d = b.root.openDir(b.graph.io, web_dir, .{ .iterate = true }) catch { - dependOnPathExistence(b, web_dir); + common.dependOnPathExistence(b, web_dir); continue; }; b.dependOnDirectoryContents(b.path(web_dir)); @@ -423,19 +424,3 @@ fn rehome( if (icons_mod) |icons| m.addImport("icons", icons); return m; } - -/// Declare to Zig 0.17's configure cache that the build read whether `rel` exists: the file itself -/// when it does, and otherwise the nearest directory that exists, since the maker cannot load a -/// configuration that depends on a path inside a missing directory ("configuration failed: -/// FileSystemFailure"). Making the file changes that directory, so the build still reconfigures. -fn dependOnPathExistence(b: *std.Build, rel: []const u8) void { - b.root.access(b.graph.io, rel, .{}) catch { - var dir = std.fs.path.dirname(rel); - while (dir) |d| : (dir = std.fs.path.dirname(d)) { - b.root.access(b.graph.io, d, .{}) catch continue; - return b.dependOnDirectoryMetadata(b.path(d)); - } - return b.dependOnDirectoryMetadata(b.path(".")); - }; - b.dependOnFileMetadata(b.path(rel)); -} diff --git a/core/profile.zig b/core/profile.zig index 8e414551d..60f9d28a7 100644 --- a/core/profile.zig +++ b/core/profile.zig @@ -867,13 +867,8 @@ test "a report says what each owner and scope cost, as ZON that reads back" { }; var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - const r = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena.allocator()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Report, .{ .gpa = arena.allocator(), .arena = zig017_arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Report, arena.allocator(), zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + const r = try std.zon.parse.fromSlice(Report, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); // The plugin's own time is what it spent; fizzy's own is what was left once that is taken out. try std.testing.expectEqualStrings("text", r.owners[0].owner); try std.testing.expect(r.owners[0].self_ms >= 1.5); @@ -957,40 +952,3 @@ test "a frame's GPU counts land in its own slot and the window's average, not be p.frameEnd(); try std.testing.expectEqual(@as(f64, 2), p.stats.gpu.?.targets); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/docs/PITFALLS.md b/docs/PITFALLS.md index 7f01b1c27..d56e16314 100644 --- a/docs/PITFALLS.md +++ b/docs/PITFALLS.md @@ -16,6 +16,30 @@ goes beside the code if it has an area, here if not. - **Tests run only from files their root module analyzes** (`tests/AGENTS.md`). `zig build check-tests-reached` names any that don't. +## The build (Zig 0.17) + +`build.zig` runs in a configurer, and its result is cached; a separate maker runs the steps. +Four things in that split cost time on the move to 0.17: + +- **The configuration is cached without regard to the environment.** A variable that + `build.zig` reads with `b.graph.environ_map` is seen on the first configure and then ignored + until something else forces a reconfigure. Anything that changes the build is a `-D` option. + Scripts that hold a setting in the environment (`scripts/release.sh`, `release.yml`) turn it + into the option. +- **`Run.setEnvironmentVariable` freezes the whole environment.** Its first call copies the + configure-time environment into the Run, and that copy is cached with the configuration. The + command then keeps the environment of the first configure (a `DOTNET_ROOT` exported later never + arrived). A command that needs its environment changed runs through velopack-zig's + `tools/run_env.zig` (`velopack.addVpkRun`), which applies the changes when it runs. A step + that reads the environment itself, as `sdk/plugin_sdk_install.zig` does, reads it at make time. +- **A file read while configuring must be declared**, or a change to it is never seen: + `b.dependOnFileContents`, `dependOnFileMetadata` (existence), `dependOnDirectoryContents` + (a listing). `--cache-poison=disallowed` does not catch an undeclared read. +- **A declared path must not be inside a missing directory.** The maker cannot load the + configuration ("configuration failed: FileSystemFailure"); a plugin without `.github/` hit it. + `common.dependOnPathExistence` (and its copy in `sdk/plugin_sdk.zig`) declares the nearest + directory that exists instead. + ## Running a second fizzy - **`fizzy --profile `** keeps config, recents, plugins, the single-instance lock and its diff --git a/plugins/shared/build/helpers.zig b/plugins/shared/build/helpers.zig index ba1d37c3a..5beda65ff 100644 --- a/plugins/shared/build/helpers.zig +++ b/plugins/shared/build/helpers.zig @@ -66,13 +66,9 @@ pub fn readManifestAt(b: *std.Build, zon_rel_path: []const u8) IdentityManifest version: []const u8, min_sdk_version: []const u8 = "", }; - const parsed = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(b.allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Target, .{ .gpa = b.allocator, .arena = zig017_arena.allocator(), .source = src, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Target, b.allocator, zig017_v) else |zig017_e| zig017_e; - }) catch |e| + // The build's allocator is freed only when the build ends: the manifest is read into it. + var diag: std.zon.parse.Diagnostics = undefined; + const parsed = std.zon.parse.fromSlice(Target, .{ .gpa = b.allocator, .arena = b.allocator, .source = src, .diagnostics = &diag, .ignore_unknown_fields = true }) catch |e| std.debug.panic("fizzy: failed to parse {s}: {}", .{ zon_rel_path, e }); _ = std.SemanticVersion.parse(parsed.version) catch std.debug.panic("fizzy: {s} .version \"{s}\" is not valid semver", .{ zon_rel_path, parsed.version }); @@ -280,40 +276,3 @@ pub fn installDylib(b: *std.Build, lib: *std.Build.Step.Compile, name: []const u }); b.getInstallStep().dependOn(&install_step.step); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/plugins/workbench/src/workbench_layout.zig b/plugins/workbench/src/workbench_layout.zig index 1e64ba59e..5648edffa 100644 --- a/plugins/workbench/src/workbench_layout.zig +++ b/plugins/workbench/src/workbench_layout.zig @@ -176,17 +176,13 @@ fn loadPanes(wb: *Workbench) void { defer wb.allocator.free(data); const bytes = wb.allocator.dupeSentinel(u8, data, 0) catch return; defer wb.allocator.free(bytes); - const snap = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(wb.allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Panes.Snapshot, .{ .gpa = wb.allocator, .arena = zig017_arena.allocator(), .source = bytes, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Panes.Snapshot, wb.allocator, zig017_v) else |zig017_e| zig017_e; - }) catch |err| { + var snap_arena: std.heap.ArenaAllocator = .init(wb.allocator); + defer snap_arena.deinit(); + var snap_diag: std.zon.parse.Diagnostics = undefined; + const snap = std.zon.parse.fromSlice(Panes.Snapshot, .{ .gpa = wb.allocator, .arena = snap_arena.allocator(), .source = bytes, .diagnostics = &snap_diag, .ignore_unknown_fields = true }) catch |err| { dvui.log.warn("workbench: ignoring {s}: {s}", .{ path, @errorName(err) }); return; }; - defer zig017ZonFree(wb.allocator, snap); var loaded = Panes.fromSnapshot(wb.allocator, snap) catch return; loaded.animated = true; @@ -236,64 +232,3 @@ fn panesPath(wb: *Workbench) ?[]u8 { std.Io.Dir.cwd().createDirPath(dvui.io, dir) catch return null; return std.fs.path.join(wb.allocator, &.{ dir, panes_file }) catch null; } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/scripts/release.sh b/scripts/release.sh index 13bb58bf4..e6e964d3b 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -98,8 +98,16 @@ sign_app="${FIZZY_MACOS_SIGN_APP:-}" sign_install="${FIZZY_MACOS_SIGN_INSTALLER:-}" notary_profile="${FIZZY_MACOS_NOTARY_PROFILE:-}" +# As build options, not the environment: zig build caches its configuration without regard to +# the environment, so a FIZZY_MACOS_* variable read by build.zig would be seen once and then ignored. +sign_args=() if [[ -n "$sign_app" && -n "$sign_install" && -n "$notary_profile" ]]; then echo "==> macOS signing: enabled (app=${sign_app}, installer=${sign_install}, notary=${notary_profile})" + sign_args=( + "-Dmacos-sign-app=$sign_app" + "-Dmacos-sign-installer=$sign_install" + "-Dmacos-notary-profile=$notary_profile" + ) else echo "==> macOS signing: DISABLED (set FIZZY_MACOS_SIGN_APP, FIZZY_MACOS_SIGN_INSTALLER, FIZZY_MACOS_NOTARY_PROFILE to enable)" if [[ -n "$sign_app" || -n "$sign_install" || -n "$notary_profile" ]]; then @@ -143,7 +151,7 @@ if [[ "${FIZZY_RELEASE_SKIP_BUILD:-0}" != "1" ]]; then fi echo "==> Building all targets (zig build packageall -Doptimize=ReleaseFast)" - zig build packageall -Doptimize=ReleaseFast + zig build packageall -Doptimize=ReleaseFast ${sign_args[@]+"${sign_args[@]}"} else echo "==> FIZZY_RELEASE_SKIP_BUILD=1, reusing existing zig-out/" fi diff --git a/scripts/zig-0.17/rewrite.sh b/scripts/zig-0.17/rewrite.sh index 5bbe76ff6..357f2c3fe 100755 --- a/scripts/zig-0.17/rewrite.sh +++ b/scripts/zig-0.17/rewrite.sh @@ -4,8 +4,7 @@ # rewritten too, and delete it in the PR that merges the branch. Idempotent. # # Hand migrations are not here. What is here either `zig fmt` does, or is a rename with one -# right answer. `zon_standin.pl` is the exception: a stand-in for the zon arena migration, -# marked ZIG017 wherever it writes, until that PR replaces it. +# right answer. set -euo pipefail cd "$(jj root 2>/dev/null || git rev-parse --show-toplevel)" here=scripts/zig-0.17 @@ -36,8 +35,8 @@ perl -0pi -e 's/\.dupeZ(\((?:[^()]++|(?1))*\))/my $a=$1; $a =~ s{\)\z}{, 0)}; ". python3 $here/fields.py $files >/dev/null python3 $here/unused.py $here $files >/dev/null -# Stand-in until the zon arena PR: 0.17's std.zon.parse, results in the old allocator. -perl $here/zon_standin.pl $files >/dev/null +# Not std.zon.parse: each call is ported by hand to its owner's arena. A 0.16 call that a merge +# from main brings fails to compile, which is the reminder. # Last: zig fmt's own upgrades (@intFromEnum -> @backingInt, ...). jj file list | grep -E "\.(zig|zon)$" | xargs "${zig[@]}" fmt >/dev/null # everything, spikes too diff --git a/scripts/zig-0.17/zon_standin.pl b/scripts/zig-0.17/zon_standin.pl deleted file mode 100644 index 880f80db5..000000000 --- a/scripts/zig-0.17/zon_standin.pl +++ /dev/null @@ -1,90 +0,0 @@ -# ZIG017 stand-in: 0.16 std.zon.parse calls on 0.17's API, results in the given allocator. Replaced by the zon arena PR. -use strict; use warnings; -local $/; -our $NEED_DUPE = 0; -my $DUPE = <<'ZIGDUPE'; - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -ZIGDUPE -my $FREE = <<'ZIGFREE'; -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} -ZIGFREE -for my $f (@ARGV) { - open my $fh, '<', $f or die; my $s = <$fh>; close $fh; my $o = $s; - $NEED_DUPE = 0; - $s =~ s{std\.zon\.parse\.(fromSliceAlloc|fromSlice)(\((?:[^()]++|(?2))*\))}{conv($2)}ge; - $s =~ s{std\.zon\.parse\.free\(}{zig017ZonFree(}g; - $s .= $DUPE if $NEED_DUPE && $s !~ /fn zig017ZonDupe\(/; - $s .= $FREE if $s =~ /zig017ZonFree\(/ && $s !~ /fn zig017ZonFree\(/; - if ($s ne $o) { open my $w, '>', $f or die; print $w $s; close $w; print "$f\n"; } -} -sub split_args { my ($a)=@_; $a =~ s/^\(//; $a =~ s/\)$//; my @p; my $d=0; my $cur=''; - for my $c (split //, $a) { if ($c eq ',' && $d==0) { push @p, $cur; $cur=''; next } $d++ if $c =~ /[\(\[\{]/; $d-- if $c =~ /[\)\]\}]/; $cur.=$c } push @p, $cur if $cur =~ /\S/; - s/^\s+|\s+$//g for @p; return @p } -sub conv { my ($a)=@_; my @p=split_args($a); return "std.zon.parse.fromSlice$a" unless @p==5; - my ($t,$al,$src,$diag,$opts)=@p; my $iuf = $opts =~ /ignore_unknown_fields\s*=\s*(\w+)/ ? ", .ignore_unknown_fields = $1" : ""; - if ($diag ne 'null') { - return "(zig017_zon: { break :zig017_zon std.zon.parse.fromSlice($t, .{ .gpa = $al, .arena = $al, .source = $src, .diagnostics = $diag$iuf }); })"; - } - $NEED_DUPE = 1; - return "(zig017_zon: { var zig017_zd: std.zon.parse.Diagnostics = undefined; var zig017_arena: std.heap.ArenaAllocator = .init($al); defer zig017_arena.deinit(); " - . "const zig017_r = std.zon.parse.fromSlice($t, .{ .gpa = $al, .arena = zig017_arena.allocator(), .source = $src, .diagnostics = &zig017_zd$iuf }); " - . "break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe($t, $al, zig017_v) else |zig017_e| zig017_e; })" } diff --git a/sdk/plugin_sdk.zig b/sdk/plugin_sdk.zig index 6802c6d10..6025a3d2e 100644 --- a/sdk/plugin_sdk.zig +++ b/sdk/plugin_sdk.zig @@ -111,13 +111,9 @@ fn readManifestAt(b: *std.Build, rel_path: []const u8) IdentityManifest { version: []const u8, min_sdk_version: []const u8 = "", }; - const parsed = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(b.allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Target, .{ .gpa = b.allocator, .arena = zig017_arena.allocator(), .source = src, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Target, b.allocator, zig017_v) else |zig017_e| zig017_e; - }) catch |e| + // The build's allocator is freed only when the build ends: the manifest is read into it. + var diag: std.zon.parse.Diagnostics = undefined; + const parsed = std.zon.parse.fromSlice(Target, .{ .gpa = b.allocator, .arena = b.allocator, .source = src, .diagnostics = &diag, .ignore_unknown_fields = true }) catch |e| std.debug.panic("fizzy: failed to parse {s}: {}", .{ rel_path, e }); _ = std.SemanticVersion.parse(parsed.version) catch std.debug.panic("fizzy: {s} .version \"{s}\" is not valid semver (expected e.g. 1.2.3)", .{ rel_path, parsed.version }); @@ -625,43 +621,6 @@ pub fn installBuiltinPlugin( }); } -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} - /// Declare to Zig 0.17's configure cache that the build read whether `rel` exists: the file itself /// when it does, and otherwise the nearest directory that exists, since the maker cannot load a /// configuration that depends on a path inside a missing directory ("configuration failed: diff --git a/sdk/replay/Snapshot.zig b/sdk/replay/Snapshot.zig index 9c7eb9b58..3dea87645 100644 --- a/sdk/replay/Snapshot.zig +++ b/sdk/replay/Snapshot.zig @@ -382,13 +382,8 @@ test "a snapshot names what a person acts on, and what a tape aims at" { // It reads back as what it says: two buttons by name, the tagged one by its tag, the mark. var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - const nodes = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena.allocator()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice([]const Node, .{ .gpa = arena.allocator(), .arena = zig017_arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe([]const Node, arena.allocator(), zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); var buttons: usize = 0; var save = false; var close = false; @@ -472,13 +467,8 @@ test "a snapshot has the dialogs dvui shows after its capture, and says which wi var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); defer arena.deinit(); - const nodes = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena.allocator()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice([]const Node, .{ .gpa = arena.allocator(), .arena = zig017_arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe([]const Node, arena.allocator(), zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + const nodes = try std.zon.parse.fromSlice([]const Node, .{ .gpa = std.testing.allocator, .arena = arena.allocator(), .source = try arena.allocator().dupeSentinel(u8, text, 0), .diagnostics = &diag }); // The windows first, each a node. try std.testing.expect(nodes.len > 3); for (nodes[0..3], [_][]const u8{ "app.window:main", "app.window:band", "app.window:dialog" }) |node, tag| { @@ -510,40 +500,3 @@ test "a snapshot has the dialogs dvui shows after its capture, and says which wi // Taken, nothing more is captured: a dialog drawn in a frame nobody asked for is free. try std.testing.expect(!taking()); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/sdk/src/Command.zig b/sdk/src/Command.zig index 1da84a7a0..f911790d4 100644 --- a/sdk/src/Command.zig +++ b/sdk/src/Command.zig @@ -198,9 +198,7 @@ pub fn Params(comptime T: type) type { const text = std.mem.trim(u8, call.args, " \t\r\n"); const source = try call.arena.dupeSentinel(u8, if (text.len == 0) ".{}" else text, 0); var diag: std.zon.parse.Diagnostics = undefined; - return (zig017_zon: { - break :zig017_zon std.zon.parse.fromSlice(Args, .{ .gpa = call.arena, .arena = call.arena, .source = source, .diagnostics = &diag }); - }) catch |err| switch (err) { + return std.zon.parse.fromSlice(Args, .{ .gpa = call.arena, .arena = call.arena, .source = source, .diagnostics = &diag }) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.ParseZon => { call.bad_args = true; diff --git a/sdk/src/Manifest.zig b/sdk/src/Manifest.zig index 7cf87ae29..ee31afbce 100644 --- a/sdk/src/Manifest.zig +++ b/sdk/src/Manifest.zig @@ -52,7 +52,7 @@ author_url: []const u8 = "", /// `dupeZ`/`readFileAllocOptions` with a sentinel) into a `Manifest`. Validates `version` (and /// `min_sdk_version`, when non-empty) are well-formed semver post-parse — zon itself has no /// semver type, so this is the manifest's own integrity check on top of the structural parse. -/// Free the result with `free`. +/// The result, its strings included, lives in `arena`; `gpa` is the parse's scratch. /// /// **Unknown fields are ignored on purpose**, which is what makes this format forward-compatible: /// `Manifest` is explicitly a growing set of optional fields (R16 added `description`, R17 added @@ -63,15 +63,15 @@ author_url: []const u8 = "", /// (`probeName`/`probeDescription`/`probeTags`/`probeVersionInfo`) would silently lose data it /// could otherwise still read. The tradeoff accepted here is that a misspelled field name is /// ignored rather than diagnosed; the visible symptom is a store listing missing that value. -pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8) !Manifest { - const m = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Manifest, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = source, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Manifest, gpa, zig017_v) else |zig017_e| zig017_e; +pub fn parse(gpa: std.mem.Allocator, arena: std.mem.Allocator, source: [:0]const u8) !Manifest { + var diag: std.zon.parse.Diagnostics = undefined; + const m = try std.zon.parse.fromSlice(Manifest, .{ + .gpa = gpa, + .arena = arena, + .source = source, + .diagnostics = &diag, + .ignore_unknown_fields = true, }); - errdefer free(gpa, m); _ = std.SemanticVersion.parse(m.version) catch return error.InvalidVersion; if (m.min_sdk_version.len > 0) { _ = std.SemanticVersion.parse(m.min_sdk_version) catch return error.InvalidMinSdkVersion; @@ -79,11 +79,6 @@ pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8) !Manifest { return m; } -/// Free a `Manifest` returned by `parse`. -pub fn free(gpa: std.mem.Allocator, m: Manifest) void { - zig017ZonFree(gpa, m); -} - test "Manifest parse round-trips identity" { const gpa = std.testing.allocator; const source: [:0]const u8 = @@ -95,8 +90,11 @@ test "Manifest parse round-trips identity" { \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + + defer arena.deinit(); + + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqualStrings("example", m.id); try std.testing.expectEqualStrings("Example", m.name); @@ -113,8 +111,9 @@ test "parse defaults min_sdk_version when omitted" { \\ .version = "0.1.0", \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqualStrings("", m.min_sdk_version); } @@ -128,8 +127,9 @@ test "parse defaults tags to empty when omitted" { \\ .version = "0.1.0", \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqual(@as(usize, 0), m.tags.len); } @@ -144,8 +144,9 @@ test "parse round-trips tags" { \\ .tags = .{ "editor", "pixel-art" }, \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqual(@as(usize, 2), m.tags.len); try std.testing.expectEqualStrings("editor", m.tags[0]); @@ -163,8 +164,9 @@ test "parse round-trips author and author_url" { \\ .author_url = "https://example.test/~somebody", \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqualStrings("somebody", m.author); try std.testing.expectEqualStrings("https://example.test/~somebody", m.author_url); @@ -175,8 +177,9 @@ test "parse defaults author and author_url to empty" { const source: [:0]const u8 = \\.{ .id = "example", .name = "Example", .version = "0.1.0" } ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqualStrings("", m.author); try std.testing.expectEqualStrings("", m.author_url); @@ -195,8 +198,9 @@ test "parse ignores fields a newer SDK added" { \\ .another = .{ "a", "b" }, \\} ; - const m = try parse(gpa, source); - defer free(gpa, m); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const m = try parse(gpa, arena.allocator(), source); try std.testing.expectEqualStrings("example", m.id); try std.testing.expectEqualStrings("0.1.0", m.version); @@ -211,66 +215,7 @@ test "parse rejects a non-semver version" { \\ .version = "not-a-version", \\} ; - try std.testing.expectError(error.InvalidVersion, parse(gpa, source)); -} - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + try std.testing.expectError(error.InvalidVersion, parse(gpa, arena.allocator(), source)); } diff --git a/sdk/src/events.zig b/sdk/src/events.zig index 1b66e4aaa..3bd1e270e 100644 --- a/sdk/src/events.zig +++ b/sdk/src/events.zig @@ -53,13 +53,8 @@ pub fn matches(pattern: []const u8, name: []const u8) bool { pub fn parse(comptime T: type, arena: std.mem.Allocator, event: Event) !T { const text = std.mem.trim(u8, event.payload, " \t\r\n"); const source = try arena.dupeSentinel(u8, if (text.len == 0) ".{}" else text, 0); - return (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(T, .{ .gpa = arena, .arena = zig017_arena.allocator(), .source = source, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(T, arena, zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + return std.zon.parse.fromSlice(T, .{ .gpa = arena, .arena = arena, .source = source, .diagnostics = &diag, .ignore_unknown_fields = true }); } // ---- what fizzy announces ---- @@ -145,40 +140,3 @@ test "a subscription takes its name, its prefix, or everything" { try std.testing.expect(!matches("fizzy.document.*", "fizzy.folder.opened")); try std.testing.expect(matches("*", "chat.turn_ended")); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/sdk/src/settings.zig b/sdk/src/settings.zig index 9e345b693..4a4b4f77e 100644 --- a/sdk/src/settings.zig +++ b/sdk/src/settings.zig @@ -464,16 +464,32 @@ pub fn Schema(comptime T: type) type { }; defer gpa.free(blob_z); - const parsed = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Payloads, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = blob_z, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Payloads, gpa, zig017_v) else |zig017_e| zig017_e; + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var diag: std.zon.parse.Diagnostics = undefined; + var parsed = std.zon.parse.fromSlice(Payloads, .{ + .gpa = gpa, + .arena = arena.allocator(), + .source = blob_z, + .diagnostics = &diag, + .ignore_unknown_fields = true, }) catch |err| { dvui.log.warn("sdk.settings: failed to parse settings: {s}", .{@errorName(err)}); return; }; + // The parse's strings are in `arena`. One that is not the declared default is copied + // out, so it is this schema's to free (`freeOwned`); a default stays the default. + inline for (struct_fields) |f| { + if (comptime typeTagFor(f.type.Payload) == .string) { + const parsed_str = @field(parsed, f.name); + if (parsed_str.ptr != @field(default_payloads, f.name).ptr) { + @field(parsed, f.name) = gpa.dupe(u8, parsed_str) catch |err| { + dvui.log.warn("sdk.settings: out of memory keeping settings: {s}", .{@errorName(err)}); + return; + }; + } + } + } freeOwned(gpa, out.*); fromPayloads(out, parsed); } @@ -641,16 +657,14 @@ pub fn Schema(comptime T: type) type { const P = f.type.Payload; const text_z = gpa.dupeSentinel(u8, text, 0) catch return false; defer gpa.free(text_z); - const parsed = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(P, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = text_z, .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(P, gpa, zig017_v) else |zig017_e| zig017_e; - }) catch return false; - // Strings follow the same ownership rule as `setString`; other payloads are - // plain values the parse allocated nothing for. + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var diag: std.zon.parse.Diagnostics = undefined; + var parsed = std.zon.parse.fromSlice(P, .{ .gpa = gpa, .arena = arena.allocator(), .source = text_z, .diagnostics = &diag }) catch return false; + // Strings follow the same ownership rule as `setString`: copied out of the + // parse's arena, the schema's to free. Other payloads are plain values. if (comptime typeTagFor(P) == .string) { + parsed = gpa.dupe(u8, parsed) catch return false; if (fieldIsOwned(f.name, v.*)) gpa.free(@field(v.*, f.name).v); } payloadPtr(v, f.name).* = parsed; @@ -1010,40 +1024,3 @@ test "a type with no dedicated control is still a legal setting, edited as zon t // `.other` payloads take part in diff-vs-default like anything else. try testing.expect(try S.diffSerialize(testing.allocator, .{}) == null); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/sdk/tape/Tape.zig b/sdk/tape/Tape.zig index be9ce34ed..360251170 100644 --- a/sdk/tape/Tape.zig +++ b/sdk/tape/Tape.zig @@ -449,13 +449,8 @@ pub fn load(gpa: std.mem.Allocator, bytes: []const u8, check: Check) !Owned { pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, check: Check) !Owned { var arena: std.heap.ArenaAllocator = .init(gpa); errdefer arena.deinit(); - const tape = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena.allocator()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Tape, .{ .gpa = arena.allocator(), .arena = zig017_arena.allocator(), .source = source, .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Tape, arena.allocator(), zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + const tape = try std.zon.parse.fromSlice(Tape, .{ .gpa = gpa, .arena = arena.allocator(), .source = source, .diagnostics = &diag }); try tape.validate(check); return .{ .arena = arena, .tape = tape }; } @@ -677,40 +672,3 @@ test "a hand-written tape parses with every default left out" { try testing.expect(owned.tape.ops[2].do.wait.until == .idle); try testing.expectEqual(@as(u32, 10_000), owned.tape.ops[2].do.wait.timeout); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/src/backend/backend_native.zig b/src/backend/backend_native.zig index 21d4c74bb..612eedfc2 100644 --- a/src/backend/backend_native.zig +++ b/src/backend/backend_native.zig @@ -158,8 +158,9 @@ var layout_store_dir: []const u8 = ""; fn layoutStoreLoad(_: ?*anyopaque) ?platform.geometry.Geometry { const gpa = std.heap.page_allocator; - const f = loadWindowFile(gpa, layout_store_dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const f = loadWindowFile(gpa, arena.allocator(), layout_store_dir); if (f.w < 1 or f.h < 1) return null; return .{ .x = f.x, .y = f.y, .w = f.w, .h = f.h, .state = if (f.maximized) .maximized else .normal }; } @@ -167,8 +168,9 @@ fn layoutStoreLoad(_: ?*anyopaque) ?platform.geometry.Geometry { fn layoutStoreSave(_: ?*anyopaque, g: platform.geometry.Geometry) void { // Read-modify-write: the regions and tree on disk stay as they are. const gpa = std.heap.page_allocator; - var f = loadWindowFile(gpa, layout_store_dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var f = loadWindowFile(gpa, arena.allocator(), layout_store_dir); f.x = g.x; f.y = g.y; f.w = g.w; @@ -396,27 +398,3 @@ pub fn setDynamicNativeMenuShortcut(index: usize, key: ?[]const u8, modifier_mas pub fn setSdlAppMetadata(name: [*:0]const u8, version: [*:0]const u8, identifier: [*:0]const u8) void { _ = sdl3.SDL_SetAppMetadata(name, version, identifier); } -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/src/backend/layout_file.zig b/src/backend/layout_file.zig index 3254f0c57..3192238ae 100644 --- a/src/backend/layout_file.zig +++ b/src/backend/layout_file.zig @@ -97,9 +97,8 @@ fn windowFilePath(buf: []u8, dir: []const u8, name: []const u8) ?[:0]const u8 { /// Reads every field of `window.zon`, falling back to `SavedFrame`'s own defaults for whatever /// is missing or unparseable (never null — simplifies every caller, which only cares about the /// subset of fields it owns). -/// Reads `window.zon` into `gpa`-owned memory. Free with `zig017ZonFree(gpa, frame)` — the -/// region list holds allocated names now, so the old return-by-value-and-forget will not do. -pub fn loadWindowFile(gpa: std.mem.Allocator, dir: []const u8) SavedFrame { +/// Reads `window.zon` into `arena` (the region list holds allocated names); `gpa` is scratch. +pub fn loadWindowFile(gpa: std.mem.Allocator, arena: std.mem.Allocator, dir: []const u8) SavedFrame { var path_buf: [1024]u8 = undefined; const path = windowFilePath(&path_buf, dir, layout_file) orelse return .{}; const data = readCapped(gpa, path) catch blk: { @@ -111,14 +110,13 @@ pub fn loadWindowFile(gpa: std.mem.Allocator, dir: []const u8) SavedFrame { defer gpa.free(data); const data_z = gpa.dupeSentinel(u8, data, 0) catch return .{}; defer gpa.free(data_z); - // `fromSliceAlloc`, not `fromSlice`: the region list holds allocated names, and `fromSlice` - // asserts at comptime that the result contains no pointers. - return (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(SavedFrame, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = data_z, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(SavedFrame, gpa, zig017_v) else |zig017_e| zig017_e; + var diag: std.zon.parse.Diagnostics = undefined; + return std.zon.parse.fromSlice(SavedFrame, .{ + .gpa = gpa, + .arena = arena, + .source = data_z, + .diagnostics = &diag, + .ignore_unknown_fields = true, }) catch .{}; } @@ -140,8 +138,9 @@ pub fn writeWindowFile(dir: []const u8, f: SavedFrame) void { /// Read-modify-write: keeps whatever frame geometry is on disk, replaces the region list. pub fn saveRegions(dir: []const u8, regions: []const SavedRegion) void { const gpa = std.heap.page_allocator; - var f = loadWindowFile(gpa, dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var f = loadWindowFile(gpa, arena.allocator(), dir); const keep = f.regions; f.regions = regions; writeWindowFile(dir, f); @@ -153,8 +152,9 @@ pub fn saveRegions(dir: []const u8, regions: []const SavedRegion) void { /// this, so an existing tree on disk is left alone. pub fn saveTree(dir: []const u8, tree: ?core.widgets.DockLayout.Snapshot) void { const gpa = std.heap.page_allocator; - var f = loadWindowFile(gpa, dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var f = loadWindowFile(gpa, arena.allocator(), dir); const keep = f.tree; f.tree = tree; writeWindowFile(dir, f); @@ -164,8 +164,9 @@ pub fn saveTree(dir: []const u8, tree: ?core.widgets.DockLayout.Snapshot) void { /// The saved seed-tree, rebuilt as a live `DockLayout`, or null when the file has none. /// Caller owns the result (`DockLayout.deinit`). pub fn loadTree(gpa: std.mem.Allocator, dir: []const u8) ?core.widgets.DockLayout { - const f = loadWindowFile(gpa, dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const f = loadWindowFile(gpa, arena.allocator(), dir); const snap = f.tree orelse return null; var dock = core.widgets.DockLayout.fromSnapshot(gpa, snap) catch return null; dock.animated = true; @@ -175,8 +176,9 @@ pub fn loadTree(gpa: std.mem.Allocator, dir: []const u8) ?core.widgets.DockLayou /// Every region `layout.zon` remembers, in `gpa`-owned memory. Call once at startup; free with /// `freeRegions`. pub fn loadRegions(gpa: std.mem.Allocator, dir: []const u8) []SavedRegion { - const f = loadWindowFile(gpa, dir); - defer zig017ZonFree(gpa, f); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + const f = loadWindowFile(gpa, arena.allocator(), dir); const out = gpa.alloc(SavedRegion, f.regions.len) catch return &.{}; var n: usize = 0; for (f.regions) |r| { @@ -234,64 +236,3 @@ pub fn freeRegions(gpa: std.mem.Allocator, regions: []SavedRegion) void { } gpa.free(regions); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -} diff --git a/src/editor/Editor.zig b/src/editor/Editor.zig index 9d53529e1..350fe287c 100644 --- a/src/editor/Editor.zig +++ b/src/editor/Editor.zig @@ -871,7 +871,7 @@ pub fn builtinManifest(editor: *Editor, id: []const u8) ?sdk.Manifest { if (std.mem.eql(u8, m.plugin_id, id)) { const frame_gpa = editor.app.arena.allocator(); const zon = frame_gpa.dupeSentinel(u8, m.plugin_options.manifest_zon, 0) catch return null; - return sdk.Manifest.parse(frame_gpa, zon) catch return null; + return sdk.Manifest.parse(frame_gpa, frame_gpa, zon) catch return null; } } return null; @@ -3457,11 +3457,12 @@ pub fn reconcileExternalSettingsChange(editor: *Editor) void { // plugin's settings. Left unreconciled until another external change retriggers a read // (rare in practice: the watcher's debounce already gives a normal atomic save time to // settle before this runs at all). - const parsed = Settings.parseOnly(gpa, data) catch |err| { + var parsed_arena: std.heap.ArenaAllocator = .init(gpa); + defer parsed_arena.deinit(); + const parsed = Settings.parseOnly(gpa, parsed_arena.allocator(), data) catch |err| { dvui.log.warn("settings watcher: could not parse external settings.zon change ({s}); skipping this reconciliation", .{@errorName(err)}); return; }; - defer Settings.freeParsed(gpa, parsed); // Fizzy fields, one at a time rather than `editor.app.settings = parsed` wholesale: `theme` is // runtime-owned (see its doc comment on `Settings`) and must not be clobbered by the raw diff --git a/src/editor/KeptDocuments.zig b/src/editor/KeptDocuments.zig index b4b4c7bc8..2c3196b96 100644 --- a/src/editor/KeptDocuments.zig +++ b/src/editor/KeptDocuments.zig @@ -359,13 +359,8 @@ pub fn loadSession(gpa: std.mem.Allocator, dir: []const u8) ?KeptDocuments { defer gpa.free(text); var arena: std.heap.ArenaAllocator = .init(gpa); defer arena.deinit(); - const saved = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(arena.allocator()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Saved, .{ .gpa = arena.allocator(), .arena = zig017_arena.allocator(), .source = text, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Saved, arena.allocator(), zig017_v) else |zig017_e| zig017_e; - }) catch |err| { + var diag: std.zon.parse.Diagnostics = undefined; + const saved = std.zon.parse.fromSlice(Saved, .{ .gpa = gpa, .arena = arena.allocator(), .source = text, .diagnostics = &diag, .ignore_unknown_fields = true }) catch |err| { dvui.log.warn("restart: the last session could not be read ({t}); documents open from disk", .{err}); return null; }; @@ -495,40 +490,3 @@ fn recover(editor: *Editor, path: []const u8, unsaved: []const u8) void { dvui.log.warn("reload: the unsaved contents of {s} are in {s}", .{ path, out }); dvui.toast(@src(), .{ .message = std.fmt.allocPrint(editor.app.arena.allocator(), "Unsaved changes to {s} were kept in {s}", .{ std.fs.path.basename(path), out }) catch "Unsaved changes were kept in fizzy's recovered folder." }); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/src/editor/verdict.zig b/src/editor/verdict.zig index 0e690246a..b1b697574 100644 --- a/src/editor/verdict.zig +++ b/src/editor/verdict.zig @@ -190,13 +190,10 @@ fn readExpect(gpa: std.mem.Allocator, file: []const u8) !Expect { defer gpa.free(bytes); const source = try gpa.dupeSentinel(u8, bytes, 0); defer gpa.free(source); - return (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Expect, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = source, .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Expect, gpa, zig017_v) else |zig017_e| zig017_e; - }); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var diag: std.zon.parse.Diagnostics = undefined; + return std.zon.parse.fromSliceNoAlloc(Expect, .{ .gpa = gpa, .arena = arena.allocator(), .source = source, .diagnostics = &diag }); } /// Once a displayed frame, after the app's: watch the tape start and end. True: the tape has @@ -366,40 +363,3 @@ fn over(limit: ?u32, value: u32) bool { const max = limit orelse return false; return value > max; } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} diff --git a/tests/integration.zig b/tests/integration.zig index 16872511f..d428ebe69 100644 --- a/tests/integration.zig +++ b/tests/integration.zig @@ -5162,13 +5162,10 @@ test "verdict: the soak tape is a demo, and its expectations read" { // It ends on a wait, so what closed last has time to close before the windows are counted. try std.testing.expect(owned.tape.ops[owned.tape.ops.len - 1].do == .wait); - const expect = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(std.testing.allocator); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(fizzy.verdict.Expect, .{ .gpa = std.testing.allocator, .arena = zig017_arena.allocator(), .source = @embedFile("soak_expect"), .diagnostics = &zig017_zd }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(fizzy.verdict.Expect, std.testing.allocator, zig017_v) else |zig017_e| zig017_e; - }); + var expect_arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer expect_arena.deinit(); + var expect_diag: std.zon.parse.Diagnostics = undefined; + const expect = try std.zon.parse.fromSliceNoAlloc(fizzy.verdict.Expect, .{ .gpa = std.testing.allocator, .arena = expect_arena.allocator(), .source = @embedFile("soak_expect"), .diagnostics = &expect_diag }); try std.testing.expect(!expect.allow_leaks); try std.testing.expectEqual(@as(u32, 0), expect.max_sdl_errors); try std.testing.expectEqual(@as(?u32, null), expect.os_windows); @@ -5338,14 +5335,10 @@ test "float: the saved layout brings it back, and Reset Layout takes it away" { gpa.free(regions); const text = try gpa.dupeSentinel(u8, out.written(), 0); defer gpa.free(text); - const saved = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice([]fizzy.backend.SavedRegion, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = text, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe([]fizzy.backend.SavedRegion, gpa, zig017_v) else |zig017_e| zig017_e; - }); - defer zig017ZonFree(gpa, saved); + var saved_arena: std.heap.ArenaAllocator = .init(gpa); + defer saved_arena.deinit(); + var saved_diag: std.zon.parse.Diagnostics = undefined; + const saved = try std.zon.parse.fromSlice([]fizzy.backend.SavedRegion, .{ .gpa = gpa, .arena = saved_arena.allocator(), .source = text, .diagnostics = &saved_diag, .ignore_unknown_fields = true }); editor.app.layout.resetLayout(gpa); try dvui.testing.settle(ManyPanelFrame.frame); @@ -6672,25 +6665,17 @@ const HeardEvents = struct { defer gpa.free(src); // A batch of disk changes is heard by its first path, everything else by its `.path`. const path = if (std.mem.eql(u8, e.name, "fizzy.files.changed")) files: { - const changed = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(sdk.events.FilesChanged, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = src, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(sdk.events.FilesChanged, gpa, zig017_v) else |zig017_e| zig017_e; - }) catch return; - defer zig017ZonFree(gpa, changed); + var changed_arena: std.heap.ArenaAllocator = .init(gpa); + defer changed_arena.deinit(); + var changed_diag: std.zon.parse.Diagnostics = undefined; + const changed = std.zon.parse.fromSlice(sdk.events.FilesChanged, .{ .gpa = gpa, .arena = changed_arena.allocator(), .source = src, .diagnostics = &changed_diag, .ignore_unknown_fields = true }) catch return; if (changed.events.len == 0) return; break :files gpa.dupe(u8, changed.events[0].path) catch return; } else plain: { - const payload = (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(gpa); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(sdk.events.Path, .{ .gpa = gpa, .arena = zig017_arena.allocator(), .source = src, .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(sdk.events.Path, gpa, zig017_v) else |zig017_e| zig017_e; - }) catch return; - defer zig017ZonFree(gpa, payload); + var payload_arena: std.heap.ArenaAllocator = .init(gpa); + defer payload_arena.deinit(); + var payload_diag: std.zon.parse.Diagnostics = undefined; + const payload = std.zon.parse.fromSlice(sdk.events.Path, .{ .gpa = gpa, .arena = payload_arena.allocator(), .source = src, .diagnostics = &payload_diag, .ignore_unknown_fields = true }) catch return; break :plain gpa.dupe(u8, payload.path) catch return; }; self.events.append(gpa, .{ .name = gpa.dupe(u8, e.name) catch return, .path = path }) catch gpa.free(path); @@ -6993,13 +6978,8 @@ test "headless: the whole editor comes up, opens a file, plays the tour and goes .ok => |r| r orelse return error.TestUnexpectedResult, else => return error.TestUnexpectedResult, }; - return (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(h.arena()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Read, .{ .gpa = h.arena(), .arena = zig017_arena.allocator(), .source = try h.arena().dupeSentinel(u8, zon, 0), .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Read, h.arena(), zig017_v) else |zig017_e| zig017_e; - }); + var diag: std.zon.parse.Diagnostics = undefined; + return std.zon.parse.fromSlice(Read, .{ .gpa = h.arena(), .arena = h.arena(), .source = try h.arena().dupeSentinel(u8, zon, 0), .diagnostics = &diag, .ignore_unknown_fields = true }); } }.read; @@ -7037,13 +7017,8 @@ test "headless: the whole editor comes up, opens a file, plays the tour and goes try std.testing.expectEqualStrings(notes, front.owner.documentPath(front)); const Caret = struct { caret: struct { line: u32, column: u32 } }; const out = host.callCommand("text.read", notes_args, host.arena()).ok.?; - const at = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(host.arena()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Caret, .{ .gpa = host.arena(), .arena = zig017_arena.allocator(), .source = try host.arena().dupeSentinel(u8, out, 0), .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Caret, host.arena(), zig017_v) else |zig017_e| zig017_e; - }); + var at_diag: std.zon.parse.Diagnostics = undefined; + const at = try std.zon.parse.fromSlice(Caret, .{ .gpa = host.arena(), .arena = host.arena(), .source = try host.arena().dupeSentinel(u8, out, 0), .diagnostics = &at_diag, .ignore_unknown_fields = true }); try std.testing.expectEqual(@as(u32, 3), at.caret.line); try std.testing.expectEqual(@as(u32, 4), at.caret.column); } @@ -7271,13 +7246,8 @@ test "headless: the whole editor comes up, opens a file, plays the tour and goes // has passed on screen, which a few test frames are not. profile: struct { frame: struct { fps: f64 } }, }; - const stats = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(host.arena()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Stats, .{ .gpa = host.arena(), .arena = zig017_arena.allocator(), .source = try host.arena().dupeSentinel(u8, zon, 0), .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Stats, host.arena(), zig017_v) else |zig017_e| zig017_e; - }); + var stats_diag: std.zon.parse.Diagnostics = undefined; + const stats = try std.zon.parse.fromSlice(Stats, .{ .gpa = host.arena(), .arena = host.arena(), .source = try host.arena().dupeSentinel(u8, zon, 0), .diagnostics = &stats_diag, .ignore_unknown_fields = true }); try std.testing.expect(for (stats.plugins) |pl| { if (std.mem.eql(u8, pl.id, "devtools")) break pl.link == .bundled; } else false); @@ -7297,13 +7267,8 @@ test "headless: the whole editor comes up, opens a file, plays the tour and goes else => return error.TestUnexpectedResult, }; const Written = struct { path: []const u8, frames: u64 }; - const written = try (zig017_zon: { - var zig017_zd: std.zon.parse.Diagnostics = undefined; - var zig017_arena: std.heap.ArenaAllocator = .init(host.arena()); - defer zig017_arena.deinit(); - const zig017_r = std.zon.parse.fromSlice(Written, .{ .gpa = host.arena(), .arena = zig017_arena.allocator(), .source = try host.arena().dupeSentinel(u8, zon, 0), .diagnostics = &zig017_zd, .ignore_unknown_fields = true }); - break :zig017_zon if (zig017_r) |zig017_v| zig017ZonDupe(Written, host.arena(), zig017_v) else |zig017_e| zig017_e; - }); + var written_diag: std.zon.parse.Diagnostics = undefined; + const written = try std.zon.parse.fromSlice(Written, .{ .gpa = host.arena(), .arena = host.arena(), .source = try host.arena().dupeSentinel(u8, zon, 0), .diagnostics = &written_diag, .ignore_unknown_fields = true }); try std.testing.expect(written.frames > 0); const input_path = try std.fs.path.join(host.arena(), &.{ written.path, "input.bin" }); const bytes = try std.Io.Dir.cwd().readFileAlloc(dvui.io, input_path, host.arena(), .unlimited); @@ -7923,64 +7888,3 @@ test "a live tape drags a floating window by its header" { std.debug.print("float x {d:.1} (from 300) after {d} frames\n", .{ live_float.x, frames }); try std.testing.expectApproxEqAbs(@as(f32, 150), live_float.x, 2); } - -// ZIG017 stand-in: a deep copy of a zon result out of the parse's arena into the caller's -// allocator, where the code before 0.17 expects to free it. Goes with the zon arena PR. -fn zig017ZonDupe(comptime T: type, gpa: std.mem.Allocator, value: T) error{OutOfMemory}!T { - switch (@typeInfo(T)) { - .pointer => |p| switch (p.size) { - .one => { - const out = try gpa.create(p.child); - out.* = try zig017ZonDupe(p.child, gpa, value.*); - return out; - }, - .slice => { - const out = if (comptime p.sentinel()) |s| try gpa.allocSentinel(p.child, value.len, s) else try gpa.alloc(p.child, value.len); - for (out, value) |*o, v| o.* = try zig017ZonDupe(p.child, gpa, v); - return out; - }, - else => comptime unreachable, - }, - .array => |a| { - var out: T = undefined; - for (&out, value) |*o, v| o.* = try zig017ZonDupe(a.child, gpa, v); - return out; - }, - .@"struct" => |s| { - var out: T = value; - inline for (s.field_names, s.field_types, s.field_attrs) |n, FT, attrs| { - if (!attrs.@"comptime") @field(out, n) = try zig017ZonDupe(FT, gpa, @field(value, n)); - } - return out; - }, - .@"union" => |u| if (u.tag_type == null) return value else switch (value) { - inline else => |payload, tag| return @unionInit(T, @tagName(tag), try zig017ZonDupe(@TypeOf(payload), gpa, payload)), - }, - .optional => |o| return if (value) |v| try zig017ZonDupe(o.child, gpa, v) else null, - else => return value, - } -} -// ZIG017 stand-in: 0.16's `std.zon.parse.free`, which 0.17 removed (results now come from an -// arena the caller owns). Goes with the zon arena PR. -fn zig017ZonFree(gpa: std.mem.Allocator, value: anytype) void { - switch (@typeInfo(@TypeOf(value))) { - .pointer => |p| switch (p.size) { - .one => { - zig017ZonFree(gpa, value.*); - gpa.destroy(value); - }, - .slice => { - for (value) |item| zig017ZonFree(gpa, item); - gpa.free(value); - }, - else => comptime unreachable, - }, - .array => for (value) |item| zig017ZonFree(gpa, item), - .@"struct" => |s| inline for (s.field_names) |n| zig017ZonFree(gpa, @field(value, n)), - .@"union" => |u| if (u.tag_type != null) switch (value) { - inline else => |payload| zig017ZonFree(gpa, payload), - }, - .optional => if (value) |some| zig017ZonFree(gpa, some), - else => {}, - } -}