diff --git a/build.zig b/build.zig index 7d1d47f83..60b6ac9fc 100644 --- a/build.zig +++ b/build.zig @@ -169,7 +169,9 @@ pub const Platform = @import("build/sdk.zig").Platform; /// const hello = b.dependency("hello", .{}); /// try fizzy.buildApp(fizzy_dep, &.{ /// fizzy.plugins.workbench, -/// .{ .name = "hello", .module = @import("hello").staticModule(hello.builder, fizzy.framework(fizzy_dep, .native)) }, +/// .{ .name = "hello", .module = @import("hello").staticModule( +/// hello.builder, fizzy.framework(fizzy_dep, .native), +/// ) }, /// }); /// ``` pub fn framework(fizzy_dep: *std.Build.Dependency, platform: Platform) Framework { @@ -179,11 +181,25 @@ pub fn framework(fizzy_dep: *std.Build.Dependency, platform: Platform) Framework switch (platform) { .native => { const fw = d.frameworks.getNative(b, d.target, d.optimize, d.cfg); - return .{ .target = d.target, .optimize = d.optimize, .dvui = fw.native.dvui, .core = fw.core, .sdk = fw.sdk, .icons = fw.icons }; + return .{ + .target = d.target, + .optimize = d.optimize, + .dvui = fw.native.dvui, + .core = fw.core, + .sdk = fw.sdk, + .icons = fw.icons, + }; }, .web => { const fw = d.frameworks.getWeb(b, d.optimize); - return .{ .target = @import("build/web.zig").target(b), .optimize = d.optimize, .dvui = fw.dvui, .core = fw.core, .sdk = fw.sdk, .icons = fw.icons }; + return .{ + .target = @import("build/web.zig").target(b), + .optimize = d.optimize, + .dvui = fw.dvui, + .core = fw.core, + .sdk = fw.sdk, + .icons = fw.icons, + }; }, } } @@ -244,17 +260,29 @@ pub const App = struct { /// const app = try fizzy.addApp(fizzy_dep, &.{ /// fizzy.plugins.workbench, /// .{ .name = "hello", .targets = &.{ -/// .{ .target = .native, .module = hello.staticModule(hello_dep.builder, fizzy.framework(fizzy_dep, .native)) }, -/// .{ .target = .web, .module = hello.staticModule(hello_dep.builder, fizzy.framework(fizzy_dep, .web)) }, +/// .{ .target = .native, .module = hello.staticModule( +/// hello_dep.builder, fizzy.framework(fizzy_dep, .native), +/// ) }, +/// .{ .target = .web, .module = hello.staticModule( +/// hello_dep.builder, fizzy.framework(fizzy_dep, .web), +/// ) }, /// } }, -/// .{ .name = "shell", .module = shell_dep.module("plugin"), .targets = &.{.{ .target = .native }} }, +/// .{ .name = "shell", .module = shell_dep.module("plugin"), +/// .targets = &.{.{ .target = .native }} }, /// }); /// ``` pub fn addApp(fizzy_dep: *std.Build.Dependency, linked: []const BundledPlugin) !App { const d = deferred orelse @panic("fizzy.addApp: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) first"); var opts = d.base; opts.app_plugins = try withFizzys(fizzy_dep.builder, linked); - const built = try @import("build/app.zig").construct(fizzy_dep.builder, d.target, d.optimize, opts, d.cfg, &deferred.?.frameworks); + const built = try @import("build/app.zig").construct( + fizzy_dep.builder, + d.target, + d.optimize, + opts, + d.cfg, + &deferred.?.frameworks, + ); deferred.?.built = true; return .{ .exe = built.native.main.exe, @@ -354,7 +382,8 @@ pub fn addWebSteps(b: *std.Build, fizzy_dep: *std.Build.Dependency) !void { std.log.err("fizzy.addWebSteps: this build already has a `web` step", .{}); return error.WebStepTaken; } - const d = deferred orelse @panic("fizzy.addWebSteps: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\", …) and call fizzy.buildApp first"); + const d = deferred orelse @panic("fizzy.addWebSteps: pass `.@\"defer-app\" = true` to b.dependency(\"fizzy\"," ++ + " …) and call fizzy.buildApp first"); if (!d.built) @panic("fizzy.addWebSteps: call fizzy.buildApp first"); b.step("web", "Build the web (wasm) app into zig-out/web/").dependOn(&b.addInstallDirectory(.{ .source_dir = fizzy_dep.namedLazyPath("web"), diff --git a/build/app.zig b/build/app.zig index bc774487a..814a182ab 100644 --- a/build/app.zig +++ b/build/app.zig @@ -167,12 +167,36 @@ pub const Own = struct { /// and a plist vpk writes. `-Dapp-windows-rc` / `-Dapp-macos-plist` override either. fn readBranding(b: *std.Build, id: Identity, own: ?Own, renamed: bool) Branding { const LazyPath = std.Build.LazyPath; - const png_opt = b.option(LazyPath, "app-icon", "PNG icon: the window's, the infobar's and the Linux package's (default: fizzy's)"); - const ico_opt = b.option(LazyPath, "app-icon-ico", "Windows .ico: the installer's, the Start Menu shortcut's, and the generated resource script's (default: fizzy's)"); + const png_opt = b.option( + LazyPath, + "app-icon", + "PNG icon: the window's, the infobar's and the Linux package's (default: fizzy's)", + ); + const ico_opt = b.option( + LazyPath, + "app-icon-ico", + "Windows .ico: the installer's, the Start Menu shortcut's, and the generated" ++ + " resource script's (default: fizzy's)", + ); const icns_opt = b.option(LazyPath, "app-icon-icns", "macOS .icns: the bundle's icon (default: fizzy's)"); - const rc_opt = b.option(LazyPath, "app-windows-rc", "Windows resource script embedded in the exe (default: fizzy's for fizzy, else one generated from the icon, names and version)"); - const plist_opt = b.option(LazyPath, "app-macos-plist", "macOS Info.plist (default: fizzy's for fizzy, else one vpk writes from app-bundle-id)"); - const package_title = b.option([]const u8, "app-package-title", "The installers' name for the app: the macOS bundle, the Windows shortcut (default: app-name)") orelse id.name; + const rc_opt = b.option( + LazyPath, + "app-windows-rc", + "Windows resource script embedded in the exe (default: fizzy's for fizzy, " ++ + "else one generated from the icon, names and version)", + ); + const plist_opt = b.option( + LazyPath, + "app-macos-plist", + "macOS Info.plist (default: fizzy's for fizzy, else one vpk writes from " ++ + "app-bundle-id)", + ); + const package_title = b.option( + []const u8, + "app-package-title", + "The installers' name for the app: the macOS bundle, the Windows shortcut " ++ + "(default: app-name)", + ) orelse id.name; const own_set = if (renamed or png_opt != null or ico_opt != null or icns_opt != null) null else own; if (own_set) |o| return .{ .png = b.path(o.png), @@ -200,7 +224,11 @@ fn readBranding(b: *std.Build, id: Identity, own: ?Own, renamed: bool) Branding fn windowsRc(b: *std.Build, id: Identity, ico: std.Build.LazyPath) std.Build.LazyPath { // `v1.2.3-dev+5` → 1,2,3,0: up to four numeric parts, anything else 0. var parts = [4]u16{ 0, 0, 0, 0 }; - const core = std.mem.trimStart(u8, id.version[0 .. std.mem.findAny(u8, id.version, "-+") orelse id.version.len], "vV"); + const core = std.mem.trimStart( + u8, + id.version[0 .. std.mem.findAny(u8, id.version, "-+") orelse id.version.len], + "vV", + ); var it = std.mem.splitScalar(u8, core, '.'); for (&parts) |*part| part.* = std.fmt.parseInt(u16, it.next() orelse break, 10) catch 0; // An rc string literal holds no control characters (they are dropped), doubles its quotes @@ -297,7 +325,8 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options .fetch_if_missing = fetch_msvc, }); - // Only `-Dwindows-msvc-libc` or the managed `.velopack-msvc/` tree: Zig 0.17 has no global `--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 @@ -309,12 +338,14 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const velopack_enabled = b.option( bool, "velopack", - "Link Velopack runtime in the install/run exe (auto-update). Default: false. `package` still produces a Velopack-linked binary when supported.", + "Link Velopack runtime in the install/run exe (auto-update). Default: false." ++ + " `package` still produces a Velopack-linked binary when supported.", ) orelse false; if (velopack_enabled and !velopack_supported_for_target) { std.log.err( - "-Dvelopack=true is unsupported for target ABI {s}: Velopack on Windows requires -Dtarget=x86_64-windows-msvc or -Dtarget=aarch64-windows-msvc.", + "-Dvelopack=true is unsupported for target ABI {s}: Velopack on Windows " ++ + "requires -Dtarget=x86_64-windows-msvc or -Dtarget=aarch64-windows-msvc.", .{@tagName(target.result.abi)}, ); return error.WindowsMsvcAbiRequired; @@ -339,14 +370,24 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const no_emit = b.option(bool, "no-emit", "Check for compile errors without emitting any code") orelse false; - const app_version_opt = b.option([]const u8, "app_version", "App version for vpk packVersion and startup log (default: fizzy's VERSION file for fizzy, else 0.0.0)"); + const app_version_opt = b.option( + []const u8, + "app_version", + "App version for vpk packVersion and startup log (default: fizzy's VERSION " ++ + "file for fizzy, else 0.0.0)", + ); // GitHub repo URL baked into the binary so Velopack's auto-update can find // the latest release via the GitHub Releases API. Override at build time // with `-Drepo-url=...` (e.g. when shipping a fork). At runtime, the env // var `FIZZY_AUTOUPDATE_URL` still overrides this for local feed testing. // An app built on fizzy has no feed unless it names one: fizzy's releases are not its updates. - const repo_url_opt = b.option([]const u8, "repo-url", "GitHub repo URL used by Velopack auto-update (default: fizzy's for fizzy, else none)"); + const repo_url_opt = b.option( + []const u8, + "repo-url", + "GitHub repo URL used by Velopack auto-update (default: fizzy's for fizzy, " ++ + "else none)", + ); const app_repo_url = repo_url_opt orelse if (opts.own) |own| own.repo_url else ""; // Comma-separated fallback repo URLs checked (in order) after `app_repo_url` @@ -354,7 +395,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // whose primary points at the new home and whose fallback points at the old // one (where the transitional release is published), then transfer the repo. // Empty by default (no fallback). - const app_repo_url_fallback = b.option([]const u8, "repo-url-fallback", "Comma-separated fallback GitHub repo URLs for Velopack auto-update, tried after -Drepo-url") orelse ""; + const app_repo_url_fallback = b.option( + []const u8, + "repo-url-fallback", + "Comma-separated fallback GitHub repo URLs for Velopack auto-update, tried " ++ + "after -Drepo-url", + ) orelse ""; var version_owned: ?[]u8 = null; // lives as long as the build process @@ -362,7 +408,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // package's, so its VERSION file is out of reach here), else 0.0.0. const app_version: []const u8 = if (app_version_opt) |v| v else if (opts.own) |own| blk: { b.dependOnFileContents(b.path(own.version_file)); - const raw = std.Io.Dir.cwd().readFileAlloc(b.graph.io, b.root.joinString(b.allocator, own.version_file) catch @panic("OOM"), b.allocator, std.Io.Limit.limited(256)) catch |e| std.debug.panic("read VERSION: {}", .{e}); + const raw = std.Io.Dir.cwd().readFileAlloc( + b.graph.io, + b.root.joinString(b.allocator, own.version_file) catch @panic("OOM"), + b.allocator, + std.Io.Limit.limited(256), + ) catch |e| std.debug.panic("read VERSION: {}", .{e}); version_owned = raw; break :blk std.mem.trimEnd(u8, raw, "\r\n"); } else "0.0.0"; @@ -372,16 +423,41 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // Application identity (see app/AppInfo.zig). Options rather than literals so an app built // on fizzy as a library can set them; fizzy passes its own values. - const app_name_opt = b.option([]const u8, "app-name", "Short lowercase app identifier (exe name, packId, config dir)"); + const app_name_opt = b.option( + []const u8, + "app-name", + "Short lowercase app identifier (exe name, packId, config dir)", + ); const app_name = app_name_opt orelse "fizzy"; const app_display_name = b.option([]const u8, "app-display-name", "Human-facing application name") orelse "Fizzy"; // An app that names no bundle id gets one of its own, not fizzy's: `dev.fizzy.`, // with anything a bundle id cannot hold made `-`. - const app_bundle_id = b.option([]const u8, "app-bundle-id", "Reverse-DNS application identifier (default: fizzy's for fizzy, else dev.fizzy.)") orelse - if (opts.own) |own| own.bundle_id else b.fmt("dev.fizzy.{s}", .{@import("bundle_id.zig").component(b.allocator, app_name)}); - const app_config_dir = b.option([]const u8, "app-config-dir", "Config directory name (defaults to app-name)") orelse app_name; - const app_registry_url = b.option([]const u8, "app-registry-url", "Plugin registry catalog URL; empty disables the store") orelse "https://plugins.fizzyed.it/catalog"; - const branding = readBranding(b, .{ .name = app_name, .display_name = app_display_name, .version = app_version }, opts.own, app_name_opt != null); + const app_bundle_id = b.option( + []const u8, + "app-bundle-id", + "Reverse-DNS application identifier (default: fizzy's for fizzy, else " ++ + "dev.fizzy.)", + ) orelse + if (opts.own) |own| own.bundle_id else b.fmt( + "dev.fizzy.{s}", + .{@import("bundle_id.zig").component(b.allocator, app_name)}, + ); + const app_config_dir = b.option( + []const u8, + "app-config-dir", + "Config directory name (defaults to app-name)", + ) orelse app_name; + const app_registry_url = b.option( + []const u8, + "app-registry-url", + "Plugin registry catalog URL; empty disables the store", + ) orelse "https://plugins.fizzyed.it/catalog"; + const branding = readBranding( + b, + .{ .name = app_name, .display_name = app_display_name, .version = app_version }, + opts.own, + app_name_opt != null, + ); build_opts.addOption([]const u8, "app_name", app_name); build_opts.addOption([]const u8, "app_display_name", app_display_name); build_opts.addOption([]const u8, "app_bundle_id", app_bundle_id); @@ -395,7 +471,12 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const app_is_fizzy = opts.own != null; // Where Report a Bug goes. Not `repo-url` as such, which is the update feed and defaults to // fizzy's: an app reports to its own repo only when it named one (`issues_url.zig`). - const app_issues_url = b.option([]const u8, "app-issues-url", "Where Report a Bug goes; empty disables it (default: an explicit repo-url's issues, fizzy's for fizzy)") orelse + const app_issues_url = b.option( + []const u8, + "app-issues-url", + "Where Report a Bug goes; empty disables it (default: an explicit repo-url's" ++ + " issues, fizzy's for fizzy)", + ) orelse try @import("issues_url.zig").derive(b.allocator, repo_url_opt, app_is_fizzy); const app_repo_url_set = repo_url_opt != null and repo_url_opt.?.len > 0; build_opts.addOption(bool, "app_is_fizzy", app_is_fizzy); @@ -405,13 +486,22 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // Off, the app runs only the plugins it links (`buildApp`'s list): no plugin dylib is built, // and nothing loads at runtime — not fizzy's own dylibs, not the user plugins directory, not // the store, not a web page's `?plugin=`. Comptime, so none of it is checked per frame. - const dynamic_plugins = b.option(bool, "dynamic-plugins", "Load plugins at runtime: fizzy's own dylibs, the user plugins directory, the store (default: true)") orelse true; + const dynamic_plugins = b.option( + bool, + "dynamic-plugins", + "Load plugins at runtime: fizzy's own dylibs, the user plugins directory, " ++ + "the store (default: true)", + ) orelse true; build_opts.addOption(bool, "dynamic_plugins", dynamic_plugins); // A consumer that wants its own shape passes `-Dapp-layout=` (a LazyPath to // `pub fn layout(?*anyopaque, *Layout)`). Fizzy itself uses `src/editor/layout.zig`. // There is no `-Dlayout=` enum of shipped presets: the shapes are fizzyedit/example-app's. - const app_layout_path = b.option(std.Build.LazyPath, "app-layout", "App-owned layout file (pub fn layout(?*anyopaque, *Layout))"); + const app_layout_path = b.option( + std.Build.LazyPath, + "app-layout", + "App-owned layout file (pub fn layout(?*anyopaque, *Layout))", + ); build_opts.addOption(bool, "has_app_layout", app_layout_path != null); const static_workbench = b.option( bool, @@ -439,8 +529,15 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const workbench_opts = b.addOptions(); workbench_opts.addOption(bool, "file_tree", workbench_file_tree); - const msvcup_before_compile: ?*std.Build.Step.Run = if (vz) |v| velopack.addMsvcupSetupStep(b, v, ".velopack-msvc") else null; - const msvcup_setup_step = b.step("msvcup-setup", "Download MSVC SDK into .velopack-msvc/ via velopack-zig (writes zig-libc-*.ini)"); + const msvcup_before_compile: ?*std.Build.Step.Run = if (vz) |v| velopack.addMsvcupSetupStep( + b, + v, + ".velopack-msvc", + ) else null; + const msvcup_setup_step = b.step( + "msvcup-setup", + "Download MSVC SDK into .velopack-msvc/ via velopack-zig (writes zig-libc-*.ini)", + ); if (msvcup_before_compile) |m| msvcup_setup_step.dependOn(&m.step); const accesskit = b.option(dvui.AccesskitOptions, "accesskit", "Enable accesskit") orelse .off; @@ -448,7 +545,8 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options const native_backend = b.option( fizzy_exe.NativeBackend, "native-backend", - "Native renderer: fizzy (fizzy's own SDL_GPU backend, custom programs) or sdl3 (dvui's SDL_Renderer backend). Default: fizzy", + "Native renderer: fizzy (fizzy's own SDL_GPU backend, custom programs) or " ++ + "sdl3 (dvui's SDL_Renderer backend). Default: fizzy", ) orelse fizzy_exe.defaultNativeBackend(target); const test_filters = b.option( @@ -459,8 +557,16 @@ pub fn readConfig(b: *std.Build, target: std.Build.ResolvedTarget, opts: Options // What an app's installers carry besides the executable and the plugin dylibs (a helper // program it starts, say), and what it tells vpk besides fizzy's own arguments. - const package_dirs = b.option([]const std.Build.LazyPath, "package-dirs", "Directories whose files `package` ships beside the executable") orelse &.{}; - const vpk_args = b.option([]const []const u8, "vpk-args", "Arguments `package` passes `vpk pack` after its own") orelse &.{}; + const package_dirs = b.option( + []const std.Build.LazyPath, + "package-dirs", + "Directories whose files `package` ships beside the executable", + ) orelse &.{}; + const vpk_args = b.option( + []const []const u8, + "vpk-args", + "Arguments `package` passes `vpk pack` after its own", + ) orelse &.{}; return .{ .vz = vz, .macos_sdl_paths = macos_sdl_paths, @@ -513,7 +619,14 @@ pub const Built = struct { /// Phase two of `build`: the executables, the web build, tests, packaging. `frameworks` holds any /// an app asked for first (`fizzy.framework`), and gets the rest. -pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, opts: Options, cfg: Config, frameworks: *Frameworks) !Built { +pub fn construct( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + opts: Options, + cfg: Config, + frameworks: *Frameworks, +) !Built { const assetpack = @import("assetpack"); const assets_module = assetpack.pack(b, b.path("assets"), .{}); // The window's icon, apart from `assets` so an app built on fizzy can give its own. @@ -528,10 +641,33 @@ pub fn construct(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std. , .{cfg.branding.pixel_art})), }); - // `zig build web` produces `zig-out/web/{web.wasm, web.js, index.html, NotoSansKR-Regular.ttf}`, + // `zig build web` produces `zig-out/web/{web.wasm, web.js, index.html, + // NotoSansKR-Regular.ttf}`, // deployable as-is to a static host. - const w = web.build(b, frameworks.getWeb(b, optimize), optimize, cfg.build_opts, cfg.workbench_opts, assets_module, app_icon_module, cfg.app_layout_path, try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .web), opts.web_plugin_deps, opts.web_plugin_dirs, opts.own != null); - const built = try native(b, target, optimize, opts, cfg, frameworks.getNative(b, target, optimize, cfg), assets_module, app_icon_module); + const w = web.build( + b, + frameworks.getWeb(b, optimize), + optimize, + cfg.build_opts, + cfg.workbench_opts, + assets_module, + app_icon_module, + cfg.app_layout_path, + try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .web), + opts.web_plugin_deps, + opts.web_plugin_dirs, + opts.own != null, + ); + const built = try native( + b, + target, + optimize, + opts, + cfg, + frameworks.getNative(b, target, optimize, cfg), + assets_module, + app_icon_module, + ); install(b, target, optimize, cfg, w, built); try addTests(b, target, optimize, cfg, built, assets_module, app_icon_module); return .{ .native = built, .web = w }; @@ -544,8 +680,21 @@ pub const Frameworks = struct { native: ?NativeFramework = null, web: ?web.Framework = null, - pub fn getNative(self: *Frameworks, b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config) NativeFramework { - if (self.native == null) self.native = @import("exe.zig").nativeFramework(b, target, optimize, cfg.accesskit, cfg.native_backend, cfg.macos_sdl_paths); + pub fn getNative( + self: *Frameworks, + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + ) NativeFramework { + if (self.native == null) self.native = @import("exe.zig").nativeFramework( + b, + target, + optimize, + cfg.accesskit, + cfg.native_backend, + cfg.macos_sdl_paths, + ); return self.native.?; } @@ -562,7 +711,16 @@ pub const Native = struct { package: FizzyExecutable, }; -fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, opts: Options, cfg: Config, framework: NativeFramework, assets_module: *std.Build.Module, app_icon_module: *std.Build.Module) !Native { +fn native( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + opts: Options, + cfg: Config, + framework: NativeFramework, + assets_module: *std.Build.Module, + app_icon_module: *std.Build.Module, +) !Native { const fizzy_exe = @import("exe.zig"); const vz = cfg.vz; const macos_sdl_paths = cfg.macos_sdl_paths; @@ -586,7 +744,25 @@ fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.Op const native_backend = cfg.native_backend; const app_plugins = try @import("sdk.zig").pluginsFor(b, opts.app_plugins, .native); - const main_fizzy = try fizzy_exe.addFizzyExecutableForTarget(b, vz, target, optimize, framework, native_backend, build_opts, workbench_opts, assets_module, app_icon_module, cfg.branding.rc, macos_sdl_paths, velopack_enabled, app_name, app_layout_path, app_plugins, dynamic_plugins); + const main_fizzy = try fizzy_exe.addFizzyExecutableForTarget( + b, + vz, + target, + optimize, + framework, + native_backend, + build_opts, + workbench_opts, + assets_module, + app_icon_module, + cfg.branding.rc, + macos_sdl_paths, + velopack_enabled, + app_name, + app_layout_path, + app_plugins, + dynamic_plugins, + ); const package_fizzy: FizzyExecutable = package_blk: { if (velopack_enabled) break :package_blk main_fizzy; @@ -612,14 +788,40 @@ fn native(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.Op pack_opts.addOption(bool, "static_image", static_image); pack_opts.addOption(bool, "dynamic_plugins", dynamic_plugins); pack_opts.addOption(bool, "has_app_layout", app_layout_path != null); - break :package_blk try fizzy_exe.addFizzyExecutableForTarget(b, vz, target, optimize, framework, native_backend, pack_opts, workbench_opts, assets_module, app_icon_module, cfg.branding.rc, macos_sdl_paths, true, app_name, app_layout_path, app_plugins, dynamic_plugins); + break :package_blk try fizzy_exe.addFizzyExecutableForTarget( + b, + vz, + target, + optimize, + framework, + native_backend, + pack_opts, + workbench_opts, + assets_module, + app_icon_module, + cfg.branding.rc, + macos_sdl_paths, + true, + app_name, + app_layout_path, + app_plugins, + dynamic_plugins, + ); }; return .{ .main = main_fizzy, .package = package_fizzy }; } -/// The steps over what `web.build` and `native` made: the installs, `run`, the dylib steps, packaging. -fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config, w: web.Web, built: Native) void { +/// The steps over what `web.build` and `native` made: the installs, `run`, the dylib steps, +/// packaging. +fn install( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + w: web.Web, + built: Native, +) void { const common = @import("common.zig"); const package = @import("package.zig"); const vz = cfg.vz; @@ -640,12 +842,19 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O const plugins_install_dir: std.Build.InstallDir = .{ .custom = b.fmt("{s}/plugins", .{zig_out_subdir}) }; if (no_emit) { b.getInstallStep().dependOn(&exe.step); - for (built.main.dylibs) |dylib| common.attachBuiltinPluginInstall(b, b.getInstallStep(), dylib.compile, dylib.id, plugins_install_dir); + for (built.main.dylibs) |dylib| common.attachBuiltinPluginInstall( + b, + b.getInstallStep(), + dylib.compile, + dylib.id, + plugins_install_dir, + ); } else { const install_artifact = b.addInstallArtifact(exe, .{ .dest_dir = .{ .override = zig_out_install_dir }, }); - // A running fizzy holds its executable on Windows: set it aside first, or the install fails. + // A running fizzy holds its executable on Windows: set it aside first, or the install + // fails. if (b.graph.host.result.os.tag == .windows and target.result.os.tag == .windows) { const aside_exe = b.addExecutable(.{ .name = "set-aside", @@ -681,7 +890,11 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O const install_dylib = plugin.installBuiltinPlugin(b, dylib.compile, dylib.id, plugins_install_dir); b.step( b.fmt("{s}-dylib", .{dylib.id}), - b.fmt("Build the {s} plugin as a dynamic library into zig-out//plugins/ (native only)", .{dylib.id}), + b.fmt( + "Build the {s} plugin as a dynamic library into zig-out//plugins/ " ++ + "(native only)", + .{dylib.id}, + ), ).dependOn(&install_dylib.step); } @@ -709,7 +922,15 @@ fn install(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.O } /// The unit and integration tests, the benchmarks, and the checks over them. -fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang.OptimizeMode, cfg: Config, built: Native, assets_module: *std.Build.Module, app_icon_module: *std.Build.Module) !void { +fn addTests( + b: *std.Build, + target: std.Build.ResolvedTarget, + optimize: std.lang.OptimizeMode, + cfg: Config, + built: Native, + assets_module: *std.Build.Module, + app_icon_module: *std.Build.Module, +) !void { const plugins = @import("plugins.zig"); const sdk = @import("sdk.zig"); const fizzy_exe = @import("exe.zig"); @@ -814,7 +1035,8 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // Profiles: taking `--profile` out of argv, and each profile's own lock name. std-only; // where a profile's folders go is `single_instance` and the editor's, run by hand. .{ "fizzy-profile-tests", "app/profile.zig" }, - // `FIZZY_QUIET`: read from the environment (the flag is `single_instance.resolveArgs`). std-only. + // `FIZZY_QUIET`: read from the environment (the flag is `single_instance.resolveArgs`). + // std-only. .{ "fizzy-quiet-tests", "app/quiet.zig" }, // `` scanning for the markdown preview's raw-HTML blocks. Under plugins/ // but std-only by design (see html_images.zig), so it tests from the app build. @@ -874,7 +1096,8 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // A state packed for a track file (zero runs, then deflate): lossless, so a canvas of // transparent pixels is not stored as tens of megabytes. std-only (see state_pack.zig). .{ "fizzy-state-pack-tests", "app/state_pack.zig" }, - // Image diff (RGBA8 to rects) and text diff (histogram lines, unified output). std-only (see + // Image diff (RGBA8 to rects) and text diff (histogram lines, unified output). std-only + // (see // core/diff.zig); `bench-diff` measures it. .{ "fizzy-diff-tests", "core/diff.zig" }, // When a document's transport shows and for how long, how far a held undo goes, which @@ -1100,7 +1323,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // steps so `zig build test` stays MSVC-free on Windows CI runners. Skipped // when cross-compiling to *-windows-msvc without an MSVC libc INI. // --------------------------------------------------------------- - const test_integration_step = b.step("test-integration", "Run fizzy headless integration tests (dvui-testing; needs MSVC on Windows)"); + const test_integration_step = b.step( + "test-integration", + "Run fizzy headless integration tests (dvui-testing; needs MSVC on Windows)", + ); const check_integration_step = b.step("check-integration", "Compile fizzy integration tests without running them"); const test_all_step = b.step("test-all", "Run unit + integration tests"); test_all_step.dependOn(test_step); @@ -1128,7 +1354,13 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. .backend = .testing, .accesskit = accesskit, }); - const dvui_test_proxy_bridge = sdk.addProxyBridgeModule(b, target, optimize, dvui_testing_dep, dvui_testing_dep.module("dvui_testing")); + const dvui_test_proxy_bridge = sdk.addProxyBridgeModule( + b, + target, + optimize, + dvui_testing_dep, + dvui_testing_dep.module("dvui_testing"), + ); // Build a module rooted at `src/fizzy.zig` carrying all the same // imports the production exe carries. Because fizzy.zig's transitive @@ -1144,7 +1376,13 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. fizzy_test_module.addImport("dvui", dvui_testing_dep.module("dvui_testing")); fizzy_test_module.addImport("backend", dvui_testing_dep.module("testing")); // The testing backend has no viewports (`backend_web.viewports`). - fizzy_test_module.addImport("viewports_none", @import("fizzy_backend").viewportsNoneModule(fizzy_exe.backendDependency(b), dvui_testing_dep.module("dvui_testing"))); + fizzy_test_module.addImport( + "viewports_none", + @import("fizzy_backend").viewportsNoneModule( + fizzy_exe.backendDependency(b), + dvui_testing_dep.module("dvui_testing"), + ), + ); fizzy_test_module.addImport("assets", assets_module); fizzy_test_module.addImport("app_icon", app_icon_module); fizzy_test_module.addImport("build_opts", sdk.buildOptsModule(build_opts)); @@ -1155,7 +1393,16 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. .optimize = optimize, .root_source_file = b.path("core/core.zig"), }); - const icons_test = core_mod.addImports(b, core_module_test, dvui_testing_dep.module("dvui_testing"), b.dependency("fizzy_sdk", .{}).builder, target, optimize, null, null); + const icons_test = core_mod.addImports( + b, + core_module_test, + dvui_testing_dep.module("dvui_testing"), + b.dependency("fizzy_sdk", .{}).builder, + target, + optimize, + null, + null, + ); fizzy_test_module.addImport("core", core_module_test); if (icons_test) |icons| fizzy_test_module.addImport("icons", icons); @@ -1208,7 +1455,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. fizzy_test_module.addImport("nightwatch", dep.module("nightwatch")); } - const sdk_module_test = sdk.wireSdkModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), dvui_test_proxy_bridge, core_module_test, fizzy_test_module); + const sdk_module_test = sdk.wireSdkModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + dvui_test_proxy_bridge, + core_module_test, + fizzy_test_module, + ); if (cfg.app_is_fizzy) { const fixture = rehome_fixture.graph(b, target, optimize); const consumer = b.createModule(.{ .target = target, .optimize = optimize, .link_libc = true }); @@ -1251,7 +1506,20 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // The single-instance backend too, so its argv and profile handling are tested where the exe // is not built (`tests/integration.zig`, "profile"). const singleton_test_dep = b.dependency("dvui_singleton_app", .{ .target = target, .optimize = optimize }); - const app_module_test = sdk.wireAppModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), core_module_test, sdk_module_test, icons_test, markdown_module_test, if (nightwatch_test_dep) |dep| dep.module("nightwatch") else null, build_opts, singleton_test_dep.module("singleton_app"), fizzy_test_module); + const app_module_test = sdk.wireAppModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + core_module_test, + sdk_module_test, + icons_test, + markdown_module_test, + if (nightwatch_test_dep) |dep| dep.module("nightwatch") else null, + build_opts, + singleton_test_dep.module("singleton_app"), + fizzy_test_module, + ); const image_module_test = image_plugin.addStaticModule(b, target, optimize, .{ .dvui = dvui_testing_dep.module("dvui_testing"), .core = core_module_test, @@ -1281,7 +1549,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); if (target.result.os.tag == .macos) { - if (@import("fizzy_backend").objcModule(fizzy_exe.backendDependency(b), fizzy_exe.backendOptions(target, optimize, macos_sdl_paths))) |objc| { + if (@import("fizzy_backend").objcModule( + fizzy_exe.backendDependency(b), + fizzy_exe.backendOptions(target, optimize, macos_sdl_paths), + )) |objc| { fizzy_test_module.addImport("objc", objc); } } else if (target.result.os.tag == .windows) { @@ -1328,12 +1599,19 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. integration_module.addImport("workbench", workbench_module_test); integration_module.addAnonymousImport("markdown_sample", .{ .root_source_file = b.path("docs/PLUGINS.md") }); // The synthetic document with the 48KB table of wrapping cells — the case table-row culling - // exists for, and the one it could get wrong. Generated by `markdown_sample_tables.py` beside it. - integration_module.addAnonymousImport("markdown_sample_tables", .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }); + // exists for, and the one it could get wrong. Generated by `markdown_sample_tables.py` beside + // it. + integration_module.addAnonymousImport( + "markdown_sample_tables", + .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }, + ); // The image-heavy fixture. Both docs above are prose and tables, so without this no test ever // laid out an image block — the one block kind whose height nothing in the source predicts // and which rescales with the pane right up until the pane is wider than the image. - integration_module.addAnonymousImport("markdown_sample_images", .{ .root_source_file = b.path("tests/data/markdown_sample_images.md") }); + integration_module.addAnonymousImport( + "markdown_sample_images", + .{ .root_source_file = b.path("tests/data/markdown_sample_images.md") }, + ); // A real tree-sitter grammar + queries, so the markdown code-fence highlighting path can be // executed rather than only compiled. The grammar itself is already linked into this binary // through the text module; the queries come from dvui's examples rather than a vendored copy, @@ -1349,7 +1627,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // The hand-written sample tape `docs/AUTOMATION.md` points at, so it cannot rot. integration_module.addAnonymousImport("demo_sample_tape", .{ .root_source_file = b.path("docs/demos/hello.zon") }); integration_module.addAnonymousImport("soak_tape", .{ .root_source_file = b.path("tests/tapes/soak.zon") }); - integration_module.addAnonymousImport("soak_expect", .{ .root_source_file = b.path("tests/tapes/soak.expect.zon") }); + integration_module.addAnonymousImport( + "soak_expect", + .{ .root_source_file = b.path("tests/tapes/soak.expect.zon") }, + ); const integration_tests = b.addTest(.{ .name = "fizzy-integration-tests", @@ -1365,7 +1646,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // 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, .c_modules = &.{app_module_test} }); + 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); @@ -1393,7 +1679,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. 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, .c_modules = &.{app_module_test} }); + 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); @@ -1418,7 +1709,12 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. if (target.result.os.tag == .windows) fuzz_tests.root_module.linkSystemLibrary("comctl32", .{}); fuzz_tests.root_module.link_libcpp = !target_is_windows_msvc; if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, fuzz_tests, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + try velopack.linkVelopack( + b, + v, + fuzz_tests, + .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }, + ); }; // Its output is the report, so a cached run must not hide it. const fuzz_run = b.addRunArtifact(fuzz_tests); @@ -1539,9 +1835,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // reasoning as `bench-text`'s samples. `PLUGINS.md` is the document that prompted the // benchmark. bench_module.addAnonymousImport("sample_huge", .{ .root_source_file = b.path("docs/PLUGINS.md") }); - bench_module.addAnonymousImport("sample_prose", .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }); + bench_module.addAnonymousImport( + "sample_prose", + .{ .root_source_file = b.path("tests/data/markdown_sample_tables.md") }, + ); bench_module.addAnonymousImport("sample_medium", .{ .root_source_file = b.path("AGENTS.md") }); - bench_module.addAnonymousImport("sample_small", .{ .root_source_file = b.path("docs/MODULARIZATION_RELEASE_NOTES.md") }); + bench_module.addAnonymousImport( + "sample_small", + .{ .root_source_file = b.path("docs/MODULARIZATION_RELEASE_NOTES.md") }, + ); const bench_markdown = b.addTest(.{ .name = "fizzy-bench-markdown", .root_module = bench_module }); bench_markdown.root_module.link_libcpp = !target_is_windows_msvc; @@ -1549,7 +1851,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. bench_markdown.root_module.linkSystemLibrary("comctl32", .{}); } - const bench_step = b.step("bench-markdown", "Benchmark the markdown preview's per-frame draw cost (prints timings)"); + const bench_step = b.step( + "bench-markdown", + "Benchmark the markdown preview's per-frame draw cost (prints timings)", + ); const run_bench = b.addRunArtifact(bench_markdown); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1575,13 +1880,21 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. bench_replay.root_module.linkSystemLibrary("comctl32", .{}); } if (velopack_enabled) if (vz) |v| { - try velopack.linkVelopack(b, v, bench_replay, .{ .target = target, .optimize = optimize, .c_modules = &.{app_module_test} }); + 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"); check_bench_replay_step.dependOn(&bench_replay.step); - const bench_step = b.step("bench-replay", "Benchmark a demo seek, silent against shown frame by frame (prints timings)"); + const bench_step = b.step( + "bench-replay", + "Benchmark a demo seek, silent against shown frame by frame (prints timings)", + ); const run_bench = b.addRunArtifact(bench_replay); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1607,13 +1920,17 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. try velopack.linkVelopack(b, v, bench_tracks, .{ .target = target, .optimize = optimize }); }; - const bench_step = b.step("bench-tracks", "Benchmark a document's track: steps, checkpoints, replays (prints timings)"); + const bench_step = b.step( + "bench-tracks", + "Benchmark a document's track: steps, checkpoints, replays (prints timings)", + ); const run_bench = b.addRunArtifact(bench_tracks); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); } - // `zig build bench-diff` — core.diff on a 4K image, a 2048x2048 sprite of 50 layers and 1 MB of text (prints + // `zig build bench-diff` — core.diff on a 4K image, a 2048x2048 sprite of 50 layers and 1 MB of + // text (prints // timings). Same rules as `bench-tape`; std-only, so it needs no window. { const bench_module = b.createModule(.{ @@ -1628,7 +1945,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); const bench_diff = b.addTest(.{ .name = "fizzy-bench-diff", .root_module = bench_module }); - const bench_step = b.step("bench-diff", "Benchmark core.diff: a 4K image, a 50-layer sprite, 1 MB of text (prints timings)"); + const bench_step = b.step( + "bench-diff", + "Benchmark core.diff: a 4K image, a 50-layer sprite, 1 MB of text (prints timings)", + ); const run_bench = b.addRunArtifact(bench_diff); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1650,7 +1970,10 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. })); const bench_tape = b.addTest(.{ .name = "fizzy-bench-tape", .root_module = bench_module }); - const bench_step = b.step("bench-tape", "Benchmark saving and loading demo tapes, ZON against binary (prints timings)"); + const bench_step = b.step( + "bench-tape", + "Benchmark saving and loading demo tapes, ZON against binary (prints timings)", + ); const run_bench = b.addRunArtifact(bench_tape); run_bench.has_side_effects = true; bench_step.dependOn(&run_bench.step); @@ -1665,7 +1988,15 @@ fn addTests(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.lang. // why Manifest.zig has its own root in the unit list above); when adding tests // to a new SDK file, check the reported test count actually went up. { - const sdk_tests_module = sdk.wireSdkModule(b, target, optimize, dvui_testing_dep.module("dvui_testing"), dvui_test_proxy_bridge, core_module_test, null); + const sdk_tests_module = sdk.wireSdkModule( + b, + target, + optimize, + dvui_testing_dep.module("dvui_testing"), + dvui_test_proxy_bridge, + core_module_test, + null, + ); const plugin_loader_module = b.createModule(.{ .target = target, .optimize = optimize, diff --git a/scripts/lint.zig b/scripts/lint.zig index da4b18c18..b37aa16c5 100644 --- a/scripts/lint.zig +++ b/scripts/lint.zig @@ -272,7 +272,10 @@ fn rawTransientSurfaces( try findings.append(allocator, .{ .rule = .@"raw-transient-surface", .line = line, - .message = try allocator.print("dvui.{s} paints its own surface; use the core.widgets / core.dialogs wrapper", .{name}), + .message = try allocator.print( + "dvui.{s} paints its own surface; use the core.widgets / core.dialogs wrapper", + .{name}, + ), }); break; }