From ba38b493d410fab522798550e38b74c9ccb242a2 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 7 May 2026 09:11:14 -0700 Subject: [PATCH] Update to Zig 0.16.0 This commit represents the majority of the work necessary to upgrade Ghostty to use Zig 0.16.0. At this point, the project builds, tests, and runs under Linux. More work may be necessary for other platforms, and possibly to fix any (currently unknown) upgrade regressions. Co-authored-by: Leah Amelia Chen --- .gitignore | 2 + build.zig | 11 +- build.zig.zon | 51 +- flake.lock | 56 +- flake.nix | 27 +- nix/devShell.nix | 1 - pkg/android-ndk/build.zig | 19 +- pkg/apple-sdk/build.zig | 16 +- pkg/breakpad/build.zig | 22 +- pkg/dcimgui/build.zig | 40 +- pkg/fontconfig/build.zig | 27 +- pkg/freetype/build.zig | 30 +- pkg/glslang/build.zig | 28 +- pkg/gtk4-layer-shell/build.zig | 29 +- pkg/gtk4-layer-shell/build.zig.zon | 5 + pkg/gtk4-layer-shell/src/main.zig | 7 +- pkg/harfbuzz/build.zig | 34 +- pkg/highway/build.zig | 15 +- pkg/libintl/build.zig | 10 +- pkg/libpng/build.zig | 27 +- pkg/libxml2/build.zig | 21 +- pkg/macos/build.zig | 20 +- pkg/oniguruma/build.zig | 12 +- pkg/sentry/build.zig | 38 +- pkg/simdutf/build.zig | 31 +- pkg/spirv-cross/build.zig | 24 +- pkg/wuffs/build.zig | 78 ++- pkg/wuffs/build.zig.zon | 5 + pkg/wuffs/src/c.zig | 18 - pkg/wuffs/src/error.zig | 2 +- pkg/wuffs/src/jpeg.zig | 2 +- pkg/wuffs/src/png.zig | 2 +- pkg/wuffs/src/swizzle.zig | 2 +- pkg/zlib/build.zig | 6 +- src/App.zig | 4 +- src/Command.zig | 98 +-- src/Surface.zig | 300 ++++----- src/apprt/action.zig | 24 +- src/apprt/embedded.zig | 18 +- src/apprt/gtk/Surface.zig | 2 +- src/apprt/gtk/adw_version.zig | 26 +- src/apprt/gtk/build/blueprint.zig | 94 +-- src/apprt/gtk/build/gresource.zig | 33 +- src/apprt/gtk/class.zig | 28 +- src/apprt/gtk/class/application.zig | 45 +- src/apprt/gtk/class/command_palette.zig | 2 +- src/apprt/gtk/class/global_shortcuts.zig | 5 +- src/apprt/gtk/class/imgui_widget.zig | 18 +- src/apprt/gtk/class/surface.zig | 26 +- src/apprt/gtk/ext/actions.zig | 6 +- src/apprt/gtk/gtk_version.zig | 42 +- src/apprt/gtk/ipc/DBus.zig | 8 +- src/apprt/gtk/media.zig | 6 +- src/apprt/gtk/portal.zig | 3 +- src/apprt/gtk/portal/OpenURI.zig | 42 +- src/apprt/gtk/post_fork.zig | 8 +- src/apprt/gtk/pre_exec.zig | 8 +- src/apprt/gtk/winproto.zig | 2 +- src/apprt/gtk/winproto/noop.zig | 2 +- src/apprt/gtk/winproto/wayland.zig | 2 +- src/apprt/gtk/winproto/x11.zig | 2 +- src/apprt/ipc.zig | 24 +- src/benchmark/Benchmark.zig | 10 +- src/benchmark/CodepointWidth.zig | 10 +- src/benchmark/GraphemeBreak.zig | 8 +- src/benchmark/IsSymbol.zig | 8 +- src/benchmark/OscParser.zig | 6 +- src/benchmark/ScreenClone.zig | 4 +- src/benchmark/TerminalParser.zig | 6 +- src/benchmark/TerminalStream.zig | 6 +- src/benchmark/cli.zig | 9 +- src/benchmark/options.zig | 6 +- src/build/Config.zig | 12 +- src/build/GhosttyBench.zig | 4 +- src/build/GhosttyDist.zig | 4 +- src/build/GhosttyDocs.zig | 6 +- src/build/GhosttyExe.zig | 4 +- src/build/GhosttyFrameData.zig | 8 +- src/build/GhosttyI18n.zig | 13 +- src/build/GhosttyLib.zig | 18 +- src/build/GhosttyLibVt.zig | 28 +- src/build/GhosttyResources.zig | 30 +- src/build/GhosttyWebdata.zig | 6 +- src/build/GhosttyXcodebuild.zig | 10 +- src/build/GhosttyZig.zig | 2 +- src/build/GitVersion.zig | 14 +- src/build/HelpStrings.zig | 2 +- src/build/SharedDeps.zig | 244 +++++--- src/build/UnicodeTables.zig | 4 +- src/build/XCFrameworkStep.zig | 4 +- src/build/args.zig | 13 + src/build/combine_archives.zig | 31 +- src/build/gtk.zig | 2 +- src/build/mdgen/main_ghostty_1.zig | 7 +- src/build/mdgen/main_ghostty_5.zig | 7 +- src/build/uucode_config.zig | 203 +++--- src/build/wasm_patch_growable_table.zig | 19 +- src/build/webgen/main_actions.zig | 4 +- src/build/webgen/main_commands.zig | 4 +- src/build/webgen/main_config.zig | 4 +- src/cli/Pager.zig | 50 +- src/cli/action.zig | 29 +- src/cli/args.zig | 38 +- src/cli/boo.zig | 6 +- src/cli/crash_report.zig | 10 +- src/cli/diagnostics.zig | 4 +- src/cli/edit_config.zig | 7 +- src/cli/explain_config.zig | 4 +- src/cli/ghostty.zig | 24 +- src/cli/help.zig | 2 +- src/cli/list_actions.zig | 4 +- src/cli/list_colors.zig | 17 +- src/cli/list_fonts.zig | 6 +- src/cli/list_keybinds.zig | 16 +- src/cli/list_themes.zig | 71 ++- src/cli/new_window.zig | 41 +- src/cli/show_face.zig | 4 +- src/cli/ssh-cache/DiskCache.zig | 128 ++-- src/cli/ssh-cache/Entry.zig | 8 +- src/cli/ssh_cache.zig | 10 +- src/cli/validate_config.zig | 8 +- src/cli/version.zig | 6 +- src/config/Config.zig | 112 ++-- src/config/c_get.zig | 2 +- src/config/command.zig | 3 +- src/config/conditional.zig | 20 +- src/config/edit.zig | 11 +- src/config/file_load.zig | 17 +- src/config/io.zig | 2 +- src/config/key.zig | 27 +- src/config/path.zig | 50 +- src/config/theme.zig | 16 +- src/crash/dir.zig | 17 +- src/crash/sentry.zig | 16 +- src/crash/sentry_envelope.zig | 6 +- src/datastruct/blocking_queue.zig | 41 +- src/datastruct/comparison.zig | 6 +- src/datastruct/segmented_pool.zig | 3 +- src/font/Atlas.zig | 4 +- src/font/Collection.zig | 7 +- src/font/Metrics.zig | 22 +- src/font/SharedGrid.zig | 155 ++++- src/font/SharedGridSet.zig | 14 +- src/font/face/freetype.zig | 44 +- src/font/library.zig | 6 +- src/font/opentype/glyf.zig | 35 +- src/font/opentype/head.zig | 8 +- src/font/opentype/hhea.zig | 7 +- src/font/opentype/os2.zig | 21 +- src/font/opentype/post.zig | 8 +- src/font/opentype/sfnt.zig | 7 +- src/font/opentype/svg.zig | 26 +- src/font/shaper/coretext.zig | 2 +- src/font/shaper/feature.zig | 33 +- src/font/shaper/harfbuzz.zig | 10 +- src/font/sprite/Face.zig | 32 +- src/font/sprite/canvas.zig | 2 +- .../testdata/U+1CC00...U+1CCFF-11x21+2.png | Bin 1032 -> 941 bytes .../testdata/U+1CC00...U+1CCFF-12x24+3.png | Bin 1295 -> 1151 bytes .../testdata/U+1CC00...U+1CCFF-18x36+4.png | Bin 2193 -> 1994 bytes .../testdata/U+1CC00...U+1CCFF-9x17+1.png | Bin 794 -> 717 bytes .../testdata/U+1CD00...U+1CDFF-11x21+2.png | Bin 1280 -> 2146 bytes .../testdata/U+1CD00...U+1CDFF-12x24+3.png | Bin 1870 -> 2529 bytes .../testdata/U+1CD00...U+1CDFF-18x36+4.png | Bin 3411 -> 4398 bytes .../testdata/U+1CD00...U+1CDFF-9x17+1.png | Bin 1103 -> 1625 bytes .../testdata/U+1CE00...U+1CEFF-11x21+2.png | Bin 1006 -> 881 bytes .../testdata/U+1CE00...U+1CEFF-12x24+3.png | Bin 1255 -> 1082 bytes .../testdata/U+1CE00...U+1CEFF-18x36+4.png | Bin 2247 -> 2371 bytes .../testdata/U+1CE00...U+1CEFF-9x17+1.png | Bin 751 -> 693 bytes .../testdata/U+1FB00...U+1FBFF-11x21+2.png | Bin 5427 -> 5871 bytes .../testdata/U+1FB00...U+1FBFF-12x24+3.png | Bin 5724 -> 6354 bytes .../testdata/U+1FB00...U+1FBFF-18x36+4.png | Bin 9975 -> 11167 bytes .../testdata/U+1FB00...U+1FBFF-9x17+1.png | Bin 4341 -> 4598 bytes .../testdata/U+2500...U+25FF-11x21+2.png | Bin 2225 -> 2409 bytes .../testdata/U+2500...U+25FF-12x24+3.png | Bin 2635 -> 2839 bytes .../testdata/U+2500...U+25FF-18x36+4.png | Bin 4570 -> 5323 bytes .../testdata/U+2500...U+25FF-9x17+1.png | Bin 1853 -> 1780 bytes .../testdata/U+2800...U+28FF-11x21+2.png | Bin 1022 -> 1010 bytes .../testdata/U+2800...U+28FF-12x24+3.png | Bin 1541 -> 1276 bytes .../testdata/U+2800...U+28FF-18x36+4.png | Bin 2501 -> 1993 bytes .../testdata/U+2800...U+28FF-9x17+1.png | Bin 917 -> 953 bytes .../testdata/U+E000...U+E0FF-11x21+2.png | Bin 1102 -> 1009 bytes .../testdata/U+E000...U+E0FF-12x24+3.png | Bin 1255 -> 1131 bytes .../testdata/U+E000...U+E0FF-18x36+4.png | Bin 2217 -> 1942 bytes .../testdata/U+E000...U+E0FF-9x17+1.png | Bin 894 -> 832 bytes .../testdata/U+F500...U+F5FF-11x21+2.png | Bin 1114 -> 1044 bytes .../testdata/U+F500...U+F5FF-12x24+3.png | Bin 1421 -> 1279 bytes .../testdata/U+F500...U+F5FF-18x36+4.png | Bin 2473 -> 2073 bytes .../testdata/U+F500...U+F5FF-9x17+1.png | Bin 872 -> 831 bytes .../testdata/U+F600...U+F6FF-11x21+2.png | Bin 495 -> 359 bytes .../testdata/U+F600...U+F6FF-12x24+3.png | Bin 637 -> 438 bytes .../testdata/U+F600...U+F6FF-18x36+4.png | Bin 1210 -> 752 bytes .../testdata/U+F600...U+F6FF-9x17+1.png | Bin 393 -> 288 bytes src/global.zig | 12 +- src/helpgen.zig | 9 +- src/input/Binding.zig | 37 +- src/input/key.zig | 10 +- src/input/key_mods.zig | 9 +- src/inspector/Inspector.zig | 2 - src/inspector/widgets/key.zig | 14 +- src/inspector/widgets/surface.zig | 8 +- src/inspector/widgets/termio.zig | 2 - src/lib/compat/README.md | 37 ++ src/lib/compat/args.zig | 241 ++++++++ src/lib/compat/clock.zig | 35 ++ src/lib/compat/dir.zig | 83 +++ src/lib/compat/env.zig | 277 +++++++++ src/lib/compat/exec.zig | 170 +++++ src/lib/compat/file.zig | 192 ++++++ src/lib/compat/init.zig | 19 + src/lib/compat/process.zig | 41 ++ src/lib/compat/reader.zig | 64 ++ src/lib/compat/segmented_list.zig | 533 ++++++++++++++++ src/lib/compat/thread.zig | 75 +++ src/lib/enum.zig | 62 +- src/lib/struct.zig | 24 +- src/lib/union.zig | 28 +- src/main_build_data.zig | 7 +- src/main_c.zig | 20 +- src/main_ghostty.zig | 22 +- src/os/TempDir.zig | 20 +- src/os/args.zig | 21 +- src/os/cgroup.zig | 8 +- src/os/desktop.zig | 12 +- src/os/env.zig | 10 +- src/os/file.zig | 5 +- src/os/flatpak.zig | 35 +- src/os/homedir.zig | 12 +- src/os/kernel_info.zig | 12 +- src/os/open.zig | 129 ++-- src/os/path.zig | 25 +- src/os/pipe.zig | 12 +- src/os/resourcesdir.zig | 23 +- src/os/systemd.zig | 20 +- src/os/xdg.zig | 9 +- src/pty.zig | 9 +- src/renderer/Overlay.zig | 4 +- src/renderer/State.zig | 2 +- src/renderer/generic.zig | 127 ++-- src/renderer/image.zig | 8 +- src/renderer/metal/api.zig | 9 +- src/renderer/metal/shaders.zig | 28 +- src/renderer/opengl/shaders.zig | 24 +- src/renderer/shadertoy.zig | 11 +- src/synthetic/cli.zig | 11 +- src/synthetic/cli/Ascii.zig | 1 - src/synthetic/cli/Utf8.zig | 1 - src/terminal/PageList.zig | 579 +++++++++--------- src/terminal/Screen.zig | 98 +-- src/terminal/Terminal.zig | 181 +++--- src/terminal/c/build_info.zig | 2 +- src/terminal/c/cell.zig | 4 +- src/terminal/c/key_encode.zig | 4 +- src/terminal/c/key_event.zig | 4 +- src/terminal/c/kitty_graphics.zig | 2 +- src/terminal/c/modes.zig | 4 +- src/terminal/c/mouse_encode.zig | 6 +- src/terminal/c/mouse_event.zig | 4 +- src/terminal/c/osc.zig | 2 +- src/terminal/c/render.zig | 10 +- src/terminal/c/row.zig | 2 +- src/terminal/c/sys.zig | 64 +- src/terminal/c/terminal.zig | 4 +- src/terminal/color.zig | 5 +- src/terminal/device_status.zig | 26 +- src/terminal/formatter.zig | 14 +- src/terminal/kitty/graphics_image.zig | 115 ++-- src/terminal/kitty/graphics_storage.zig | 14 +- src/terminal/modes.zig | 48 +- src/terminal/mouse.zig | 2 +- src/terminal/osc/parsers/color.zig | 25 +- src/terminal/page.zig | 118 ++-- src/terminal/render.zig | 2 +- src/terminal/search/Thread.zig | 26 +- src/terminal/stream.zig | 4 +- src/terminal/style.zig | 4 +- src/terminal/tmux/control.zig | 2 +- src/terminal/tmux/output.zig | 24 +- src/termio/Exec.zig | 91 +-- src/termio/Termio.zig | 64 +- src/termio/Thread.zig | 4 +- src/termio/mailbox.zig | 6 +- src/termio/shell_integration.zig | 82 +-- src/termio/stream_handler.zig | 48 +- src/tripwire.zig | 2 +- src/unicode/grapheme.zig | 12 +- src/unicode/props.zig | 2 +- src/unicode/props_uucode.zig | 8 +- src/unicode/symbols_uucode.zig | 8 +- test_align | 0 290 files changed, 5142 insertions(+), 2866 deletions(-) delete mode 100644 pkg/wuffs/src/c.zig create mode 100644 src/build/args.zig create mode 100644 src/lib/compat/README.md create mode 100644 src/lib/compat/args.zig create mode 100644 src/lib/compat/clock.zig create mode 100644 src/lib/compat/dir.zig create mode 100644 src/lib/compat/env.zig create mode 100644 src/lib/compat/exec.zig create mode 100644 src/lib/compat/file.zig create mode 100644 src/lib/compat/init.zig create mode 100644 src/lib/compat/process.zig create mode 100644 src/lib/compat/reader.zig create mode 100644 src/lib/compat/segmented_list.zig create mode 100644 src/lib/compat/thread.zig delete mode 100755 test_align diff --git a/.gitignore b/.gitignore index 699ac9a5f..51b929544 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ zig-cache/ .zig-cache/ zig-out/ +zig-pkg/ build-cmake/ CMakeCache.txt CMakeFiles/ @@ -29,3 +30,4 @@ glad.zip vgcore.* +/sprite_face_test* diff --git a/build.zig b/build.zig index 8ef7701e2..ca8a0ddbd 100644 --- a/build.zig +++ b/build.zig @@ -25,9 +25,10 @@ pub fn build(b: *std.Build) !void { // use that as the version source of truth. Otherwise we fall back // to what is in the build.zig.zon. const file_version: ?[]const u8 = if (b.build_root.handle.readFileAlloc( - b.allocator, + b.graph.io, "VERSION", - 128, + b.allocator, + .limited(128), )) |content| std.mem.trim( u8, content, @@ -298,7 +299,7 @@ pub fn build(b: *std.Build) !void { // We need to rebuild Ghostty with a baseline CPU target. const valgrind_exe = exe: { var valgrind_config = config; - valgrind_config.target = valgrind_config.baselineTarget(); + valgrind_config.target = valgrind_config.baselineTarget(b.graph.io); break :exe try buildpkg.GhosttyExe.init( b, &valgrind_config, @@ -343,7 +344,7 @@ pub fn build(b: *std.Build) !void { .filters = test_filters, .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), - .target = config.baselineTarget(), + .target = config.baselineTarget(b.graph.io), .optimize = .Debug, .strip = false, .omit_frame_pointer = false, @@ -358,7 +359,7 @@ pub fn build(b: *std.Build) !void { // Verify our internal libghostty header. const ghostty_h = b.addTranslateC(.{ .root_source_file = b.path("include/ghostty.h"), - .target = config.baselineTarget(), + .target = config.baselineTarget(b.graph.io), .optimize = .Debug, }); test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule()); diff --git a/build.zig.zon b/build.zig.zon index 814145c30..cf8e8161d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,62 +3,73 @@ .version = "1.3.2-dev", .paths = .{""}, .fingerprint = 0x64407a2a0b4147e5, - .minimum_zig_version = "0.15.2", + .minimum_zig_version = "0.16.0", .dependencies = .{ - // Zig libs + // External translate-c + // NOTE: We might not need to keep this around forever, but we need it + // for at least the 0.16.0 release cycle since we need fixes in + // Aro/translate-c itself. + // + // Since this is a key part of the build toolchain, it's *not* an + // optional (read: not lazy) dependency. + .translate_c = .{ + .url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz", + .hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa", + }, + // Zig libs .libxev = .{ // mitchellh/libxev - .url = "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz", - .hash = "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs", + .url = "https://github.com/mitchellh/libxev/archive/9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz", + .hash = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F", .lazy = true, }, .vaxis = .{ // rockorager/libvaxis - .url = "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz", - .hash = "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS", + .url = "https://github.com/rockorager/libvaxis/archive/1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz", + .hash = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka", .lazy = true, }, .z2d = .{ // vancluever/z2d - .url = "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz", - .hash = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ", + .url = "https://github.com/vancluever/z2d/archive/refs/tags/v0.11.0.tar.gz", + .hash = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR", .lazy = true, }, .zig_objc = .{ // mitchellh/zig-objc - .url = "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz", - .hash = "zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK", + .url = "https://github.com/mitchellh/zig-objc/archive/c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz", + .hash = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi", .lazy = true, }, .zig_js = .{ // mitchellh/zig-js - .url = "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz", - .hash = "zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi", + .url = "https://github.com/mitchellh/zig-js/archive/3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz", + .hash = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj", .lazy = true, }, .uucode = .{ // jacobsandlund/uucode - .url = "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz", - .hash = "uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9", + .url = "https://github.com/jacobsandlund/uucode/archive/2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz", + .hash = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA", }, .zig_wayland = .{ // codeberg ifreund/zig-wayland - .url = "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz", - .hash = "wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe", + .url = "https://codeberg.org/ifreund/zig-wayland/archive/v0.6.0.tar.gz", + .hash = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX", .lazy = true, }, .zf = .{ // natecraddock/zf - .url = "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz", - .hash = "zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh", + .url = "https://github.com/natecraddock/zf/archive/c35c421f84895193246db06c40683c1a30e616ef.tar.gz", + .hash = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW", .lazy = true, }, .gobject = .{ // https://github.com/ghostty-org/zig-gobject based on zig_gobject // Temporary until we generate them at build time automatically. - .url = "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst", - .hash = "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-", + .url = "https://github.com/ghostty-org/zig-gobject/releases/download/0.8.0-2026-04-23-26-1/ghostty-gobject-0.8.0-2026-04-23-26-1.tar.zst", + .hash = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp", .lazy = true, }, diff --git a/flake.lock b/flake.lock index 410632788..5a92eb7ac 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1761588595, - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", "owner": "edolstra", "repo": "flake-compat", - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1770586272, - "narHash": "sha256-Ucci8mu8QfxwzyfER2DQDbvW9t1BnTUJhBmY7ybralo=", + "lastModified": 1778144356, + "narHash": "sha256-dGM+QCstz/DyLB68+JK5GWyMx4QSqmOJEVgZmy63d/g=", "owner": "nix-community", "repo": "home-manager", - "rev": "b1f916ba052341edc1f80d4b2399f1092a4873ca", + "rev": "e4419d3123b780d5f4c0bceeace450424387638c", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1770537093, - "narHash": "sha256-XV30uo8tXuxdzuV8l3sojmlPRLd/8tpMsOp4lNzLGUo=", - "rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51", + "lastModified": 1778124196, + "narHash": "sha256-Z5mLDoR8p0d7psIY4LnyaHHRykXngMcWXTZ9JWvtvPc=", + "rev": "68a8af93ff4297686cb68880845e61e5e2e41d92", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre942631.fef9403a3e4d/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre993588.68a8af93ff42/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -88,11 +88,11 @@ ] }, "locked": { - "lastModified": 1776789209, - "narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=", + "lastModified": 1778158259, + "narHash": "sha256-PwPyM4vM1uzxYqT5txYzauxx9Tfgji/iTPO9KwbTcUc=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "14fe971844e841297ddd2ce9783d6892b467af39", + "rev": "cd6faec62cb29fda88b2195b91edb2dfe6dce9c2", "type": "github" }, "original": { @@ -101,40 +101,18 @@ "type": "github" } }, - "zig_2": { - "inputs": { - "nixpkgs": [ - "zon2nix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1777234348, - "narHash": "sha256-fKw44a4qbUuI5eTG8k0gPbqMV5TOrjYF35PBzsYgd2U=", - "ref": "refs/heads/main", - "rev": "2c781c0609ecda600ab98f98cca417bbd981bd53", - "revCount": 1677, - "type": "git", - "url": "https://codeberg.org/jcollie/zig-overlay.git" - }, - "original": { - "type": "git", - "url": "https://codeberg.org/jcollie/zig-overlay.git" - } - }, "zon2nix": { "inputs": { "nixpkgs": [ "nixpkgs" - ], - "zig": "zig_2" + ] }, "locked": { - "lastModified": 1777314365, - "narHash": "sha256-eLxQaD0wc96Neqkln8wHS0rNq/chPODifFkhwrwilEU=", + "lastModified": 1778078000, + "narHash": "sha256-0djKIL0E8Y1W495bzIkmbCub571ubghZx3Fg7xQPNrk=", "owner": "jcollie", "repo": "zon2nix", - "rev": "a5a1d412ad1ab6305511997bbc92b3a9dd6cb784", + "rev": "a9f46c6fe9177135a3001e26ec76715bb998c6cf", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 0a4e5340a..5ca0cd4f9 100644 --- a/flake.nix +++ b/flake.nix @@ -74,11 +74,24 @@ }; in { devShells = forAllPlatforms (pkgs: { - default = pkgs.callPackage ./nix/devShell.nix { - zig = - if pkgs.stdenv.hostPlatform.isDarwin - then zig.packages.${pkgs.stdenv.hostPlatform.system}.brew."0.15.2" - else zig.packages.${pkgs.stdenv.hostPlatform.system}."0.15.2"; + default = pkgs.callPackage ./nix/devShell.nix + (let + libfyaml = if pkgs.stdenv.hostPlatform.isDarwin then + pkgs.libfyaml.overrideAttrs (prev: { + # Manually fix libfyaml.pc until NixOS/nixpkgs#515614 is available + postInstall = (prev.postInstall or "") + '' + substituteInPlace "$dev/lib/pkgconfig/libfyaml.pc" \ + --replace-fail " none required" "" + ''; + }) + else pkgs.libfyaml; + + appstream = pkgs.appstream.override { libfyaml = libfyaml; }; + libadwaita = pkgs.libadwaita.override { appstream = appstream; }; + blueprint-compiler = pkgs.blueprint-compiler.override { libadwaita = libadwaita; }; + in + { + zig = zig.packages.${pkgs.stdenv.hostPlatform.system}."0.16.0"; wraptest = pkgs.callPackage ./nix/pkgs/wraptest.nix {}; zon2nix = zon2nix; @@ -90,7 +103,9 @@ wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {}; }; }; - }; + + inherit appstream libadwaita blueprint-compiler; + }); }); packages = diff --git a/nix/devShell.nix b/nix/devShell.nix index b530c56cd..6fa1f14b1 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -27,7 +27,6 @@ wasmtime, wraptest, zig, - zig_0_15, zip, llvmPackages_latest, bzip2, diff --git a/pkg/android-ndk/build.zig b/pkg/android-ndk/build.zig index 5b005665b..2f921e7d6 100644 --- a/pkg/android-ndk/build.zig +++ b/pkg/android-ndk/build.zig @@ -118,16 +118,16 @@ pub fn addPaths(b: *std.Build, step: *std.Build.Step.Compile) !void { fn findNDKPath(b: *std.Build) ?[]const u8 { // Check if user has set the environment variable for the NDK path. - if (std.process.getEnvVarOwned(b.allocator, "ANDROID_NDK_HOME") catch null) |value| { + if (b.graph.environ_map.get("ANDROID_NDK_HOME")) |value| { if (value.len == 0) return null; - var dir = std.fs.openDirAbsolute(value, .{}) catch return null; - defer dir.close(); + var dir = std.Io.Dir.openDirAbsolute(b.graph.io, value, .{}) catch return null; + defer dir.close(b.graph.io); return value; } // Check the common environment variables for the Android SDK path and look for the NDK inside it. inline for (.{ "ANDROID_HOME", "ANDROID_SDK_ROOT" }) |env| { - if (std.process.getEnvVarOwned(b.allocator, env) catch null) |sdk| { + if (b.graph.environ_map.get(env)) |sdk| { if (sdk.len > 0) { if (findLatestNDK(b, sdk)) |ndk| return ndk; } @@ -135,10 +135,9 @@ fn findNDKPath(b: *std.Build) ?[]const u8 { } // As a fallback, we assume the most common/default SDK path based on the OS. - const home = std.process.getEnvVarOwned( - b.allocator, + const home = b.graph.environ_map.get( if (builtin.os.tag == .windows) "LOCALAPPDATA" else "HOME", - ) catch return null; + ) orelse return null; const default_sdk_path = b.pathJoin( &.{ @@ -157,8 +156,8 @@ fn findNDKPath(b: *std.Build) ?[]const u8 { fn findLatestNDK(b: *std.Build, sdk_path: []const u8) ?[]const u8 { const ndk_dir = b.pathJoin(&.{ sdk_path, "ndk" }); - var dir = std.fs.openDirAbsolute(ndk_dir, .{ .iterate = true }) catch return null; - defer dir.close(); + var dir = std.Io.Dir.openDirAbsolute(b.graph.io, ndk_dir, .{ .iterate = true }) catch return null; + defer dir.close(b.graph.io); var latest_: ?struct { name: []const u8, @@ -166,7 +165,7 @@ fn findLatestNDK(b: *std.Build, sdk_path: []const u8) ?[]const u8 { } = null; var iterator = dir.iterate(); - while (iterator.next() catch null) |file| { + while (iterator.next(b.graph.io) catch null) |file| { if (file.kind != .directory) continue; const version = std.SemanticVersion.parse(file.name) catch continue; if (latest_) |latest| { diff --git a/pkg/apple-sdk/build.zig b/pkg/apple-sdk/build.zig index f897919f4..463d4ed4f 100644 --- a/pkg/apple-sdk/build.zig +++ b/pkg/apple-sdk/build.zig @@ -53,14 +53,18 @@ pub fn addPaths( // Detect our SDK using the "findNative" Zig stdlib function. // This is really important because it forces using `xcrun` to // find the SDK path. - const libc = std.zig.LibCInstallation.findNative(.{ - .allocator = b.allocator, - .target = &step.rootModuleTarget(), - .verbose = false, - }) catch break :darwin; + const libc = std.zig.LibCInstallation.findNative( + b.allocator, + b.graph.io, + .{ + .environ_map = &b.graph.environ_map, + .target = &step.rootModuleTarget(), + .verbose = false, + }, + ) catch break :darwin; // Render the file compatible with the `--libc` Zig flag. - var stream: std.io.Writer.Allocating = .init(b.allocator); + var stream: std.Io.Writer.Allocating = .init(b.allocator); defer stream.deinit(); try libc.render(&stream.writer); diff --git a/pkg/breakpad/build.zig b/pkg/breakpad/build.zig index 56d51b159..ce02b34fe 100644 --- a/pkg/breakpad/build.zig +++ b/pkg/breakpad/build.zig @@ -9,11 +9,11 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libcpp = true, }), .linkage = .static, }); - lib.linkLibCpp(); - lib.addIncludePath(b.path("vendor")); + lib.root_module.addIncludePath(b.path("vendor")); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); @@ -23,20 +23,20 @@ pub fn build(b: *std.Build) !void { defer flags.deinit(b.allocator); if (b.lazyDependency("breakpad", .{})) |upstream| { - lib.addIncludePath(upstream.path("src")); - lib.addCSourceFiles(.{ + lib.root_module.addIncludePath(upstream.path("src")); + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = common, .flags = flags.items, }); if (target.result.os.tag.isDarwin()) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = common_apple, .flags = flags.items, }); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = client_apple, .flags = flags.items, @@ -44,19 +44,19 @@ pub fn build(b: *std.Build) !void { switch (target.result.os.tag) { .macos => { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = common_mac, .flags = flags.items, }); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = client_mac, .flags = flags.items, }); }, - .ios => lib.addCSourceFiles(.{ + .ios => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = client_ios, .flags = flags.items, @@ -67,12 +67,12 @@ pub fn build(b: *std.Build) !void { } if (target.result.os.tag == .linux) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = common_linux, .flags = flags.items, }); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = client_linux, .flags = flags.items, diff --git a/pkg/dcimgui/build.zig b/pkg/dcimgui/build.zig index 924e7c932..f74d3673b 100644 --- a/pkg/dcimgui/build.zig +++ b/pkg/dcimgui/build.zig @@ -22,18 +22,16 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, }), .linkage = .static, }); - lib.linkLibC(); - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime headers. - // The MSVC SDK include directories (added via linkLibC) contain - // both C and C++ headers, so linkLibCpp is not needed. - if (target.result.abi != .msvc) { - lib.linkLibCpp(); - } b.installArtifact(lib); // Zig module @@ -87,8 +85,8 @@ pub fn build(b: *std.Build) !void { // Add the core Dear Imgui source files if (b.lazyDependency("imgui", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); - lib.addCSourceFiles(.{ + lib.root_module.addIncludePath(upstream.path("")); + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "imgui_demo.cpp", @@ -107,20 +105,20 @@ pub fn build(b: *std.Build) !void { ); if (freetype) { - lib.addCSourceFile(.{ + lib.root_module.addCSourceFile(.{ .file = upstream.path("misc/freetype/imgui_freetype.cpp"), .flags = flags.items, }); if (b.systemIntegrationOption("freetype", .{})) { - lib.linkSystemLibrary2("freetype2", dynamic_link_opts); + lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { const freetype_dep = b.dependency("freetype", .{ .target = target, .optimize = optimize, .@"enable-libpng" = true, }); - lib.linkLibrary(freetype_dep.artifact("freetype")); + lib.root_module.linkLibrary(freetype_dep.artifact("freetype")); if (freetype_dep.builder.lazyDependency( "freetype", .{}, @@ -131,7 +129,7 @@ pub fn build(b: *std.Build) !void { } if (backend_metal) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path("backends"), .files = &.{"imgui_impl_metal.mm"}, .flags = flags.items, @@ -143,7 +141,7 @@ pub fn build(b: *std.Build) !void { ); } if (backend_osx) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path("backends"), .files = &.{"imgui_impl_osx.mm"}, .flags = flags.items, @@ -155,7 +153,7 @@ pub fn build(b: *std.Build) !void { ); } if (backend_opengl3) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path("backends"), .files = &.{"imgui_impl_opengl3.cpp"}, .flags = flags.items, @@ -170,8 +168,8 @@ pub fn build(b: *std.Build) !void { // Add the C bindings if (b.lazyDependency("bindings", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); - lib.addCSourceFiles(.{ + lib.root_module.addIncludePath(upstream.path("")); + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "dcimgui.cpp", @@ -179,7 +177,7 @@ pub fn build(b: *std.Build) !void { }, .flags = flags.items, }); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = b.path(""), .files = &.{"ext.cpp"}, .flags = flags.items, @@ -201,7 +199,7 @@ pub fn build(b: *std.Build) !void { }), }); test_exe.root_module.addOptions("build_options", options); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); const tests_run = b.addRunArtifact(test_exe); const test_step = b.step("test", "Run tests"); test_step.dependOn(&tests_run.step); diff --git a/pkg/fontconfig/build.zig b/pkg/fontconfig/build.zig index 7c87d1f2e..cd507454f 100644 --- a/pkg/fontconfig/build.zig +++ b/pkg/fontconfig/build.zig @@ -41,7 +41,7 @@ pub fn build(b: *std.Build) !void { if (b.systemIntegrationOption("fontconfig", .{})) { module.linkSystemLibrary("fontconfig", dynamic_link_opts); - test_exe.linkSystemLibrary2("fontconfig", dynamic_link_opts); + test_exe.root_module.linkSystemLibrary("fontconfig", dynamic_link_opts); } else { const lib = try buildLib(b, module, .{ .target = target, @@ -54,7 +54,7 @@ pub fn build(b: *std.Build) !void { .dynamic_link_opts = dynamic_link_opts, }); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); } } @@ -71,15 +71,18 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); + + const dynamic_link_opts = options.dynamic_link_opts; + if (target.result.os.tag != .windows) { - lib.linkSystemLibrary("pthread"); + lib.root_module.linkSystemLibrary("pthread", dynamic_link_opts); } - lib.addIncludePath(b.path("override/include")); + lib.root_module.addIncludePath(b.path("override/include")); module.addIncludePath(b.path("override/include")); var flags: std.ArrayList([]const u8) = .empty; @@ -194,19 +197,17 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu } } - const dynamic_link_opts = options.dynamic_link_opts; - // Freetype2 _ = b.systemIntegrationOption("freetype", .{}); // So it shows up in help if (freetype_enabled) { if (b.systemIntegrationOption("freetype", .{})) { - lib.linkSystemLibrary2("freetype2", dynamic_link_opts); + lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { if (b.lazyDependency( "freetype", .{ .target = target, .optimize = optimize }, )) |freetype_dep| { - lib.linkLibrary(freetype_dep.artifact("freetype")); + lib.root_module.linkLibrary(freetype_dep.artifact("freetype")); } } } @@ -227,22 +228,22 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu } if (b.systemIntegrationOption("libxml2", .{})) { - lib.linkSystemLibrary2("libxml-2.0", dynamic_link_opts); + lib.root_module.linkSystemLibrary("libxml-2.0", dynamic_link_opts); } else { if (b.lazyDependency("libxml2", .{ .target = target, .optimize = optimize, .iconv = libxml2_iconv_enabled, })) |libxml2_dep| { - lib.linkLibrary(libxml2_dep.artifact("xml2")); + lib.root_module.linkLibrary(libxml2_dep.artifact("xml2")); } } } if (b.lazyDependency("fontconfig", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, diff --git a/pkg/freetype/build.zig b/pkg/freetype/build.zig index b85310a5b..c1f4535c7 100644 --- a/pkg/freetype/build.zig +++ b/pkg/freetype/build.zig @@ -39,7 +39,7 @@ pub fn build(b: *std.Build) !void { if (b.systemIntegrationOption("freetype", .{})) { module.linkSystemLibrary("freetype2", dynamic_link_opts); if (test_exe) |exe| { - exe.linkSystemLibrary2("freetype2", dynamic_link_opts); + exe.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } } else { const lib = try buildLib(b, module, .{ @@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void { }); if (test_exe) |exe| { - exe.linkLibrary(lib); + exe.root_module.linkLibrary(lib); } } } @@ -68,10 +68,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); @@ -101,10 +101,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu // Zlib if (b.systemIntegrationOption("zlib", .{})) { - lib.linkSystemLibrary2("zlib", dynamic_link_opts); + lib.root_module.linkSystemLibrary("zlib", dynamic_link_opts); } else { const zlib_dep = b.dependency("zlib", .{ .target = target, .optimize = optimize }); - lib.linkLibrary(zlib_dep.artifact("z")); + lib.root_module.linkLibrary(zlib_dep.artifact("z")); } // Libpng @@ -113,50 +113,50 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu try flags.append(b.allocator, "-DFT_CONFIG_OPTION_USE_PNG=1"); if (b.systemIntegrationOption("libpng", .{})) { - lib.linkSystemLibrary2("libpng", dynamic_link_opts); + lib.root_module.linkSystemLibrary("libpng", dynamic_link_opts); } else { const libpng_dep = b.dependency( "libpng", .{ .target = target, .optimize = optimize }, ); - lib.linkLibrary(libpng_dep.artifact("png")); + lib.root_module.linkLibrary(libpng_dep.artifact("png")); } } if (b.lazyDependency("freetype", .{})) |upstream| { - lib.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("include")); module.addIncludePath(upstream.path("include")); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, }); switch (target.result.os.tag) { - .linux => lib.addCSourceFile(.{ + .linux => lib.root_module.addCSourceFile(.{ .file = upstream.path("builds/unix/ftsystem.c"), .flags = flags.items, }), - .windows => lib.addCSourceFile(.{ + .windows => lib.root_module.addCSourceFile(.{ .file = upstream.path("builds/windows/ftsystem.c"), .flags = flags.items, }), - else => lib.addCSourceFile(.{ + else => lib.root_module.addCSourceFile(.{ .file = upstream.path("src/base/ftsystem.c"), .flags = flags.items, }), } switch (target.result.os.tag) { .windows => { - lib.addCSourceFile(.{ + lib.root_module.addCSourceFile(.{ .file = upstream.path("builds/windows/ftdebug.c"), .flags = flags.items, }); - lib.addWin32ResourceFile(.{ + lib.root_module.addWin32ResourceFile(.{ .file = upstream.path("src/base/ftver.rc"), }); }, - else => lib.addCSourceFile(.{ + else => lib.root_module.addCSourceFile(.{ .file = upstream.path("src/base/ftdebug.c"), .flags = flags.items, }), diff --git a/pkg/glslang/build.zig b/pkg/glslang/build.zig index 1dc82a6e3..92e614b1c 100644 --- a/pkg/glslang/build.zig +++ b/pkg/glslang/build.zig @@ -26,7 +26,7 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }), }); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); const tests_run = b.addRunArtifact(test_exe); const test_step = b.step("test", "Run tests"); test_step.dependOn(&tests_run.step); @@ -47,20 +47,18 @@ fn buildGlslang( .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, }), .linkage = .static, }); - lib.linkLibC(); - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime headers. - // The MSVC SDK include directories (added via linkLibC) contain - // both C and C++ headers, so linkLibCpp is not needed. - if (target.result.abi != .msvc) { - lib.linkLibCpp(); - } - if (upstream_) |upstream| lib.addIncludePath(upstream.path("")); - lib.addIncludePath(b.path("override")); + if (upstream_) |upstream| lib.root_module.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(b.path("override")); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); @@ -82,7 +80,7 @@ fn buildGlslang( } if (upstream_) |upstream| { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .flags = flags.items, .files = &.{ @@ -141,7 +139,7 @@ fn buildGlslang( }); if (target.result.os.tag != .windows) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .flags = flags.items, .files = &.{ @@ -149,7 +147,7 @@ fn buildGlslang( }, }); } else { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .flags = flags.items, .files = &.{ diff --git a/pkg/gtk4-layer-shell/build.zig b/pkg/gtk4-layer-shell/build.zig index 818b48f45..06c0390b0 100644 --- a/pkg/gtk4-layer-shell/build.zig +++ b/pkg/gtk4-layer-shell/build.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Translator = @import("translate_c").Translator; const version = @import("build.zig.zon").version; @@ -17,11 +18,21 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); + const translate_c = b.dependency("translate_c", .{}); + const headers: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + // Needs the gtk.h header - module.linkSystemLibrary("gtk4", dynamic_link_opts); + headers.linkSystemLibrary("gtk4", dynamic_link_opts); if (b.systemIntegrationOption("gtk4-layer-shell", .{})) { - module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts); + headers.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts); + module.addImport("c", headers.mod); } else { _ = try buildLib(b, module, .{ .target = target, @@ -42,6 +53,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), }); b.installArtifact(lib); @@ -52,13 +64,12 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu const upstream = upstream_ orelse return lib; const wayland_protocols = wayland_protocols_ orelse return lib; - lib.linkLibC(); - lib.addIncludePath(upstream.path("include")); - lib.addIncludePath(upstream.path("src")); + lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("src")); module.addIncludePath(upstream.path("include")); // GTK - lib.linkSystemLibrary2("gtk4", dynamic_link_opts); + lib.root_module.linkSystemLibrary("gtk4", dynamic_link_opts); // Wayland headers and source files { @@ -92,9 +103,9 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu const source_scanner = b.addSystemCommand(&.{ "wayland-scanner", "private-code" }); source_scanner.addFileArg(xml); const source = source_scanner.addOutputFileArg(b.fmt("{s}.c", .{name})); - lib.addCSourceFile(.{ .file = source }); + lib.root_module.addCSourceFile(.{ .file = source }); } - lib.addIncludePath(wf.getDirectory()); + lib.root_module.addIncludePath(wf.getDirectory()); } lib.installHeadersDirectory( @@ -113,7 +124,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu "stubbed-surface.c", "xdg-surface-server.c", }; - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path("src"), .files = srcs, .flags = &.{ diff --git a/pkg/gtk4-layer-shell/build.zig.zon b/pkg/gtk4-layer-shell/build.zig.zon index 9329e374f..2ffb7069a 100644 --- a/pkg/gtk4-layer-shell/build.zig.zon +++ b/pkg/gtk4-layer-shell/build.zig.zon @@ -14,5 +14,10 @@ .hash = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", .lazy = true, }, + .translate_c = .{ + .url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz", + .hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa", + .lazy = true, + }, }, } diff --git a/pkg/gtk4-layer-shell/src/main.zig b/pkg/gtk4-layer-shell/src/main.zig index a15313231..85d607ec6 100644 --- a/pkg/gtk4-layer-shell/src/main.zig +++ b/pkg/gtk4-layer-shell/src/main.zig @@ -1,8 +1,9 @@ const std = @import("std"); -const c = @cImport({ - @cInclude("gtk4-layer-shell.h"); -}); +// const c = @cImport({ +// @cInclude("gtk4-layer-shell.h"); +// }); +const c = @import("c"); const gdk = @import("gdk"); const gtk = @import("gtk"); diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig index b482bc8a3..86e2df74e 100644 --- a/pkg/harfbuzz/build.zig +++ b/pkg/harfbuzz/build.zig @@ -54,9 +54,9 @@ pub fn build(b: *std.Build) !void { var it = module.import_table.iterator(); while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*); if (b.systemIntegrationOption("freetype", .{})) { - test_exe.linkSystemLibrary2("freetype2", dynamic_link_opts); + test_exe.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { - test_exe.linkLibrary(freetype.artifact("freetype")); + test_exe.root_module.linkLibrary(freetype.artifact("freetype")); } const tests_run = b.addRunArtifact(test_exe); const test_step = b.step("test", "Run tests"); @@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void { if (b.systemIntegrationOption("harfbuzz", .{})) { module.linkSystemLibrary("harfbuzz", dynamic_link_opts); - test_exe.linkSystemLibrary2("harfbuzz", dynamic_link_opts); + test_exe.root_module.linkSystemLibrary("harfbuzz", dynamic_link_opts); } else { const lib = try buildLib(b, module, .{ .target = target, @@ -77,7 +77,7 @@ pub fn build(b: *std.Build) !void { .dynamic_link_opts = dynamic_link_opts, }); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); } } @@ -99,18 +99,16 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, }), .linkage = .static, }); - lib.linkLibC(); - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime headers. - // The MSVC SDK include directories (added via linkLibC) contain - // both C and C++ headers, so linkLibCpp is not needed. - if (target.result.abi != .msvc) { - lib.linkLibCpp(); - } if (target.result.os.tag.isDarwin()) { try apple_sdk.addPaths(b, lib); @@ -154,10 +152,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu }); if (b.systemIntegrationOption("freetype", .{})) { - lib.linkSystemLibrary2("freetype2", dynamic_link_opts); + lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { - lib.linkLibrary(freetype.artifact("freetype")); + lib.root_module.linkLibrary(freetype.artifact("freetype")); if (freetype.builder.lazyDependency( "freetype", @@ -170,14 +168,14 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu if (coretext_enabled) { try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"}); - lib.linkFramework("CoreText"); + lib.root_module.linkFramework("CoreText", .{}); module.linkFramework("CoreText", .{}); } if (b.lazyDependency("harfbuzz", .{})) |upstream| { - lib.addIncludePath(upstream.path("src")); + lib.root_module.addIncludePath(upstream.path("src")); module.addIncludePath(upstream.path("src")); - lib.addCSourceFile(.{ + lib.root_module.addCSourceFile(.{ .file = upstream.path("src/harfbuzz.cc"), .flags = flags.items, }); diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig index 0ac776123..9e8ac22f2 100644 --- a/pkg/highway/build.zig +++ b/pkg/highway/build.zig @@ -18,17 +18,16 @@ pub fn build(b: *std.Build) !void { .root_source_file = b.path("src/detect.zig"), .target = target, .optimize = optimize, + // Our highway package is free of libc at runtime (uses no symbols) + // but does require libc headers at compile time. + .link_libc = true, }), .linkage = .static, }); - // Our highway package is free of libc at runtime (uses no symbols) - // but does require libc headers at compile time. - lib.linkLibC(); - - lib.addIncludePath(b.path("src/cpp")); + lib.root_module.addIncludePath(b.path("src/cpp")); if (upstream_) |upstream| { - lib.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); } @@ -95,7 +94,7 @@ pub fn build(b: *std.Build) !void { }); } - lib.addCSourceFiles(.{ .flags = flags.items, .files = &.{ + lib.root_module.addCSourceFiles(.{ .flags = flags.items, .files = &.{ "src/cpp/abort.cc", "src/cpp/per_target.cc", "src/cpp/targets.cpp", @@ -120,7 +119,7 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }), }); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); var it = module.import_table.iterator(); while (it.next()) |entry| test_exe.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*); diff --git a/pkg/libintl/build.zig b/pkg/libintl/build.zig index 32221e5ad..8ef26c7a4 100644 --- a/pkg/libintl/build.zig +++ b/pkg/libintl/build.zig @@ -35,11 +35,11 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); - lib.addIncludePath(b.path("")); + lib.root_module.addIncludePath(b.path("")); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); @@ -47,9 +47,9 @@ pub fn build(b: *std.Build) !void { } if (b.lazyDependency("gettext", .{})) |upstream| { - lib.addIncludePath(upstream.path("gettext-runtime/intl")); - lib.addIncludePath(upstream.path("gettext-runtime/intl/gnulib-lib")); - lib.addCSourceFiles(.{ + lib.root_module.addIncludePath(upstream.path("gettext-runtime/intl")); + lib.root_module.addIncludePath(upstream.path("gettext-runtime/intl/gnulib-lib")); + lib.root_module.addCSourceFiles(.{ .root = upstream.path("gettext-runtime/intl"), .files = srcs, .flags = flags.items, diff --git a/pkg/libpng/build.zig b/pkg/libpng/build.zig index 8734b28f9..559f42a06 100644 --- a/pkg/libpng/build.zig +++ b/pkg/libpng/build.zig @@ -9,18 +9,10 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); - if (target.result.os.tag == .linux) { - lib.linkSystemLibrary("m"); - } - if (target.result.os.tag.isDarwin()) { - const apple_sdk = @import("apple_sdk"); - try apple_sdk.addPaths(b, lib); - } - // For dynamic linking, we prefer dynamic linking and to search by // mode first. Mode first will search all paths for a dynamic library // before falling back to static. @@ -28,20 +20,27 @@ pub fn build(b: *std.Build) !void { .preferred_link_mode = .dynamic, .search_strategy = .mode_first, }; + if (target.result.os.tag == .linux) { + lib.root_module.linkSystemLibrary("m", dynamic_link_opts); + } + if (target.result.os.tag.isDarwin()) { + const apple_sdk = @import("apple_sdk"); + try apple_sdk.addPaths(b, lib); + } if (b.systemIntegrationOption("zlib", .{})) { - lib.linkSystemLibrary2("zlib", dynamic_link_opts); + lib.root_module.linkSystemLibrary("zlib", dynamic_link_opts); } else { if (b.lazyDependency( "zlib", .{ .target = target, .optimize = optimize }, )) |zlib_dep| { - lib.linkLibrary(zlib_dep.artifact("z")); - lib.addIncludePath(b.path("")); + lib.root_module.linkLibrary(zlib_dep.artifact("z")); + lib.root_module.addIncludePath(b.path("")); } if (b.lazyDependency("libpng", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(upstream.path("")); } } @@ -61,7 +60,7 @@ pub fn build(b: *std.Build) !void { }); } - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, diff --git a/pkg/libxml2/build.zig b/pkg/libxml2/build.zig index a9b3e4b1a..07a45e947 100644 --- a/pkg/libxml2/build.zig +++ b/pkg/libxml2/build.zig @@ -11,18 +11,25 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); - if (upstream_) |upstream| lib.addIncludePath(upstream.path("include")); - lib.addIncludePath(b.path("override/include")); + if (upstream_) |upstream| lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(b.path("override/include")); if (target.result.os.tag == .windows) { - lib.addIncludePath(b.path("override/config/win32")); - lib.linkSystemLibrary("ws2_32"); + lib.root_module.addIncludePath(b.path("override/config/win32")); + // For dynamic linking, we prefer dynamic linking and to search by + // mode first. Mode first will search all paths for a dynamic library + // before falling back to static. + const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, + }; + lib.root_module.linkSystemLibrary("ws2_32", dynamic_link_opts); } else { - lib.addIncludePath(b.path("override/config/posix")); + lib.root_module.addIncludePath(b.path("override/config/posix")); } var flags: std.ArrayList([]const u8) = .empty; @@ -98,7 +105,7 @@ pub fn build(b: *std.Build) !void { } if (upstream_) |upstream| { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, diff --git a/pkg/macos/build.zig b/pkg/macos/build.zig index 0525e481e..448abc280 100644 --- a/pkg/macos/build.zig +++ b/pkg/macos/build.zig @@ -21,21 +21,21 @@ pub fn build(b: *std.Build) !void { .linkage = .static, }); - lib.addCSourceFile(.{ + lib.root_module.addCSourceFile(.{ .file = b.path("os/zig_macos.c"), .flags = &.{"-std=c99"}, }); - lib.addCSourceFile(.{ + lib.root_module.addCSourceFile(.{ .file = b.path("text/ext.c"), }); - lib.linkFramework("CoreFoundation"); - lib.linkFramework("CoreGraphics"); - lib.linkFramework("CoreText"); - lib.linkFramework("CoreVideo"); - lib.linkFramework("QuartzCore"); - lib.linkFramework("IOSurface"); + lib.root_module.linkFramework("CoreFoundation", .{}); + lib.root_module.linkFramework("CoreGraphics", .{}); + lib.root_module.linkFramework("CoreText", .{}); + lib.root_module.linkFramework("CoreVideo", .{}); + lib.root_module.linkFramework("QuartzCore", .{}); + lib.root_module.linkFramework("IOSurface", .{}); if (target.result.os.tag == .macos) { - lib.linkFramework("Carbon"); + lib.root_module.linkFramework("Carbon", .{}); module.linkFramework("Carbon", .{}); } @@ -63,7 +63,7 @@ pub fn build(b: *std.Build) !void { if (target.result.os.tag.isDarwin()) { try apple_sdk.addPaths(b, test_exe); } - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); var it = module.import_table.iterator(); while (it.next()) |entry| { diff --git a/pkg/oniguruma/build.zig b/pkg/oniguruma/build.zig index d142e5eb1..f7df86e1b 100644 --- a/pkg/oniguruma/build.zig +++ b/pkg/oniguruma/build.zig @@ -41,7 +41,7 @@ pub fn build(b: *std.Build) !void { module.linkSystemLibrary("oniguruma", dynamic_link_opts); if (test_exe) |exe| { - exe.linkSystemLibrary2("oniguruma", dynamic_link_opts); + exe.root_module.linkSystemLibrary("oniguruma", dynamic_link_opts); } } else { const lib = try buildLib(b, module, .{ @@ -50,7 +50,7 @@ pub fn build(b: *std.Build) !void { }); if (test_exe) |exe| { - exe.linkLibrary(lib); + exe.root_module.linkLibrary(lib); } } } @@ -64,12 +64,12 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); const t = target.result; const is_windows = t.os.tag == .windows; - lib.linkLibC(); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); @@ -77,10 +77,10 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu } if (b.lazyDependency("oniguruma", .{})) |upstream| { - lib.addIncludePath(upstream.path("src")); + lib.root_module.addIncludePath(upstream.path("src")); module.addIncludePath(upstream.path("src")); - lib.addConfigHeader(b.addConfigHeader(.{ + lib.root_module.addConfigHeader(b.addConfigHeader(.{ .style = .{ .cmake = upstream.path("src/config.h.cmake.in") }, }, .{ .PACKAGE = "oniguruma", @@ -109,7 +109,7 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu "-fno-sanitize-trap=undefined", }); } - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .flags = flags.items, .files = &.{ diff --git a/pkg/sentry/build.zig b/pkg/sentry/build.zig index 3c88df56d..7eb755f65 100644 --- a/pkg/sentry/build.zig +++ b/pkg/sentry/build.zig @@ -17,10 +17,10 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); @@ -46,9 +46,9 @@ pub fn build(b: *std.Build) !void { if (b.lazyDependency("sentry", .{})) |upstream| { module.addIncludePath(upstream.path("include")); - lib.addIncludePath(upstream.path("include")); - lib.addIncludePath(upstream.path("src")); - lib.addCSourceFiles(.{ + lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("src")); + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, @@ -56,7 +56,7 @@ pub fn build(b: *std.Build) !void { // Linux-only if (target.result.os.tag == .linux) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "vendor/stb_sprintf.c", @@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void { // Symbolizer + Unwinder if (target.result.os.tag == .windows) { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/sentry_windows_dbghelp.c", @@ -78,7 +78,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }); } else { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/sentry_unix_pageallocator.c", @@ -92,7 +92,7 @@ pub fn build(b: *std.Build) !void { // Module finder switch (target.result.os.tag) { - .windows => lib.addCSourceFiles(.{ + .windows => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/modulefinder/sentry_modulefinder_windows.c", @@ -100,7 +100,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }), - .macos, .ios => lib.addCSourceFiles(.{ + .macos, .ios => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/modulefinder/sentry_modulefinder_apple.c", @@ -108,7 +108,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }), - .linux => lib.addCSourceFiles(.{ + .linux => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/modulefinder/sentry_modulefinder_linux.c", @@ -126,7 +126,7 @@ pub fn build(b: *std.Build) !void { // Transport switch (transport) { - .curl => lib.addCSourceFiles(.{ + .curl => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/transports/sentry_transport_curl.c", @@ -134,7 +134,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }), - .winhttp => lib.addCSourceFiles(.{ + .winhttp => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/transports/sentry_transport_winhttp.c", @@ -142,7 +142,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }), - .none => lib.addCSourceFiles(.{ + .none => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/transports/sentry_transport_none.c", @@ -153,7 +153,7 @@ pub fn build(b: *std.Build) !void { // Backend switch (backend) { - .crashpad => lib.addCSourceFiles(.{ + .crashpad => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/backends/sentry_backend_crashpad.cpp", @@ -162,7 +162,7 @@ pub fn build(b: *std.Build) !void { }), .breakpad => { - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/backends/sentry_backend_breakpad.cpp", @@ -174,15 +174,15 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, })) |breakpad_dep| { - lib.linkLibrary(breakpad_dep.artifact("breakpad")); + lib.root_module.linkLibrary(breakpad_dep.artifact("breakpad")); // We need to add this because Sentry includes some breakpad // headers that include this vendored file... - lib.addIncludePath(breakpad_dep.path("vendor")); + lib.root_module.addIncludePath(breakpad_dep.path("vendor")); } }, - .inproc => lib.addCSourceFiles(.{ + .inproc => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/backends/sentry_backend_inproc.c", @@ -190,7 +190,7 @@ pub fn build(b: *std.Build) !void { .flags = flags.items, }), - .none => lib.addCSourceFiles(.{ + .none => lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = &.{ "src/backends/sentry_backend_none.c", diff --git a/pkg/simdutf/build.zig b/pkg/simdutf/build.zig index 0859edc26..52afef667 100644 --- a/pkg/simdutf/build.zig +++ b/pkg/simdutf/build.zig @@ -10,26 +10,21 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, + // We link libcpp even with no_libcxx because simdutf requires + // libc++ headers at build time. But it doesn't require libc++ at + // runtime. For Ghostty itself, we have CI tests to verify this. + // + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, }), .linkage = .static, }); - lib.addIncludePath(b.path("vendor")); - lib.linkLibC(); - libcpp: { - if (target.result.abi == .msvc) { - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime headers. - // The MSVC SDK include directories (added via linkLibC) contain - // both C and C++ headers, so linkLibCpp is not needed. - break :libcpp; - } - - // We link libcpp even with no_libcxx because simdutf requires - // libc++ headers at build time. But it doesn't require libc++ - // at runtime. For Ghostty itself, we have CI tests to verify this. - lib.linkLibCpp(); - } + lib.root_module.addIncludePath(b.path("vendor")); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); @@ -74,7 +69,7 @@ pub fn build(b: *std.Build) !void { try flags.append(b.allocator, "-fPIC"); } - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .flags = flags.items, .files = &.{ "vendor/simdutf.cpp", diff --git a/pkg/spirv-cross/build.zig b/pkg/spirv-cross/build.zig index 72ce61eb6..3b85e8f49 100644 --- a/pkg/spirv-cross/build.zig +++ b/pkg/spirv-cross/build.zig @@ -34,12 +34,12 @@ pub fn build(b: *std.Build) !void { if (b.systemIntegrationOption("spirv-cross", .{})) { module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts); if (test_exe) |exe| { - exe.linkSystemLibrary2("spirv-cross-c-shared", dynamic_link_opts); + exe.root_module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts); } } else { const lib = try buildSpirvCross(b, module, target, optimize); b.installArtifact(lib); - if (test_exe) |exe| exe.linkLibrary(lib); + if (test_exe) |exe| exe.root_module.linkLibrary(lib); } } @@ -54,18 +54,16 @@ fn buildSpirvCross( .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, + // On MSVC, we must not use linkLibCpp because Zig unconditionally + // passes -nostdinc++ and then adds its bundled libc++/libc++abi + // include paths, which conflict with MSVC's own C++ runtime + // headers. The MSVC SDK include directories (added via linkLibC) + // contain both C and C++ headers, so linkLibCpp is not needed. + .link_libcpp = target.result.abi != .msvc, }), .linkage = .static, }); - lib.linkLibC(); - // On MSVC, we must not use linkLibCpp because Zig unconditionally - // passes -nostdinc++ and then adds its bundled libc++/libc++abi - // include paths, which conflict with MSVC's own C++ runtime headers. - // The MSVC SDK include directories (added via linkLibC) contain - // both C and C++ headers, so linkLibCpp is not needed. - if (target.result.abi != .msvc) { - lib.linkLibCpp(); - } if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); @@ -86,9 +84,9 @@ fn buildSpirvCross( } if (b.lazyDependency("spirv_cross", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(upstream.path("")); module.addIncludePath(upstream.path("")); - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .flags = flags.items, .files = &.{ diff --git a/pkg/wuffs/build.zig b/pkg/wuffs/build.zig index 95cef3e09..7f71d7b01 100644 --- a/pkg/wuffs/build.zig +++ b/pkg/wuffs/build.zig @@ -1,4 +1,38 @@ const std = @import("std"); +const Translator = @import("translate_c").Translator; + +// All the C macros defined so that the header matches the build. +const defines = [_][]const u8{ + "WUFFS_CONFIG__MODULES", + "WUFFS_CONFIG__MODULE__AUX__BASE", + "WUFFS_CONFIG__MODULE__AUX__IMAGE", + "WUFFS_CONFIG__MODULE__BASE", + "WUFFS_CONFIG__MODULE__ADLER32", + "WUFFS_CONFIG__MODULE__CRC32", + "WUFFS_CONFIG__MODULE__DEFLATE", + "WUFFS_CONFIG__MODULE__JPEG", + "WUFFS_CONFIG__MODULE__PNG", + "WUFFS_CONFIG__MODULE__ZLIB", +}; + +// Generated C code, includes the macros above. Designed to mimic old c.zig. +// TODO: is this still needed, or are the -D flags enough? +const wuffs_c_source = wuffs_c_source: { + const include: []const u8 = "#include "; + const len = len: { + var len: usize = 0; + for (defines) |d| len += std.fmt.count("#define {s}\n", .{d}); + len += std.fmt.count("{s}\n", .{include}); + break :len len; + }; + + var buf: [len:0]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + for (defines) |d| writer.print("#define {s}\n", .{d}) catch unreachable; + writer.print("{s}\n", .{include}) catch unreachable; + buf[len] = 0; + break :wuffs_c_source buf; +}; pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); @@ -15,25 +49,35 @@ pub fn build(b: *std.Build) !void { .name = "test", .root_module = module, }); - unit_tests.linkLibC(); - var flags: std.ArrayList([]const u8) = .empty; - defer flags.deinit(b.allocator); - try flags.append(b.allocator, "-DWUFFS_IMPLEMENTATION"); - if (target.result.abi == .msvc) { - try flags.append(b.allocator, "-fno-sanitize=undefined"); - try flags.append(b.allocator, "-fno-sanitize-trap=undefined"); - } - inline for (@import("src/c.zig").defines) |key| { - try flags.append(b.allocator, "-D" ++ key); - } - - if (b.lazyDependency("wuffs", .{})) |wuffs_dep| { - module.addIncludePath(wuffs_dep.path("release/c")); - module.addCSourceFile(.{ - .file = wuffs_dep.path("release/c/wuffs-v0.4.c"), - .flags = flags.items, + { + const translate_c = b.dependency("translate_c", .{}); + const wuffs_c: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("wuffs_c.h", &wuffs_c_source), + .target = target, + .optimize = optimize, }); + + var flags: std.ArrayList([]const u8) = .empty; + defer flags.deinit(b.allocator); + try flags.append(b.allocator, "-DWUFFS_IMPLEMENTATION"); + if (target.result.abi == .msvc) { + try flags.append(b.allocator, "-fno-sanitize=undefined"); + try flags.append(b.allocator, "-fno-sanitize-trap=undefined"); + } + inline for (defines) |key| { + try flags.append(b.allocator, "-D" ++ key); + } + + if (b.lazyDependency("wuffs", .{})) |wuffs_dep| { + wuffs_c.addIncludePath(wuffs_dep.path("release/c")); + wuffs_c.mod.addCSourceFile(.{ + .file = wuffs_dep.path("release/c/wuffs-v0.4.c"), + .flags = flags.items, + }); + } + + module.addImport("wuffs_c", wuffs_c.mod); } if (b.lazyDependency("pixels", .{})) |pixels_dep| { diff --git a/pkg/wuffs/build.zig.zon b/pkg/wuffs/build.zig.zon index eb99ba0b0..5e3ff95a1 100644 --- a/pkg/wuffs/build.zig.zon +++ b/pkg/wuffs/build.zig.zon @@ -3,6 +3,11 @@ .version = "0.0.0", .fingerprint = 0x67c0c059de921c4f, .dependencies = .{ + .translate_c = .{ + .url = "https://codeberg.org/vancluever/translate-c/archive/c401682d0cbc6bc1f883d84886b8b1346922268d.tar.gz", + .hash = "translate_c-1.0.0-Q_BUWjnzBgDZX5ADyqP5K0kVqd-otnnj6Sld8Kzouvsa", + }, + // google/wuffs .wuffs = .{ .url = "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", diff --git a/pkg/wuffs/src/c.zig b/pkg/wuffs/src/c.zig deleted file mode 100644 index d94247df3..000000000 --- a/pkg/wuffs/src/c.zig +++ /dev/null @@ -1,18 +0,0 @@ -pub const c = @cImport({ - for (defines) |d| @cDefine(d, "1"); - @cInclude("wuffs-v0.4.c"); -}); - -/// All the C macros defined so that the header matches the build. -pub const defines: []const []const u8 = &[_][]const u8{ - "WUFFS_CONFIG__MODULES", - "WUFFS_CONFIG__MODULE__AUX__BASE", - "WUFFS_CONFIG__MODULE__AUX__IMAGE", - "WUFFS_CONFIG__MODULE__BASE", - "WUFFS_CONFIG__MODULE__ADLER32", - "WUFFS_CONFIG__MODULE__CRC32", - "WUFFS_CONFIG__MODULE__DEFLATE", - "WUFFS_CONFIG__MODULE__JPEG", - "WUFFS_CONFIG__MODULE__PNG", - "WUFFS_CONFIG__MODULE__ZLIB", -}; diff --git a/pkg/wuffs/src/error.zig b/pkg/wuffs/src/error.zig index 0be55cf4e..e4ca5c1da 100644 --- a/pkg/wuffs/src/error.zig +++ b/pkg/wuffs/src/error.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("wuffs_c"); pub const Error = std.mem.Allocator.Error || error{ WuffsError, Overflow }; diff --git a/pkg/wuffs/src/jpeg.zig b/pkg/wuffs/src/jpeg.zig index 69d91c8a9..a7bc4bdd2 100644 --- a/pkg/wuffs/src/jpeg.zig +++ b/pkg/wuffs/src/jpeg.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const c = @import("c.zig").c; +const c = @import("wuffs_c"); const Error = @import("error.zig").Error; const check = @import("error.zig").check; const ImageData = @import("main.zig").ImageData; diff --git a/pkg/wuffs/src/png.zig b/pkg/wuffs/src/png.zig index 57a0e63bb..4bf3d759c 100644 --- a/pkg/wuffs/src/png.zig +++ b/pkg/wuffs/src/png.zig @@ -1,6 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const c = @import("c.zig").c; +const c = @import("wuffs_c"); const Error = @import("error.zig").Error; const check = @import("error.zig").check; const ImageData = @import("main.zig").ImageData; diff --git a/pkg/wuffs/src/swizzle.zig b/pkg/wuffs/src/swizzle.zig index 352cf2b50..9e7ffa5cb 100644 --- a/pkg/wuffs/src/swizzle.zig +++ b/pkg/wuffs/src/swizzle.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const c = @import("c.zig").c; +const c = @import("wuffs_c"); const Error = @import("error.zig").Error; const log = std.log.scoped(.wuffs_swizzler); diff --git a/pkg/zlib/build.zig b/pkg/zlib/build.zig index 64db13aa1..3fa9e6bd2 100644 --- a/pkg/zlib/build.zig +++ b/pkg/zlib/build.zig @@ -9,17 +9,17 @@ pub fn build(b: *std.Build) !void { .root_module = b.createModule(.{ .target = target, .optimize = optimize, + .link_libc = true, }), .linkage = .static, }); - lib.linkLibC(); if (target.result.os.tag.isDarwin()) { const apple_sdk = @import("apple_sdk"); try apple_sdk.addPaths(b, lib); } if (b.lazyDependency("zlib", .{})) |upstream| { - lib.addIncludePath(upstream.path("")); + lib.root_module.addIncludePath(upstream.path("")); lib.installHeadersDirectory( upstream.path(""), "", @@ -48,7 +48,7 @@ pub fn build(b: *std.Build) !void { "-D_CRT_NONSTDC_NO_DEPRECATE", }); } - lib.addCSourceFiles(.{ + lib.root_module.addCSourceFiles(.{ .root = upstream.path(""), .files = srcs, .flags = flags.items, diff --git a/src/App.zig b/src/App.zig index 93ee7dea1..16457c20a 100644 --- a/src/App.zig +++ b/src/App.zig @@ -56,7 +56,7 @@ font_grid_set: font.SharedGridSet, // Used to rate limit desktop notifications. Some platforms (notably macOS) will // run out of resources if desktop notifications are sent too fast and the OS // will kill Ghostty. -last_notification_time: ?std.time.Instant = null, +last_notification_time: ?std.Io.Timestamp = null, last_notification_digest: u64 = 0, /// The conditional state of the configuration. See the equivalent field @@ -95,7 +95,7 @@ pub fn init( self.* = .{ .alloc = alloc, - .surfaces = .{}, + .surfaces = .empty, .mailbox = .{}, .font_grid_set = font_grid_set, .config_conditional_state = .{}, diff --git a/src/Command.zig b/src/Command.zig index b81936257..b103010a9 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -26,11 +26,14 @@ const TempDir = internal_os.TempDir; const mem = std.mem; const linux = std.os.linux; const posix = std.posix; +const compat_dir = @import("lib/compat/dir.zig"); +const compat_exec = @import("lib/compat/exec.zig"); +const compat_process = @import("lib/compat/process.zig"); const debug = std.debug; const testing = std.testing; const Allocator = std.mem.Allocator; -const File = std.fs.File; -const EnvMap = std.process.EnvMap; +const File = std.Io.File; +const EnvMap = std.process.Environ.Map; const apprt = @import("apprt.zig"); /// Function prototype for a function executed /in the child process/ after the @@ -65,7 +68,7 @@ env: ?*const EnvMap = null, /// Working directory to change to in the child process. If not set, the /// working directory of the calling process is preserved. -cwd: ?[]const u8 = null, +cwd: ?[:0]const u8 = null, /// The file handle to set for stdin/out/err. If this isn't set, we do /// nothing explicitly so it is up to the behavior of the operating system. @@ -106,7 +109,7 @@ pseudo_console: if (builtin.os.tag == .windows) ?windows.exp.HPCON else void = data: ?*anyopaque = null, /// Process ID is set after start is called. -pid: ?posix.pid_t = null, +pid: ?posix.system.pid_t = null, /// The various methods a process may exit. pub const Exit = if (builtin.os.tag == .windows) union(enum) { @@ -128,9 +131,9 @@ pub const Exit = if (builtin.os.tag == .windows) union(enum) { return if (posix.W.IFEXITED(status)) Exit{ .Exited = posix.W.EXITSTATUS(status) } else if (posix.W.IFSIGNALED(status)) - Exit{ .Signal = posix.W.TERMSIG(status) } + Exit{ .Signal = @intFromEnum(posix.W.TERMSIG(status)) } else if (posix.W.IFSTOPPED(status)) - Exit{ .Stopped = posix.W.STOPSIG(status) } + Exit{ .Stopped = @intFromEnum(posix.W.STOPSIG(status)) } else Exit{ .Unknown = status }; } @@ -186,7 +189,7 @@ fn startPosix(self: *Command, arena: Allocator) !void { @compileError("missing env vars"); // Fork. - const pid = try posix.fork(); + const pid = try compat_process.fork(); if (pid != 0) { // Parent, return immediately. @@ -206,7 +209,7 @@ fn startPosix(self: *Command, arena: Allocator) !void { return error.ExecFailedInChild; // Setup our working directory - if (self.cwd) |cwd| posix.chdir(cwd) catch { + if (self.cwd) |cwd| compat_dir.chdir(cwd) catch { // This can fail if we don't have permission to go to // this directory or if due to race conditions it doesn't // exist or any various other reasons. We don't want to @@ -219,20 +222,20 @@ fn startPosix(self: *Command, arena: Allocator) !void { global_state.rlimits.restore(); // If there are pre exec callbacks, call them now. - if (self.os_pre_exec) |f| if (f(self)) |exitcode| posix.exit(exitcode); - if (self.rt_pre_exec) |f| if (f(self)) |exitcode| posix.exit(exitcode); + if (self.os_pre_exec) |f| if (f(self)) |exitcode| posix.system.exit(exitcode); + if (self.rt_pre_exec) |f| if (f(self)) |exitcode| posix.system.exit(exitcode); // Finally, replace our process. // Note: we must use the "p"-variant of exec here because we // do not guarantee our command is looked up already in the path. - const err = posix.execvpeZ(self.path, argsZ, envp); + const err = compat_exec.execvpeZ(self.path, argsZ, envp); // If we are executing this code, the exec failed. We're in the // child process so there isn't much we can do. We try to output // something reasonable. Its important to note we MUST NOT return // any other error condition from here on out. var stderr_buf: [1024]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&stderr_buf); + var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &stderr_buf); const stderr = &stderr_writer.interface; switch (err) { error.FileNotFound => stderr.print( @@ -283,7 +286,7 @@ fn startWindows(self: *Command, arena: Allocator) !void { .creation = windows.OPEN_EXISTING, }, ) else null; - defer if (null_fd) |fd| posix.close(fd); + defer if (null_fd) |fd| posix.system.close(fd); // TODO: In the case of having FDs instead of pty, need to set up // attributes such that the child process only inherits these handles, @@ -395,9 +398,9 @@ fn setupFd(src: File.Handle, target: i32) !void { .freebsd, .ios, .macos => { // Mac doesn't support dup3 so we use dup2. We purposely clear // CLO_ON_EXEC for this fd. - const flags = try posix.fcntl(src, posix.F.GETFD, 0); + const flags = try posix.system.fcntl(src, posix.F.GETFD, 0); if (flags & posix.FD_CLOEXEC != 0) { - _ = try posix.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC)); + _ = try posix.system.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC)); } try posix.dup2(src, target); @@ -425,7 +428,7 @@ pub fn wait(self: Command, block: bool) !Exit { return .{ .Exited = exit_code }; } - const res = if (block) posix.waitpid(self.pid.?, 0) else res: { + const res = if (block) compat_process.waitpid(self.pid.?, 0) else res: { // We specify NOHANG because its not our fault if the process we launch // for the tty doesn't properly waitpid its children. We don't want // to hang the terminal over it. @@ -434,7 +437,7 @@ pub fn wait(self: Command, block: bool) !Exit { // wait call has not been performed, so we need to keep trying until we get // a non-zero pid back, otherwise we end up with zombie processes. while (true) { - const res = posix.waitpid(self.pid.?, std.c.W.NOHANG); + const res = compat_process.waitpid(self.pid.?, std.c.W.NOHANG); if (res.pid != 0) break :res res; } }; @@ -587,7 +590,7 @@ test "Command: os pre exec 1" { fn do(_: *Command) ?u8 { // This runs in the child, so we can exit and it won't // kill the test runner. - posix.exit(42); + posix.system.exit(42); } }).do, .rt_pre_exec = null, @@ -638,7 +641,7 @@ test "Command: rt pre exec 1" { fn do(_: *Command) ?u8 { // This runs in the child, so we can exit and it won't // kill the test runner. - posix.exit(42); + posix.system.exit(42); } }).do, .rt_post_fork = null, @@ -697,8 +700,8 @@ test "Command: rt post fork 1" { try testing.expectError(error.PostForkError, cmd.testingStart()); } -fn createTestStdout(dir: std.fs.Dir) !File { - const file = try dir.createFile("stdout.txt", .{ .read = true }); +fn createTestStdout(io: std.Io, dir: std.Io.Dir) !File { + const file = try dir.createFile(io, "stdout.txt", .{ .read = true }); if (builtin.os.tag == .windows) { try windows.SetHandleInformation( file.handle, @@ -710,8 +713,8 @@ fn createTestStdout(dir: std.fs.Dir) !File { return file; } -fn createTestStderr(dir: std.fs.Dir) !File { - const file = try dir.createFile("stderr.txt", .{ .read = true }); +fn createTestStderr(io: std.Io, dir: std.Io.Dir) !File { + const file = try dir.createFile(io, "stderr.txt", .{ .read = true }); if (builtin.os.tag == .windows) { try windows.SetHandleInformation( file.handle, @@ -726,8 +729,8 @@ fn createTestStderr(dir: std.fs.Dir) !File { test "Command: redirect stdout to file" { var td = try TempDir.init(); defer td.deinit(); - var stdout = try createTestStdout(td.dir); - defer stdout.close(); + var stdout = try createTestStdout(testing.io, td.dir); + defer stdout.close(testing.io); var cmd: Command = if (builtin.os.tag == .windows) .{ .path = "C:\\Windows\\System32\\whoami.exe", @@ -756,8 +759,13 @@ test "Command: redirect stdout to file" { try testing.expectEqual(@as(u32, 0), @as(u32, exit.Exited)); // Read our stdout - try stdout.seekTo(0); - const contents = try stdout.readToEndAlloc(testing.allocator, 1024 * 128); + const contents = contents: { + const size = (try stdout.stat(testing.io)).size; + const data = try testing.allocator.alloc(u8, size); + errdefer testing.allocator.free(data); + try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0)); + break :contents data; + }; defer testing.allocator.free(contents); try testing.expect(contents.len > 0); } @@ -765,8 +773,8 @@ test "Command: redirect stdout to file" { test "Command: custom env vars" { var td = try TempDir.init(); defer td.deinit(); - var stdout = try createTestStdout(td.dir); - defer stdout.close(); + var stdout = try createTestStdout(testing.io, td.dir); + defer stdout.close(testing.io); var env = EnvMap.init(testing.allocator); defer env.deinit(); @@ -801,8 +809,13 @@ test "Command: custom env vars" { try testing.expect(exit.Exited == 0); // Read our stdout - try stdout.seekTo(0); - const contents = try stdout.readToEndAlloc(testing.allocator, 4096); + const contents = contents: { + const size = (try stdout.stat(testing.io)).size; + const data = try testing.allocator.alloc(u8, size); + errdefer testing.allocator.free(data); + try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0)); + break :contents data; + }; defer testing.allocator.free(contents); if (builtin.os.tag == .windows) { @@ -815,8 +828,8 @@ test "Command: custom env vars" { test "Command: custom working directory" { var td = try TempDir.init(); defer td.deinit(); - var stdout = try createTestStdout(td.dir); - defer stdout.close(); + var stdout = try createTestStdout(testing.io, td.dir); + defer stdout.close(testing.io); var cmd: Command = if (builtin.os.tag == .windows) .{ .path = "C:\\Windows\\System32\\cmd.exe", @@ -847,8 +860,13 @@ test "Command: custom working directory" { try testing.expect(exit.Exited == 0); // Read our stdout - try stdout.seekTo(0); - const contents = try stdout.readToEndAlloc(testing.allocator, 4096); + const contents = contents: { + const size = (try stdout.stat(testing.io)).size; + const data = try testing.allocator.alloc(u8, size); + errdefer testing.allocator.free(data); + try testing.expectEqual(size, try stdout.readPositionalAll(testing.io, data, 0)); + break :contents data; + }; defer testing.allocator.free(contents); if (builtin.os.tag == .windows) { @@ -872,10 +890,10 @@ test "Command: posix fork handles execveZ failure" { } var td = try TempDir.init(); defer td.deinit(); - var stdout = try createTestStdout(td.dir); - defer stdout.close(); - var stderr = try createTestStderr(td.dir); - defer stderr.close(); + var stdout = try createTestStdout(testing.io, td.dir); + defer stdout.close(testing.io); + var stderr = try createTestStderr(testing.io, td.dir); + defer stderr.close(testing.io); var cmd: Command = .{ .path = "/not/a/binary", @@ -904,7 +922,7 @@ fn testingStart(self: *Command) !void { self.start(testing.allocator) catch |err| { if (err == error.ExecFailedInChild) { // I am a child process, I must not get confused and continue running the rest of the test suite. - posix.exit(1); + posix.system.exit(1); } return err; }; diff --git a/src/Surface.zig b/src/Surface.zig index 5d16f3326..34f5ecd52 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -168,13 +168,13 @@ readonly: bool = false, /// precision timestamp. It does not necessarily need to correspond to the /// actual time, but we must be able to compare two subsequent timestamps to get /// the wall clock time that has elapsed between timestamps. -command_timer: ?std.time.Instant = null, +command_timer: ?std.Io.Timestamp = null, /// Search state search: ?Search = null, /// Used to rate limit BEL handling. -last_bell_time: ?std.time.Instant = null, +last_bell_time: ?std.Io.Timestamp = null, /// The effect of an input event. This can be used by callers to take /// the appropriate action after an input event. For example, key @@ -239,7 +239,7 @@ const Mouse = struct { /// The left click time was the last time the left click was done. This /// is always set on the first left click. left_click_count: u8 = 0, - left_click_time: std.time.Instant = undefined, + left_click_time: std.Io.Timestamp = undefined, /// The last x/y sent for mouse reports. event_point: ?terminal.point.Coordinate = null, @@ -568,8 +568,8 @@ pub fn init( errdefer renderer_impl.deinit(); // The mutex used to protect our renderer state. - const mutex = try alloc.create(std.Thread.Mutex); - mutex.* = .{}; + const mutex = try alloc.create(std.Io.Mutex); + mutex.* = .init; errdefer alloc.destroy(mutex); // Create the renderer thread @@ -590,7 +590,11 @@ pub fn init( self.* = .{ .id = id: { while (true) { - const candidate = std.crypto.random.int(u64); + const candidate = candidate: { + const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() }; + const rng = rng_impl.interface(); + break :candidate rng.int(u64); + }; if (candidate == 0) continue; break :id candidate; } @@ -641,12 +645,12 @@ pub fn init( // If an error occurs, we don't want to block surface startup. log.warn("error getting env map for surface err={}", .{err}); break :env internal_os.getEnvMap(alloc) catch - std.process.EnvMap.init(alloc); + std.process.Environ.Map.init(alloc); }; errdefer env.deinit(); // don't leak GHOSTTY_LOG to any subprocesses - env.remove("GHOSTTY_LOG"); + _ = env.orderedRemove("GHOSTTY_LOG"); var buf: [18]u8 = undefined; try env.put( @@ -726,7 +730,7 @@ pub fn init( rendererpkg.Thread.threadMain, .{&self.renderer_thread}, ); - self.renderer_thr.setName("renderer") catch {}; + self.renderer_thr.setName(std.Io.Threaded.global_single_threaded.io(), "renderer") catch {}; // Start our IO thread self.io_thr = try std.Thread.spawn( @@ -734,7 +738,7 @@ pub fn init( termio.Thread.threadMain, .{ &self.io_thread, &self.io }, ); - self.io_thr.setName("io") catch {}; + self.io_thr.setName(std.Io.Threaded.global_single_threaded.io(), "io") catch {}; // Determine our initial window size if configured. We need to do this // quite late in the process because our height/width are in grid dimensions, @@ -908,8 +912,8 @@ pub fn activateInspector(self: *Surface) !void { // Put the inspector onto the render state { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); assert(self.renderer_state.inspector == null); self.renderer_state.inspector = self.inspector; } @@ -925,8 +929,8 @@ pub fn deactivateInspector(self: *Surface) void { // Remove the inspector from the render state { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); assert(self.renderer_state.inspector != null); self.renderer_state.inspector = null; } @@ -956,8 +960,8 @@ pub fn needsConfirmQuit(self: *Surface) bool { .always => true, .false => false, .true => true: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); break :true !self.io.terminal.cursorIsAtPrompt(); }, }; @@ -1104,9 +1108,9 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { .password_input => |v| try self.passwordInput(v), .ring_bell => bell: { - const now = std.time.Instant.now() catch unreachable; + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); if (self.last_bell_time) |last| { - if (now.since(last) < 100 * std.time.ns_per_ms) break :bell; + if (last.durationTo(now).toMilliseconds() < 100) break :bell; } self.last_bell_time = now; _ = self.rt_app.performAction( @@ -1134,15 +1138,22 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { }, .start_command => { - self.command_timer = try .now(); + self.command_timer = .now(std.Io.Threaded.global_single_threaded.io(), .awake); }, .stop_command => |v| timer: { - const end: std.time.Instant = try .now(); + const end: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); const start = self.command_timer orelse break :timer; self.command_timer = null; + const duration_raw = start.durationTo(end).nanoseconds; + assert(duration_raw >= 0 and duration_raw <= std.math.maxInt(u64)); - const duration: Duration = .{ .duration = end.since(start) }; + const duration: Duration = .{ + .duration = @as( + u64, + @intCast(std.math.clamp(start.durationTo(end).nanoseconds, 0, std.math.maxInt(u64))), + ), + }; log.debug("command took {f}", .{duration}); _ = self.rt_app.performAction( @@ -1188,8 +1199,8 @@ fn selectionScrollTick(self: *Surface) !void { const delta: isize = if (pos.y < 0) -1 else 1; // We need our locked state for the remainder - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; // If our screen changed while this is happening, we stop our @@ -1277,8 +1288,8 @@ fn childExited(self: *Surface, info: apprt.surface.Message.ChildExited) void { // If the native GUI can't be shown, display a text message in the // terminal. - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; t.carriageReturn(); t.linefeed() catch break :terminal; @@ -1317,8 +1328,8 @@ fn childExitedAbnormally( }); const runtime_str = try std.fmt.allocPrint(alloc, "{d} ms", .{info.runtime_ms}); - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; // No matter what move the cursor back to the column 0. @@ -1384,8 +1395,8 @@ fn childExitedAbnormally( /// Called when the terminal detects there is a password input prompt. fn passwordInput(self: *Surface, v: bool) !void { { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // If our password input state is unchanged then we don't // waste time doing anything more. @@ -1540,8 +1551,8 @@ fn modsChanged(self: *Surface, mods: input.Mods) void { // highlight links. Additionally, mark the screen as dirty so // that the highlight state of all links is properly updated. { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.renderer_state.mouse.mods = self.mouseModsWithCapture(self.mouse.mods); // We use the clear screen dirty flag to force a rebuild of all @@ -1908,8 +1919,8 @@ pub fn dumpText( alloc: Allocator, sel: terminal.Selection, ) !Text { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); return try self.dumpTextLocked(alloc, sel); } @@ -2034,15 +2045,15 @@ pub fn dumpTextLocked( /// Returns true if the terminal has a selection. pub fn hasSelection(self: *const Surface) bool { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); return self.io.terminal.screens.active.selection != null; } /// Returns the selected text. This is allocated. pub fn selectionString(self: *Surface, alloc: Allocator) !?[:0]const u8 { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const sel = self.io.terminal.screens.active.selection orelse return null; return try self.io.terminal.screens.active.selectionString(alloc, .{ .sel = sel, @@ -2057,8 +2068,8 @@ pub fn pwd( self: *const Surface, alloc: Allocator, ) Allocator.Error!?[]const u8 { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const terminal_pwd = self.io.terminal.getPwd() orelse return null; return try alloc.dupe(u8, terminal_pwd); } @@ -2075,7 +2086,7 @@ fn resolvePathForOpening( const resolved = try std.fs.path.resolve(self.alloc, &.{ terminal_pwd, path }); - std.fs.accessAbsolute(resolved, .{}) catch { + std.Io.Dir.accessAbsolute(std.Io.Threaded.global_single_threaded.io(), resolved, .{}) catch { self.alloc.free(resolved); return null; }; @@ -2089,10 +2100,10 @@ fn resolvePathForOpening( /// Returns the x/y coordinate of where the IME (Input Method Editor) /// keyboard should be rendered. pub fn imePoint(self: *const Surface) apprt.IMEPos { - self.renderer_state.mutex.lock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); const cursor = self.renderer_state.terminal.screens.active.cursor; const preedit_width: usize = if (self.renderer_state.preedit) |preedit| preedit.width() else 0; - self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // TODO: need to handle when scrolling and the cursor is not // in the visible portion of the screen. @@ -2508,8 +2519,8 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void { crash.sentry.thread_state = self.crashThreadState(); defer crash.sentry.thread_state = null; - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // We clear our selection when ANY OF: // 1. We have an existing preedit @@ -2544,7 +2555,7 @@ pub fn preeditCallback(self: *Surface, preedit_: ?[]const u8) !void { // Allocate the codepoints slice const Codepoint = rendererpkg.State.Preedit.Codepoint; - var codepoints: std.ArrayListUnmanaged(Codepoint) = .{}; + var codepoints: std.ArrayList(Codepoint) = .empty; defer codepoints.deinit(self.alloc); while (it.nextCodepoint()) |cp| { const width: usize = @intCast(unicode.table.get(cp).width); @@ -2675,8 +2686,8 @@ pub fn keyCallback( )) |v| return v; // If we allow KAM and KAM is enabled then we do nothing. if (self.config.vt_kam_allowed) { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed; } @@ -2706,8 +2717,8 @@ pub fn keyCallback( { // Refresh our link state const pos = self.rt_surface.getCursorPos() catch break :mouse_mods; - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.mouseRefreshLinks( pos, self.posToViewport(pos.x, pos.y), @@ -2799,8 +2810,8 @@ pub fn keyCallback( // some data to send to the pty, then we move the viewport down to the // bottom. We also clear the selection for any key other then modifiers. if (!event.key.modifier()) { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.config.selection_clear_on_typing or event.key == .escape) @@ -3231,8 +3242,8 @@ fn encodeKey( } fn encodeKeyOpts(self: *const Surface) input.key_encode.Options { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t = &self.io.terminal; var opts: input.key_encode.Options = .fromTerminal(t); @@ -3360,9 +3371,9 @@ pub fn focusCallback(self: *Surface, focused: bool) !void { // Update the focus state and notify the terminal { - self.renderer_state.mutex.lock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); self.io.terminal.flags.focused = focused; - self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.queueIo(.{ .focused = focused }, .unlocked); } } @@ -3494,8 +3505,8 @@ pub fn scrollCallback( // log.info("SCROLL: delta_y={} delta_x={}", .{ y.delta, x.delta }); { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // If we have an active mouse reporting mode, clear the selection. // The selection can occur if the user uses the shift mod key to @@ -3695,8 +3706,8 @@ fn mouseShiftCapture(self: *const Surface, lock: bool) bool { .false, .true => {}, } - if (lock) self.renderer_state.mutex.lock(); - defer if (lock) self.renderer_state.mutex.unlock(); + if (lock) self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer if (lock) self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // If the terminal explicitly requests it then we always allow it // since we processed never/always at this point. @@ -3717,8 +3728,8 @@ fn mouseShiftCapture(self: *const Surface, lock: bool) bool { /// Returns true if the mouse is currently captured by the terminal /// (i.e. reporting events). pub fn mouseCaptured(self: *Surface) bool { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); return self.io.terminal.flags.mouse_event != .none; } @@ -3774,19 +3785,13 @@ pub fn mouseButtonCallback( // If we are within the interval that the click would register // an increment then we do not extend the selection. - if (std.time.Instant.now()) |now| { - const since = now.since(self.mouse.left_click_time); - if (since <= self.config.mouse_interval) { - // Click interval very short, we may be increasing - // click counts so we don't extend the selection. - break :extend_selection; - } - } else |err| { - // This is a weird behavior, I think either behavior is actually - // fine. This failure should be exceptionally rare anyways. - // My thinking here is that we can't be sure if we should extend - // the selection or not so we just don't. - log.warn("failed to get time, not extending selection err={}", .{err}); + const since = self.mouse.left_click_time.untilNow( + std.Io.Threaded.global_single_threaded.io(), + .awake, + ).nanoseconds; + if (since <= self.config.mouse_interval) { + // Click interval very short, we may be increasing + // click counts so we don't extend the selection. break :extend_selection; } @@ -3810,8 +3815,8 @@ pub fn mouseButtonCallback( // the left button is released. This is to avoid the clipboard // being updated on every mouse move which would be noisy. if (self.config.copy_on_select != .false) { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const prev_ = self.io.terminal.screens.active.selection; if (prev_) |prev| { try self.setSelection(terminal.Selection.init( @@ -3827,8 +3832,8 @@ pub fn mouseButtonCallback( // clicked link will swallow the event. if (self.mouse.over_link) { const pos = try self.rt_surface.getCursorPos(); - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.processLinks(pos)) |processed| { if (processed) return true; } else |err| { @@ -3848,8 +3853,8 @@ pub fn mouseButtonCallback( // Report mouse events if enabled { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.isMouseReporting()) report: { // If we have shift-pressed and we aren't allowed to capture it, // then we do not do a mouse report. @@ -3888,8 +3893,8 @@ pub fn mouseButtonCallback( // For left button clicks we always record some information for // selection/highlighting purposes. if (button == .left and action == .press) click: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; const screen: *terminal.Screen = self.renderer_state.terminal.screens.active; @@ -3940,26 +3945,22 @@ pub fn mouseButtonCallback( self.mouse.left_click_ypos = pos.y; // Setup our click counter and timer - if (std.time.Instant.now()) |now| { - // If we have mouse clicks, then we check if the time elapsed - // is less than and our interval and if so, increase the count. - if (self.mouse.left_click_count > 0) { - const since = now.since(self.mouse.left_click_time); - if (since > self.config.mouse_interval) { - self.mouse.left_click_count = 0; - } + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); + // If we have mouse clicks, then we check if the time elapsed + // is less than and our interval and if so, increase the count. + if (self.mouse.left_click_count > 0) { + const since = self.mouse.left_click_time.durationTo(now); + if (since.nanoseconds > self.config.mouse_interval) { + self.mouse.left_click_count = 0; } - - self.mouse.left_click_time = now; - self.mouse.left_click_count += 1; - - // We only support up to triple-clicks. - if (self.mouse.left_click_count > 3) self.mouse.left_click_count = 1; - } else |err| { - self.mouse.left_click_count = 1; - log.err("error reading time, mouse multi-click won't work err={}", .{err}); } + self.mouse.left_click_time = now; + self.mouse.left_click_count += 1; + + // We only support up to triple-clicks. + if (self.mouse.left_click_count > 3) self.mouse.left_click_count = 1; + // In all cases below, we set the selection directly rather than use // `setSelection` because we want to avoid copying the selection // to the selection clipboard. For left mouse clicks we only set @@ -4040,8 +4041,8 @@ pub fn mouseButtonCallback( // want to be careful in the future we can add a function to apprts // that let's us know. if (button == .right and action == .press) sel: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // Get our viewport pin const screen: *terminal.Screen = self.renderer_state.terminal.screens.active; @@ -4112,8 +4113,8 @@ pub fn mouseButtonCallback( } else { // Pasting can trigger a lock grab in complete clipboard // request so we need to unlock. - self.renderer_state.mutex.unlock(); - defer self.renderer_state.mutex.lock(); + self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); _ = try self.startClipboardRequest(.standard, .paste); // We don't need to clear selection because we didn't have @@ -4127,8 +4128,8 @@ pub fn mouseButtonCallback( // Pasting can trigger a lock grab in complete clipboard // request so we need to unlock. - self.renderer_state.mutex.unlock(); - defer self.renderer_state.mutex.lock(); + self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); _ = try self.startClipboardRequest(.standard, .paste); }, } @@ -4141,8 +4142,8 @@ pub fn mouseButtonCallback( } fn maybePromptClick(self: *Surface) !bool { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; const screen: *terminal.Screen = t.screens.active; @@ -4461,8 +4462,8 @@ pub fn mousePressureCallback( if (self.mouse.click_state[left_idx] == .press and stage == .deep) select: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // This should always be set in this state but we don't want // to handle state inconsistency here. @@ -4520,8 +4521,8 @@ pub fn cursorPosCallback( try self.queueRender(); } - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // No mouse point so we don't highlight links self.renderer_state.mouse.point = null; @@ -4547,8 +4548,8 @@ pub fn cursorPosCallback( self.mouse.over_link = false; // We are reading/writing state for the remainder - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // Stop selection scrolling when inside the viewport within a 1px buffer // for fullscreen windows, but only when selection scrolling is active. @@ -5041,8 +5042,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool // CSI/ESC triggers a scroll. { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.scrollToBottom() catch |err| { log.warn("error scrolling to bottom err={}", .{err}); }; @@ -5068,8 +5069,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool // Text triggers a scroll. { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.scrollToBottom() catch |err| { log.warn("error scrolling to bottom err={}", .{err}); }; @@ -5081,8 +5082,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool // in cursor keys mode. We're in "normal" mode if cursor // keys mode is NOT set. const normal = normal: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // With the lock held, we must scroll to the bottom. // We always scroll to the bottom for these inputs. @@ -5101,8 +5102,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .reset => { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.renderer_state.terminal.fullReset(); }, @@ -5172,7 +5173,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool terminal.search.Thread.threadMain, .{&s.state}, ); - s.thread.setName("search") catch {}; + s.thread.setName(std.Io.Threaded.global_single_threaded.io(), "search") catch {}; break :init s; }; @@ -5207,8 +5208,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .copy_to_clipboard => |format| { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.io.terminal.screens.active.selection) |sel| { try self.copySelectionToClipboards( @@ -5239,8 +5240,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool if (!self.mouse.over_link) return false; const pos = try self.rt_surface.getCursorPos(); - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (try self.linkAtPos(pos)) |link_info| { const url_text = switch (link_info.action) { .open => url_text: { @@ -5385,8 +5386,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool // alternate screen then clear screen does nothing so we want to // return false so the keybind can be unconsumed. { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (self.io.terminal.screens.active_key == .alternate) return false; } @@ -5409,8 +5410,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .scroll_to_row => |n| { { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const t: *terminal.Terminal = self.renderer_state.terminal; t.screens.active.scroll(.{ .row = n }); } @@ -5420,8 +5421,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .scroll_to_selection => { { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const sel = self.io.terminal.screens.active.selection orelse return false; const tl = sel.topLeft(self.io.terminal.screens.active); self.io.terminal.screens.active.scroll(.{ .pin = tl }); @@ -5659,8 +5660,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool ), .select_all => { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const sel = self.io.terminal.screens.active.selectAll(); if (sel) |s| { @@ -5792,8 +5793,8 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, .adjust_selection => |direction| { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const screen: *terminal.Screen = self.io.terminal.screens.active; const sel = if (screen.selection) |*sel| sel else { @@ -5889,23 +5890,24 @@ fn writeScreenFile( // Open our scrollback file var file = try tmp_dir.dir.createFile( + std.Io.Threaded.global_single_threaded.io(), filename, switch (builtin.os.tag) { .windows => .{}, - else => .{ .mode = 0o600 }, + else => .{ .permissions = .fromMode(0o600) }, }, ); - defer file.close(); + defer file.close(std.Io.Threaded.global_single_threaded.io()); // Screen.dumpString writes byte-by-byte, so buffer it var buf: [4096]u8 = undefined; - var file_writer = file.writer(&buf); + var file_writer = file.writer(std.Io.Threaded.global_single_threaded.io(), &buf); var buf_writer = &file_writer.interface; // Write the scrollback contents. This requires a lock. { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // We only dump history if we have history. We still keep // the file and write the empty file to the pty so that this @@ -5968,7 +5970,11 @@ fn writeScreenFile( // Get the final path var path_buf: [std.fs.max_path_bytes]u8 = undefined; - const path = try tmp_dir.dir.realpath(filename, &path_buf); + const path = path_buf[0..try tmp_dir.dir.realPathFile( + std.Io.Threaded.global_single_threaded.io(), + filename, + &path_buf, + )]; switch (write_screen.action) { .copy => { @@ -6066,8 +6072,8 @@ fn completeClipboardPaste( if (data.len == 0) return; const encode_opts: input.paste.Options = encode_opts: { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const opts: input.paste.Options = .fromTerminal(&self.io.terminal); // If we have paste protection enabled, we detect unsafe pastes and return @@ -6187,12 +6193,12 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const // how fast identical notifications can be sent sequentially. const hash_algorithm = std.hash.Wyhash; - const now = try std.time.Instant.now(); + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); // Set a limit of one desktop notification per second so that the OS // doesn't kill us when we run out of resources. if (self.app.last_notification_time) |last| { - if (now.since(last) < 1 * std.time.ns_per_s) { + if (last.durationTo(now).toSeconds() < 1) { log.warn("rate limiting desktop notifications", .{}); return; } @@ -6209,7 +6215,7 @@ fn showDesktopNotification(self: *Surface, title: [:0]const u8, body: [:0]const // notifications with identical content. if (self.app.last_notification_time) |last| { if (self.app.last_notification_digest == new_digest) { - if (now.since(last) < 5 * std.time.ns_per_s) { + if (last.durationTo(now).toSeconds() < 5) { log.warn("suppressing identical desktop notification", .{}); return; } diff --git a/src/apprt/action.zig b/src/apprt/action.zig index f6865af83..ec138547a 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -419,8 +419,11 @@ pub const Action = union(Key) { /// Sync with: ghostty_action_u pub const CValue = cvalue: { const key_fields = @typeInfo(Key).@"enum".fields; - var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined; - for (key_fields, 0..) |field, i| { + var names: [key_fields.len][]const u8 = undefined; + var types: [key_fields.len]type = undefined; + var attrs: [key_fields.len]std.builtin.Type.UnionField.Attributes = undefined; + + for (key_fields, &names, &types, &attrs) |field, *name, *ty, *attr| { const action = @unionInit(Action, field.name, undefined); const Type = t: { const Type = @TypeOf(@field(action, field.name)); @@ -429,19 +432,12 @@ pub const Action = union(Key) { break :t Type; }; - union_fields[i] = .{ - .name = field.name, - .type = Type, - .alignment = @alignOf(Type), - }; + name.* = field.name; + ty.* = Type; + attr.* = .{ .@"align" = @alignOf(Type) }; } - break :cvalue @Type(.{ .@"union" = .{ - .layout = .@"extern", - .tag_type = null, - .fields = &union_fields, - .decls = &.{}, - } }); + break :cvalue @Union(.@"extern", null, &names, &types, &attrs); }; /// Sync with: ghostty_action_s @@ -1003,5 +999,5 @@ pub const SearchSelected = struct { }; test { - _ = std.testing.refAllDeclsRecursive(@This()); + _ = std.testing.refAllDecls(@This()); } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 730913eba..84282d017 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -949,7 +949,7 @@ pub const Surface = struct { }; } - pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap { + pub fn defaultTermioEnv(self: *const Surface) !std.process.Environ.Map { const alloc = self.app.core_app.alloc; var env = try internal_os.getEnvMap(alloc); errdefer env.deinit(); @@ -999,7 +999,7 @@ pub const Inspector = struct { content_scale: f64 = 1, /// Our previous instant used to calculate delta time for animations. - instant: ?std.time.Instant = null, + instant: ?std.Io.Timestamp = null, const Backend = enum { metal, @@ -1228,7 +1228,7 @@ pub const Inspector = struct { const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO(); // Determine our delta time - const now = try std.time.Instant.now(); + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); io.DeltaTime = if (self.instant) |prev| delta: { const since_ns: f64 = @floatFromInt(now.since(prev)); const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s); @@ -1611,8 +1611,8 @@ pub const CAPI = struct { result: *Text, ) bool { const core_surface = &surface.core_surface; - core_surface.renderer_state.mutex.lock(); - defer core_surface.renderer_state.mutex.unlock(); + core_surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer core_surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // If we don't have a selection, do nothing. const core_sel = core_surface.io.terminal.screens.active.selection orelse return false; @@ -1631,8 +1631,8 @@ pub const CAPI = struct { sel: Selection, result: *Text, ) bool { - surface.core_surface.renderer_state.mutex.lock(); - defer surface.core_surface.renderer_state.mutex.unlock(); + surface.core_surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer surface.core_surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); const core_sel = sel.core( surface.core_surface.renderer_state.terminal.screens.active, @@ -2194,8 +2194,8 @@ pub const CAPI = struct { result: *Text, ) bool { const surface = &ptr.core_surface; - surface.renderer_state.mutex.lock(); - defer surface.renderer_state.mutex.unlock(); + surface.renderer_state.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer surface.renderer_state.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // Get our word selection const sel = sel: { diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 715973671..c5ba39277 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -94,7 +94,7 @@ pub fn setClipboard( ); } -pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap { +pub fn defaultTermioEnv(self: *Self) !std.process.Environ.Map { return try self.surface.defaultTermioEnv(); } diff --git a/src/apprt/gtk/adw_version.zig b/src/apprt/gtk/adw_version.zig index 6f7be52da..1ac750fbd 100644 --- a/src/apprt/gtk/adw_version.zig +++ b/src/apprt/gtk/adw_version.zig @@ -4,18 +4,16 @@ const std = @import("std"); // Ghostty, we need to import `adwaita.h` directly to ensure that the version // macros match the version of `libadwaita` that we are building/linking // against. -const c = @cImport({ - @cInclude("adwaita.h"); -}); +const adw_c = @import("adw_c"); const adw = @import("adw"); const log = std.log.scoped(.gtk); pub const comptime_version: std.SemanticVersion = .{ - .major = c.ADW_MAJOR_VERSION, - .minor = c.ADW_MINOR_VERSION, - .patch = c.ADW_MICRO_VERSION, + .major = adw_c.ADW_MAJOR_VERSION, + .minor = adw_c.ADW_MINOR_VERSION, + .patch = adw_c.ADW_MICRO_VERSION, }; pub fn getRuntimeVersion() std.SemanticVersion { @@ -89,14 +87,14 @@ test "versionAtLeast" { const funs = &.{ atLeast, runtimeAtLeast }; inline for (funs) |fun| { - try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION)); - try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1)); - try testing.expect(!fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION)); - try testing.expect(!fun(c.ADW_MAJOR_VERSION + 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION)); - try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION)); - try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION + 1, c.ADW_MICRO_VERSION)); - try testing.expect(fun(c.ADW_MAJOR_VERSION - 1, c.ADW_MINOR_VERSION, c.ADW_MICRO_VERSION + 1)); - try testing.expect(fun(c.ADW_MAJOR_VERSION, c.ADW_MINOR_VERSION - 1, c.ADW_MICRO_VERSION + 1)); + try testing.expect(fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION)); + try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION + 1)); + try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION + 1, adw_c.ADW_MICRO_VERSION)); + try testing.expect(!fun(adw_c.ADW_MAJOR_VERSION + 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION)); + try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION)); + try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION + 1, adw_c.ADW_MICRO_VERSION)); + try testing.expect(fun(adw_c.ADW_MAJOR_VERSION - 1, adw_c.ADW_MINOR_VERSION, adw_c.ADW_MICRO_VERSION + 1)); + try testing.expect(fun(adw_c.ADW_MAJOR_VERSION, adw_c.ADW_MINOR_VERSION - 1, adw_c.ADW_MICRO_VERSION + 1)); } } diff --git a/src/apprt/gtk/build/blueprint.zig b/src/apprt/gtk/build/blueprint.zig index 4920ce6f8..fb3f6ae23 100644 --- a/src/apprt/gtk/build/blueprint.zig +++ b/src/apprt/gtk/build/blueprint.zig @@ -6,10 +6,7 @@ //! Example: blueprint.zig 1 5 output.ui input.blp const std = @import("std"); - -pub const c = @cImport({ - @cInclude("adwaita.h"); -}); +const adw_c = @import("adw_c"); pub const blueprint_compiler_help = \\ @@ -26,23 +23,24 @@ pub const blueprint_compiler_help = ; const adwaita_version = std.SemanticVersion{ - .major = c.ADW_MAJOR_VERSION, - .minor = c.ADW_MINOR_VERSION, - .patch = c.ADW_MICRO_VERSION, + .major = adw_c.ADW_MAJOR_VERSION, + .minor = adw_c.ADW_MINOR_VERSION, + .patch = adw_c.ADW_MICRO_VERSION, }; + const required_blueprint_version = std.SemanticVersion{ .major = 0, .minor = 16, .patch = 0, }; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var debug_allocator: std.heap.DebugAllocator(.{}) = .init; defer _ = debug_allocator.deinit(); const alloc = debug_allocator.allocator(); // Get our args - var it = try std.process.argsWithAllocator(alloc); + var it = try init.minimal.args.iterateAllocator(alloc); defer it.deinit(); _ = it.next(); // Skip argv0 const arg_major = it.next() orelse return error.NoMajorVersion; @@ -63,51 +61,37 @@ pub fn main() !void { \\compile this blueprint. Please install it, ensure that it is \\available on your PATH, and then retry building Ghostty. , .{required_adwaita_version}); - std.posix.exit(1); + std.process.exit(1); } // Version checks { - var stdout: std.ArrayListUnmanaged(u8) = .empty; - defer stdout.deinit(alloc); - var stderr: std.ArrayListUnmanaged(u8) = .empty; - defer stderr.deinit(alloc); - - var blueprint_compiler = std.process.Child.init( - &.{ - "blueprint-compiler", - "--version", - }, - alloc, - ); - blueprint_compiler.stdout_behavior = .Pipe; - blueprint_compiler.stderr_behavior = .Pipe; - try blueprint_compiler.spawn(); - try blueprint_compiler.collectOutput( - alloc, - &stdout, - &stderr, - std.math.maxInt(u16), - ); - const term = blueprint_compiler.wait() catch |err| switch (err) { + const blueprint_compiler = std.process.run(alloc, init.io, .{ + .argv = &.{ "blueprint-compiler", "--version" }, + }) catch |err| switch (err) { error.FileNotFound => { std.debug.print( \\`blueprint-compiler` not found. ++ blueprint_compiler_help, .{required_blueprint_version}, ); - std.posix.exit(1); + std.process.exit(1); }, else => return err, }; - switch (term) { - .Exited => |rc| if (rc != 0) std.process.exit(1), + defer { + alloc.free(blueprint_compiler.stdout); + alloc.free(blueprint_compiler.stderr); + } + + switch (blueprint_compiler.term) { + .exited => |rc| if (rc != 0) std.process.exit(1), else => std.process.exit(1), } const version = try std.SemanticVersion.parse(std.mem.trim( u8, - stdout.items, + blueprint_compiler.stdout, &std.ascii.whitespace, )); if (version.order(required_blueprint_version) == .lt) { @@ -116,57 +100,45 @@ pub fn main() !void { ++ blueprint_compiler_help, .{required_blueprint_version}, ); - std.posix.exit(1); + std.process.exit(1); } } // Compilation { - var stdout: std.ArrayListUnmanaged(u8) = .empty; - defer stdout.deinit(alloc); - var stderr: std.ArrayListUnmanaged(u8) = .empty; - defer stderr.deinit(alloc); - - var blueprint_compiler = std.process.Child.init( - &.{ + const blueprint_compiler = std.process.run(alloc, init.io, .{ + .argv = &.{ "blueprint-compiler", "compile", "--output", output, input, }, - alloc, - ); - blueprint_compiler.stdout_behavior = .Pipe; - blueprint_compiler.stderr_behavior = .Pipe; - try blueprint_compiler.spawn(); - try blueprint_compiler.collectOutput( - alloc, - &stdout, - &stderr, - std.math.maxInt(u16), - ); - const term = blueprint_compiler.wait() catch |err| switch (err) { + }) catch |err| switch (err) { error.FileNotFound => { std.debug.print( \\`blueprint-compiler` not found. ++ blueprint_compiler_help, .{required_blueprint_version}, ); - std.posix.exit(1); + std.process.exit(1); }, else => return err, }; + defer { + alloc.free(blueprint_compiler.stdout); + alloc.free(blueprint_compiler.stderr); + } - switch (term) { - .Exited => |rc| { + switch (blueprint_compiler.term) { + .exited => |rc| { if (rc != 0) { - std.debug.print("{s}", .{stderr.items}); + std.debug.print("{s}", .{blueprint_compiler.stderr}); std.process.exit(1); } }, else => { - std.debug.print("{s}", .{stderr.items}); + std.debug.print("{s}", .{blueprint_compiler.stderr}); std.process.exit(1); }, } diff --git a/src/apprt/gtk/build/gresource.zig b/src/apprt/gtk/build/gresource.zig index c50ea8cd5..57c32a353 100644 --- a/src/apprt/gtk/build/gresource.zig +++ b/src/apprt/gtk/build/gresource.zig @@ -122,18 +122,17 @@ pub fn blueprint(comptime bp: Blueprint) [:0]const u8 { } } -pub fn main() !void { - var debug_allocator: std.heap.DebugAllocator(.{}) = .init; - defer _ = debug_allocator.deinit(); - const alloc = debug_allocator.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.arena.allocator(); // Collect the UI files that are passed in as arguments. - var ui_files: std.ArrayListUnmanaged([]const u8) = .empty; + var ui_files: std.ArrayList([]const u8) = .empty; defer { for (ui_files.items) |item| alloc.free(item); ui_files.deinit(alloc); } - var it = try std.process.argsWithAllocator(alloc); + + var it = try init.minimal.args.iterateAllocator(alloc); defer it.deinit(); while (it.next()) |arg| { if (!std.mem.endsWith(u8, arg, ".ui")) continue; @@ -144,7 +143,7 @@ pub fn main() !void { } var buf: [4096]u8 = undefined; - var stdout = std.fs.File.stdout().writer(&buf); + var stdout = std.Io.File.stdout().writer(init.io, &buf); const writer = &stdout.interface; try writer.writeAll( \\ @@ -152,8 +151,8 @@ pub fn main() !void { \\ ); - try genRoot(writer); - try genIcons(writer); + try genRoot(init.io, writer); + try genIcons(init.io, writer); try genUi(alloc, writer, &ui_files); try writer.writeAll( @@ -167,19 +166,19 @@ pub fn main() !void { /// Generate the icon resources. This works by looking up all the icons /// specified by `icon_sizes` in `images/icons/`. They are asserted to exist /// by trying to access the file. -fn genIcons(writer: *std.Io.Writer) !void { +fn genIcons(io: std.Io, writer: *std.Io.Writer) !void { try writer.print( \\ \\ , .{build_info.resource_path}); - const cwd = std.fs.cwd(); + const cwd: std.Io.Dir = .cwd(); inline for (icon_sizes) |size| { // 1x { const alias = std.fmt.comptimePrint("{d}x{d}", .{ size, size }); const source = std.fmt.comptimePrint("images/gnome/{d}.png", .{size}); - try cwd.access(source, .{}); + try cwd.access(io, source, .{}); try writer.print( \\ {s} \\ @@ -192,7 +191,7 @@ fn genIcons(writer: *std.Io.Writer) !void { { const alias = std.fmt.comptimePrint("{d}x{d}@2", .{ size, size }); const source = std.fmt.comptimePrint("images/gnome/{d}.png", .{size * 2}); - try cwd.access(source, .{}); + try cwd.access(io, source, .{}); try writer.print( \\ {s} \\ @@ -209,19 +208,19 @@ fn genIcons(writer: *std.Io.Writer) !void { } /// Generate the resources at the root prefix. -fn genRoot(writer: *std.Io.Writer) !void { +fn genRoot(io: std.Io, writer: *std.Io.Writer) !void { try writer.print( \\ \\ , .{build_info.resource_path}); - const cwd = std.fs.cwd(); + const cwd: std.Io.Dir = .cwd(); inline for (css) |name| { const source = std.fmt.comptimePrint( "{s}/{s}", .{ css_path, name }, ); - try cwd.access(source, .{}); + try cwd.access(io, source, .{}); try writer.print( \\ {s} \\ @@ -242,7 +241,7 @@ fn genRoot(writer: *std.Io.Writer) !void { fn genUi( alloc: Allocator, writer: *std.Io.Writer, - files: *const std.ArrayListUnmanaged([]const u8), + files: *const std.ArrayList([]const u8), ) !void { try writer.print( \\ diff --git a/src/apprt/gtk/class.zig b/src/apprt/gtk/class.zig index 942666cf4..a1dba7169 100644 --- a/src/apprt/gtk/class.zig +++ b/src/apprt/gtk/class.zig @@ -145,16 +145,24 @@ pub fn Common( /// as the virtual method but the self parameter points to the /// target instead of the original class. fn ImplementFunc(comptime T: type) type { - var params: [fn_info.params.len]std.builtin.Type.Fn.Param = undefined; - @memcpy(¶ms, fn_info.params); - params[0].type = *ClassInstance(T); - return @Type(.{ .@"fn" = .{ - .calling_convention = fn_info.calling_convention, - .is_generic = fn_info.is_generic, - .is_var_args = fn_info.is_var_args, - .return_type = fn_info.return_type, - .params = ¶ms, - } }); + var types: [fn_info.params.len]type = undefined; + var attrs: [fn_info.params.len]std.builtin.Type.Fn.Param.Attributes = undefined; + + for (fn_info.params, &types, &attrs) |info, *ty, *attr| { + ty.* = info.type.?; + attr.* = .{ .@"noalias" = info.is_noalias }; + } + types[0] = *ClassInstance(T); + + return @Fn( + &types, + &attrs, + fn_info.return_type.?, + .{ + .@"callconv" = fn_info.calling_convention, + .varargs = fn_info.is_var_args, + }, + ); } }; } diff --git a/src/apprt/gtk/class/application.zig b/src/apprt/gtk/class/application.zig index 873674cec..666a2e1dc 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -14,6 +14,7 @@ const state = &@import("../../../global.zig").state; const i18n = @import("../../../os/main.zig").i18n; const apprt = @import("../../../apprt.zig"); const CoreApp = @import("../../../App.zig"); +const compat_file = @import("../../../lib/compat/file.zig"); const configpkg = @import("../../../config.zig"); const input = @import("../../../input.zig"); const internal_os = @import("../../../os/main.zig"); @@ -294,7 +295,7 @@ pub const Application = extern struct { // Setup our GTK init env vars setGtkEnv(&config) catch |err| switch (err) { - error.NoSpaceLeft => { + error.WriteFailed => { // If we fail to set GTK environment variables then we still // try to start the application... log.warn( @@ -338,7 +339,7 @@ pub const Application = extern struct { // I'm unsure of any scenario where this happens. Because we don't // want to litter null checks everywhere, we just exit here. log.warn("gdk display is null, exiting", .{}); - std.posix.exit(1); + std.process.exit(1); }; // Setup our windowing protocol logic @@ -1060,7 +1061,11 @@ pub const Application = extern struct { } } - fn loadCustomCss(self: *Self) (std.fs.File.ReadError || Allocator.Error)!void { + const LoadCustomCssError = std.Io.File.OpenError || + compat_file.ReadToEndAllocError || + std.mem.Allocator.Error; + + fn loadCustomCss(self: *Self) LoadCustomCssError!void { const priv: *Private = self.private(); const alloc = self.allocator(); const display = gdk.Display.getDefault() orelse { @@ -1084,7 +1089,11 @@ pub const Application = extern struct { .optional => |path| .{ path, true }, .required => |path| .{ path, false }, }; - const file = std.fs.openFileAbsolute(path, .{}) catch |err| { + const file = std.Io.Dir.openFileAbsolute( + std.Io.Threaded.global_single_threaded.io(), + path, + .{}, + ) catch |err| { if (err != error.FileNotFound or !optional) { log.warn( "error opening gtk-custom-css file {s}: {}", @@ -1093,12 +1102,14 @@ pub const Application = extern struct { } continue; }; - defer file.close(); + defer file.close(std.Io.Threaded.global_single_threaded.io()); const css_file_size_limit = 5 * 1024 * 1024; // 5MB log.info("loading gtk-custom-css path={s}", .{path}); - const contents = file.readToEndAlloc( + + const contents = compat_file.readToEndAlloc( + file, alloc, css_file_size_limit, ) catch |err| switch (err) { @@ -1108,6 +1119,7 @@ pub const Application = extern struct { }, else => |e| return e, }; + defer alloc.free(contents); const bytes = glib.Bytes.new(contents.ptr, contents.len); @@ -1398,7 +1410,7 @@ pub const Application = extern struct { const priv = self.private(); assert(priv.signal_source == null); priv.signal_source = glib.unixSignalAdd( - std.posix.SIG.USR2, + @intFromEnum(std.posix.SIG.USR2), handleSigusr2, self, ); @@ -1851,11 +1863,8 @@ pub const Application = extern struct { fn init(class: *Class) callconv(.c) void { // Register our compiled resources exactly once. { - const c = @cImport({ - // generated header files - @cInclude("ghostty_resources.h"); - }); - if (c.ghostty_get_resource()) |ptr| { + const ghostty_gtk_resources = @import("ghostty_gtk_resources"); + if (ghostty_gtk_resources.ghostty_get_resource()) |ptr| { gio.resourcesRegister(@ptrCast(@alignCast(ptr))); } else { // If we fail to load resources then things will @@ -2831,7 +2840,7 @@ const Action = struct { /// given the runtime environment or configuration. /// /// This must be called BEFORE GTK initialization. -fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void { +fn setGtkEnv(config: *const CoreConfig) std.Io.Writer.Error!void { assert(gtk.isInitialized() == 0); var gdk_debug: struct { @@ -2900,8 +2909,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void { { var buf: [1024]u8 = undefined; - var fmt = std.io.fixedBufferStream(&buf); - const writer = fmt.writer(); + var writer: std.Io.Writer = .fixed(&buf); var first: bool = true; inline for (@typeInfo(@TypeOf(gdk_debug)).@"struct".fields) |field| { if (@field(gdk_debug, field.name)) { @@ -2911,15 +2919,14 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void { } } try writer.writeByte(0); - const value = fmt.getWritten(); + const value = writer.buffered(); log.warn("setting GDK_DEBUG={s}", .{value[0 .. value.len - 1]}); _ = internal_os.setenv("GDK_DEBUG", value[0 .. value.len - 1 :0]); } { var buf: [1024]u8 = undefined; - var fmt = std.io.fixedBufferStream(&buf); - const writer = fmt.writer(); + var writer: std.Io.Writer = .fixed(&buf); var first: bool = true; inline for (@typeInfo(@TypeOf(gdk_disable)).@"struct".fields) |field| { if (@field(gdk_disable, field.name)) { @@ -2929,7 +2936,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void { } } try writer.writeByte(0); - const value = fmt.getWritten(); + const value = writer.buffered(); log.warn("setting GDK_DISABLE={s}", .{value[0 .. value.len - 1]}); _ = internal_os.setenv("GDK_DISABLE", value[0 .. value.len - 1 :0]); } diff --git a/src/apprt/gtk/class/command_palette.zig b/src/apprt/gtk/class/command_palette.zig index 6101e82e8..0d66bbe94 100644 --- a/src/apprt/gtk/class/command_palette.zig +++ b/src/apprt/gtk/class/command_palette.zig @@ -153,7 +153,7 @@ pub const CommandPalette = extern struct { priv.source.removeAll(); const alloc = Application.default().allocator(); - var commands: std.ArrayList(*Command) = .{}; + var commands: std.ArrayList(*Command) = .empty; defer { for (commands.items) |cmd| cmd.unref(); commands.deinit(alloc); diff --git a/src/apprt/gtk/class/global_shortcuts.zig b/src/apprt/gtk/class/global_shortcuts.zig index cf0f31a6e..f8f7602ab 100644 --- a/src/apprt/gtk/class/global_shortcuts.zig +++ b/src/apprt/gtk/class/global_shortcuts.zig @@ -628,6 +628,9 @@ fn generateToken(buf: *Token) [:0]const u8 { return std.fmt.bufPrintZ( buf, "ghostty_{x:0<7}", - .{std.crypto.random.int(u28)}, + .{rand_int: { + const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() }; + break :rand_int rng_impl.interface().int(u28); + }}, ) catch unreachable; } diff --git a/src/apprt/gtk/class/imgui_widget.zig b/src/apprt/gtk/class/imgui_widget.zig index 0ef753a87..473787f92 100644 --- a/src/apprt/gtk/class/imgui_widget.zig +++ b/src/apprt/gtk/class/imgui_widget.zig @@ -61,13 +61,13 @@ pub const ImguiWidget = extern struct { ig_context: ?*cimgui.c.ImGuiContext = null, /// Our previous instant used to calculate delta time for animations. - instant: ?std.time.Instant = null, + instant: ?std.Io.Timestamp = null, /// Tick callback ID for timed updates. tick_callback_id: c_uint = 0, /// Last render time for throttling to 30 FPS. - last_render_time: ?std.time.Instant = null, + last_render_time: ?std.Io.Timestamp = null, pub var offset: c_int = 0; }; @@ -140,10 +140,11 @@ pub const ImguiWidget = extern struct { const priv = self.private(); const io: *cimgui.c.ImGuiIO = cimgui.c.ImGui_GetIO(); + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); + // Determine our delta time - const now = std.time.Instant.now() catch unreachable; io.DeltaTime = if (priv.instant) |prev| delta: { - const since_ns: f64 = @floatFromInt(now.since(prev)); + const since_ns: f64 = @floatFromInt(prev.durationTo(now).nanoseconds); const ns_per_s: f64 = @floatFromInt(std.time.ns_per_s); const since_s: f32 = @floatCast(since_ns / ns_per_s); break :delta @max(0.00001, since_s); @@ -298,7 +299,7 @@ pub const ImguiWidget = extern struct { // Update last render time for tick callback throttling. const priv = self.private(); - priv.last_render_time = std.time.Instant.now() catch null; + priv.last_render_time = .now(std.Io.Threaded.global_single_threaded.io(), .awake); // Setup our frame. We render twice because some ImGui behaviors // take multiple renders to process. I don't know how to make this @@ -457,15 +458,10 @@ pub const ImguiWidget = extern struct { const self: *Self = gobject.ext.cast(Self, widget) orelse return 0; const priv = self.private(); - const now = std.time.Instant.now() catch { - self.queueRender(); - return 1; - }; - // Throttle to 30 FPS (~33ms between frames) const frame_time_ns: u64 = std.time.ns_per_s / 30; const should_render = if (priv.last_render_time) |last| - now.since(last) >= frame_time_ns + last.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).nanoseconds >= frame_time_ns else true; diff --git a/src/apprt/gtk/class/surface.zig b/src/apprt/gtk/class/surface.zig index 179c779d7..4ab46fa4f 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -1561,7 +1561,7 @@ pub const Surface = extern struct { return self.private().cursor_pos; } - pub fn defaultTermioEnv(self: *Self) !std.process.EnvMap { + pub fn defaultTermioEnv(self: *Self) !std.process.Environ.Map { const app = Application.default(); const alloc = app.allocator(); var env = try internal_os.getEnvMap(alloc); @@ -1570,23 +1570,23 @@ pub const Surface = extern struct { if (app.savedLanguage()) |language| { try env.put("LANG", language); } else { - env.remove("LANG"); + _ = env.orderedRemove("LANG"); } // Don't leak these GTK environment variables to child processes. - env.remove("GDK_DEBUG"); - env.remove("GDK_DISABLE"); - env.remove("GSK_RENDERER"); + _ = env.orderedRemove("GDK_DEBUG"); + _ = env.orderedRemove("GDK_DISABLE"); + _ = env.orderedRemove("GSK_RENDERER"); // Remove some environment variables that are set when Ghostty is launched // from a `.desktop` file, by D-Bus activation, or systemd. - env.remove("GIO_LAUNCHED_DESKTOP_FILE"); - env.remove("GIO_LAUNCHED_DESKTOP_FILE_PID"); - env.remove("DBUS_STARTER_ADDRESS"); - env.remove("DBUS_STARTER_BUS_TYPE"); - env.remove("INVOCATION_ID"); - env.remove("JOURNAL_STREAM"); - env.remove("NOTIFY_SOCKET"); + _ = env.orderedRemove("GIO_LAUNCHED_DESKTOP_FILE"); + _ = env.orderedRemove("GIO_LAUNCHED_DESKTOP_FILE_PID"); + _ = env.orderedRemove("DBUS_STARTER_ADDRESS"); + _ = env.orderedRemove("DBUS_STARTER_BUS_TYPE"); + _ = env.orderedRemove("INVOCATION_ID"); + _ = env.orderedRemove("JOURNAL_STREAM"); + _ = env.orderedRemove("NOTIFY_SOCKET"); // Unset environment varies set by snaps if we're running in a snap. // This allows Ghostty to further launch additional snaps. @@ -1613,7 +1613,7 @@ pub const Surface = extern struct { } /// Filter out environment variables that start with forbidden prefixes. - fn filterSnapPaths(gpa: std.mem.Allocator, env_map: *std.process.EnvMap) !void { + fn filterSnapPaths(gpa: std.mem.Allocator, env_map: *std.process.Environ.Map) !void { comptime assert(build_config.snap); const snap_vars = [_][]const u8{ diff --git a/src/apprt/gtk/ext/actions.zig b/src/apprt/gtk/ext/actions.zig index 3232bc18b..6ba9c678b 100644 --- a/src/apprt/gtk/ext/actions.zig +++ b/src/apprt/gtk/ext/actions.zig @@ -176,7 +176,11 @@ test "adding actions to an object" { _ = addAsGroup(gtk.Box, box, "test", &actions); } - const expected = std.crypto.random.intRangeAtMost(i32, 1, std.math.maxInt(u31)); + const expected = expected: { + const rng_impl: std.Random.IoSource = .{ .io = testing.io }; + const rng = rng_impl.interface(); + break :expected rng.intRangeAtMost(i32, 1, std.math.maxInt(u31)); + }; const parameter = glib.Variant.newInt32(expected); try testing.expect(box.as(gtk.Widget).activateActionVariant("test.test", parameter) != 0); diff --git a/src/apprt/gtk/gtk_version.zig b/src/apprt/gtk/gtk_version.zig index 71edb076d..97e0a5236 100644 --- a/src/apprt/gtk/gtk_version.zig +++ b/src/apprt/gtk/gtk_version.zig @@ -3,18 +3,16 @@ const std = @import("std"); // Until the gobject bindings are built at the same time we are building // Ghostty, we need to import `gtk/gtk.h` directly to ensure that the version // macros match the version of `gtk4` that we are building/linking against. -const c = @cImport({ - @cInclude("gtk/gtk.h"); -}); +const gtk_c = @import("gtk_c"); const gtk = @import("gtk"); const log = std.log.scoped(.gtk); pub const comptime_version: std.SemanticVersion = .{ - .major = c.GTK_MAJOR_VERSION, - .minor = c.GTK_MINOR_VERSION, - .patch = c.GTK_MICRO_VERSION, + .major = gtk_c.GTK_MAJOR_VERSION, + .minor = gtk_c.GTK_MINOR_VERSION, + .patch = gtk_c.GTK_MICRO_VERSION, }; pub fn getRuntimeVersion() std.SemanticVersion { @@ -105,17 +103,17 @@ test "atLeast" { const funs = &.{ atLeast, runtimeAtLeast }; inline for (funs) |fun| { - try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION + 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION + 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); - try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); - try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION)); - try testing.expect(fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1)); - try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION - 1, c.GTK_MICRO_VERSION + 1)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION - 1, gtk_c.GTK_MICRO_VERSION + 1)); } } @@ -125,16 +123,16 @@ test "runtimeUntil" { // This is an array in case we add a comptime variant. const funs = &.{runtimeUntil}; inline for (funs) |fun| { - try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); - try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1)); - try testing.expect(fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION)); - try testing.expect(fun(c.GTK_MAJOR_VERSION + 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(fun(gtk_c.GTK_MAJOR_VERSION + 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION + 1, c.GTK_MICRO_VERSION)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION - 1, c.GTK_MINOR_VERSION, c.GTK_MICRO_VERSION + 1)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION + 1, gtk_c.GTK_MICRO_VERSION)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION - 1, gtk_c.GTK_MINOR_VERSION, gtk_c.GTK_MICRO_VERSION + 1)); - try testing.expect(!fun(c.GTK_MAJOR_VERSION, c.GTK_MINOR_VERSION - 1, c.GTK_MICRO_VERSION + 1)); + try testing.expect(!fun(gtk_c.GTK_MAJOR_VERSION, gtk_c.GTK_MINOR_VERSION - 1, gtk_c.GTK_MICRO_VERSION + 1)); } } diff --git a/src/apprt/gtk/ipc/DBus.zig b/src/apprt/gtk/ipc/DBus.zig index fa4a6723e..76e3c1ad1 100644 --- a/src/apprt/gtk/ipc/DBus.zig +++ b/src/apprt/gtk/ipc/DBus.zig @@ -28,10 +28,12 @@ payload_builder: *glib.VariantBuilder, /// Used to build the parameters for the IPC. parameters_builder: *glib.VariantBuilder, +pub const InitError = Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors; + /// Initialize the helper. -pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!Self { +pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) InitError!Self { var buf: [256]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&buf); + var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &buf); const stderr = &stderr_writer.interface; // Get the appropriate bus name and object path for contacting the @@ -133,7 +135,7 @@ pub fn addParameter(self: *Self, variant: *glib.Variant) void { /// should be done with this object other than call `deinit`. pub fn send(self: *Self) (std.Io.Writer.Error || apprt.ipc.Errors)!void { var buf: [256]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&buf); + var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &buf); const stderr = &stderr_writer.interface; // finish building the parameters diff --git a/src/apprt/gtk/media.zig b/src/apprt/gtk/media.zig index 1015c933f..b51badf85 100644 --- a/src/apprt/gtk/media.zig +++ b/src/apprt/gtk/media.zig @@ -10,7 +10,11 @@ const gtk = @import("gtk"); pub fn fromFilename(path: [:0]const u8) ?*gtk.MediaFile { assert(std.fs.path.isAbsolute(path)); - std.fs.accessAbsolute(path, .{ .mode = .read_only }) catch |err| { + std.Io.Dir.accessAbsolute( + std.Io.Threaded.global_single_threaded.io(), + path, + .{ .read = true }, + ) catch |err| { log.warn("unable to access {s}: {t}", .{ path, err }); return null; }; diff --git a/src/apprt/gtk/portal.zig b/src/apprt/gtk/portal.zig index 3e51042d6..7a5fdaab7 100644 --- a/src/apprt/gtk/portal.zig +++ b/src/apprt/gtk/portal.zig @@ -11,7 +11,8 @@ const token_format = std.fmt.comptimePrint("{{x:0>{}}}", .{token_hex_len}); /// Generate a token suitable for use in requests to the XDG Desktop Portal pub fn generateToken() usize { - return std.crypto.random.int(usize); + const rng_impl: std.Random.IoSource = .{ .io = std.Io.Threaded.global_single_threaded.io() }; + return rng_impl.interface().int(usize); } /// Format a request token consistently for use in portal object paths and payloads. diff --git a/src/apprt/gtk/portal/OpenURI.zig b/src/apprt/gtk/portal/OpenURI.zig index 97aa013e5..2c697b9df 100644 --- a/src/apprt/gtk/portal/OpenURI.zig +++ b/src/apprt/gtk/portal/OpenURI.zig @@ -23,7 +23,7 @@ app: *App, dbus: ?*gio.DBusConnection = null, /// Mutex to protect modification of the entries map or the cleanup timer. -mutex: std.Thread.Mutex = .{}, +mutex: std.Io.Mutex = .init, /// Map to store data about any in-flight calls to the portal. entries: std.AutoArrayHashMapUnmanaged(usize, *Entry) = .empty, @@ -78,7 +78,7 @@ const RequestData = struct { /// Data about any in-flight calls to the portal. pub const Entry = struct { /// When the request started. - start: std.time.Instant, + start: std.Io.Timestamp, /// A token used by the portal to identify requests and responses. The /// actual format of the token does not really matter as long as it can be /// used as part of a D-Bus object path. `usize` was chosen since it's easy @@ -123,8 +123,8 @@ pub fn setDbusConnection(self: *OpenURI, dbus: ?*gio.DBusConnection) void { pub fn deinit(self: *OpenURI) void { const alloc = self.app.app.allocator(); - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (!self.alive) return; self.alive = false; @@ -157,8 +157,8 @@ pub fn start(self: *OpenURI, value: apprt.action.OpenUrl) (Allocator.Error || Er alloc.destroy(request); } - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); // Create an entry that is used to track the results of the D-Bus method // call. @@ -166,7 +166,7 @@ pub fn start(self: *OpenURI, value: apprt.action.OpenUrl) (Allocator.Error || Er const entry = try alloc.create(Entry); errdefer alloc.destroy(entry); entry.* = .{ - .start = std.time.Instant.now() catch return error.TimerUnavailable, + .start = std.Io.Timestamp.now(std.Io.Threaded.global_single_threaded.io(), .awake), .token = token, .kind = value.kind, .uri = try alloc.dupeZ(u8, value.url), @@ -235,8 +235,8 @@ fn destroyEntry(alloc: Allocator, entry: *Entry) void { } fn failRequest(self: *OpenURI, token: usize) ?*Entry { - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (!self.alive) return null; @@ -422,8 +422,8 @@ fn requestCallback( return; } - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (!self.alive) return; @@ -461,8 +461,8 @@ fn responseReceived( return; }; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); if (!self.alive) return; @@ -534,22 +534,18 @@ fn cleanup(ud: ?*anyopaque) callconv(.c) c_int { const alloc = self.app.app.allocator(); - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(std.Io.Threaded.global_single_threaded.io()); + defer self.mutex.unlock(std.Io.Threaded.global_single_threaded.io()); self.cleanup_timer = null; if (!self.alive) return @intFromBool(glib.SOURCE_REMOVE); - const now = std.time.Instant.now() catch { - // `now()` should never fail, but if it does, don't crash, just return. - // This might cause a small memory leak in rare circumstances but it - // should get cleaned up the next time a URL is clicked. - return @intFromBool(glib.SOURCE_REMOVE); - }; - loop: while (true) { for (self.entries.entries.items(.value)) |entry| { - if (now.since(entry.start) > cleanup_timeout * std.time.ns_per_s) { + if (entry.start.untilNow( + std.Io.Threaded.global_single_threaded.io(), + .awake, + ).toSeconds() > cleanup_timeout) { log.warn("open uri request timed out token={x}", .{entry.token}); self.unsubscribeFromResponse(entry); _ = self.entries.swapRemove(entry.token); diff --git a/src/apprt/gtk/post_fork.zig b/src/apprt/gtk/post_fork.zig index ff0219508..756f48b82 100644 --- a/src/apprt/gtk/post_fork.zig +++ b/src/apprt/gtk/post_fork.zig @@ -85,12 +85,10 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void { return; }; - const start = std.time.Instant.now() catch unreachable; + const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); loop: while (true) { - const now = std.time.Instant.now() catch unreachable; - - if (now.since(start) > 250 * std.time.ns_per_ms) { + if (start.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).toMilliseconds() > 250) { if (cmd.rt_pre_exec_info.linux_cgroup_hard_fail) { log.err("transition to new transient systemd scope {s} took too long", .{expected_cgroup}); return error.PostForkError; @@ -116,6 +114,6 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void { } } - std.Thread.sleep(25 * std.time.ns_per_ms); + std.Io.sleep(std.Io.Threaded.global_single_threaded.io(), .fromMilliseconds(25), .awake) catch unreachable; } } diff --git a/src/apprt/gtk/pre_exec.zig b/src/apprt/gtk/pre_exec.zig index 6f6a9ed51..f34e68617 100644 --- a/src/apprt/gtk/pre_exec.zig +++ b/src/apprt/gtk/pre_exec.zig @@ -47,12 +47,10 @@ pub fn preExec(cmd: *Command) ?u8 { var expected_cgroup_buf: [256]u8 = undefined; const expected_cgroup = cgroup.fmtScope(&expected_cgroup_buf, pid); - const start = std.time.Instant.now() catch unreachable; + const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); while (true) { - const now = std.time.Instant.now() catch unreachable; - - if (now.since(start) > 250 * std.time.ns_per_ms) { + if (start.untilNow(std.Io.Threaded.global_single_threaded.io(), .awake).toMilliseconds() > 250) { if (cmd.rt_pre_exec_info.linux_cgroup_hard_fail) { log.err("transition to new transient systemd scope took too long", .{}); return 127; @@ -74,7 +72,7 @@ pub fn preExec(cmd: *Command) ?u8 { if (std.mem.eql(u8, current_cgroup, expected_cgroup)) return null; } - std.Thread.sleep(25 * std.time.ns_per_ms); + std.Io.sleep(std.Io.Threaded.global_single_threaded.io(), .fromMilliseconds(25), .awake) catch unreachable; } return null; diff --git a/src/apprt/gtk/winproto.zig b/src/apprt/gtk/winproto.zig index d409d6c26..a6bc14d96 100644 --- a/src/apprt/gtk/winproto.zig +++ b/src/apprt/gtk/winproto.zig @@ -141,7 +141,7 @@ pub const Window = union(Protocol) { }; } - pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void { + pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void { switch (self.*) { inline else => |*v| try v.addSubprocessEnv(env), } diff --git a/src/apprt/gtk/winproto/noop.zig b/src/apprt/gtk/winproto/noop.zig index 950ee0f37..3b9a3fbc0 100644 --- a/src/apprt/gtk/winproto/noop.zig +++ b/src/apprt/gtk/winproto/noop.zig @@ -67,7 +67,7 @@ pub const Window = struct { return true; } - pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {} + pub fn addSubprocessEnv(_: *Window, _: *std.process.Environ.Map) !void {} pub fn setUrgent(_: *Window, _: bool) !void {} }; diff --git a/src/apprt/gtk/winproto/wayland.zig b/src/apprt/gtk/winproto/wayland.zig index 12c7fb8a2..a53035869 100644 --- a/src/apprt/gtk/winproto/wayland.zig +++ b/src/apprt/gtk/winproto/wayland.zig @@ -225,7 +225,7 @@ pub const Window = struct { }; } - pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void { + pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void { _ = self; _ = env; } diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index 8109959da..3ef33137a 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -317,7 +317,7 @@ pub const Window = struct { self.last_applied_decoration_hints = hints; } - pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void { + pub fn addSubprocessEnv(self: *Window, env: *std.process.Environ.Map) !void { var buf: [64]u8 = undefined; const window_id = try std.fmt.bufPrint( &buf, diff --git a/src/apprt/ipc.zig b/src/apprt/ipc.zig index b37647e02..e4a3e41d1 100644 --- a/src/apprt/ipc.zig +++ b/src/apprt/ipc.zig @@ -122,8 +122,12 @@ pub const Action = union(enum) { /// Sync with: ghostty_ipc_action_u pub const CValue = cvalue: { const key_fields = @typeInfo(Key).@"enum".fields; - var union_fields: [key_fields.len]std.builtin.Type.UnionField = undefined; - for (key_fields, 0..) |field, i| { + + var names: [key_fields.len][]const u8 = undefined; + var types: [key_fields.len]type = undefined; + var attrs: [key_fields.len]std.builtin.Type.UnionField.Attributes = undefined; + + for (key_fields, &names, &types, &attrs) |field, *name, *ty, *attr| { const action = @unionInit(Action, field.name, undefined); const Type = t: { const Type = @TypeOf(@field(action, field.name)); @@ -131,20 +135,12 @@ pub const Action = union(enum) { if (Type != void and @hasDecl(Type, "C")) break :t Type.C; break :t Type; }; - - union_fields[i] = .{ - .name = field.name, - .type = Type, - .alignment = @alignOf(Type), - }; + name.* = field.name; + ty.* = Type; + attr.* = .{ .@"align" = @alignOf(Type) }; } - break :cvalue @Type(.{ .@"union" = .{ - .layout = .@"extern", - .tag_type = null, - .fields = &union_fields, - .decls = &.{}, - } }); + break :cvalue @Union(.@"extern", null, &names, &types, &attrs); }; /// Sync with: ghostty_ipc_action_s diff --git a/src/benchmark/Benchmark.zig b/src/benchmark/Benchmark.zig index 41c3695d4..fa7de241b 100644 --- a/src/benchmark/Benchmark.zig +++ b/src/benchmark/Benchmark.zig @@ -64,21 +64,23 @@ pub fn run( signpost.log.release(); }; - const start = std.time.Instant.now() catch return error.BenchmarkFailed; + const start: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); while (true) { // Run our step function. If it fails, we return the error. try self.vtable.stepFn(self.ptr); result.iterations += 1; // Get our current monotonic time and check our exit conditions. - const now = std.time.Instant.now() catch return error.BenchmarkFailed; + const now: std.Io.Timestamp = .now(std.Io.Threaded.global_single_threaded.io(), .awake); + const elapsed = start.durationTo(now).nanoseconds; + assert(elapsed >= 0); const exit = switch (mode) { .once => true, - .duration => |ns| now.since(start) >= ns, + .duration => |ns| elapsed >= ns, }; if (exit) { - result.duration = now.since(start); + result.duration = @as(u64, @intCast(std.math.clamp(elapsed, 0, std.math.maxInt(u64)))); return result; } } diff --git a/src/benchmark/CodepointWidth.zig b/src/benchmark/CodepointWidth.zig index 30d3f91e7..ca9174ed6 100644 --- a/src/benchmark/CodepointWidth.zig +++ b/src/benchmark/CodepointWidth.zig @@ -20,7 +20,7 @@ const log = std.log.scoped(.@"terminal-stream-bench"); opts: Options, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, pub const Options = struct { /// The type of codepoint width calculation to use. @@ -93,7 +93,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *CodepointWidth = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -114,7 +114,7 @@ fn stepWcwidth(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -141,7 +141,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -173,7 +173,7 @@ fn stepSimd(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; diff --git a/src/benchmark/GraphemeBreak.zig b/src/benchmark/GraphemeBreak.zig index 8278c5c2f..5e320ea19 100644 --- a/src/benchmark/GraphemeBreak.zig +++ b/src/benchmark/GraphemeBreak.zig @@ -17,7 +17,7 @@ const log = std.log.scoped(.@"terminal-stream-bench"); opts: Options, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, pub const Options = struct { /// The type of codepoint width calculation to use. @@ -82,7 +82,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *GraphemeBreak = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -92,7 +92,7 @@ fn stepNoop(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -115,7 +115,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; diff --git a/src/benchmark/IsSymbol.zig b/src/benchmark/IsSymbol.zig index 4fbffd1ec..8204f00a3 100644 --- a/src/benchmark/IsSymbol.zig +++ b/src/benchmark/IsSymbol.zig @@ -17,7 +17,7 @@ const log = std.log.scoped(.@"is-symbol-bench"); opts: Options, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, pub const Options = struct { /// Which test to run. @@ -80,7 +80,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *IsSymbol = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -90,7 +90,7 @@ fn stepUucode(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -117,7 +117,7 @@ fn stepTable(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; diff --git a/src/benchmark/OscParser.zig b/src/benchmark/OscParser.zig index d4b416de8..cd3273280 100644 --- a/src/benchmark/OscParser.zig +++ b/src/benchmark/OscParser.zig @@ -13,7 +13,7 @@ const log = std.log.scoped(.@"osc-parser-bench"); opts: Options, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, parser: Parser, @@ -70,7 +70,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *OscParser = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -80,7 +80,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var r = f.reader(&read_buf); + var r = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var osc_buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { diff --git a/src/benchmark/ScreenClone.zig b/src/benchmark/ScreenClone.zig index 108eaa0c6..b0dafff9a 100644 --- a/src/benchmark/ScreenClone.zig +++ b/src/benchmark/ScreenClone.zig @@ -99,7 +99,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { s.nextSlice("hello"); // Setup our terminal state - const data_f: std.fs.File = (options.dataFile( + const data_f: std.Io.File = (options.dataFile( self.opts.data, ) catch |err| { log.warn("error opening data file err={}", .{err}); @@ -110,7 +110,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { defer stream.deinit(); var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = data_f.reader(&read_buf); + var f_reader = data_f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); const r = &f_reader.interface; var buf: [4096]u8 = undefined; diff --git a/src/benchmark/TerminalParser.zig b/src/benchmark/TerminalParser.zig index 78c933121..49dc4b138 100644 --- a/src/benchmark/TerminalParser.zig +++ b/src/benchmark/TerminalParser.zig @@ -13,7 +13,7 @@ const log = std.log.scoped(.@"terminal-stream-bench"); opts: Options, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, pub const Options = struct { /// The data to read as a filepath. If this is "-" then @@ -61,7 +61,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *TerminalParser = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -76,7 +76,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { // aren't currently IO bound. const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); var r = &f_reader.interface; var p: terminalpkg.Parser = .init(); diff --git a/src/benchmark/TerminalStream.zig b/src/benchmark/TerminalStream.zig index 1cac656e2..07ead9fe4 100644 --- a/src/benchmark/TerminalStream.zig +++ b/src/benchmark/TerminalStream.zig @@ -30,7 +30,7 @@ handler: Handler, stream: Stream, /// The file, opened in the setup function. -data_f: ?std.fs.File = null, +data_f: ?std.Io.File = null, pub const Options = struct { /// The size of the terminal. This affects benchmarking when @@ -99,7 +99,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *TerminalStream = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(std.Io.Threaded.global_single_threaded.io()); self.data_f = null; } } @@ -115,7 +115,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [4096]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(std.Io.Threaded.global_single_threaded.io(), &read_buf); const r = &f_reader.interface; var buf: [4096]u8 = undefined; diff --git a/src/benchmark/cli.zig b/src/benchmark/cli.zig index 13f070774..ef4185dbf 100644 --- a/src/benchmark/cli.zig +++ b/src/benchmark/cli.zig @@ -1,6 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const cli = @import("../cli.zig"); +const compat_args = @import("../lib/compat/args.zig"); +const compat_init = @import("../lib/compat/init.zig"); /// The available actions for the CLI. This is the list of available /// benchmarks. View docs for each individual one in the predictably @@ -36,7 +38,8 @@ pub const Action = enum { }; /// An entrypoint for the benchmark CLI. -pub fn main() !void { +pub fn main(init: std.process.Init.Minimal) !void { + compat_init.run(init); const alloc = std.heap.c_allocator; const action_ = try cli.action.detectArgs(Action, alloc); const action = action_ orelse return error.NoAction; @@ -48,7 +51,7 @@ pub const Args = union(enum) { /// The arguments passed to the CLI via argc/argv. cli, - /// Simple string arguments, parsed via std.process.ArgIteratorGeneral. + /// Simple string arguments, parsed via ArgIteratorGeneral. string: []const u8, }; @@ -81,7 +84,7 @@ fn mainActionImpl( try cli.args.parse(Options, alloc, &opts, &iter); }, .string => |str| { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, str, ); diff --git a/src/benchmark/options.zig b/src/benchmark/options.zig index 049e80f48..a117259a2 100644 --- a/src/benchmark/options.zig +++ b/src/benchmark/options.zig @@ -6,15 +6,15 @@ const std = @import("std"); /// across our CLI. If the path is not set then no file is returned. /// If the path is "-", then we will return stdin. If the path is /// a file then we will open and return the handle. -pub fn dataFile(path_: ?[]const u8) !?std.fs.File { +pub fn dataFile(path_: ?[]const u8) !?std.Io.File { const path = path_ orelse return null; // Stdin if (std.mem.eql(u8, path, "-")) return .stdin(); // Normal file - const file = try std.fs.cwd().openFile(path, .{}); - errdefer file.close(); + const file = try std.Io.Dir.cwd().openFile(std.Io.Threaded.global_single_threaded.io(), path, .{}); + errdefer file.close(std.Io.Threaded.global_single_threaded.io()); return file; } diff --git a/src/build/Config.zig b/src/build/Config.zig index 0a9947317..dfb62512f 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -67,7 +67,7 @@ emit_unicode_table_gen: bool = false, is_dep: bool = false, /// Environmental properties -env: std.process.EnvMap, +env: *const std.process.Environ.Map, pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Config { // Setup our standard Zig target and optimize options, i.e. @@ -124,10 +124,10 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf // defaults. const gtk_targets = gtk.targets(b); - // We use env vars throughout the build so we grab them immediately here. - var env = try std.process.getEnvMap(b.allocator); - errdefer env.deinit(); + // Grab the environment from build state + const env = &b.graph.environ_map; + // We use env vars throughout the build so we grab them immediately here. var config: Config = .{ .optimize = optimize, .target = target, @@ -591,7 +591,7 @@ pub fn terminalOptions(self: *const Config, artifact: TerminalBuildOptions.Artif } /// Returns a baseline CPU target retaining all the other CPU configs. -pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget { +pub fn baselineTarget(self: *const Config, io: std.Io) std.Build.ResolvedTarget { // Set our cpu model as baseline. There may need to be other modifications // we need to make such as resetting CPU features but for now this works. var q = self.target.query; @@ -601,7 +601,7 @@ pub fn baselineTarget(self: *const Config) std.Build.ResolvedTarget { // handle the native case. return .{ .query = q, - .result = std.zig.system.resolveTargetQuery(q) catch + .result = std.zig.system.resolveTargetQuery(io, q) catch @panic("unable to resolve baseline query"), }; } diff --git a/src/build/GhosttyBench.zig b/src/build/GhosttyBench.zig index 27dda8809..fbf8029d2 100644 --- a/src/build/GhosttyBench.zig +++ b/src/build/GhosttyBench.zig @@ -23,9 +23,9 @@ pub fn init( // We always want our datagen to be fast because it // takes awhile to run. .optimize = .ReleaseFast, + .link_libc = true, }), }); - exe.linkLibC(); _ = try deps.add(exe); try steps.append(b.allocator, exe); } @@ -39,9 +39,9 @@ pub fn init( .target = deps.config.target, // We always want our benchmarks to be in release mode. .optimize = .ReleaseFast, + .link_libc = true, }), }); - exe.linkLibC(); _ = try deps.add(exe); try steps.append(b.allocator, exe); } diff --git a/src/build/GhosttyDist.zig b/src/build/GhosttyDist.zig index 448047f4b..fe8b863fb 100644 --- a/src/build/GhosttyDist.zig +++ b/src/build/GhosttyDist.zig @@ -237,11 +237,11 @@ pub const Resource = struct { /// Returns true if the dist path exists at build time. pub fn exists(self: *const Resource, b: *std.Build) bool { - if (b.build_root.handle.access(self.dist, .{})) { + if (b.build_root.handle.access(b.graph.io, self.dist, .{})) { // If we have a ".git" directory then we're a git checkout // and we never want to use the dist path. This shouldn't happen // so show a warning to the user. - if (b.build_root.handle.access(".git", .{})) { + if (b.build_root.handle.access(b.graph.io, ".git", .{})) { std.log.warn( "dist resource '{s}' should not be in a git checkout", .{self.dist}, diff --git a/src/build/GhosttyDocs.zig b/src/build/GhosttyDocs.zig index cd75fc061..2b207f237 100644 --- a/src/build/GhosttyDocs.zig +++ b/src/build/GhosttyDocs.zig @@ -50,7 +50,7 @@ pub fn init( generate_markdown.root_module.addOptions("build_options", generate_markdown_options); const generate_markdown_step = b.addRunArtifact(generate_markdown); - const markdown_output = generate_markdown_step.captureStdOut(); + const markdown_output = generate_markdown_step.captureStdOut(.{}); try steps.append(b.allocator, &b.addInstallFile( markdown_output, @@ -68,7 +68,7 @@ pub fn init( generate_html.addFileArg(markdown_output); try steps.append(b.allocator, &b.addInstallFile( - generate_html.captureStdOut(), + generate_html.captureStdOut(.{}), "share/ghostty/doc/" ++ manpage.name ++ "." ++ manpage.section ++ ".html", ).step); @@ -83,7 +83,7 @@ pub fn init( generate_manpage.addFileArg(markdown_output); try steps.append(b.allocator, &b.addInstallFile( - generate_manpage.captureStdOut(), + generate_manpage.captureStdOut(.{}), "share/man/man" ++ manpage.section ++ "/" ++ manpage.name ++ "." ++ manpage.section, ).step); } diff --git a/src/build/GhosttyExe.zig b/src/build/GhosttyExe.zig index caa564bf0..9d9d5072b 100644 --- a/src/build/GhosttyExe.zig +++ b/src/build/GhosttyExe.zig @@ -50,7 +50,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty switch (cfg.target.result.os.tag) { .windows => { exe.subsystem = .Windows; - exe.addWin32ResourceFile(.{ + exe.root_module.addWin32ResourceFile(.{ .file = b.path("dist/windows/ghostty.rc"), }); }, @@ -85,7 +85,7 @@ fn checkNixShell(exe: *std.Build.Step.Compile, cfg: *const Config) !void { if (!cfg.target.query.isNativeOs()) return; // Verify we're in NixOS - std.fs.accessAbsolute("/etc/NIXOS", .{}) catch return; + std.Io.Dir.accessAbsolute(exe.step.owner.graph.io, "/etc/NIXOS", .{}) catch return; // If we're in a nix shell, not a problem if (cfg.env.get("IN_NIX_SHELL") != null) return; diff --git a/src/build/GhosttyFrameData.zig b/src/build/GhosttyFrameData.zig index 8469759f9..be61f029f 100644 --- a/src/build/GhosttyFrameData.zig +++ b/src/build/GhosttyFrameData.zig @@ -40,16 +40,16 @@ pub fn distResources(b: *std.Build) struct { .name = "framegen", .root_module = b.createModule(.{ .target = b.graph.host, + .link_libc = true, }), }); - exe.addCSourceFile(.{ + exe.root_module.addCSourceFile(.{ .file = b.path("src/build/framegen/main.c"), .flags = &.{}, }); - exe.linkLibC(); if (b.systemIntegrationOption("zlib", .{})) { - exe.linkSystemLibrary2("zlib", .{ + exe.root_module.linkSystemLibrary("zlib", .{ .preferred_link_mode = .dynamic, .search_strategy = .mode_first, }); @@ -58,7 +58,7 @@ pub fn distResources(b: *std.Build) struct { .target = b.graph.host, .optimize = .ReleaseFast, })) |zlib_dep| { - exe.linkLibrary(zlib_dep.artifact("z")); + exe.root_module.linkLibrary(zlib_dep.artifact("z")); } } diff --git a/src/build/GhosttyI18n.zig b/src/build/GhosttyI18n.zig index 0874676cb..434c59a5d 100644 --- a/src/build/GhosttyI18n.zig +++ b/src/build/GhosttyI18n.zig @@ -34,7 +34,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyI18n { msgfmt.addFileArg(b.path("po/" ++ locale ++ ".po")); try steps.append(b.allocator, &b.addInstallFile( - msgfmt.captureStdOut(), + msgfmt.captureStdOut(.{}), std.fmt.comptimePrint( "share/locale/{s}/LC_MESSAGES/{s}.mo", .{ target_locale, domain }, @@ -103,14 +103,15 @@ fn createUpdateStep(b: *std.Build) !*std.Build.Step { } var gtk_dir = try b.build_root.handle.openDir( + b.graph.io, "src/apprt/gtk", .{ .iterate = true }, ); - defer gtk_dir.close(); + defer gtk_dir.close(b.graph.io); var walk = try gtk_dir.walk(b.allocator); defer walk.deinit(); - while (try walk.next()) |src| { + while (try walk.next(b.graph.io)) |src| { switch (src.kind) { .file => if (!std.mem.endsWith( u8, @@ -178,15 +179,15 @@ fn createUpdateStep(b: *std.Build) !*std.Build.Step { xgettext_merge.addFileArg(gtk_pot); const usf = b.addUpdateSourceFiles(); usf.addCopyFileToSource( - xgettext_merge.captureStdOut(), + xgettext_merge.captureStdOut(.{}), "po/" ++ domain ++ ".pot", ); inline for (locales) |locale| { const msgmerge = b.addSystemCommand(&.{ "msgmerge", "--quiet", "--no-fuzzy-matching" }); msgmerge.addFileArg(b.path("po/" ++ locale ++ ".po")); - msgmerge.addFileArg(xgettext_merge.captureStdOut()); - usf.addCopyFileToSource(msgmerge.captureStdOut(), "po/" ++ locale ++ ".po"); + msgmerge.addFileArg(xgettext_merge.captureStdOut(.{})); + usf.addCopyFileToSource(msgmerge.captureStdOut(.{}), "po/" ++ locale ++ ".po"); } return &usf.step; diff --git a/src/build/GhosttyLib.zig b/src/build/GhosttyLib.zig index b762da8bb..6fe28370c 100644 --- a/src/build/GhosttyLib.zig +++ b/src/build/GhosttyLib.zig @@ -29,12 +29,12 @@ pub fn initStatic( .strip = deps.config.strip, .omit_frame_pointer = deps.config.strip, .unwind_tables = if (deps.config.strip) .none else .sync, + .link_libc = true, }), // Fails on self-hosted x86_64 on macOS .use_llvm = true, }); - lib.linkLibC(); // These must be bundled since we're compiling into a static lib. // Otherwise, you get undefined symbol errors. @@ -72,6 +72,14 @@ pub fn initShared( b: *std.Build, deps: *const SharedDeps, ) !GhosttyLib { + // For dynamic linking, we prefer dynamic linking and to search by + // mode first. Mode first will search all paths for a dynamic library + // before falling back to static. + const dynamic_link_opts: std.Build.Module.LinkSystemLibraryOptions = .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, + }; + const lib = b.addLibrary(.{ .name = "ghostty", .linkage = .dynamic, @@ -99,12 +107,12 @@ pub fn initShared( { // The CRT initialization code in msvcrt.lib calls __vcrt_initialize // and __acrt_initialize, which are in the static CRT libraries. - lib.linkSystemLibrary("libvcruntime"); + lib.root_module.linkSystemLibrary("libvcruntime", dynamic_link_opts); // ucrt.lib is in the Windows SDK 'ucrt' dir. Detect the SDK // installation and add the UCRT library path. const arch = deps.config.target.result.cpu.arch; - const sdk = std.zig.WindowsSdk.find(b.allocator, arch) catch null; + const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, arch, &b.graph.environ_map) catch null; if (sdk) |s| { if (s.windows10sdk) |w10| { const arch_str: []const u8 = switch (arch) { @@ -120,11 +128,11 @@ pub fn initShared( ) catch null; if (ucrt_lib_path) |path| { - lib.addLibraryPath(.{ .cwd_relative = path }); + lib.root_module.addLibraryPath(.{ .cwd_relative = path }); } } } - lib.linkSystemLibrary("libucrt"); + lib.root_module.linkSystemLibrary("libucrt", dynamic_link_opts); } // Get our debug symbols diff --git a/src/build/GhosttyLibVt.zig b/src/build/GhosttyLibVt.zig index 65f945bfd..8ff25f503 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -174,7 +174,12 @@ pub fn initStaticAppleUniversal( .os_tag = p.os_tag, .os_version_min = Config.osVersionMin(p.os_tag), }; - if (detectAppleSDK(b.resolveTargetQuery(target_query).result)) { + if (detectAppleSDK( + b.graph.io, + b.allocator, + &b.graph.environ_map, + b.resolveTargetQuery(target_query).result, + )) { const dev_zig = try zig.retarget(b, cfg, deps, b.resolveTargetQuery(target_query)); result.put(p.device, try initStatic(b, &dev_zig)); @@ -438,12 +443,21 @@ pub fn xcframework( } /// Returns true if the Apple SDK for the given target is installed. -fn detectAppleSDK(target: std.Target) bool { - _ = std.zig.LibCInstallation.findNative(.{ - .allocator = std.heap.page_allocator, - .target = &target, - .verbose = false, - }) catch return false; +fn detectAppleSDK( + io: std.Io, + alloc: std.mem.Allocator, + environ_map: *const std.process.Environ.Map, + target: std.Target, +) bool { + _ = std.zig.LibCInstallation.findNative( + alloc, + io, + .{ + .environ_map = environ_map, + .target = &target, + .verbose = false, + }, + ) catch return false; return true; } diff --git a/src/build/GhosttyResources.zig b/src/build/GhosttyResources.zig index 6f857655b..673c7e56a 100644 --- a/src/build/GhosttyResources.zig +++ b/src/build/GhosttyResources.zig @@ -21,9 +21,9 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty .strip = false, .omit_frame_pointer = false, .unwind_tables = .sync, + .link_libc = true, }), }); - build_data_exe.linkLibC(); deps.help_strings.addImport(build_data_exe); @@ -39,7 +39,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run = b.addRunArtifact(build_data_exe); run.addArg("+terminfo"); const wf = b.addWriteFiles(); - const source = wf.addCopyFile(run.captureStdOut(), "ghostty.terminfo"); + const source = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.terminfo"); if (cfg.emit_terminfo) { const source_install = b.addInstallFile( @@ -63,8 +63,8 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run_step = RunStep.create(b, "infotocap"); run_step.addArg("infotocap"); run_step.addFileArg(source); - const out_source = run_step.captureStdOut(); - _ = run_step.captureStdErr(); // so we don't see stderr + const out_source = run_step.captureStdOut(.{}); + _ = run_step.captureStdErr(.{}); // so we don't see stderr const cap_install = b.addInstallFile( out_source, @@ -84,7 +84,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const path = run_step.addOutputFileArg(terminfo_share_dir); run_step.addFileArg(source); - _ = run_step.captureStdErr(); // so we don't see stderr + _ = run_step.captureStdErr(.{}); // so we don't see stderr // Ensure that `share/terminfo` is a directory, otherwise the `cp // -R` will create a file named `share/terminfo` @@ -143,7 +143,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run = b.addRunArtifact(build_data_exe); run.addArg("+fish"); const wf = b.addWriteFiles(); - _ = wf.addCopyFile(run.captureStdOut(), "ghostty.fish"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.fish"); const install_step = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), @@ -158,7 +158,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run = b.addRunArtifact(build_data_exe); run.addArg("+zsh"); const wf = b.addWriteFiles(); - _ = wf.addCopyFile(run.captureStdOut(), "_ghostty"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "_ghostty"); const install_step = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), @@ -173,7 +173,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run = b.addRunArtifact(build_data_exe); run.addArg("+bash"); const wf = b.addWriteFiles(); - _ = wf.addCopyFile(run.captureStdOut(), "ghostty.bash"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.bash"); const install_step = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), @@ -190,22 +190,22 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty { const run = b.addRunArtifact(build_data_exe); run.addArg("+vim-syntax"); - _ = wf.addCopyFile(run.captureStdOut(), "syntax/ghostty.vim"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "syntax/ghostty.vim"); } { const run = b.addRunArtifact(build_data_exe); run.addArg("+vim-ftdetect"); - _ = wf.addCopyFile(run.captureStdOut(), "ftdetect/ghostty.vim"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "ftdetect/ghostty.vim"); } { const run = b.addRunArtifact(build_data_exe); run.addArg("+vim-ftplugin"); - _ = wf.addCopyFile(run.captureStdOut(), "ftplugin/ghostty.vim"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "ftplugin/ghostty.vim"); } { const run = b.addRunArtifact(build_data_exe); run.addArg("+vim-compiler"); - _ = wf.addCopyFile(run.captureStdOut(), "compiler/ghostty.vim"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "compiler/ghostty.vim"); } const vim_step = b.addInstallDirectory(.{ @@ -233,7 +233,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty const run = b.addRunArtifact(build_data_exe); run.addArg("+sublime"); const wf = b.addWriteFiles(); - _ = wf.addCopyFile(run.captureStdOut(), "ghostty.sublime-syntax"); + _ = wf.addCopyFile(run.captureStdOut(.{}), "ghostty.sublime-syntax"); const install_step = b.addInstallDirectory(.{ .source_dir = wf.getDirectory(), @@ -358,10 +358,10 @@ fn addLinuxAppResources( // Template output has a single header line we want to remove. // We use `tail` to do it since its part of the POSIX standard. const tail = b.addSystemCommand(&.{ "tail", "-n", "+2" }); - tail.setStdIn(.{ .lazy_path = tpl.getOutput() }); + tail.setStdIn(.{ .lazy_path = tpl.getOutputFile() }); const copy = b.addInstallFile( - tail.captureStdOut(), + tail.captureStdOut(.{}), template[1], ); diff --git a/src/build/GhosttyWebdata.zig b/src/build/GhosttyWebdata.zig index e29b20c25..7057ad034 100644 --- a/src/build/GhosttyWebdata.zig +++ b/src/build/GhosttyWebdata.zig @@ -40,7 +40,7 @@ pub fn init( } const webgen_config_step = b.addRunArtifact(webgen_config); - const webgen_config_out = webgen_config_step.captureStdOut(); + const webgen_config_out = webgen_config_step.captureStdOut(.{}); try steps.append(b.allocator, &b.addInstallFile( webgen_config_out, @@ -71,7 +71,7 @@ pub fn init( } const webgen_actions_step = b.addRunArtifact(webgen_actions); - const webgen_actions_out = webgen_actions_step.captureStdOut(); + const webgen_actions_out = webgen_actions_step.captureStdOut(.{}); try steps.append(b.allocator, &b.addInstallFile( webgen_actions_out, @@ -102,7 +102,7 @@ pub fn init( } const webgen_commands_step = b.addRunArtifact(webgen_commands); - const webgen_commands_out = webgen_commands_step.captureStdOut(); + const webgen_commands_out = webgen_commands_step.captureStdOut(.{}); try steps.append(b.allocator, &b.addInstallFile( webgen_commands_out, diff --git a/src/build/GhosttyXcodebuild.zig b/src/build/GhosttyXcodebuild.zig index 81af994ca..6f7e18dc3 100644 --- a/src/build/GhosttyXcodebuild.zig +++ b/src/build/GhosttyXcodebuild.zig @@ -48,21 +48,21 @@ pub fn init( }, }; - const env = try std.process.getEnvMap(b.allocator); + const env = b.graph.environ_map; const app_path = b.fmt("macos/build/{s}/Ghostty.app", .{xc_config}); // Our step to build the Ghostty macOS app. const build = build: { // External environment variables can mess up xcodebuild, so // we create a new empty environment. - const env_map = try b.allocator.create(std.process.EnvMap); + const env_map = try b.allocator.create(std.process.Environ.Map); env_map.* = .init(b.allocator); if (env.get("PATH")) |v| try env_map.put("PATH", v); const step = RunStep.create(b, "xcodebuild"); step.has_side_effects = true; step.cwd = b.path("macos"); - step.env_map = env_map; + step.environ_map = env_map; step.addArgs(&.{ "xcodebuild", "-target", @@ -91,14 +91,14 @@ pub fn init( }; const xctest = xctest: { - const env_map = try b.allocator.create(std.process.EnvMap); + const env_map = try b.allocator.create(std.process.Environ.Map); env_map.* = .init(b.allocator); if (env.get("PATH")) |v| try env_map.put("PATH", v); const step = RunStep.create(b, "xcodebuild test"); step.has_side_effects = true; step.cwd = b.path("macos"); - step.env_map = env_map; + step.environ_map = env_map; step.addArgs(&.{ "xcodebuild", "test", diff --git a/src/build/GhosttyZig.zig b/src/build/GhosttyZig.zig index 44c300e15..202c6464e 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -135,7 +135,7 @@ fn initVt( deps.unicode_tables.addModuleImport(vt); // We need uucode for grapheme break support - deps.addUucode(b, vt, cfg.target, cfg.optimize); + vt.addImport("uucode", deps.uucode_mod); // If SIMD is enabled, add all our SIMD dependencies. if (cfg.simd) { diff --git a/src/build/GitVersion.zig b/src/build/GitVersion.zig index 41cc7f84f..2365a110c 100644 --- a/src/build/GitVersion.zig +++ b/src/build/GitVersion.zig @@ -23,7 +23,7 @@ pub fn detect(b: *std.Build) !Version { const tmp: []u8 = b.runAllowFail( &[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "rev-parse", "--abbrev-ref", "HEAD" }, &code, - .Ignore, + .ignore, ) catch |err| switch (err) { error.FileNotFound => return error.GitNotFound, error.ExitCodeFailure => return error.GitNotRepository, @@ -44,19 +44,19 @@ pub fn detect(b: *std.Build) !Version { const output = b.runAllowFail( &[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "-c", "log.showSignature=false", "log", "--pretty=format:%h", "-n", "1" }, &code, - .Ignore, + .ignore, ) catch |err| switch (err) { error.FileNotFound => return error.GitNotFound, else => return err, }; - break :short_hash std.mem.trimRight(u8, output, "\r\n "); + break :short_hash std.mem.trimEnd(u8, output, "\r\n "); }; const tag = b.runAllowFail( &[_][]const u8{ "git", "-C", b.build_root.path orelse ".", "describe", "--exact-match", "--tags" }, &code, - .Ignore, + .ignore, ) catch |err| switch (err) { error.FileNotFound => return error.GitNotFound, error.ExitCodeFailure => "", // expected @@ -70,7 +70,7 @@ pub fn detect(b: *std.Build) !Version { "diff", "--quiet", "--exit-code", - }, &code, .Ignore) catch |err| switch (err) { + }, &code, .ignore) catch |err| switch (err) { error.FileNotFound => return error.GitNotFound, error.ExitCodeFailure => {}, // expected else => return err, @@ -80,7 +80,7 @@ pub fn detect(b: *std.Build) !Version { return .{ .short_hash = short_hash, .changes = changes, - .tag = if (tag.len > 0) std.mem.trimRight(u8, tag, "\r\n ") else null, - .branch = std.mem.trimRight(u8, branch, "\r\n "), + .tag = if (tag.len > 0) std.mem.trimEnd(u8, tag, "\r\n ") else null, + .branch = std.mem.trimEnd(u8, branch, "\r\n "), }; } diff --git a/src/build/HelpStrings.zig b/src/build/HelpStrings.zig index 96505caba..24ae82e2a 100644 --- a/src/build/HelpStrings.zig +++ b/src/build/HelpStrings.zig @@ -34,7 +34,7 @@ pub fn init(b: *std.Build, cfg: *const Config) !HelpStrings { // Generated Zig files have to end with .zig const wf = b.addWriteFiles(); - const output = wf.addCopyFile(help_run.captureStdOut(), "helpgen.zig"); + const output = wf.addCopyFile(help_run.captureStdOut(.{}), "helpgen.zig"); return .{ .exe = exe, diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index b68be92d0..106a0d641 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -2,6 +2,12 @@ const SharedDeps = @This(); const std = @import("std"); const builtin = @import("builtin"); +// NOTE: we only translate some headers right now with the external translate-c +// package (for headers that are currently having issues translating in +// 0.16.0). While we likely won't need to use the external package eventually +// once bugs are fixed, we will need to use similar patterns for all +// translations eventually, as cImport is going away. +const Translator = @import("translate_c").Translator; const Config = @import("Config.zig"); const HelpStrings = @import("HelpStrings.zig"); @@ -9,6 +15,7 @@ const MetallibStep = @import("MetallibStep.zig"); const UnicodeTables = @import("UnicodeTables.zig"); const GhosttyFrameData = @import("GhosttyFrameData.zig"); const DistResource = @import("GhosttyDist.zig").Resource; +const gtk_helpers = @import("gtk.zig"); config: *const Config, @@ -19,6 +26,43 @@ unicode_tables: UnicodeTables, framedata: GhosttyFrameData, uucode_tables: std.Build.LazyPath, +/// Singleton uucode module, instantiated once in `init` and reused +/// everywhere so that ghostty and vaxis share the same compiled tables in +/// each final binary instead of each linking its own copy. +/// +/// Sharing one instance is also a hard requirement (not just an +/// optimization) for Zig 0.16's strict module model. `SharedDeps.add` runs +/// many times across different (target, optimize) tuples (macos-aarch64, +/// macos-x86_64, ios-aarch64, Debug + ReleaseFast, etc.), and on each +/// call we have to wire uucode into both the step's root module and into +/// vaxis_mod (because vaxis's `Parser.zig` does `@import("uucode")` and +/// we pass `external_uucode = true` to vaxis's build.zig so vaxis doesn't +/// instantiate its own uucode dep). If those two import bindings ever +/// resolve to *different* `*Module` pointers within a single Compile +/// step's analysis, Zig fails with: +/// +/// vaxis/src/Parser.zig: file exists in modules 'uucode' and 'uucode0' +/// +/// because all those uucode module instances share the same physical +/// `uucode/src/root.zig` file on disk, and Zig requires every file to belong +/// to exactly one module within a Compile graph. +/// +/// The natural way to keep them the same would be to call +/// `b.lazyDependency("uucode", .{ .tables_path, .build_config_path })` +/// from each call site and let Zig's dependency cache deduplicate +/// identical args. That fails because of a bug in Zig's +/// `userLazyPathsAreTheSame` (Build.zig) where the `.src_path` and +/// `.generated` equality checks are inverted: `if (std.mem.eql(...)) +/// return false` instead of `if (!std.mem.eql(...)) return false`. The +/// dep cache key therefore always misses whenever any arg is a +/// `b.path(...)` LazyPath, so each call returns a fresh `*Dependency` +/// with a fresh `*Module`. Hoisting the dep into one eager +/// `b.dependency` call here sidesteps the cache entirely. +/// +/// This conflict is independent of whether vaxis itself is acquired as a +/// singleton or per-target dep. +uucode_mod: *std.Build.Module, + /// Used to keep track of a list of file sources. pub const LazyPathList = std.ArrayList(std.Build.LazyPath); @@ -31,12 +75,20 @@ pub fn init(b: *std.Build, cfg: *const Config) !SharedDeps { break :blk uucode.namedLazyPath("tables.zig"); }; + // Instantiate the singleton uucode module that both ghostty and vaxis + // import. See the doc comment on `uucode_mod`. + const uucode_mod = b.dependency("uucode", .{ + .tables_path = uucode_tables, + .build_config_path = b.path("src/build/uucode_config.zig"), + }).module("uucode"); + var result: SharedDeps = .{ .config = cfg, .help_strings = try .init(b, cfg), .unicode_tables = try .init(b, uucode_tables), .framedata = try .init(b), .uucode_tables = uucode_tables, + .uucode_mod = uucode_mod, // Setup by retarget .options = undefined, @@ -135,6 +187,9 @@ pub fn add( // Every exe needs the terminal options self.config.terminalOptions(.ghostty).add(b, step.root_module); + // Every exe needs the uucode module + step.root_module.addImport("uucode", self.uucode_mod); + // C imports for locale constants and functions { const c = b.addTranslateC(.{ @@ -143,11 +198,15 @@ pub fn add( .optimize = optimize, }); if (target.result.os.tag.isDarwin()) { - const libc = try std.zig.LibCInstallation.findNative(.{ - .allocator = b.allocator, - .target = &target.result, - .verbose = false, - }); + const libc = try std.zig.LibCInstallation.findNative( + b.allocator, + b.graph.io, + .{ + .environ_map = &b.graph.environ_map, + .target = &target.result, + .verbose = false, + }, + ); c.addSystemIncludePath(.{ .cwd_relative = libc.sys_include_dir.? }); } step.root_module.addImport("locale-c", c.createModule()); @@ -166,11 +225,15 @@ pub fn add( }); switch (target.result.os.tag) { .macos => { - const libc = try std.zig.LibCInstallation.findNative(.{ - .allocator = b.allocator, - .target = &target.result, - .verbose = false, - }); + const libc = try std.zig.LibCInstallation.findNative( + b.allocator, + b.graph.io, + .{ + .environ_map = &b.graph.environ_map, + .target = &target.result, + .verbose = false, + }, + ); c.addSystemIncludePath(.{ .cwd_relative = libc.sys_include_dir.? }); }, else => {}, @@ -194,10 +257,10 @@ pub fn add( ); if (b.systemIntegrationOption("freetype", .{})) { - step.linkSystemLibrary2("bzip2", dynamic_link_opts); - step.linkSystemLibrary2("freetype2", dynamic_link_opts); + step.root_module.linkSystemLibrary("bzip2", dynamic_link_opts); + step.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { - step.linkLibrary(freetype_dep.artifact("freetype")); + step.root_module.linkLibrary(freetype_dep.artifact("freetype")); try static_libs.append( b.allocator, freetype_dep.artifact("freetype").getEmittedBin(), @@ -219,9 +282,9 @@ pub fn add( harfbuzz_dep.module("harfbuzz"), ); if (b.systemIntegrationOption("harfbuzz", .{})) { - step.linkSystemLibrary2("harfbuzz", dynamic_link_opts); + step.root_module.linkSystemLibrary("harfbuzz", dynamic_link_opts); } else { - step.linkLibrary(harfbuzz_dep.artifact("harfbuzz")); + step.root_module.linkLibrary(harfbuzz_dep.artifact("harfbuzz")); try static_libs.append( b.allocator, harfbuzz_dep.artifact("harfbuzz").getEmittedBin(), @@ -243,9 +306,9 @@ pub fn add( ); if (b.systemIntegrationOption("fontconfig", .{})) { - step.linkSystemLibrary2("fontconfig", dynamic_link_opts); + step.root_module.linkSystemLibrary("fontconfig", dynamic_link_opts); } else { - step.linkLibrary(fontconfig_dep.artifact("fontconfig")); + step.root_module.linkLibrary(fontconfig_dep.artifact("fontconfig")); try static_libs.append( b.allocator, fontconfig_dep.artifact("fontconfig").getEmittedBin(), @@ -263,7 +326,7 @@ pub fn add( .target = target, .optimize = optimize, })) |libpng_dep| { - step.linkLibrary(libpng_dep.artifact("png")); + step.root_module.linkLibrary(libpng_dep.artifact("png")); try static_libs.append( b.allocator, libpng_dep.artifact("png").getEmittedBin(), @@ -277,7 +340,7 @@ pub fn add( .target = target, .optimize = optimize, })) |zlib_dep| { - step.linkLibrary(zlib_dep.artifact("z")); + step.root_module.linkLibrary(zlib_dep.artifact("z")); try static_libs.append( b.allocator, zlib_dep.artifact("z").getEmittedBin(), @@ -295,9 +358,9 @@ pub fn add( oniguruma_dep.module("oniguruma"), ); if (b.systemIntegrationOption("oniguruma", .{})) { - step.linkSystemLibrary2("oniguruma", dynamic_link_opts); + step.root_module.linkSystemLibrary("oniguruma", dynamic_link_opts); } else { - step.linkLibrary(oniguruma_dep.artifact("oniguruma")); + step.root_module.linkLibrary(oniguruma_dep.artifact("oniguruma")); try static_libs.append( b.allocator, oniguruma_dep.artifact("oniguruma").getEmittedBin(), @@ -312,13 +375,13 @@ pub fn add( })) |glslang_dep| { step.root_module.addImport("glslang", glslang_dep.module("glslang")); if (b.systemIntegrationOption("glslang", .{})) { - step.linkSystemLibrary2("glslang", dynamic_link_opts); - step.linkSystemLibrary2( + step.root_module.linkSystemLibrary("glslang", dynamic_link_opts); + step.root_module.linkSystemLibrary( "glslang-default-resource-limits", dynamic_link_opts, ); } else { - step.linkLibrary(glslang_dep.artifact("glslang")); + step.root_module.linkLibrary(glslang_dep.artifact("glslang")); try static_libs.append( b.allocator, glslang_dep.artifact("glslang").getEmittedBin(), @@ -336,9 +399,9 @@ pub fn add( spirv_cross_dep.module("spirv_cross"), ); if (b.systemIntegrationOption("spirv-cross", .{})) { - step.linkSystemLibrary2("spirv-cross-c-shared", dynamic_link_opts); + step.root_module.linkSystemLibrary("spirv-cross-c-shared", dynamic_link_opts); } else { - step.linkLibrary(spirv_cross_dep.artifact("spirv_cross")); + step.root_module.linkLibrary(spirv_cross_dep.artifact("spirv_cross")); try static_libs.append( b.allocator, spirv_cross_dep.artifact("spirv_cross").getEmittedBin(), @@ -357,7 +420,7 @@ pub fn add( "sentry", sentry_dep.module("sentry"), ); - step.linkLibrary(sentry_dep.artifact("sentry")); + step.root_module.linkLibrary(sentry_dep.artifact("sentry")); try static_libs.append( b.allocator, sentry_dep.artifact("sentry").getEmittedBin(), @@ -404,18 +467,18 @@ pub fn add( if (step.rootModuleTarget().os.tag == .linux) { const triple = try step.rootModuleTarget().linuxTriple(b.allocator); const path = b.fmt("/usr/lib/{s}", .{triple}); - if (std.fs.accessAbsolute(path, .{})) { - step.addLibraryPath(.{ .cwd_relative = path }); + if (std.Io.Dir.accessAbsolute(b.graph.io, path, .{})) { + step.root_module.addLibraryPath(.{ .cwd_relative = path }); } else |_| {} } // C files - step.linkLibC(); - step.addIncludePath(b.path("src/stb")); + step.root_module.link_libc = true; + step.root_module.addIncludePath(b.path("src/stb")); // Disable ubsan for MSVC: Zig's ubsan runtime cannot be bundled // on Windows (LNK4229), leaving __ubsan_handle_* unresolved when // the static archive is consumed by an external linker. - step.addCSourceFiles(.{ + step.root_module.addCSourceFiles(.{ .files = &.{"src/stb/stb.c"}, .flags = if (step.rootModuleTarget().abi == .msvc) &.{ "-fno-sanitize=undefined", "-fno-sanitize-trap=undefined" } @@ -423,7 +486,7 @@ pub fn add( &.{}, }); if (step.rootModuleTarget().os.tag == .linux) { - step.addIncludePath(b.path("src/apprt/gtk")); + step.root_module.addIncludePath(b.path("src/apprt/gtk")); } // libcpp is required for various dependencies. On MSVC, we must @@ -433,7 +496,7 @@ pub fn add( // include directories (already added via linkLibC above) contain // both C and C++ headers, so linkLibCpp is not needed. if (step.rootModuleTarget().abi != .msvc) { - step.linkLibCpp(); + step.root_module.link_libcpp = true; } // We always require the system SDK so that our system headers are available. @@ -452,8 +515,14 @@ pub fn add( if (b.lazyDependency("opengl", .{})) |dep| { step.root_module.addImport("opengl", dep.module("opengl")); } - if (b.lazyDependency("vaxis", .{})) |dep| { - step.root_module.addImport("vaxis", dep.module("vaxis")); + if (b.lazyDependency("vaxis", .{ + .target = target, + .optimize = optimize, + .external_uucode = true, + })) |dep| { + const vaxis = dep.module("vaxis"); + step.root_module.addImport("vaxis", vaxis); + vaxis.addImport("uucode", self.uucode_mod); } if (b.lazyDependency("wuffs", .{ .target = target, @@ -473,7 +542,6 @@ pub fn add( })) |dep| { step.root_module.addImport("z2d", dep.module("z2d")); } - self.addUucode(b, step.root_module, target, optimize); if (b.lazyDependency("zf", .{ .target = target, .optimize = optimize, @@ -502,7 +570,7 @@ pub fn add( "macos", macos_dep.module("macos"), ); - step.linkLibrary( + step.root_module.linkLibrary( macos_dep.artifact("macos"), ); try static_libs.append( @@ -512,7 +580,7 @@ pub fn add( } if (self.config.renderer == .opengl) { - step.linkFramework("OpenGL"); + step.root_module.linkFramework("OpenGL", .{}); } // Apple platforms do not include libc libintl so we bundle it. @@ -523,7 +591,7 @@ pub fn add( .target = target, .optimize = optimize, })) |libintl_dep| { - step.linkLibrary(libintl_dep.artifact("intl")); + step.root_module.linkLibrary(libintl_dep.artifact("intl")); try static_libs.append( b.allocator, libintl_dep.artifact("intl").getEmittedBin(), @@ -543,7 +611,7 @@ pub fn add( .@"backend-opengl3" = !target.result.os.tag.isDarwin(), })) |dep| { step.root_module.addImport("dcimgui", dep.module("dcimgui")); - step.linkLibrary(dep.artifact("dcimgui")); + step.root_module.linkLibrary(dep.artifact("dcimgui")); try static_libs.append( b.allocator, dep.artifact("dcimgui").getEmittedBin(), @@ -592,15 +660,15 @@ pub fn add( // If we're building an exe then we have additional dependencies. if (step.kind != .lib) { // We always statically compile glad - step.addIncludePath(b.path("vendor/glad/include/")); - step.addCSourceFile(.{ + step.root_module.addIncludePath(b.path("vendor/glad/include/")); + step.root_module.addCSourceFile(.{ .file = b.path("vendor/glad/src/gl.c"), .flags = &.{}, }); // When we're targeting flatpak we ALWAYS link GTK so we // get access to glib for dbus. - if (self.config.flatpak) step.linkSystemLibrary2("gtk4", dynamic_link_opts); + if (self.config.flatpak) step.root_module.linkSystemLibrary("gtk4", dynamic_link_opts); switch (self.config.app_runtime) { .none => {}, @@ -643,12 +711,38 @@ fn addGtkNg( step.root_module.addImport(name, gobject.module(module)); } } + { + // translate-c stuff + const translate_c = b.dependency("translate_c", .{}); - step.linkSystemLibrary2("gtk4", dynamic_link_opts); - step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts); + { + // GTK headers + const translated: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("gtk_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + translated.linkSystemLibrary("gtk4", dynamic_link_opts); + step.root_module.addImport("gtk_c", translated.mod); + } + { + // Adwaita headers + const translated: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("adw_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + translated.linkSystemLibrary("libadwaita-1", dynamic_link_opts); + step.root_module.addImport("adw_c", translated.mod); + } + } if (self.config.x11) { - step.linkSystemLibrary2("X11", dynamic_link_opts); + step.root_module.linkSystemLibrary("X11", dynamic_link_opts); if (gobject_) |gobject| { step.root_module.addImport( "gdk_x11", @@ -732,24 +826,34 @@ fn addGtkNg( // IMPORTANT: gtk4-layer-shell must be linked BEFORE // wayland-client, as it relies on shimming libwayland's APIs. if (b.systemIntegrationOption("gtk4-layer-shell", .{})) { - step.linkSystemLibrary2("gtk4-layer-shell-0", dynamic_link_opts); + step.root_module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts); } else { // gtk4-layer-shell *must* be dynamically linked, // so we don't add it as a static library const shared_lib = gtk4_layer_shell.artifact("gtk4-layer-shell"); b.installArtifact(shared_lib); - step.linkLibrary(shared_lib); + step.root_module.linkLibrary(shared_lib); } } - step.linkSystemLibrary2("wayland-client", dynamic_link_opts); + step.root_module.linkSystemLibrary("wayland-client", dynamic_link_opts); } { // Get our gresource c/h files and add them to our build. const dist = gtkNgDistResources(b); - step.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} }); - step.addIncludePath(dist.resources_h.path(b).dirname()); + const translate_c = b.dependency("translate_c", .{}); + const translated: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("c.h", + \\#include + ), + .target = target, + .optimize = optimize, + }); + translated.linkSystemLibrary("glib-2.0", dynamic_link_opts); + translated.addIncludePath(dist.resources_h.path(b).dirname()); + translated.mod.addCSourceFile(.{ .file = dist.resources_c.path(b), .flags = &.{} }); + step.root_module.addImport("ghostty_gtk_resources", translated.mod); } } @@ -890,11 +994,22 @@ pub fn gtkNgDistResources( .root_module = b.createModule(.{ .root_source_file = b.path("src/apprt/gtk/build/blueprint.zig"), .target = b.graph.host, + .link_libc = true, }), }); - blueprint_exe.linkLibC(); - blueprint_exe.linkSystemLibrary2("gtk4", dynamic_link_opts); - blueprint_exe.linkSystemLibrary2("libadwaita-1", dynamic_link_opts); + { + // Adwaita headers + const translate_c = b.dependency("translate_c", .{}); + const translated: Translator = .init(translate_c, .{ + .c_source_file = b.addWriteFiles().add("adw_c.h", + \\#include + ), + .target = b.graph.host, + .optimize = .Debug, + }); + translated.linkSystemLibrary("libadwaita-1", dynamic_link_opts); + blueprint_exe.root_module.addImport("adw_c", translated.mod); + } for (gresource.blueprints) |bp| { const blueprint_run = b.addRunArtifact(blueprint_exe); @@ -923,7 +1038,7 @@ pub fn gtkNgDistResources( xml_run.addFileArg(ui_file); } - break :gresource_xml xml_run.captureStdOut(); + break :gresource_xml xml_run.captureStdOut(.{}); }; const generate_c = b.addSystemCommand(&.{ @@ -964,23 +1079,6 @@ pub fn gtkNgDistResources( }; } -pub fn addUucode( - self: *const SharedDeps, - b: *std.Build, - module: *std.Build.Module, - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, -) void { - if (b.lazyDependency("uucode", .{ - .target = target, - .optimize = optimize, - .tables_path = self.uucode_tables, - .build_config_path = b.path("src/build/uucode_config.zig"), - })) |dep| { - module.addImport("uucode", dep.module("uucode")); - } -} - // For dynamic linking, we prefer dynamic linking and to search by // mode first. Mode first will search all paths for a dynamic library // before falling back to static. diff --git a/src/build/UnicodeTables.zig b/src/build/UnicodeTables.zig index 17a839eaf..843ada51f 100644 --- a/src/build/UnicodeTables.zig +++ b/src/build/UnicodeTables.zig @@ -54,8 +54,8 @@ pub fn init(b: *std.Build, uucode_tables: std.Build.LazyPath) !UnicodeTables { // Generated Zig files have to end with .zig const wf = b.addWriteFiles(); - const props_output = wf.addCopyFile(props_run.captureStdOut(), "props.zig"); - const symbols_output = wf.addCopyFile(symbols_run.captureStdOut(), "symbols.zig"); + const props_output = wf.addCopyFile(props_run.captureStdOut(.{}), "props.zig"); + const symbols_output = wf.addCopyFile(symbols_run.captureStdOut(.{}), "symbols.zig"); return .{ .props_exe = props_exe, diff --git a/src/build/XCFrameworkStep.zig b/src/build/XCFrameworkStep.zig index 39f0f9bac..5f1b676fb 100644 --- a/src/build/XCFrameworkStep.zig +++ b/src/build/XCFrameworkStep.zig @@ -63,8 +63,8 @@ pub fn create(b: *std.Build, opts: Options) *XCFrameworkStep { run.addArg("-output"); run.addArg(opts.out_path); run.expectExitCode(0); - _ = run.captureStdOut(); - _ = run.captureStdErr(); + _ = run.captureStdOut(.{}); + _ = run.captureStdErr(.{}); break :run run; }; run_create.step.dependOn(&run_delete.step); diff --git a/src/build/args.zig b/src/build/args.zig new file mode 100644 index 000000000..3f3b6b49c --- /dev/null +++ b/src/build/args.zig @@ -0,0 +1,13 @@ +const std = @import("std"); + +/// Helper function for args allocation. Caller fully owns the slice and can +/// deinit as need be. +pub fn argsAlloc(alloc: std.mem.Allocator, args: std.process.Args) std.mem.Allocator.Error![][:0]const u8 { + var result: std.ArrayList([:0]const u8) = .empty; + errdefer result.deinit(alloc); + var it = args.iterate(); + while (it.next()) |arg| { + try result.append(alloc, arg); + } + return try result.toOwnedSlice(alloc); +} diff --git a/src/build/combine_archives.zig b/src/build/combine_archives.zig index 04ec8e978..f7606a15b 100644 --- a/src/build/combine_archives.zig +++ b/src/build/combine_archives.zig @@ -10,12 +10,14 @@ //! Usage: combine_archives [input2.a ...] const std = @import("std"); +const argsAlloc = @import("args.zig").argsAlloc; -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - const alloc = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.arena.allocator(); + + const args = try argsAlloc(alloc, init.minimal.args); + defer alloc.free(args); - const args = try std.process.argsAlloc(alloc); if (args.len < 4) { std.log.err("usage: combine_archives ", .{}); std.process.exit(1); @@ -37,19 +39,20 @@ pub fn main() !void { } try script.appendSlice(alloc, "SAVE\nEND\n"); - var child: std.process.Child = .init(&.{ zig_exe, "ar", "-M" }, alloc); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + var child = try std.process.spawn(init.io, .{ + .argv = &.{ zig_exe, "ar", "-M" }, + .stdin = .pipe, + .stdout = .inherit, + .stderr = .inherit, + }); - try child.spawn(); - try child.stdin.?.writeAll(script.items); - child.stdin.?.close(); + try child.stdin.?.writeStreamingAll(init.io, script.items); + child.stdin.?.close(init.io); child.stdin = null; - const term = try child.wait(); - if (term.Exited != 0) { - std.log.err("zig ar -M exited with code {d}", .{term.Exited}); + const term = try child.wait(init.io); + if (term.exited != 0) { + std.log.err("zig ar -M exited with code {d}", .{term.exited}); std.process.exit(1); } } diff --git a/src/build/gtk.zig b/src/build/gtk.zig index 7adb3cdb7..1035367e1 100644 --- a/src/build/gtk.zig +++ b/src/build/gtk.zig @@ -14,7 +14,7 @@ pub fn targets(b: *std.Build) Targets { const output = b.runAllowFail( &.{ "pkg-config", "--variable=targets", "gtk4" }, &code, - .Ignore, + .ignore, ) catch return .{}; const x11 = std.mem.indexOf(u8, output, "x11") != null; diff --git a/src/build/mdgen/main_ghostty_1.zig b/src/build/mdgen/main_ghostty_1.zig index 2bb413d93..e511768c9 100644 --- a/src/build/mdgen/main_ghostty_1.zig +++ b/src/build/mdgen/main_ghostty_1.zig @@ -1,12 +1,11 @@ const std = @import("std"); const gen = @import("mdgen.zig"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - const alloc = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.arena.allocator(); var buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer); const writer = &stdout_writer.interface; try gen.substitute(alloc, @embedFile("ghostty_1_header.md"), writer); try gen.genActions(writer); diff --git a/src/build/mdgen/main_ghostty_5.zig b/src/build/mdgen/main_ghostty_5.zig index 2123b0bce..4f7c4882a 100644 --- a/src/build/mdgen/main_ghostty_5.zig +++ b/src/build/mdgen/main_ghostty_5.zig @@ -1,12 +1,11 @@ const std = @import("std"); const gen = @import("mdgen.zig"); -pub fn main() !void { - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - const alloc = gpa.allocator(); +pub fn main(init: std.process.Init) !void { + const alloc = init.arena.allocator(); var buffer: [1024]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer); const writer = &stdout_writer.interface; try gen.substitute(alloc, @embedFile("ghostty_5_header.md"), writer); try gen.genConfig(writer, false); diff --git a/src/build/uucode_config.zig b/src/build/uucode_config.zig index 2bb0d4508..e7a4f08c4 100644 --- a/src/build/uucode_config.zig +++ b/src/build/uucode_config.zig @@ -1,109 +1,138 @@ const std = @import("std"); const assert = std.debug.assert; const config = @import("config.zig"); -const config_x = @import("config.x.zig"); -const d = config.default; -const wcwidth = config_x.wcwidth; -const grapheme_break_no_control = config_x.grapheme_break_no_control; const Allocator = std.mem.Allocator; -fn computeWidth( - alloc: std.mem.Allocator, - cp: u21, - data: anytype, - backing: anytype, - tracking: anytype, -) Allocator.Error!void { - _ = alloc; - _ = cp; - _ = backing; - _ = tracking; - - // This condition is needed as Ghostty currently has a singular concept for - // the `width` of a code point, while `uucode` splits the concept into - // `wcwidth_standalone` and `wcwidth_zero_in_grapheme`. The two cases where - // we want to use the `wcwidth_standalone` despite the code point occupying - // zero width in a grapheme (`wcwidth_zero_in_grapheme`) are emoji - // modifiers and prepend code points. For emoji modifiers we want to - // support displaying them in isolation as color patches, and if prepend - // characters were to be width 0 they would disappear from the output with - // Ghostty's current width 0 handling. Future work will take advantage of - // the new uucode `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split. - if (data.wcwidth_zero_in_grapheme and !data.is_emoji_modifier and data.grapheme_break_no_control != .prepend) { - data.width = 0; - } else { - data.width = @min(2, data.wcwidth_standalone); - } -} - -const width = config.Extension{ - .inputs = &.{ - "wcwidth_standalone", - "wcwidth_zero_in_grapheme", - "is_emoji_modifier", - "grapheme_break_no_control", +pub const fields = &config.mergeFields(config.fields, &.{ + .{ .name = "width", .type = u2 }, + .{ .name = "is_symbol", .type = bool }, +}); +pub const build_components = &config.mergeComponents(config.build_components, &.{ + .{ + .Impl = WidthComponent, + .inputs = &.{ + "wcwidth_standalone", + "wcwidth_zero_in_grapheme", + "is_emoji_modifier", + "grapheme_break_no_control", + }, + .fields = &.{"width"}, }, - .compute = &computeWidth, - .fields = &.{ - .{ .name = "width", .type = u2 }, + .{ + .Impl = IsSymbolComponent, + .inputs = &.{ "block", "general_category" }, + .fields = &.{"is_symbol"}, }, -}; +}); -fn computeIsSymbol( - alloc: Allocator, - cp: u21, - data: anytype, - backing: anytype, - tracking: anytype, -) Allocator.Error!void { - _ = alloc; - _ = cp; - _ = backing; - _ = tracking; - const block = data.block; - data.is_symbol = data.general_category == .other_private_use or - block == .arrows or - block == .dingbats or - block == .emoticons or - block == .miscellaneous_symbols or - block == .enclosed_alphanumerics or - block == .enclosed_alphanumeric_supplement or - block == .miscellaneous_symbols_and_pictographs or - block == .transport_and_map_symbols; -} - -const is_symbol = config.Extension{ - .inputs = &.{ "block", "general_category" }, - .compute = &computeIsSymbol, - .fields = &.{ - .{ .name = "is_symbol", .type = bool }, - }, -}; +pub const get_components: []const config.Component = &.{}; pub const tables = [_]config.Table{ .{ .name = "runtime", - .extensions = &.{}, .fields = &.{ - d.field("is_emoji_presentation"), - d.field("case_folding_full"), + "is_emoji_presentation", + "case_folding_full", + }, + }, + .{ + // Fields that libvaxis needs that aren't included in the `runtime` + // table. + .name = "libvaxis_only", + .fields = &.{ + "east_asian_width", + "general_category", + "grapheme_break", }, }, .{ .name = "buildtime", - .extensions = &.{ - wcwidth, - grapheme_break_no_control, - width, - is_symbol, - }, .fields = &.{ - width.field("width"), - wcwidth.field("wcwidth_zero_in_grapheme"), - grapheme_break_no_control.field("grapheme_break_no_control"), - is_symbol.field("is_symbol"), - d.field("is_emoji_vs_base"), + "width", + "wcwidth_zero_in_grapheme", + "grapheme_break_no_control", + "is_symbol", + "is_emoji_vs_base", }, }, }; + +const WidthComponent = struct { + pub fn build( + comptime InputRow: type, + comptime Row: type, + allocator: std.mem.Allocator, + io: std.Io, + inputs: config.MultiSlice(InputRow), + rows: *config.MultiSlice(Row), + backing: anytype, + tracking: anytype, + ) !void { + _ = allocator; + _ = io; + _ = backing; + _ = tracking; + + rows.len = config.num_code_points; + const items = rows.items(.width); + const standalone = inputs.items(.wcwidth_standalone); + const zero_in_grapheme = inputs.items(.wcwidth_zero_in_grapheme); + const is_emoji_modifier = inputs.items(.is_emoji_modifier); + const grapheme_break_no_control = inputs.items(.grapheme_break_no_control); + + // This condition is needed as Ghostty currently has a singular concept for + // the `width` of a code point, while `uucode` splits the concept into + // `wcwidth_standalone` and `wcwidth_zero_in_grapheme`. The two cases where + // we want to use the `wcwidth_standalone` despite the code point occupying + // zero width in a grapheme (`wcwidth_zero_in_grapheme`) are emoji + // modifiers and prepend code points. For emoji modifiers we want to + // support displaying them in isolation as color patches, and if prepend + // characters were to be width 0 they would disappear from the output with + // Ghostty's current width 0 handling. Future work will take advantage of + // the new uucode `wcwidth_standalone` vs `wcwidth_zero_in_grapheme` split. + for (0..config.num_code_points) |i| { + if (zero_in_grapheme[i] and !is_emoji_modifier[i] and grapheme_break_no_control[i] != .prepend) { + items[i] = 0; + } else { + items[i] = @min(2, standalone[i]); + } + } + } +}; + +const IsSymbolComponent = struct { + pub fn build( + comptime InputRow: type, + comptime Row: type, + allocator: std.mem.Allocator, + io: std.Io, + inputs: config.MultiSlice(InputRow), + rows: *config.MultiSlice(Row), + backing: anytype, + tracking: anytype, + ) !void { + _ = allocator; + _ = io; + _ = backing; + _ = tracking; + + rows.len = config.num_code_points; + const items = rows.items(.is_symbol); + const block = inputs.items(.block); + const general_category = inputs.items(.general_category); + + for (0..config.num_code_points) |i| { + items[i] = + general_category[i] == .other_private_use or + block[i] == .arrows or + block[i] == .dingbats or + block[i] == .emoticons or + block[i] == .miscellaneous_symbols or + block[i] == .enclosed_alphanumerics or + block[i] == .enclosed_alphanumeric_supplement or + block[i] == .miscellaneous_symbols_and_pictographs or + block[i] == .transport_and_map_symbols; + } + } +}; diff --git a/src/build/wasm_patch_growable_table.zig b/src/build/wasm_patch_growable_table.zig index c40c0a9c8..2818b9bdc 100644 --- a/src/build/wasm_patch_growable_table.zig +++ b/src/build/wasm_patch_growable_table.zig @@ -11,16 +11,16 @@ const std = @import("std"); const testing = std.testing; const Allocator = std.mem.Allocator; +const argsAlloc = @import("args.zig").argsAlloc; -pub fn main() !void { +pub fn main(init: std.process.Init) !void { // This is a one-off patcher, so we leak all our memory on purpose // and let the OS clean it up when we exit. - var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; - const alloc = gpa.allocator(); + const alloc = init.arena.allocator(); // Parse args: program input output - const args = try std.process.argsAlloc(alloc); - defer std.process.argsFree(alloc, args); + const args = try argsAlloc(alloc, init.minimal.args); + defer alloc.free(args); if (args.len != 3) { std.log.err("usage: wasm_growable_table ", .{}); std.process.exit(1); @@ -30,7 +30,8 @@ pub fn main() !void { // Patch the file. const output: []const u8 = try patchTableGrowable( alloc, - try std.fs.cwd().readFileAlloc( + try std.Io.Dir.cwd().readFileAlloc( + init.io, alloc, args[1], std.math.maxInt(usize), @@ -38,9 +39,9 @@ pub fn main() !void { ); // Write our output - const out_file = try std.fs.cwd().createFile(args[2], .{}); - defer out_file.close(); - try out_file.writeAll(output); + const out_file = try std.Io.Dir.cwd().createFile(init.io, args[2], .{}); + defer out_file.close(init.io); + try out_file.writePositionalAll(init.io, output); } /// Patch the WASM binary's table section to remove the maximum size diff --git a/src/build/webgen/main_actions.zig b/src/build/webgen/main_actions.zig index b0de6537d..38cda3f7a 100644 --- a/src/build/webgen/main_actions.zig +++ b/src/build/webgen/main_actions.zig @@ -1,9 +1,9 @@ const std = @import("std"); const helpgen_actions = @import("../../input/helpgen_actions.zig"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var buffer: [2048]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer); const stdout = &stdout_writer.interface; try helpgen_actions.generate(stdout, .markdown, true, std.heap.page_allocator); } diff --git a/src/build/webgen/main_commands.zig b/src/build/webgen/main_commands.zig index 65f144522..c895948d4 100644 --- a/src/build/webgen/main_commands.zig +++ b/src/build/webgen/main_commands.zig @@ -2,9 +2,9 @@ const std = @import("std"); const Action = @import("../../cli/ghostty.zig").Action; const help_strings = @import("help_strings"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var buffer: [2048]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer); const stdout = &stdout_writer.interface; try genActions(stdout); } diff --git a/src/build/webgen/main_config.zig b/src/build/webgen/main_config.zig index 1363fadc4..82ecc950f 100644 --- a/src/build/webgen/main_config.zig +++ b/src/build/webgen/main_config.zig @@ -2,9 +2,9 @@ const std = @import("std"); const Config = @import("../../config/Config.zig"); const help_strings = @import("help_strings"); -pub fn main() !void { +pub fn main(init: std.process.Init) !void { var buffer: [2048]u8 = undefined; - var stdout_writer = std.fs.File.stdout().writer(&buffer); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buffer); const stdout = &stdout_writer.interface; try genConfig(stdout); } diff --git a/src/cli/Pager.zig b/src/cli/Pager.zig index 247c998e1..0f837e5e7 100644 --- a/src/cli/Pager.zig +++ b/src/cli/Pager.zig @@ -6,16 +6,18 @@ //! Setting either env var to an empty string disables paging. //! If stdout is not a TTY, writes go directly to stdout. const Pager = @This(); +const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const internal_os = @import("../os/main.zig"); +const compat_env = @import("../lib/compat/env.zig"); /// The pager child process, if one was spawned. child: ?std.process.Child = null, /// The buffered file writer used for both the pager pipe and direct /// stdout paths. -file_writer: std.fs.File.Writer = undefined, +file_writer: std.Io.File.Writer = undefined, /// Initialize the pager. If stdout is a TTY, this spawns the pager /// process. Otherwise, output goes directly to stdout. @@ -26,9 +28,9 @@ pub fn init(alloc: Allocator) Pager { /// Writes to the pager process if available; otherwise, stdout. pub fn writer(self: *Pager, buffer: []u8) *std.Io.Writer { if (self.child) |child| { - self.file_writer = child.stdin.?.writer(buffer); + self.file_writer = child.stdin.?.writer(std.Io.Threaded.global_single_threaded.io(), buffer); } else { - self.file_writer = std.fs.File.stdout().writer(buffer); + self.file_writer = std.Io.File.stdout().writer(std.Io.Threaded.global_single_threaded.io(), buffer); } return &self.file_writer.interface; } @@ -40,41 +42,49 @@ pub fn deinit(self: *Pager) void { // pager sees EOF, then wait for it to exit. self.file_writer.interface.flush() catch {}; if (child.stdin) |stdin| { - stdin.close(); + stdin.close(std.Io.Threaded.global_single_threaded.io()); child.stdin = null; } - _ = child.wait() catch {}; + _ = child.wait(std.Io.Threaded.global_single_threaded.io()) catch {}; } self.* = undefined; } fn initPager(alloc: Allocator) ?std.process.Child { - const stdout_file: std.fs.File = .stdout(); - if (!stdout_file.isTty()) return null; + const stdout_file: std.Io.File = .stdout(); + if (!(stdout_file.isTty(std.Io.Threaded.global_single_threaded.io()) catch return null)) return null; + + var env = compat_env.getEnvMap(alloc) catch return null; + defer env.deinit(); // Resolve the pager command: $GHOSTTY_PAGER > $PAGER > `less`. // An empty value for either env var disables paging. - const ghostty_var = internal_os.getenv(alloc, "GHOSTTY_PAGER") catch null; - defer if (ghostty_var) |v| v.deinit(alloc); - const pager_var = internal_os.getenv(alloc, "PAGER") catch null; - defer if (pager_var) |v| v.deinit(alloc); - + const ghostty_var = env.get("GHOSTTY_PAGER"); + const pager_var = env.get("PAGER"); const cmd: ?[]const u8 = cmd: { - if (ghostty_var) |v| break :cmd if (v.value.len > 0) v.value else null; - if (pager_var) |v| break :cmd if (v.value.len > 0) v.value else null; + if (ghostty_var) |v| break :cmd if (v.len > 0) v else null; + if (pager_var) |v| break :cmd if (v.len > 0) v else null; break :cmd "less"; }; if (cmd == null) return null; - var child: std.process.Child = .init(&.{cmd.?}, alloc); - child.stdin_behavior = .Pipe; - child.stdout_behavior = .Inherit; - child.stderr_behavior = .Inherit; + const env_block: std.process.Environ.Block = switch (builtin.os.tag) { + .linux, .freebsd, .macos => env.createPosixBlock(alloc, .{}) catch return null, + .windows => env.createWindowsBlock(alloc, .{}) catch return null, + else => return null, // unsupported OS + }; - child.spawn() catch return null; - return child; + var threaded: std.Io.Threaded = .init(alloc, .{ .environ = .{ .block = env_block } }); + const io = threaded.io(); + + return std.process.spawn(io, .{ + .argv = &.{cmd.?}, + .stdin = .pipe, + .stdout = .inherit, + .stderr = .inherit, + }) catch null; } test "pager: non-tty" { diff --git a/src/cli/action.zig b/src/cli/action.zig index 41173a9f1..276a0cfee 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -1,5 +1,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const compat_args = @import("../lib/compat/args.zig"); pub const DetectError = error{ /// Multiple actions were detected. You can specify at most one @@ -12,7 +13,7 @@ pub const DetectError = error{ /// Detect the action from CLI args. pub fn detectArgs(comptime E: type, alloc: Allocator) !?E { - var iter = try std.process.argsWithAllocator(alloc); + var iter = try compat_args.getArgs().iterateAllocator(alloc); defer iter.deinit(); return try detectIter(E, &iter); } @@ -83,7 +84,7 @@ test "detect direct match" { const alloc = testing.allocator; const Enum = enum { foo, bar, baz }; - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+foo", ); @@ -97,7 +98,7 @@ test "detect invalid match" { const alloc = testing.allocator; const Enum = enum { foo, bar, baz }; - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+invalid", ); @@ -113,7 +114,7 @@ test "detect multiple actions" { const alloc = testing.allocator; const Enum = enum { foo, bar, baz }; - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+foo +bar", ); @@ -129,7 +130,7 @@ test "detect no match" { const alloc = testing.allocator; const Enum = enum { foo, bar, baz }; - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "--some-flag", ); @@ -154,7 +155,7 @@ test "detect special case action" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "--special +bar", ); @@ -164,7 +165,7 @@ test "detect special case action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+bar --special", ); @@ -174,7 +175,7 @@ test "detect special case action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+bar", ); @@ -200,7 +201,7 @@ test "detect special case fallback" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "--special", ); @@ -210,7 +211,7 @@ test "detect special case fallback" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+bar --special", ); @@ -220,7 +221,7 @@ test "detect special case fallback" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "--special +bar", ); @@ -246,7 +247,7 @@ test "detect special case abort_if_no_action" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "-e", ); @@ -256,7 +257,7 @@ test "detect special case abort_if_no_action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "+foo -e", ); @@ -266,7 +267,7 @@ test "detect special case abort_if_no_action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( alloc, "-e +bar", ); diff --git a/src/cli/args.zig b/src/cli/args.zig index bd5060d69..543303176 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -5,6 +5,7 @@ const Allocator = mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; const diags = @import("diagnostics.zig"); const internal_os = @import("../os/main.zig"); +const compat_args = @import("../lib/compat/args.zig"); const Diagnostic = diags.Diagnostic; const DiagnosticList = diags.DiagnosticList; const CommaSplitter = @import("CommaSplitter.zig"); @@ -489,18 +490,13 @@ pub fn parseTaggedUnion(comptime T: type, alloc: Allocator, v: []const u8) !T { // We need to create a struct that looks like this union field. // This lets us use parseIntoField as if its a dedicated struct. - const Target = @Type(.{ .@"struct" = .{ - .layout = .auto, - .fields = &.{.{ - .name = field.name, - .type = field.type, - .default_value_ptr = null, - .is_comptime = false, - .alignment = @alignOf(field.type), - }}, - .decls = &.{}, - .is_tuple = false, - } }); + const Target = @Struct( + .auto, + null, + &.{field.name}, + &.{field.type}, + &.{.{ .@"align" = @alignOf(field.type) }}, + ); // Parse the value into the struct var t: Target = undefined; @@ -677,7 +673,7 @@ test "parse: simple" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a=42 --b --b-f=false", ); @@ -689,7 +685,7 @@ test "parse: simple" { try testing.expect(!data.@"b-f"); // Reparsing works - var iter2 = try std.process.ArgIteratorGeneral(.{}).init( + var iter2 = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a=84", ); @@ -711,7 +707,7 @@ test "parse: quoted value" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a=\"42\" --b=\"hello!\"", ); @@ -731,7 +727,7 @@ test "parse: empty value resets to default" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a= --b=", ); @@ -750,7 +746,7 @@ test "parse: positional arguments are invalid" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a=84 what", ); @@ -774,7 +770,7 @@ test "parse: diagnostic tracking" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--what --a=42", ); @@ -858,7 +854,7 @@ test "parse: compatibility handler" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--a=yuh", ); @@ -884,7 +880,7 @@ test "parse: compatibility renamed" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--old=true --b=true", ); @@ -1373,7 +1369,7 @@ pub fn argsIterator(alloc_gpa: Allocator) internal_os.args.ArgIterator.InitError test "ArgsIterator" { const testing = std.testing; - const child = try std.process.ArgIteratorGeneral(.{}).init( + const child = try compat_args.ArgIteratorGeneral(.{}).init( testing.allocator, "--what +list-things --a=42", ); diff --git a/src/cli/boo.zig b/src/cli/boo.zig index 2834eadbd..030072351 100644 --- a/src/cli/boo.zig +++ b/src/cli/boo.zig @@ -3,6 +3,7 @@ const builtin = @import("builtin"); const args = @import("args.zig"); const Action = @import("ghostty.zig").Action; const Allocator = std.mem.Allocator; +const compat_env = @import("../lib/compat/env.zig"); const vaxis = @import("vaxis"); const framedata = @import("framedata").compressed; @@ -173,6 +174,9 @@ const Boo = struct { /// The `boo` command is used to display the animation from the Ghostty website in the terminal pub fn run(gpa: Allocator) !u8 { + var env_map = try compat_env.getEnvMap(gpa); + defer env_map.deinit(); + // Disable on non-desktop systems. switch (builtin.os.tag) { .windows, .macos, .linux, .freebsd => {}, @@ -194,7 +198,7 @@ pub fn run(gpa: Allocator) !u8 { gpa.free(decompressed_data); } - var app = try vxfw.App.init(gpa); + var app = try vxfw.App.init(std.Io.Threaded.global_single_threaded.io(), gpa, &env_map, &.{}); defer app.deinit(); var boo: Boo = undefined; diff --git a/src/cli/crash_report.zig b/src/cli/crash_report.zig index f0940fdab..3fe1a1959 100644 --- a/src/cli/crash_report.zig +++ b/src/cli/crash_report.zig @@ -39,8 +39,8 @@ pub fn run(alloc_gpa: Allocator) !u8 { } var buffer: [1024]u8 = undefined; - var stdout_file: std.fs.File = .stdout(); - var stdout_writer = stdout_file.writer(&buffer); + var stdout_file: std.Io.File = .stdout(); + var stdout_writer = stdout_file.writer(std.Io.Threaded.global_single_threaded.io(), &buffer); const stdout = &stdout_writer.interface; const result = runInner(alloc, &stdout_file, stdout); @@ -50,7 +50,7 @@ pub fn run(alloc_gpa: Allocator) !u8 { fn runInner( alloc: Allocator, - stdout_file: *std.fs.File, + stdout_file: *std.Io.File, stdout: *std.Io.Writer, ) !u8 { const crash_dir = try crash.defaultDir(alloc); @@ -66,7 +66,7 @@ fn runInner( // If we have no reports, then we're done. If we have a tty then we // print a message, otherwise we do nothing. if (reports.items.len == 0) { - if (std.posix.isatty(stdout_file.handle)) { + if (try stdout_file.isTty(std.Io.Threaded.global_single_threaded.io())) { try stdout.writeAll("No crash reports! 👻\n"); } return 0; @@ -76,7 +76,7 @@ fn runInner( for (reports.items) |report| { var buf: [128]u8 = undefined; - const now = std.time.nanoTimestamp(); + const now = std.Io.Timestamp.now(std.Io.Threaded.global_single_threaded.io(), .real).toNanoseconds(); const diff = now - report.mtime; const since = if (diff <= 0) "now" else s: { const d = Config.Duration{ .duration = @intCast(diff) }; diff --git a/src/cli/diagnostics.zig b/src/cli/diagnostics.zig index 7f4dcc45e..0e4048a01 100644 --- a/src/cli/diagnostics.zig +++ b/src/cli/diagnostics.zig @@ -93,7 +93,7 @@ pub const Location = union(enum) { /// and potentially in the future structure them differently. pub const DiagnosticList = struct { /// The list of diagnostics. - list: std.ArrayListUnmanaged(Diagnostic) = .{}, + list: std.ArrayList(Diagnostic) = .empty, /// Precomputed data for diagnostics. This is used specifically /// when we build libghostty so that we can precompute the messages @@ -111,7 +111,7 @@ pub const DiagnosticList = struct { }; const Precompute = if (precompute_enabled) struct { - messages: std.ArrayListUnmanaged([:0]const u8) = .{}, + messages: std.ArrayList([:0]const u8) = .empty, pub fn clone( self: *const Precompute, diff --git a/src/cli/edit_config.zig b/src/cli/edit_config.zig index c08651a06..e6f091d55 100644 --- a/src/cli/edit_config.zig +++ b/src/cli/edit_config.zig @@ -6,6 +6,7 @@ const Allocator = std.mem.Allocator; const Action = @import("ghostty.zig").Action; const configpkg = @import("../config.zig"); const internal_os = @import("../os/main.zig"); +const compat_exec = @import("../lib/compat/exec.zig"); const Config = configpkg.Config; pub const Options = struct { @@ -48,7 +49,7 @@ pub fn run(alloc: Allocator) !u8 { // critical where setting up the defer cleanup is a problem. var buffer: [1024]u8 = undefined; - var stderr_writer = std.fs.File.stderr().writer(&buffer); + var stderr_writer = std.Io.File.stderr().writer(std.Io.Threaded.global_single_threaded.io(), &buffer); const stderr = &stderr_writer.interface; var opts: Options = .{}; @@ -137,7 +138,7 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { } const command = command: { - var buffer: std.io.Writer.Allocating = .init(alloc); + var buffer: std.Io.Writer.Allocating = .init(alloc); defer buffer.deinit(); const writer = &buffer.writer; try writer.writeAll(editor); @@ -158,7 +159,7 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { // so this is not a big deal. comptime assert(builtin.link_libc); - const err = std.posix.execvpeZ( + const err = compat_exec.execvpeZ( "/bin/sh", &.{ "/bin/sh", "-c", command }, std.c.environ, diff --git a/src/cli/explain_config.zig b/src/cli/explain_config.zig index 4f034afef..575a3823b 100644 --- a/src/cli/explain_config.zig +++ b/src/cli/explain_config.zig @@ -75,9 +75,9 @@ pub fn run(alloc: Allocator) !u8 { // respective lookup. A bare positional argument tries config // options first, then keybind actions as a fallback. const name = keybind_name orelse option_name orelse positional orelse { - var stderr: std.fs.File = .stderr(); + var stderr: std.Io.File = .stderr(); var buffer: [4096]u8 = undefined; - var stderr_writer = stderr.writer(&buffer); + var stderr_writer = stderr.writer(std.Io.Threaded.global_single_threaded.io(), &buffer); try stderr_writer.interface.writeAll("Usage: ghostty +explain-config