diff --git a/.clang-format b/.clang-format index 55ee2979d..0ca181e4a 100644 --- a/.clang-format +++ b/.clang-format @@ -179,4 +179,3 @@ WhitespaceSensitiveMacros: - PP_STRINGIZE - BOOST_PP_STRINGIZE --- - diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 67cc0314f..1f800408f 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -153,7 +153,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: XCode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version diff --git a/.github/workflows/release-tip.yml b/.github/workflows/release-tip.yml index b97cb3c77..4b6817685 100644 --- a/.github/workflows/release-tip.yml +++ b/.github/workflows/release-tip.yml @@ -362,7 +362,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Build XCFramework run: nix develop -c zig build -Demit-lib-vt -Doptimize=ReleaseFast @@ -454,7 +454,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: XCode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version @@ -710,7 +710,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: XCode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version @@ -907,7 +907,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: XCode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 107d64036..3df8e0a5b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -335,7 +335,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Build XCFramework run: nix develop -c zig build -Demit-lib-vt @@ -645,7 +645,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Build run: | @@ -1077,7 +1077,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version @@ -1137,7 +1137,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version @@ -1196,9 +1196,29 @@ jobs: - name: Test GTK Build run: nix develop -c zig build -Dapp-runtime=gtk -Demit-docs -Demit-webdata - # This relies on the cache being populated by the commands above. + # NOTE: This used to point to the Zig global cache directory before + # 0.16.0. Now, the cache directory exclusively stores "minified" + # archives of each dependency (i.e., only the paths defined in + # build.zig.zon), and an uncompressed, working copy is stored in + # PROJECT_ROOT/zig-pkg. The zig-pkg directory is warmed up during the + # regular fetching process from the global cache directory if + # possible. + # + # Nothing else should change; zon2nix's store path mimics what a + # zig-pkg directory should look like, so actual build steps should be + # fine, it's just here where we assume that all dependencies have been + # downloaded, and we want to test the validity of the package + # directory, do we need to have it pointing to zig-pkg and not the + # zon2nix store path. + # + # Note that this can also be attempted by running: + # zig build --fetch=needed + # + # Note that --system PKGDIR should not be added to this invocation as + # it disables fetching altogether (even, seemingly, from the local + # cache). - name: Test System Build - run: nix develop -c zig build --system ${ZIG_GLOBAL_CACHE_DIR}/p + run: nix develop -c zig build --system "$(realpath zig-pkg)" test-lib-vt: if: github.repository == 'ghostty-org/ghostty' && needs.skip.outputs.skip != 'true' @@ -1377,7 +1397,7 @@ jobs: authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - name: Xcode Select - run: sudo xcode-select -s /Applications/Xcode_26.3.app + run: sudo xcode-select -s /Applications/Xcode_26.6.app - name: Xcode Version run: xcodebuild -version diff --git a/.github/workflows/vouch-check-issue.yml b/.github/workflows/vouch-check-issue.yml index 5312931e6..b096a6004 100644 --- a/.github/workflows/vouch-check-issue.yml +++ b/.github/workflows/vouch-check-issue.yml @@ -18,7 +18,7 @@ jobs: with: sparse-checkout: .github/issue-unvouched-message - - uses: mitchellh/vouch/action/check-issue@d66fa29a64600490892131ad87597c30c91fcac4 # unreleased main + - uses: mitchellh/vouch/action/check-issue@d66fa29a64600490892131ad87597c30c91fcac4 # v1.5.0 with: issue-number: ${{ github.event.issue.number }} auto-close: true diff --git a/.gitignore b/.gitignore index c1e9554fc..bf57717b3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,10 +11,11 @@ zig-cache/ .zig-cache/ zig-pkg/ zig-out/ +zig-pkg/ build-cmake/ CMakeCache.txt CMakeFiles/ -/build.zig.zon.bak +build.zig.zon.bak /result* /.nixos-test-history example/*.wasm @@ -30,3 +31,4 @@ glad.zip vgcore.* +/sprite_face_test* diff --git a/HACKING.md b/HACKING.md index 8a3c22f7c..7ba584881 100644 --- a/HACKING.md +++ b/HACKING.md @@ -67,14 +67,6 @@ sudo xcode-select --switch /Applications/Xcode.app > You do not need to be running on macOS 26 to build Ghostty, you can > still use Xcode 26 on macOS 15 stable. -> [!WARNING] -> -> Zig 0.15.x has a [known linking issue](https://codeberg.org/ziglang/zig/issues/31658) -> with **Xcode 26.4**. If you are on Xcode 26.4, you must use a -> Homebrew-installed Zig (`brew install zig@0.15`) or our Nix flake, -> both of which contain a patch that works around the issue. Alternatively, -> you can downgrade to **Xcode 26.3**. - ## AI and Agents If you're using AI assistance with Ghostty, Ghostty provides an diff --git a/build.zig b/build.zig index 8ef7701e2..c371119f5 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, @@ -352,19 +353,18 @@ pub fn build(b: *std.Build) !void { // Crash on x86_64 without this .use_llvm = true, }); - if (config.emit_test_exe) b.installArtifact(test_exe); + if (config.emit_test_exe) { + const test_exe_install = b.addInstallArtifact(test_exe, .{}); + config.addPatchElf(test_exe, &test_exe_install.step); + test_step.dependOn(&test_exe_install.step); + } _ = try deps.add(test_exe); - // Verify our internal libghostty header. - const ghostty_h = b.addTranslateC(.{ - .root_source_file = b.path("include/ghostty.h"), - .target = config.baselineTarget(), - .optimize = .Debug, - }); - test_exe.root_module.addImport("ghostty.h", ghostty_h.createModule()); + addGhosttyH(b, test_exe.root_module, config.baselineTarget(b.graph.io), .Debug); // Normal test running const test_run = b.addRunArtifact(test_exe); + config.addPatchElf(test_exe, &test_run.step); test_step.dependOn(&test_run.step); // Normal tests always test our libghostty modules @@ -379,6 +379,7 @@ pub fn build(b: *std.Build) !void { "--gen-suppressions=all", }); valgrind_run.addArtifactArg(test_exe); + config.addPatchElf(test_exe, &valgrind_run.step); test_valgrind_step.dependOn(&valgrind_run.step); } @@ -390,3 +391,28 @@ pub fn build(b: *std.Build) !void { try translations_step.addError("cannot update translations when i18n is disabled", .{}); } } + +fn addGhosttyH( + b: *std.Build, + module: *std.Build.Module, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) void { + const translate_c = b.lazyImport(@This(), "translate_c") orelse return; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return; + + const translated: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add( + "hb_c.h", + \\#include + , + ), + .target = target, + .optimize = optimize, + .link_libc = true, + }); + + translated.addSystemIncludePath(b.path("include")); + + module.addImport("ghostty.h", translated.mod); +} diff --git a/build.zig.zon b/build.zig.zon index 594f1ef11..6f8287505 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,62 +3,71 @@ .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. + .translate_c = .{ + .lazy = true, + .url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + }, + // Zig libs .libxev = .{ // mitchellh/libxev - .url = "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz", - .hash = "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs", + .url = "https://deps.files.ghostty.org/libxev-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://deps.files.ghostty.org/vaxis-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://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.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://deps.files.ghostty.org/zig_objc-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://deps.files.ghostty.org/zig_js-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://deps.files.ghostty.org/uucode-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://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.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://deps.files.ghostty.org/zf-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://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst", + .hash = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp", .lazy = true, }, @@ -90,7 +99,7 @@ .lazy = true, }, .wayland_protocols = .{ - .url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", + .url = "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz", .hash = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA", .lazy = true, }, diff --git a/build.zig.zon.json b/build.zig.zon.json index 18137518a..e23b6f873 100644 --- a/build.zig.zon.json +++ b/build.zig.zon.json @@ -1,4 +1,9 @@ { + "aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD": { + "name": "aro", + "url": "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz", + "hash": "sha256-rjNfhWjmA/1WR/xuHo4ls4fnDCbsI1VZSvb8SFRvwso=" + }, "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr": { "name": "bindings", "url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz", @@ -29,10 +34,10 @@ "url": "https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz", "hash": "sha256-FKLtu1Ccs+UamlPj9eQ12/WXFgS0uDPmPmB26MCpl7U=" }, - "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-": { + "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp": { "name": "gobject", - "url": "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst", - "hash": "sha256-OxS9/XC5aMJj1KhOcFP1ZZN7PI4ADw4f7ocx6V64mOc=" + "url": "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst", + "hash": "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o=" }, "N-V-__8AALiNBAA-_0gprYr92CjrMj1I5bqNu0TSJOnjFNSr": { "name": "gtk4_layer_shell", @@ -51,7 +56,7 @@ }, "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI": { "name": "imgui", - "url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz", + "url": "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz", "hash": "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs=" }, "N-V-__8AALZGBAAS5NLVH-c8eC-6VtCdcH-9nUvVfUSkWS__": { @@ -69,10 +74,10 @@ "url": "https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz", "hash": "sha256-/syVtGzwXo4/yKQUdQ4LparQDYnp/fF16U/wQcrxoDo=" }, - "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs": { + "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F": { "name": "libxev", - "url": "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz", - "hash": "sha256-1B9oJExVyOWRj+Y9d9eHkOBTlOYuEkcwGBUKdlgRhkg=" + "url": "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz", + "hash": "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0=" }, "N-V-__8AAG3RoQEyRC2Vw7Qoro5SYBf62IHn3HjqtNVY6aWK": { "name": "libxml2", @@ -109,70 +114,75 @@ "url": "https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz", "hash": "sha256-tStvz8Ref6abHwahNiwVVHNETizAmZVVaxVsU7pmV+M=" }, - "uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM": { - "name": "uucode", - "url": "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732", - "hash": "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8=" + "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB": { + "name": "translate_c", + "url": "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + "hash": "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U=" }, - "uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9": { + "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA": { "name": "uucode", - "url": "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz", - "hash": "sha256-jLrhrmCXQ1T+LQP1JTBBB3Jn+1hCZfODbC4SdlfNdKg=" + "url": "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz", + "hash": "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20=" }, - "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS": { + "vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW": { "name": "vaxis", - "url": "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz", - "hash": "sha256-zTyrZrIffM+GJIt973tKDeWHmOCwbn7KLDdQxSiK00Y=" + "url": "git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940", + "hash": "sha256-bIXu8lGwGo42QbItC0jOi/eN7u+f4snknBexw7dc0DI=" + }, + "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka": { + "name": "vaxis", + "url": "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz", + "hash": "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M=" }, "N-V-__8AAKrHGAAs2shYq8UkE6bGcR1QJtLTyOE_lcosMn6t": { "name": "wayland", "url": "https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz", "hash": "sha256-6kGR1o5DdnflHzqs3ieCmBAUTpMdOXoyfcYDXiw5xQ0=" }, + "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": { + "name": "wayland_protocols", + "url": "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz", + "hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=" + }, "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S": { "name": "wayland_protocols", "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", "hash": "sha256-XO3K3egbdeYPI+XoO13SuOtO+5+Peb16NH0UiusFMPg=" }, - "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA": { - "name": "wayland_protocols", - "url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", - "hash": "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM=" - }, "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs": { "name": "wuffs", "url": "https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz", "hash": "sha256-nkzSCr6W5sTG7enDBXEIhgEm574uLD41UVR2wlC+HBM=" }, - "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ": { + "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR": { "name": "z2d", - "url": "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz", - "hash": "sha256-qD+XexnAjSanRAwr5ZIaPY1aQhNW5DFVJ4PYLwhIr2E=" + "url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz", + "hash": "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA=" }, - "zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh": { + "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW": { "name": "zf", - "url": "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz", - "hash": "sha256-BfAZILill3I/nBf1oWwol77N34Jcpm4hudC+XSeMgZY=" + "url": "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz", + "hash": "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA=" }, - "zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi": { + "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj": { "name": "zig_js", - "url": "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz", - "hash": "sha256-r6GdXwrv+jTu0AkTlyN/FuO+N4X+l20gsbS59wrE7V4=" + "url": "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz", + "hash": "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0=" }, - "zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK": { + "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi": { "name": "zig_objc", - "url": "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz", - "hash": "sha256-jWFQ5BrV880qqa9KypltWuRLqNSh21rDxt6Jxp0EoMM=" + "url": "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz", + "hash": "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0=" }, - "wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe": { + "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX": { "name": "zig_wayland", - "url": "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz", - "hash": "sha256-1wRkixysjdFMyrATxlXdukAc34MwfNj0B6ydYVn+UKw=" + "url": "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz", + "hash": "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE=" }, - "zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms": { + "zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI": { "name": "zigimg", - "url": "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz", - "hash": "sha256-vkcTloGX+vRw7e6GYJLO9eocYaEOYjXYE0dT7jscZ4A=" + "url": "git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70", + "hash": "sha256-0IYATQldT6eJxRR2T/2CsIYZuzomqjvmdVyjmsjguyE=" }, "N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o": { "name": "zlib", diff --git a/build.zig.zon.nix b/build.zig.zon.nix index b3b0487b9..cfda6af29 100644 --- a/build.zig.zon.nix +++ b/build.zig.zon.nix @@ -6,7 +6,7 @@ fetchurl, fetchgit, runCommandLocal, - zig_0_15, + zig_0_16, zstd, name ? "zig-packages", }: let @@ -16,12 +16,16 @@ }: runCommandLocal name { - nativeBuildInputs = [zig_0_15]; + nativeBuildInputs = [zig_0_16]; } '' - hash="$(cd "$TMPDIR" && zig fetch --global-cache-dir "$TMPDIR" ${artifact})" - mv "$TMPDIR/p/$hash" "$out" - chmod 755 "$out" + # workaround https://codeberg.org/ziglang/zig/issues/31866 + # https://github.com/Cloudef/zig2nix/issues/54 + mkdir "$TMPDIR/src" "$TMPDIR/cache" + touch "$TMPDIR/src/build.zig" + hash="$(cd "$TMPDIR/src" && zig fetch --global-cache-dir "$TMPDIR/cache" ${artifact})" + mkdir "$out" + tar zxvf "$TMPDIR/cache/p/$hash.tar.gz" --directory "$out/" --strip-components=1 ''; fetchZig = { @@ -93,6 +97,15 @@ fetcher.${proto}; in linkFarm name [ + { + name = "aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD"; + path = fetchZigArtifact { + name = "aro"; + url = "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz"; + hash = "sha256-rjNfhWjmA/1WR/xuHo4ls4fnDCbsI1VZSvb8SFRvwso="; + unpack = true; + }; + } { name = "N-V-__8AANT61wB--nJ95Gj_ctmzAtcjloZ__hRqNw5lC1Kr"; path = fetchZigArtifact { @@ -148,11 +161,11 @@ in }; } { - name = "gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-"; + name = "gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp"; path = fetchZigArtifact { name = "gobject"; - url = "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst"; - hash = "sha256-OxS9/XC5aMJj1KhOcFP1ZZN7PI4ADw4f7ocx6V64mOc="; + url = "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst"; + hash = "sha256-cZGf9a1+sDeORKWYIdzQ/5KLT7l/9pgPc2cIbqfBc4o="; unpack = true; }; } @@ -187,7 +200,7 @@ in name = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI"; path = fetchZigArtifact { name = "imgui"; - url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz"; + url = "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz"; hash = "sha256-yBbCDox18+Fa6Gc1DnmSVQLRpqhZOLsac7iSfl8x+cs="; unpack = false; }; @@ -220,11 +233,11 @@ in }; } { - name = "libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs"; + name = "libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F"; path = fetchZigArtifact { name = "libxev"; - url = "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz"; - hash = "sha256-1B9oJExVyOWRj+Y9d9eHkOBTlOYuEkcwGBUKdlgRhkg="; + url = "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz"; + hash = "sha256-fOU1oxIxfoEgoLuWz7fVX6M+zmqpo7gqZObWiH/aDE0="; unpack = true; }; } @@ -292,29 +305,38 @@ in }; } { - name = "uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM"; + name = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB"; path = fetchZigArtifact { - name = "uucode"; - url = "git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732"; - hash = "sha256-sHPh+TQSdUGus/QTbj7KSJJkTuNTrK4VNmQDjS30Lf8="; + name = "translate_c"; + url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz"; + hash = "sha256-i7TSSiLU9YGtLtEjgVqy2jSnKLDTeKHX+EFuXEMXJ+U="; unpack = true; }; } { - name = "uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9"; + name = "uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA"; path = fetchZigArtifact { name = "uucode"; - url = "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz"; - hash = "sha256-jLrhrmCXQ1T+LQP1JTBBB3Jn+1hCZfODbC4SdlfNdKg="; + url = "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz"; + hash = "sha256-R5RXW5tWIaDq5JOF2+oWd5YOYOyns6WH7f687WE+b20="; unpack = true; }; } { - name = "vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS"; + name = "vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW"; path = fetchZigArtifact { name = "vaxis"; - url = "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz"; - hash = "sha256-zTyrZrIffM+GJIt973tKDeWHmOCwbn7KLDdQxSiK00Y="; + url = "git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940"; + hash = "sha256-bIXu8lGwGo42QbItC0jOi/eN7u+f4snknBexw7dc0DI="; + unpack = true; + }; + } + { + name = "vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka"; + path = fetchZigArtifact { + name = "vaxis"; + url = "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz"; + hash = "sha256-z3J3w+oYapfAZZR+MvWXwg+th1hePDA1TptnKFO4r+M="; unpack = true; }; } @@ -327,6 +349,15 @@ in unpack = false; }; } + { + name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA"; + path = fetchZigArtifact { + name = "wayland_protocols"; + url = "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz"; + hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="; + unpack = false; + }; + } { name = "N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S"; path = fetchZigArtifact { @@ -336,15 +367,6 @@ in unpack = false; }; } - { - name = "N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA"; - path = fetchZigArtifact { - name = "wayland_protocols"; - url = "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz"; - hash = "sha256-3S3xSrX0EDgleq7cxLX7msDuAY8/D5SvkJcCjmDTMiM="; - unpack = false; - }; - } { name = "N-V-__8AAAzZywE3s51XfsLbP9eyEw57ae9swYB9aGB6fCMs"; path = fetchZigArtifact { @@ -355,56 +377,56 @@ in }; } { - name = "z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ"; + name = "z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR"; path = fetchZigArtifact { name = "z2d"; - url = "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz"; - hash = "sha256-qD+XexnAjSanRAwr5ZIaPY1aQhNW5DFVJ4PYLwhIr2E="; + url = "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz"; + hash = "sha256-/Yje2P+ro7IrPodQ26YKk7RsPSYj1Cqx6bdrmykXEKA="; unpack = true; }; } { - name = "zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh"; + name = "zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW"; path = fetchZigArtifact { name = "zf"; - url = "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz"; - hash = "sha256-BfAZILill3I/nBf1oWwol77N34Jcpm4hudC+XSeMgZY="; + url = "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz"; + hash = "sha256-LHurWwqK8jhP8AZd/CXrMJNeVsKftRCTN31/EgEgKaA="; unpack = true; }; } { - name = "zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi"; + name = "zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj"; path = fetchZigArtifact { name = "zig_js"; - url = "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz"; - hash = "sha256-r6GdXwrv+jTu0AkTlyN/FuO+N4X+l20gsbS59wrE7V4="; + url = "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz"; + hash = "sha256-Q1643wb/MCX0QkPa9QEY9xeQbqpC2IOrjER0ZyL3mH0="; unpack = true; }; } { - name = "zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK"; + name = "zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi"; path = fetchZigArtifact { name = "zig_objc"; - url = "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz"; - hash = "sha256-jWFQ5BrV880qqa9KypltWuRLqNSh21rDxt6Jxp0EoMM="; + url = "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz"; + hash = "sha256-t8cTTJm0crcoGAyOqXO0wB2w7Yit80J2Orwr6prd/G0="; unpack = true; }; } { - name = "wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe"; + name = "wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX"; path = fetchZigArtifact { name = "zig_wayland"; - url = "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz"; - hash = "sha256-1wRkixysjdFMyrATxlXdukAc34MwfNj0B6ydYVn+UKw="; + url = "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz"; + hash = "sha256-3m/ITNhZUJ/5uD/Tqm+0uZSktGoYgWF5oldOqOCUkIE="; unpack = true; }; } { - name = "zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms"; + name = "zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI"; path = fetchZigArtifact { name = "zigimg"; - url = "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz"; - hash = "sha256-vkcTloGX+vRw7e6GYJLO9eocYaEOYjXYE0dT7jscZ4A="; + url = "git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70"; + hash = "sha256-0IYATQldT6eJxRR2T/2CsIYZuzomqjvmdVyjmsjguyE="; unpack = true; }; } diff --git a/build.zig.zon.txt b/build.zig.zon.txt index cf784ad7a..dc0010aab 100644 --- a/build.zig.zon.txt +++ b/build.zig.zon.txt @@ -1,6 +1,9 @@ -git+https://github.com/jacobsandlund/uucode#5f05f8f83a75caea201f12cc8ea32a2d82ea9732 +git+https://github.com/rockorager/libvaxis.git#c1e1f23be38951c425cdf31af455ba23ef178940 +git+https://github.com/zigimg/zigimg#d695acd97c02e57bb151e8f659d1280f5cd6ca70 https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz https://deps.files.ghostty.org/JetBrainsMono-2.304.tar.gz +https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz +https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz https://deps.files.ghostty.org/NerdFontsSymbolsOnly-3.4.0.tar.gz https://deps.files.ghostty.org/breakpad-b99f444ba5f6b98cac261cbb391d8766b34a5918.tar.gz https://deps.files.ghostty.org/fontconfig-2.14.2.tar.gz @@ -8,29 +11,28 @@ https://deps.files.ghostty.org/freetype-1220b81f6ecfb3fd222f76cf9106fecfa6554ab0 https://deps.files.ghostty.org/gettext-0.24.tar.gz https://deps.files.ghostty.org/ghostty-themes-release-20260713-155359-c3968b3.tgz https://deps.files.ghostty.org/glslang-12201278a1a05c0ce0b6eb6026c65cd3e9247aa041b1c260324bf29cee559dd23ba1.tar.gz -https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst +https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst https://deps.files.ghostty.org/gtk4-layer-shell-1.1.0.tar.gz https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz https://deps.files.ghostty.org/highway-66486a10623fa0d72fe91260f96c892e41aceb06.tar.gz https://deps.files.ghostty.org/libpng-1220aa013f0c83da3fb64ea6d327f9173fa008d10e28bc9349eac3463457723b1c66.tar.gz -https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz +https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz https://deps.files.ghostty.org/libxml2-2.11.5.tar.gz https://deps.files.ghostty.org/oniguruma-1220c15e72eadd0d9085a8af134904d9a0f5dfcbed5f606ad60edc60ebeccd9706bb.tar.gz https://deps.files.ghostty.org/pixels-12207ff340169c7d40c570b4b6a97db614fe47e0d83b5801a932dcd44917424c8806.tar.gz https://deps.files.ghostty.org/plasma_wayland_protocols-12207e0851c12acdeee0991e893e0132fc87bb763969a585dc16ecca33e88334c566.tar.gz https://deps.files.ghostty.org/sentry-1220446be831adcca918167647c06c7b825849fa3fba5f22da394667974537a9c77e.tar.gz https://deps.files.ghostty.org/spirv_cross-1220fb3b5586e8be67bc3feb34cbe749cf42a60d628d2953632c2f8141302748c8da.tar.gz -https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz -https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz +https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz +https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz +https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz +https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz https://deps.files.ghostty.org/wayland-9cb3d7aa9dc995ffafdbdef7ab86a949d0fb0e7d.tar.gz https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz https://deps.files.ghostty.org/wuffs-122037b39d577ec2db3fd7b2130e7b69ef6cc1807d68607a7c232c958315d381b5cd.tar.gz -https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz -https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz -https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz -https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz -https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz +https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz +https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz +https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz +https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz https://deps.files.ghostty.org/zlib-1220fed0c74e1019b3ee29edae2051788b080cd96e90d56836eea857b0b966742efb.tar.gz -https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz -https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz -https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz +https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz diff --git a/example/zig-formatter/src/main.zig b/example/zig-formatter/src/main.zig index df21a2046..8c3fafe37 100644 --- a/example/zig-formatter/src/main.zig +++ b/example/zig-formatter/src/main.zig @@ -1,24 +1,20 @@ const std = @import("std"); const ghostty_vt = @import("ghostty-vt"); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); - +pub fn main(init: std.process.Init) !void { // Create a terminal - var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 150, .rows = 80 }); - defer t.deinit(alloc); + var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{ .cols = 150, .rows = 80 }); + defer t.deinit(init.gpa); // Create a read-only VT stream for parsing terminal sequences var stream = t.vtStream(); defer stream.deinit(); // Read from stdin - const stdin = std.fs.File.stdin(); + const stdin = std.Io.File.stdin(); var buf: [4096]u8 = undefined; while (true) { - const n = try stdin.readAll(&buf); + const n = try stdin.readStreaming(init.io, &.{&buf}); if (n == 0) break; // Replace \n with \r\n @@ -35,7 +31,7 @@ pub fn main() !void { }); // Write to stdout - var stdout_writer = std.fs.File.stdout().writer(&buf); + var stdout_writer = std.Io.File.stdout().writer(init.io, &buf); const stdout = &stdout_writer.interface; try stdout.print("{f}", .{formatter}); try stdout.flush(); diff --git a/example/zig-vt-stream/src/main.zig b/example/zig-vt-stream/src/main.zig index 87d8857dd..7202af6b8 100644 --- a/example/zig-vt-stream/src/main.zig +++ b/example/zig-vt-stream/src/main.zig @@ -1,13 +1,9 @@ const std = @import("std"); const ghostty_vt = @import("ghostty-vt"); -pub fn main() !void { - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); - - var t: ghostty_vt.Terminal = try .init(alloc, .{ .cols = 80, .rows = 24 }); - defer t.deinit(alloc); +pub fn main(init: std.process.Init) !void { + var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{ .cols = 80, .rows = 24 }); + defer t.deinit(init.gpa); // Create a read-only VT stream for parsing terminal sequences var stream = t.vtStream(); @@ -34,7 +30,7 @@ pub fn main() !void { stream.nextSlice("Line A\r\nLine B\r\nLine C\r\n"); // Get the final terminal state as a plain string - const str = try t.plainString(alloc); - defer alloc.free(str); + const str = try t.plainString(init.gpa); + defer init.gpa.free(str); std.debug.print("{s}\n", .{str}); } diff --git a/example/zig-vt/src/main.zig b/example/zig-vt/src/main.zig index f57c70087..ee1308805 100644 --- a/example/zig-vt/src/main.zig +++ b/example/zig-vt/src/main.zig @@ -1,26 +1,20 @@ const std = @import("std"); const ghostty_vt = @import("ghostty-vt"); -pub fn main() !void { - // Use a debug allocator so we get leak checking. You probably want - // to replace this for release builds. - var gpa: std.heap.DebugAllocator(.{}) = .init; - defer _ = gpa.deinit(); - const alloc = gpa.allocator(); - +pub fn main(init: std.process.Init) !void { // Initialize a terminal. - var t: ghostty_vt.Terminal = try .init(alloc, .{ + var t: ghostty_vt.Terminal = try .init(init.io, init.gpa, .{ .cols = 6, .rows = 40, }); - defer t.deinit(alloc); + defer t.deinit(init.gpa); // Write some text. It'll wrap because this is too long for our // columns size above (6). try t.printString("Hello, World!"); // Get the plain string view of the terminal screen. - const str = try t.plainString(alloc); - defer alloc.free(str); + const str = try t.plainString(init.gpa); + defer init.gpa.free(str); std.debug.print("{s}\n", .{str}); } diff --git a/flake.lock b/flake.lock index 410632788..ee1ca0d1e 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": 1782657028, + "narHash": "sha256-PHTCpYZCMzJYS3phhywqRAZphKVr2zjvlGYa+H20ZZ4=", "owner": "nix-community", "repo": "home-manager", - "rev": "b1f916ba052341edc1f80d4b2399f1092a4873ca", + "rev": "4ad9aaae70c9aaab504127f926c0fa9cfbc2b365", "type": "github" }, "original": { @@ -38,11 +38,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1770537093, - "narHash": "sha256-XV30uo8tXuxdzuV8l3sojmlPRLd/8tpMsOp4lNzLGUo=", - "rev": "fef9403a3e4d31b0a23f0bacebbec52c248fbb51", + "lastModified": 1782545840, + "narHash": "sha256-CAi8oAZaE6pTkcYQBnnOlvmfMgG/p1AO0FohKKN3J7I=", + "rev": "3d46470bb3030020f7e1361f33514854f5bfa86d", "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.05pre942631.fef9403a3e4d/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1023445.3d46470bb303/nixexprs.tar.xz" }, "original": { "type": "tarball", @@ -88,11 +88,11 @@ ] }, "locked": { - "lastModified": 1776789209, - "narHash": "sha256-G6B7Q4TXn7MZ1mB+f9rymjsYF5PLWoSvmbxijb/99bw=", + "lastModified": 1782609341, + "narHash": "sha256-OfCPJFS/2Z2ZyjE4adR7PL+ZEqvDQFi2XNI1L8s6wKU=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "14fe971844e841297ddd2ce9783d6892b467af39", + "rev": "5386fea92c45b154bbeb52d14ef5504f82eda86c", "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": 1784117571, + "narHash": "sha256-Hvnmnuu0VpHiZl+8z+UkMoxC7JZJvRYBqu2Asb0ZJOE=", "owner": "jcollie", "repo": "zon2nix", - "rev": "a5a1d412ad1ab6305511997bbc92b3a9dd6cb784", + "rev": "0ce628fb78309cdb13d098ae9c6fdbf87c75d25b", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 0a4e5340a..7591603f9 100644 --- a/flake.nix +++ b/flake.nix @@ -74,23 +74,42 @@ }; 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"; - wraptest = pkgs.callPackage ./nix/pkgs/wraptest.nix {}; - zon2nix = zon2nix; + 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; - python3 = pkgs.python3.override { - self = pkgs.python3; - packageOverrides = pyfinal: pyprev: { - blessed = pyfinal.callPackage ./nix/pkgs/blessed.nix {}; - ucs-detect = pyfinal.callPackage ./nix/pkgs/ucs-detect.nix {}; - wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {}; + 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; + + python3 = pkgs.python3.override { + self = pkgs.python3; + packageOverrides = pyfinal: pyprev: { + blessed = pyfinal.callPackage ./nix/pkgs/blessed.nix {}; + ucs-detect = pyfinal.callPackage ./nix/pkgs/ucs-detect.nix {}; + wcwidth = pyfinal.callPackage ./nix/pkgs/wcwidth.nix {}; + }; }; - }; - }; + + inherit appstream libadwaita blueprint-compiler; + }); }); packages = diff --git a/flatpak/zig-packages.json b/flatpak/zig-packages.json index 0d2a521c4..598e64e1a 100644 --- a/flatpak/zig-packages.json +++ b/flatpak/zig-packages.json @@ -1,4 +1,10 @@ [ + { + "type": "archive", + "url": "https://github.com/vancluever/arocc/archive/ecbc5c799574e0da2758a961b12efa586007f03c.tar.gz", + "dest": "vendor/p/aro-0.0.0-JSD1Qk8rOQDnuVcD4jAwMpHitA6pADRKzQ7M7hKRwxvD", + "sha256": "aa54487bd727e8fa36744ff0a824dedb2f4ca45f4c2d4d9d2437cf5fd3d0ad1a" + }, { "type": "archive", "url": "https://deps.files.ghostty.org/DearBindings_v0.17_ImGui_v1.92.5-docking.tar.gz", @@ -37,9 +43,9 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/gobject-2025-11-08-23-1.tar.zst", - "dest": "vendor/p/gobject-0.3.0-Skun7ANLnwDvEfIpVmohcppXgOvg_I6YOJFmPIsKfXk-", - "sha256": "d9bd4306f0081d8e4b848b6adfeabd2fab49822ee2b679eb4801dcedf5d60c48" + "url": "https://deps.files.ghostty.org/gobject-2026-04-23-26-1.tar.zst", + "dest": "vendor/p/gobject-0.3.1-Skun7E1KnwBGMX5nslHYG1yWHaSevywxQO8oM7tTOgIp", + "sha256": "642e324b3644c00464a2161e4af497f73cbbe8ce02a4c0edb92aacb365748047" }, { "type": "archive", @@ -61,7 +67,7 @@ }, { "type": "archive", - "url": "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz", + "url": "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz", "dest": "vendor/p/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI", "sha256": "c816c20e8c75f3e15ae867350e79925502d1a6a85938bb1a73b8927e5f31f9cb" }, @@ -85,9 +91,9 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/libxev-34fa50878aec6e5fa8f532867001ab3c36fae23e.tar.gz", - "dest": "vendor/p/libxev-0.0.0-86vtc4IcEwCqEYxEYoN_3KXmc6A9VLcm22aVImfvecYs", - "sha256": "6003ea6b96e4a518a128f932327d79a11bd30996b13b73baeb29916379487dd7" + "url": "https://deps.files.ghostty.org/libxev-9ce8e8e6ff89e583258a7f8e7adeeeaeae8611bf.tar.gz", + "dest": "vendor/p/libxev-0.0.0-86vtcwIRFADbH4hk-EjROXxlrKIRPQdA41XiTSytYO-F", + "sha256": "e2b9b224cd78101156abb8f46479a51f6cce66c4815c632856e20a1440dd02b7" }, { "type": "archive", @@ -131,23 +137,29 @@ "dest": "vendor/p/N-V-__8AANb6pwD7O1WG6L5nvD_rNMvnSc9Cpg1ijSlTYywv", "sha256": "b52b6fcfc45e7fa69b1f06a1362c155473444e2cc09995556b156c53ba6657e3" }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + "dest": "vendor/p/translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + "sha256": "4fd2a2b21dbc49c22bea6854a59a7568dc77183760531ace3cf8da1267696afd" + }, + { + "type": "archive", + "url": "https://deps.files.ghostty.org/uucode-2826a37a4562284fdacd8fa029d49509cc9bffcd.tar.gz", + "dest": "vendor/p/uucode-0.2.0-ZZjBPlK5VADj7fdoq7G8LIHzD5o6FSkcBXXrRWr4jnrA", + "sha256": "7e76fc7fab1e7ac728c52b35bbb3e5b8c639841abfc7fe1a4bcb13050594bc9e" + }, { "type": "git", - "url": "https://github.com/jacobsandlund/uucode", - "commit": "5f05f8f83a75caea201f12cc8ea32a2d82ea9732", - "dest": "vendor/p/uucode-0.1.0-ZZjBPj96QADXyt5sqwBJUnhaDYs_qBeeKijZvlRa0eqM" + "url": "https://github.com/rockorager/libvaxis.git", + "commit": "c1e1f23be38951c425cdf31af455ba23ef178940", + "dest": "vendor/p/vaxis-0.6.0-BWNV_MjFCQCs9UDHiRkrgw_ayeiPkzOe4xVbaAqXkUWW" }, { "type": "archive", - "url": "https://deps.files.ghostty.org/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9.tar.gz", - "dest": "vendor/p/uucode-0.2.0-ZZjBPqZVVABQepOqZHR7vV_NcaN-wats0IB6o-Exj6m9", - "sha256": "d0abee0f4f8bd6eae3c051777e16e7c42d8964aaaa015591c4e565703f465f95" - }, - { - "type": "archive", - "url": "https://deps.files.ghostty.org/vaxis-7dbb9fd3122e4ffad262dd7c151d80d863b68558.tar.gz", - "dest": "vendor/p/vaxis-0.5.1-BWNV_LosCQAGmCCNOLljCIw6j6-yt53tji6n6rwJ2BhS", - "sha256": "2e72332bc89c5b541ec6e6bd48769e1f3fb757c4006f3d1af940b54f9b088ef6" + "url": "https://deps.files.ghostty.org/vaxis-1dbbe575dff4586fe51e3217aa5c3fecdcbb6089.tar.gz", + "dest": "vendor/p/vaxis-0.6.0-BWNV_CrbCQCscGpzsAlR402rYQ_tV3aAl081c2iRRkka", + "sha256": "3ea050e905887c46bca1a2c18e41d6edf574c1aa6ebfbc5cf3c52846275e4fa8" }, { "type": "archive", @@ -157,15 +169,15 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", - "dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", - "sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8" + "url": "https://deps.files.ghostty.org/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA.tar.gz", + "dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA", + "sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223" }, { "type": "archive", - "url": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/archive/1.47/wayland-protocols-1.47.tar.gz", - "dest": "vendor/p/N-V-__8AAFdWDwA0ktbNUi9pFBHCRN4weXIgIfCrVjfGxqgA", - "sha256": "dd2df14ab5f41038257aaedcc4b5fb9ac0ee018f3f0f94af9097028e60d33223" + "url": "https://deps.files.ghostty.org/wayland-protocols-258d8f88f2c8c25a830c6316f87d23ce1a0f12d9.tar.gz", + "dest": "vendor/p/N-V-__8AAKw-DAAaV8bOAAGqA0-oD7o-HNIlPFYKRXSPT03S", + "sha256": "5cedcadde81b75e60f23e5e83b5dd2b8eb4efb9f8f79bd7a347d148aeb0530f8" }, { "type": "archive", @@ -175,39 +187,39 @@ }, { "type": "archive", - "url": "https://deps.files.ghostty.org/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ.tar.gz", - "dest": "vendor/p/z2d-0.10.0-j5P_Hu-6FgBsZNgwphIqh17jDnj8_yPtD8yzjO6PpHRQ", - "sha256": "69f21da2efd5ee0937fe55c4d09e48afc4fb2f91a01ef167c8c275ae046797f7" + "url": "https://deps.files.ghostty.org/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR.tar.gz", + "dest": "vendor/p/z2d-0.11.0-j5P_HtLzDwBGyQt49DrT0v4BuVqI_SRs6CXsuj7eBVhR", + "sha256": "d48b117307f767ca379bf31bd171880e66dcafb89eca4a8f923222619992fcb4" }, { "type": "archive", - "url": "https://deps.files.ghostty.org/zf-3c52637b7e937c5ae61fd679717da3e276765b23.tar.gz", - "dest": "vendor/p/zf-0.10.3-OIRy8RuJAACKA3Lohoumrt85nRbHwbpMcUaLES8vxDnh", - "sha256": "3b015d928af04e9e26272bc15eb4dbb4d9a9d469eb6d290a0ddae673b77c4568" + "url": "https://deps.files.ghostty.org/zf-c35c421f84895193246db06c40683c1a30e616ef.tar.gz", + "dest": "vendor/p/zf-0.11.0-OIRy8X-RAAAwaRXHMYpj2uvBnuGTZWEE_3V7acqHQNtW", + "sha256": "ca4bc109fd9d97cedae2f5bdb879cc78c346d43c14a34e3a2abd2ee9208c8547" }, { "type": "archive", - "url": "https://deps.files.ghostty.org/zig_js-04db83c617da1956ac5adc1cb9ba1e434c1cb6fd.tar.gz", - "dest": "vendor/p/zig_js-0.0.0-rjCAV-6GAADxFug7rDmPH-uM_XcnJ5NmuAMJCAscMjhi", - "sha256": "4c2018e56015d39504b8090386ad9ce9393f38380085d9c32373bf7e56fc73a3" + "url": "https://deps.files.ghostty.org/zig_js-3c23860e47fdcdc5af805efb7fd0bdac5fd3e9bc.tar.gz", + "dest": "vendor/p/zig_js-0.0.0-rjCAV7-GAADvMTBL7lPMuvDk7xgS9PCMIZWiOUXLZSlj", + "sha256": "a2ff488a244301f4e59469a5232aad6869b01acc50f7d7ed2647c029a57d4b3b" }, { "type": "archive", - "url": "https://deps.files.ghostty.org/zig_objc-f356ed02833f0f1b8e84d50bed9e807bf7cdc0ae.tar.gz", - "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp5gTAQCvxxR7oVIrPXxXwsfKgVP7_wqoOQrZjFeK", - "sha256": "dd84af737625356fcd722cb30909f3b2e8d702667cf579714aa7eabc0ac08ecc" + "url": "https://deps.files.ghostty.org/zig_objc-c8de82ff80281215ad92900866dab7103a8efa8b.tar.gz", + "dest": "vendor/p/zig_objc-0.0.0-Ir_Sp9gsAQCPAJc0oF5xoWePHWP6Y6tCphDeyNUThJoi", + "sha256": "831eaec554bb23ef20229f1e705d8f53e8a29c829840bb8d0f5edb05c76d6df0" }, { "type": "archive", - "url": "https://deps.files.ghostty.org/zig_wayland-1b5c038ec10da20ed3a15b0b2a6db1c21383e8ea.tar.gz", - "dest": "vendor/p/wayland-0.5.0-dev-lQa1khrMAQDJDwYFKpdH3HizherB7sHo5dKMECfvxQHe", - "sha256": "4f146b735ed0d527f520e3bf71d3e93f72c3d0fa583ae8edd3a4851f7079124e" + "url": "https://deps.files.ghostty.org/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX.tar.gz", + "dest": "vendor/p/wayland-0.6.0-lQa1kqz8AQADQmdNJsNhLoNHcnEGEUjrOaPV-dtEnEmX", + "sha256": "759a632e36a948e0e412d2d74a43a69d2f34e65d40289a16af7aa3725852ef25" }, { - "type": "archive", - "url": "https://github.com/ivanstepanovftw/zigimg/archive/d7b7ab0ba0899643831ef042bd73289510b39906.tar.gz", - "dest": "vendor/p/zigimg-0.1.0-8_eo2vHnEwCIVW34Q14Ec-xUlzIoVg86-7FU2ypPtxms", - "sha256": "2c1ed76ba2b35514544b0c27c9633ecba7c31be9080e37e7a010c93b5a1bc553" + "type": "git", + "url": "https://github.com/zigimg/zigimg", + "commit": "d695acd97c02e57bb151e8f659d1280f5cd6ca70", + "dest": "vendor/p/zigimg-0.1.0-8_eo2oyaFwBZwJpmqPkCfVXWBrHcqbYwmrp1I6bTD3lI" }, { "type": "archive", diff --git a/include/ghostty/vt/terminal.h b/include/ghostty/vt/terminal.h index 9ae4e6c45..8470607b3 100644 --- a/include/ghostty/vt/terminal.h +++ b/include/ghostty/vt/terminal.h @@ -785,12 +785,13 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_FILE = 16, /** - * Enable or disable Kitty image loading via the temporary file medium. + * Enable Kitty image loading via the temporary file medium, restricted to + * the provided directory. The string data is copied into the terminal. * - * A NULL value pointer is a no-op. Has no effect when Kitty graphics - * are disabled at build time. + * A NULL value pointer disables the temporary file medium. Has no effect + * when Kitty graphics are disabled at build time. * - * Input type: bool* + * Input type: GhosttyString* */ GHOSTTY_TERMINAL_OPT_KITTY_IMAGE_MEDIUM_TEMP_FILE = 17, @@ -1139,12 +1140,13 @@ typedef enum GHOSTTY_ENUM_TYPED { GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_FILE = 27, /** - * Whether the temporary file medium is enabled for Kitty image loading - * on the active screen. + * The directory allowed for Kitty image loading via the temporary file + * medium on the active screen. The string is empty when the medium is + * disabled. * * Returns GHOSTTY_NO_VALUE when Kitty graphics are disabled at build time. * - * Output type: bool * + * Output type: GhosttyString * */ GHOSTTY_TERMINAL_DATA_KITTY_IMAGE_MEDIUM_TEMP_FILE = 28, diff --git a/nix/build-support/check-zig-cache.sh b/nix/build-support/check-zig-cache.sh index 9a3927846..52ab1b173 100755 --- a/nix/build-support/check-zig-cache.sh +++ b/nix/build-support/check-zig-cache.sh @@ -79,7 +79,7 @@ elif [ "$1" != "--update" ]; then exit 1 fi -zon2nix "$BUILD_ZIG_ZON" --15 --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json" --flatpak "$WORK_DIR/zig-packages.json" +zon2nix "$BUILD_ZIG_ZON" --16 --nix "$WORK_DIR/build.zig.zon.nix" --txt "$WORK_DIR/build.zig.zon.txt" --json "$WORK_DIR/build.zig.zon.json" --flatpak "$WORK_DIR/zig-packages.json" alejandra --quiet "$WORK_DIR/build.zig.zon.nix" prettier --log-level warn --write "$WORK_DIR/build.zig.zon.json" prettier --log-level warn --write "$WORK_DIR/zig-packages.json" diff --git a/nix/build-support/update-mirror.nu b/nix/build-support/update-mirror.nu index 8571ddea6..b234172bf 100755 --- a/nix/build-support/update-mirror.nu +++ b/nix/build-support/update-mirror.nu @@ -2,23 +2,34 @@ # This script downloads external dependencies from build.zig.zon.json that # are not already mirrored at deps.files.ghostty.org, saves them to a local -# directory, and updates build.zig.zon to point to the new mirror URLs. +# directory, and updates the build.zig.zon files (the root one as well as +# every pkg/*/build.zig.zon) to point to the new mirror URLs. # # The downloaded files are unmodified so their checksums and content hashes # will match the originals. # # After running this script, the files in the output directory can be uploaded -# to blob storage, and build.zig.zon will already be updated with the new URLs. +# to blob storage, and the build.zig.zon files will already be updated with +# the new URLs. def main [ --output: string = "tmp-mirror", # Output directory for the mirrored files --prefix: string = "https://deps.files.ghostty.org/", # Final URL prefix to ignore --dry-run, # Print what would be downloaded without downloading ] { let script_dir = ($env.CURRENT_FILE | path dirname) - let input_file = ($script_dir | path join ".." ".." "build.zig.zon.json") - let zon_file = ($script_dir | path join ".." ".." "build.zig.zon") + let root_dir = ($script_dir | path join ".." ".." | path expand) + let input_file = ($root_dir | path join "build.zig.zon.json") let output_dir = $output + # All build.zig.zon files that may reference external URLs: the root + # one plus every package under pkg/. build.zig.zon.json is generated + # from the full dependency tree so it covers all of these. + let zon_files = ( + [($root_dir | path join "build.zig.zon")] + | append (glob ($root_dir | path join "pkg" "*" "build.zig.zon")) + | sort + ) + # Ensure the output directory exists mkdir $output_dir @@ -75,22 +86,34 @@ def main [ if $dry_run { print "Dry run complete - no files were downloaded\n" - print $"Would update ($url_replacements | length) URLs in build.zig.zon" } else { print "All dependencies downloaded successfully\n" - print $"Updating ($zon_file)..." - - # Backup the old file - let backup_file = $"($zon_file).bak" - cp $zon_file $backup_file - print $"Backed up to ($backup_file)" - - mut zon_content = (open $zon_file) - for replacement in $url_replacements { - $zon_content = ($zon_content | str replace $replacement.old $replacement.new) - } - $zon_content | save -f $zon_file + } - print $"Updated ($url_replacements | length) URLs in build.zig.zon" + # Apply the URL replacements to every build.zig.zon that references them. + for zon_file in $zon_files { + mut zon_content = (open --raw $zon_file) + mut count = 0 + for replacement in $url_replacements { + if ($zon_content | str contains $replacement.old) { + $zon_content = ($zon_content | str replace --all $replacement.old $replacement.new) + $count = $count + 1 + } + } + + if $count == 0 { + continue + } + + let relative_path = ($zon_file | path relative-to $root_dir) + if $dry_run { + print $"Would update ($count) URLs in ($relative_path)" + } else { + # Backup the old file + let backup_file = $"($zon_file).bak" + cp $zon_file $backup_file + $zon_content | save -f $zon_file + print $"Updated ($count) URLs in ($relative_path), backup at ($relative_path).bak" + } } } 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/nix/libghostty-vt.nix b/nix/libghostty-vt.nix index 2b67fc838..78072ed1f 100644 --- a/nix/libghostty-vt.nix +++ b/nix/libghostty-vt.nix @@ -11,7 +11,7 @@ versionCheckHook, darwin, xcbuild, - zig_0_15, + zig_0_16, revision ? "dirty", optimize ? "Debug", simd ? true, @@ -59,7 +59,7 @@ stdenv.mkDerivation (finalAttrs: { [ git pkg-config - zig_0_15 + zig_0_16 ] ++ lib.optionals stdenv.hostPlatform.isDarwin [ darwin.cctools @@ -264,7 +264,7 @@ stdenv.mkDerivation (finalAttrs: { meta = { homepage = "https://ghostty.org"; license = lib.licenses.mit; - platforms = zig_0_15.meta.platforms; + platforms = zig_0_16.meta.platforms; pkgConfigModules = [ "libghostty-vt" "libghostty-vt-static" diff --git a/nix/package.nix b/nix/package.nix index fd952c9de..31db683c2 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -10,7 +10,7 @@ git, ncurses, pkg-config, - zig_0_15, + zig_0_16, pandoc, revision ? "dirty", optimize ? "Debug", @@ -63,7 +63,7 @@ in ncurses pandoc pkg-config - zig_0_15 + zig_0_16 gobject-introspection wrapGAppsHook4 blueprint-compiler 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..ba49927cf 100644 --- a/pkg/apple-sdk/build.zig +++ b/pkg/apple-sdk/build.zig @@ -1,6 +1,31 @@ const std = @import("std"); const builtin = @import("builtin"); +// The cache. This always uses b.allocator and never frees memory +// (which is idiomatic for a Zig build exe). We cache the libc txt +// file we create because it is expensive to generate (subprocesses). +pub const Cache = struct { + const Key = struct { + arch: std.Target.Cpu.Arch, + os: std.Target.Os.Tag, + abi: std.Target.Abi, + }; + + pub const Value = union(enum) { + native: struct { + libc: std.Build.LazyPath, + framework: []const u8, + system_include: []const u8, + library: []const u8, + }, + cross: struct { + libc: std.Build.LazyPath, + }, + }; + + var map: std.AutoHashMapUnmanaged(Key, ?Value) = .{}; +}; + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -8,40 +33,11 @@ pub fn build(b: *std.Build) !void { _ = optimize; } -/// Setup the step to point to the proper Apple SDK for libc and +/// Fetch or load the paths for the proper Apple SDK for libc and /// frameworks. When running on a Darwin host, this uses the native /// SDK installed on the system via `xcrun`. When cross-compiling from /// a non-Darwin host, it falls back to Zig's bundled Darwin headers. -pub fn addPaths( - b: *std.Build, - step: *std.Build.Step.Compile, -) !void { - // The cache. This always uses b.allocator and never frees memory - // (which is idiomatic for a Zig build exe). We cache the libc txt - // file we create because it is expensive to generate (subprocesses). - const Cache = struct { - const Key = struct { - arch: std.Target.Cpu.Arch, - os: std.Target.Os.Tag, - abi: std.Target.Abi, - }; - - const Value = union(enum) { - native: struct { - libc: std.Build.LazyPath, - framework: []const u8, - system_include: []const u8, - library: []const u8, - }, - cross: struct { - libc: std.Build.LazyPath, - }, - }; - - var map: std.AutoHashMapUnmanaged(Key, ?Value) = .{}; - }; - - const target = step.rootModuleTarget(); +pub fn pathsForTarget(b: *std.Build, target: std.Target) !Cache.Value { const gop = try Cache.map.getOrPut(b.allocator, .{ .arch = target.cpu.arch, .os = target.os.tag, @@ -53,14 +49,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 = &target, + .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); @@ -112,7 +112,7 @@ pub fn addPaths( // Fall back to Zig's bundled Darwin headers for libc resolution. const zig_lib_path = b.graph.zig_lib_directory.path.?; const include_dir = b.pathJoin(&.{ - zig_lib_path, "libc", "include", "any-macos-any", + zig_lib_path, "libc", "include", "any-darwin-any", }); const wf = b.addWriteFiles(); @@ -129,7 +129,7 @@ pub fn addPaths( gop.value_ptr.* = .{ .cross = .{ .libc = path } }; } - const value = gop.value_ptr.* orelse return switch (target.os.tag) { + return gop.value_ptr.* orelse return switch (target.os.tag) { // Return a more descriptive error. Before we just returned the // generic error but this was confusing a lot of community members. // It costs us nothing in the build script to return something better. @@ -139,8 +139,18 @@ pub fn addPaths( .watchos => error.XcodeWatchOSSDKNotFound, else => error.XcodeAppleSDKNotFound, }; +} - switch (value) { +/// Setup the step to point to the proper Apple SDK for libc and +/// frameworks. When running on a Darwin host, this uses the native +/// SDK installed on the system via `xcrun`. When cross-compiling from +/// a non-Darwin host, it falls back to Zig's bundled Darwin headers. +pub fn addPaths( + b: *std.Build, + step: *std.Build.Step.Compile, +) !void { + const target = step.rootModuleTarget(); + switch (try pathsForTarget(b, target)) { .native => |native| { step.setLibCFile(native.libc); 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/dcimgui/build.zig.zon b/pkg/dcimgui/build.zig.zon index 95d0120e1..c488e6122 100644 --- a/pkg/dcimgui/build.zig.zon +++ b/pkg/dcimgui/build.zig.zon @@ -15,7 +15,7 @@ .imgui = .{ // https://github.com/ocornut/imgui - .url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.5-docking.tar.gz", + .url = "https://deps.files.ghostty.org/N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI.tar.gz", .hash = "N-V-__8AAEbOfQBnvcFcCX2W5z7tDaN8vaNZGamEQtNOe0UI", .lazy = true, }, 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..8ab67e62b 100644 --- a/pkg/gtk4-layer-shell/build.zig +++ b/pkg/gtk4-layer-shell/build.zig @@ -17,12 +17,32 @@ pub fn build(b: *std.Build) !void { .target = target, .optimize = optimize, }); - // Needs the gtk.h header - module.linkSystemLibrary("gtk4", dynamic_link_opts); - if (b.systemIntegrationOption("gtk4-layer-shell", .{})) { - module.linkSystemLibrary("gtk4-layer-shell-0", dynamic_link_opts); - } else { + translate: { + const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; + const Translator = translate_c.Translator; + + const link_system_libs_full: [2]Translator.LinkSystemLib = .{ + .{ .name = "gtk4", .options = dynamic_link_opts }, + .{ .name = "gtk4-layer-shell-0", .options = dynamic_link_opts }, + }; + + const headers = Translator.init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("c.h", + \\#include + ), + .target = target, + .optimize = optimize, + .link_system_libs = if (b.systemIntegrationOption("gtk4-layer-shell", .{})) + &link_system_libs_full + else + link_system_libs_full[0..1], + }); + module.addImport("c", headers.mod); + } + + if (!b.systemIntegrationOption("gtk4-layer-shell", .{})) { _ = try buildLib(b, module, .{ .target = target, .optimize = optimize, @@ -42,6 +62,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 +73,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 +112,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 +133,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..32c7adfc1 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://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + .lazy = true, + }, }, } diff --git a/pkg/gtk4-layer-shell/src/main.zig b/pkg/gtk4-layer-shell/src/main.zig index a15313231..04e0f0f35 100644 --- a/pkg/gtk4-layer-shell/src/main.zig +++ b/pkg/gtk4-layer-shell/src/main.zig @@ -1,8 +1,6 @@ const std = @import("std"); -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/blob.zig b/pkg/harfbuzz/blob.zig index 9472e4c75..94a6b5c98 100644 --- a/pkg/harfbuzz/blob.zig +++ b/pkg/harfbuzz/blob.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const Error = @import("errors.zig").Error; /// Data type holding the memory modes available to client programs. diff --git a/pkg/harfbuzz/buffer.zig b/pkg/harfbuzz/buffer.zig index b97c1bef4..ea70bbca8 100644 --- a/pkg/harfbuzz/buffer.zig +++ b/pkg/harfbuzz/buffer.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const common = @import("common.zig"); const Error = @import("errors.zig").Error; const Direction = common.Direction; diff --git a/pkg/harfbuzz/build.zig b/pkg/harfbuzz/build.zig index b482bc8a3..5c78b262e 100644 --- a/pkg/harfbuzz/build.zig +++ b/pkg/harfbuzz/build.zig @@ -1,6 +1,8 @@ const std = @import("std"); const apple_sdk = @import("apple_sdk"); +const root_build_container = @This(); + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -8,31 +10,6 @@ pub fn build(b: *std.Build) !void { const coretext_enabled = b.option(bool, "enable-coretext", "Build coretext") orelse false; const freetype_enabled = b.option(bool, "enable-freetype", "Build freetype") orelse true; - const freetype = b.dependency("freetype", .{ - .target = target, - .optimize = optimize, - .@"enable-libpng" = true, - }); - const macos = b.dependency("macos", .{ .target = target, .optimize = optimize }); - - const module = harfbuzz: { - const module = b.addModule("harfbuzz", .{ - .root_source_file = b.path("main.zig"), - .target = target, - .optimize = optimize, - .imports = &.{ - .{ .name = "freetype", .module = freetype.module("freetype") }, - .{ .name = "macos", .module = macos.module("macos") }, - }, - }); - - const options = b.addOptions(); - options.addOption(bool, "coretext", coretext_enabled); - options.addOption(bool, "freetype", freetype_enabled); - module.addOptions("build_options", options); - break :harfbuzz module; - }; - // 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. @@ -41,33 +18,63 @@ pub fn build(b: *std.Build) !void { .search_strategy = .mode_first, }; - const test_exe = b.addTest(.{ - .name = "test", - .root_module = b.createModule(.{ + const freetype = b.dependency("freetype", .{ + .target = target, + .optimize = optimize, + .@"enable-libpng" = true, + }); + + const module = harfbuzz: { + const module = b.addModule("harfbuzz", .{ .root_source_file = b.path("main.zig"), .target = target, .optimize = optimize, - }), + .imports = if (target.result.os.tag.isDarwin()) + &.{ + .{ .name = "freetype", .module = freetype.module("freetype") }, + .{ + .name = "macos", + .module = b.dependency("macos", .{ .target = target, .optimize = optimize }) + .module("macos"), + }, + } + else + &.{ + .{ .name = "freetype", .module = freetype.module("freetype") }, + }, + }); + + try HarfBuzzC.addImportToModule(b, module, .{ + .target = target, + .optimize = optimize, + .harfbuzz = if (b.systemIntegrationOption("harfbuzz", .{})) .{ .dynamic = dynamic_link_opts } else .static, + .coretext = coretext_enabled, + .freetype = if (freetype_enabled) ft: { + break :ft if (b.systemIntegrationOption("freetype", .{})) + .{ .dynamic = dynamic_link_opts } + else + .static; + } else null, + }); + + const options = b.addOptions(); + options.addOption(bool, "coretext", coretext_enabled); + options.addOption(bool, "freetype", freetype_enabled); + module.addOptions("build_options", options); + break :harfbuzz module; + }; + + const test_exe = b.addTest(.{ + .name = "test", + .root_module = module, }); - { - 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); - } else { - test_exe.linkLibrary(freetype.artifact("freetype")); - } - const tests_run = b.addRunArtifact(test_exe); - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&tests_run.step); - } + const tests_run = b.addRunArtifact(test_exe); + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&tests_run.step); - if (b.systemIntegrationOption("harfbuzz", .{})) { - module.linkSystemLibrary("harfbuzz", dynamic_link_opts); - test_exe.linkSystemLibrary2("harfbuzz", dynamic_link_opts); - } else { - const lib = try buildLib(b, module, .{ + if (!b.systemIntegrationOption("harfbuzz", .{})) { + const lib = try buildLib(b, .{ .target = target, .optimize = optimize, @@ -77,11 +84,11 @@ pub fn build(b: *std.Build) !void { .dynamic_link_opts = dynamic_link_opts, }); - test_exe.linkLibrary(lib); + test_exe.root_module.linkLibrary(lib); } } -fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Build.Step.Compile { +fn buildLib(b: *std.Build, options: anytype) !*std.Build.Step.Compile { const target = options.target; const optimize = options.optimize; @@ -99,18 +106,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,30 +159,20 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu }); if (b.systemIntegrationOption("freetype", .{})) { - lib.linkSystemLibrary2("freetype2", dynamic_link_opts); - module.linkSystemLibrary("freetype2", dynamic_link_opts); + lib.root_module.linkSystemLibrary("freetype2", dynamic_link_opts); } else { - lib.linkLibrary(freetype.artifact("freetype")); - - if (freetype.builder.lazyDependency( - "freetype", - .{}, - )) |freetype_dep| { - module.addIncludePath(freetype_dep.path("include")); - } + lib.root_module.linkLibrary(freetype.artifact("freetype")); } } if (coretext_enabled) { try flags.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"}); - lib.linkFramework("CoreText"); - module.linkFramework("CoreText", .{}); + lib.root_module.linkFramework("CoreText", .{}); } if (b.lazyDependency("harfbuzz", .{})) |upstream| { - lib.addIncludePath(upstream.path("src")); - module.addIncludePath(upstream.path("src")); - lib.addCSourceFile(.{ + lib.root_module.addIncludePath(upstream.path("src")); + lib.root_module.addCSourceFile(.{ .file = upstream.path("src/harfbuzz.cc"), .flags = flags.items, }); @@ -192,3 +187,173 @@ fn buildLib(b: *std.Build, module: *std.Build.Module, options: anytype) !*std.Bu return lib; } + +const HarfBuzzC = struct { + const AddImportToModuleOptions = struct { + const LinkMode = union(enum) { + static, + dynamic: std.Build.Module.LinkSystemLibraryOptions, + }; + + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + harfbuzz: LinkMode, + coretext: bool, + freetype: ?LinkMode, + }; + + fn fmtInclude(w: *std.Io.Writer, name: []const u8, mode: AddImportToModuleOptions.LinkMode) !void { + if (mode == .dynamic) { + try w.print("#include <{s}>\n", .{name}); + } else { + try w.print("#include \"{s}\"\n", .{name}); + } + } + + fn addImportToModule( + b: *std.Build, + module: *std.Build.Module, + options: AddImportToModuleOptions, + ) !void { + // TODO: There's a decent amount of duplication here right now. + // Basically we want to mirror what we're passing in buildLib to make + // sure that the translation is generated as correct as possible. + // Eventually, we want to try and unravel this as much as we can, to + // the point that ultimately all C flags and even link options are + // self-contained in the translation artifact. + // + // This is a bit tricky right now as there are situations where we + // provide a static library built straight off of the C file, hence the + // duplication. + // + // NOTE: This function de-allocates nothing as b.allocator is an arena + // (unfortunately not documented, but a cursory search in various + // communities or the issue trackers should turn up confirmation). + + const translate_c = b.lazyImport(root_build_container, "translate_c") orelse return; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse return; + + const c_source = c_source: { + var source_builder: std.Io.Writer.Allocating = .init(b.allocator); + try fmtInclude(&source_builder.writer, "hb.h", options.harfbuzz); + + if (options.coretext) { + try fmtInclude(&source_builder.writer, "hb-coretext.h", options.harfbuzz); + } + + if (options.freetype != null) { + try fmtInclude(&source_builder.writer, "hb-ft.h", options.harfbuzz); + } + + break :c_source source_builder.written(); + }; + + // Assemble system libs + const system_libs = libs: { + var libs_builder: std.ArrayList(translate_c.Translator.LinkSystemLib) = .empty; + if (options.harfbuzz == .dynamic) + try libs_builder.append(b.allocator, .{ .name = "harfbuzz", .options = options.harfbuzz.dynamic }); + if (options.freetype) |ft| { + if (ft == .dynamic) + try libs_builder.append(b.allocator, .{ .name = "freetype2", .options = ft.dynamic }); + } + + break :libs libs_builder.items; + }; + + // Assemble flags + const flags = flags: { + var flag_builder: std.ArrayList([]const u8) = .empty; + try flag_builder.appendSlice(b.allocator, &.{ + "-DHAVE_STDBOOL_H", + }); + // 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. + if (options.target.result.abi == .msvc) { + try flag_builder.appendSlice(b.allocator, &.{ + "-fno-sanitize=undefined", + "-fno-sanitize-trap=undefined", + }); + } + if (options.target.result.os.tag != .windows) { + try flag_builder.appendSlice(b.allocator, &.{ + "-DHAVE_UNISTD_H", + "-DHAVE_SYS_MMAN_H", + "-DHAVE_PTHREAD=1", + }); + } + + // Freetype flags/non-system include paths + if (options.freetype != null) { + try flag_builder.appendSlice(b.allocator, &.{ + "-DHAVE_FREETYPE=1", + + // Let's just assume a new freetype + "-DHAVE_FT_GET_VAR_BLEND_COORDINATES=1", + "-DHAVE_FT_SET_VAR_BLEND_COORDINATES=1", + "-DHAVE_FT_DONE_MM_VAR=1", + "-DHAVE_FT_GET_TRANSFORM=1", + }); + } + + // Coretext + if (options.coretext) { + try flag_builder.appendSlice(b.allocator, &.{"-DHAVE_CORETEXT=1"}); + try flag_builder.appendSlice(b.allocator, &.{"-fblocks"}); + } + + break :flags flag_builder.items; + }; + + const hb_c: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add( + "hb_c.h", + c_source, + ), + .target = options.target, + .optimize = options.optimize, + .link_libc = true, + .link_system_libs = system_libs, + .libc_file = if (options.target.result.os.tag.isDarwin()) libc_file: { + switch (try @import("apple_sdk").pathsForTarget(b, options.target.result)) { + inline else => |paths| break :libc_file paths.libc, + } + } else null, + .extra_args = flags, + }); + + if (options.harfbuzz == .static) { + if (b.lazyDependency("harfbuzz", .{})) |upstream| { + hb_c.addIncludePath(upstream.path("src")); + } + } + + // Freetype non-system include paths + if (options.freetype) |freetype_enabled| { + if (freetype_enabled == .static) { + const ft_dep = b.dependency("freetype", .{ + .target = options.target, + .optimize = options.optimize, + .@"enable-libpng" = true, + }); + + if (ft_dep.builder.lazyDependency( + "freetype", + .{}, + )) |freetype_lazy_dep| { + hb_c.addIncludePath(freetype_lazy_dep.path("include")); + } + } + } + + // Coretext + if (options.coretext) { + // NOTE: We should not necessarily need to add this directly to C + // translation, so we just add it to the module. + hb_c.mod.linkFramework("CoreText", .{}); + } + + module.addImport("hb_c", hb_c.mod); + } +}; diff --git a/pkg/harfbuzz/build.zig.zon b/pkg/harfbuzz/build.zig.zon index b7d155e9c..c4ee2bdfe 100644 --- a/pkg/harfbuzz/build.zig.zon +++ b/pkg/harfbuzz/build.zig.zon @@ -4,6 +4,12 @@ .fingerprint = 0xbd60917cd18865d8, .paths = .{""}, .dependencies = .{ + .translate_c = .{ + .url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + .lazy = true, + }, + // harfbuzz/harfbuzz .harfbuzz = .{ .url = "https://deps.files.ghostty.org/harfbuzz-11.0.0.tar.xz", diff --git a/pkg/harfbuzz/c.zig b/pkg/harfbuzz/c.zig deleted file mode 100644 index 49e87dce7..000000000 --- a/pkg/harfbuzz/c.zig +++ /dev/null @@ -1,8 +0,0 @@ -const builtin = @import("builtin"); -const build_options = @import("build_options"); - -pub const c = @cImport({ - @cInclude("hb.h"); - if (build_options.freetype) @cInclude("hb-ft.h"); - if (build_options.coretext) @cInclude("hb-coretext.h"); -}); diff --git a/pkg/harfbuzz/common.zig b/pkg/harfbuzz/common.zig index 840ff5868..01955907a 100644 --- a/pkg/harfbuzz/common.zig +++ b/pkg/harfbuzz/common.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); /// The direction of a text segment or buffer. /// diff --git a/pkg/harfbuzz/coretext.zig b/pkg/harfbuzz/coretext.zig index 73042624c..72dfd53b2 100644 --- a/pkg/harfbuzz/coretext.zig +++ b/pkg/harfbuzz/coretext.zig @@ -1,6 +1,6 @@ const macos = @import("macos"); const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const Face = @import("face.zig").Face; const Font = @import("font.zig").Font; const Error = @import("errors.zig").Error; diff --git a/pkg/harfbuzz/face.zig b/pkg/harfbuzz/face.zig index 5ffad5d3e..942894b92 100644 --- a/pkg/harfbuzz/face.zig +++ b/pkg/harfbuzz/face.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); /// A font face is an object that represents a single face from within a font family. /// diff --git a/pkg/harfbuzz/font.zig b/pkg/harfbuzz/font.zig index 13544fb9f..4202ca4dd 100644 --- a/pkg/harfbuzz/font.zig +++ b/pkg/harfbuzz/font.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const Face = @import("face.zig").Face; const Error = @import("errors.zig").Error; diff --git a/pkg/harfbuzz/freetype.zig b/pkg/harfbuzz/freetype.zig index d90d3f83c..65419a6e6 100644 --- a/pkg/harfbuzz/freetype.zig +++ b/pkg/harfbuzz/freetype.zig @@ -1,6 +1,6 @@ const freetype = @import("freetype"); const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const Face = @import("face.zig").Face; const Font = @import("font.zig").Font; const Error = @import("errors.zig").Error; diff --git a/pkg/harfbuzz/main.zig b/pkg/harfbuzz/main.zig index 08a4f9c2a..6e46eb42c 100644 --- a/pkg/harfbuzz/main.zig +++ b/pkg/harfbuzz/main.zig @@ -7,7 +7,7 @@ const font = @import("font.zig"); const shapepkg = @import("shape.zig"); const versionpkg = @import("version.zig"); -pub const c = @import("c.zig").c; +pub const c = @import("hb_c"); pub const freetype = @import("freetype.zig"); pub const coretext = @import("coretext.zig"); pub const MemoryMode = blob.MemoryMode; diff --git a/pkg/harfbuzz/shape.zig b/pkg/harfbuzz/shape.zig index b49020c3c..38f56c4ac 100644 --- a/pkg/harfbuzz/shape.zig +++ b/pkg/harfbuzz/shape.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); const Font = @import("font.zig").Font; const Buffer = @import("buffer.zig").Buffer; const Feature = @import("common.zig").Feature; diff --git a/pkg/harfbuzz/version.zig b/pkg/harfbuzz/version.zig index dbe25b522..5546ca377 100644 --- a/pkg/harfbuzz/version.zig +++ b/pkg/harfbuzz/version.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const c = @import("c.zig").c; +const c = @import("hb_c"); pub const Version = struct { major: u32, diff --git a/pkg/highway/build.zig b/pkg/highway/build.zig index a02447285..fce823bf3 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("")); } @@ -102,7 +101,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", @@ -127,7 +126,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..5569baad0 100644 --- a/pkg/macos/build.zig +++ b/pkg/macos/build.zig @@ -2,6 +2,66 @@ const std = @import("std"); const builtin = @import("builtin"); const apple_sdk = @import("apple_sdk"); +const Framework = struct { + const Tag = enum { all, macos }; + + tag: Tag, + name: []const u8, + headers: []const []const u8, +}; + +const frameworks = [_]Framework{ + .{ .tag = .all, .name = "CoreFoundation", .headers = &.{"CoreFoundation.h"} }, + .{ .tag = .all, .name = "CoreGraphics", .headers = &.{"CoreGraphics.h"} }, + .{ .tag = .all, .name = "CoreText", .headers = &.{"CoreText.h"} }, + .{ .tag = .all, .name = "CoreVideo", .headers = &.{ "CoreVideo.h", "CVPixelBuffer.h" } }, + .{ .tag = .all, .name = "QuartzCore", .headers = &.{"CALayer.h"} }, + .{ .tag = .all, .name = "IOSurface", .headers = &.{"IOSurfaceRef.h"} }, + .{ .tag = .macos, .name = "Carbon", .headers = &.{"Carbon.h"} }, +}; + +const extra_headers = [_][]const u8{ + "dispatch/dispatch.h", + "os/log.h", + "os/signpost.h", +}; + +const framework_header_fmt = "#include <{s}/{s}>\n"; +const extra_header_fmt = "#include <{s}>\n"; + +fn cSourceLen(tag: Framework.Tag) usize { + var len: usize = 0; + for (frameworks) |framework| { + if (tag != .macos and framework.tag == .macos) continue; + for (framework.headers) |h| len += std.fmt.count(framework_header_fmt, .{ framework.name, h }); + } + for (extra_headers) |h| len += std.fmt.count(extra_header_fmt, .{h}); + return len; +} + +fn genCSource(comptime tag: Framework.Tag) [cSourceLen(tag):0]u8 { + const len = cSourceLen(tag); + var buf: [len:0]u8 = undefined; + var writer: std.Io.Writer = .fixed(&buf); + for (frameworks) |framework| { + if (tag != .macos and framework.tag == .macos) continue; + for (framework.headers) |h| writer.print(framework_header_fmt, .{ framework.name, h }) catch unreachable; + } + for (extra_headers) |h| writer.print(extra_header_fmt, .{h}) catch unreachable; + buf[len] = 0; + return buf; +} + +const c_source_macos = genCSource(.macos); +const c_source_other = genCSource(.all); + +fn linkFrameworks(tag: Framework.Tag, module: *std.Build.Module) !void { + for (frameworks) |framework| { + if (tag != .macos and framework.tag == .macos) continue; + module.linkFramework(framework.name, .{}); + } +} + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -12,6 +72,29 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); + translate: { + const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; + const macos_c: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add( + "macos_c.h", + if (target.result.os.tag == .macos) &c_source_macos else &c_source_other, + ), + .target = target, + .optimize = optimize, + .libc_file = if (target.result.os.tag.isDarwin()) libc_file: { + switch (try apple_sdk.pathsForTarget(b, target.result)) { + inline else => |paths| break :libc_file paths.libc, + } + } else null, + }); + + // Blocks need to be enabled to use MacOS headers + macos_c.run.addArg("-fblocks"); + + module.addImport("macos_c", macos_c.mod); + } + const lib = b.addLibrary(.{ .name = "macos", .root_module = b.createModule(.{ @@ -21,34 +104,18 @@ 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"); - if (target.result.os.tag == .macos) { - lib.linkFramework("Carbon"); - module.linkFramework("Carbon", .{}); - } - if (target.result.os.tag.isDarwin()) { - module.linkFramework("CoreFoundation", .{}); - module.linkFramework("CoreGraphics", .{}); - module.linkFramework("CoreText", .{}); - module.linkFramework("CoreVideo", .{}); - module.linkFramework("QuartzCore", .{}); - module.linkFramework("IOSurface", .{}); - - try apple_sdk.addPaths(b, lib); + inline for (.{ lib.root_module, module }) |mod| { + try linkFrameworks(if (target.result.os.tag == .macos) .macos else .all, mod); } + try apple_sdk.addPaths(b, lib); b.installArtifact(lib); { @@ -63,7 +130,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/macos/build.zig.zon b/pkg/macos/build.zig.zon index 4c22ad7c1..58b008c33 100644 --- a/pkg/macos/build.zig.zon +++ b/pkg/macos/build.zig.zon @@ -5,5 +5,10 @@ .paths = .{""}, .dependencies = .{ .apple_sdk = .{ .path = "../apple-sdk" }, + .translate_c = .{ + .url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + .lazy = true, + }, }, } diff --git a/pkg/macos/main.zig b/pkg/macos/main.zig index e4f4b9504..68d615672 100644 --- a/pkg/macos/main.zig +++ b/pkg/macos/main.zig @@ -10,25 +10,7 @@ pub const text = @import("text.zig"); pub const video = @import("video.zig"); pub const iosurface = @import("iosurface.zig"); -// All of our C imports consolidated into one place. We used to -// import them one by one in each package but Zig 0.14 has some -// kind of issue with that I wasn't able to minimize. -pub const c = @cImport({ - @cInclude("CoreFoundation/CoreFoundation.h"); - @cInclude("CoreGraphics/CoreGraphics.h"); - @cInclude("CoreText/CoreText.h"); - @cInclude("CoreVideo/CoreVideo.h"); - @cInclude("CoreVideo/CVPixelBuffer.h"); - @cInclude("QuartzCore/CALayer.h"); - @cInclude("IOSurface/IOSurfaceRef.h"); - @cInclude("dispatch/dispatch.h"); - @cInclude("os/log.h"); - @cInclude("os/signpost.h"); - - if (builtin.os.tag == .macos) { - @cInclude("Carbon/Carbon.h"); - } -}); +pub const c = @import("macos_c"); test { @import("std").testing.refAllDecls(@This()); 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..a92fa3a07 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"); @@ -70,11 +65,15 @@ pub fn build(b: *std.Build) !void { try flags.append(b.allocator, "-std=c++17"); } - if (target.result.os.tag == .freebsd or target.result.abi == .musl) { + if (target.result.os.tag == .freebsd or + target.result.abi == .musl or + target.result.abi == .android or + target.result.abi == .androideabi) + { 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..b90c7e9fb 100644 --- a/pkg/wuffs/build.zig +++ b/pkg/wuffs/build.zig @@ -1,5 +1,38 @@ const std = @import("std"); +// 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(.{}); const optimize = b.standardOptimizeOption(.{}); @@ -15,25 +48,41 @@ 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, + translate: { + const translate_c = b.lazyImport(@This(), "translate_c") orelse break :translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :translate; + const wuffs_c: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("wuffs_c.h", &wuffs_c_source), + .target = target, + .optimize = optimize, + .libc_file = if (target.result.os.tag.isDarwin()) libc_file: { + switch (try @import("apple_sdk").pathsForTarget(b, target.result)) { + inline else => |paths| break :libc_file paths.libc, + } + } else null, }); + + 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..1cf438e8e 100644 --- a/pkg/wuffs/build.zig.zon +++ b/pkg/wuffs/build.zig.zon @@ -3,6 +3,12 @@ .version = "0.0.0", .fingerprint = 0x67c0c059de921c4f, .dependencies = .{ + .translate_c = .{ + .lazy = true, + .url = "https://deps.files.ghostty.org/translate_c-a916baf225a3cfc93a982225462db612604ae290.tar.gz", + .hash = "translate_c-0.0.0-Q_BUWj8_BwCr4na219OimiKKj4IVSq0aKtMXCQmIk4AB", + }, + // 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/main.zig b/pkg/wuffs/src/main.zig index 207d83f9a..b4519a983 100644 --- a/pkg/wuffs/src/main.zig +++ b/pkg/wuffs/src/main.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); pub const png = @import("png.zig"); @@ -16,5 +17,19 @@ pub const ImageData = struct { }; test { - std.testing.refAllDeclsRecursive(@This()); + refAllDeclsRecursive(@This()); +} + +/// Copied from 0.15.2 stdlib (MIT license). +fn refAllDeclsRecursive(comptime T: type) void { + if (!builtin.is_test) return; + inline for (comptime std.meta.declarations(T)) |decl| { + if (@TypeOf(@field(T, decl.name)) == type) { + switch (@typeInfo(@field(T, decl.name))) { + .@"struct", .@"enum", .@"union", .@"opaque" => refAllDeclsRecursive(@field(T, decl.name)), + else => {}, + } + } + _ = &@field(T, decl.name); + } } 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..c87867ea9 100644 --- a/src/App.zig +++ b/src/App.zig @@ -15,6 +15,7 @@ const Config = configpkg.Config; const BlockingQueue = @import("datastruct/main.zig").BlockingQueue; const renderer = @import("renderer.zig"); const font = @import("font/main.zig"); +const global = @import("global.zig"); const log = std.log.scoped(.app); @@ -56,7 +57,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 +96,7 @@ pub fn init( self.* = .{ .alloc = alloc, - .surfaces = .{}, + .surfaces = .empty, .mailbox = .{}, .font_grid_set = font_grid_set, .config_conditional_state = .{}, @@ -236,7 +237,7 @@ pub fn needsConfirmQuit(self: *const App) bool { /// Drain the mailbox. fn drainMailbox(self: *App, rt_app: *apprt.App) !void { - while (self.mailbox.pop()) |message| { + while (self.mailbox.pop(global.io())) |message| { if (comptime std.log.logEnabled(.debug, .app)) { switch (message) { // these tend to be way too verbose for normal debugging @@ -566,7 +567,7 @@ pub const Mailbox = struct { /// Send a message to the surface. pub fn push(self: Mailbox, msg: Message, timeout: Queue.Timeout) Queue.Size { - const result = self.mailbox.push(msg, timeout); + const result = self.mailbox.push(global.io(), msg, timeout); // Wake up our app loop self.rt_app.wakeup(); diff --git a/src/Command.zig b/src/Command.zig index b81936257..3fc498a9a 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -14,12 +14,15 @@ //! * posix_spawn is used for Mac, but doesn't support the necessary //! features for tty setup. //! +//! +//! TODO: This may have changed a lot now with the new I/O implementations in +//! >= 0.16.0, so this might warrant a recheck. const Command = @This(); const std = @import("std"); const builtin = @import("builtin"); const configpkg = @import("config.zig"); -const global_state = &@import("global.zig").state; +const global = @import("global.zig"); const internal_os = @import("os/main.zig"); const windows = internal_os.windows; const TempDir = internal_os.TempDir; @@ -29,8 +32,8 @@ const posix = std.posix; 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. @@ -98,7 +101,7 @@ rt_post_fork_info: RtPostForkInfo, /// If set, then the process will be created attached to this pseudo console. /// `stdin`, `stdout`, and `stderr` will be ignored if set. -pseudo_console: if (builtin.os.tag == .windows) ?windows.exp.HPCON else void = +pseudo_console: if (builtin.os.tag == .windows) ?windows.HPCON else void = if (builtin.os.tag == .windows) null else {}, /// User data that is sent to the callback. Set with setData and getData @@ -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 fork(); if (pid != 0) { // Parent, return immediately. @@ -205,37 +208,68 @@ fn startPosix(self: *Command, arena: Allocator) !void { if (self.stderr) |f| setupFd(f.handle, posix.STDERR_FILENO) catch return error.ExecFailedInChild; - // Setup our working directory - if (self.cwd) |cwd| posix.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 - // crash the entire process if this fails so we ignore it. - // We don't log because that'll show up in the output. - }; + // Setup our working directory. + // + // NOTE: 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 crash the entire process if this fails so we + // ignore it. We don't log because that'll show up in the output. + if (self.cwd) |cwd| _ = posix.system.chdir(cwd); // Restore any rlimits that were set by Ghostty. This might fail but // any failures are ignored (its best effort). - global_state.rlimits.restore(); + global.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: posix.E = execve: { + // This functionality has been taken from Zig stdlib, a simplified + // version of the exec bits with PATH search so that we can just + // offload to execve below. + const file_slice = std.mem.sliceTo(self.path, 0); + if (std.mem.findScalar(u8, file_slice, '/') != null) { + break :execve posix.errno(posix.system.execve(self.path, argsZ, envp)); + } + + var path_expanded_buf: [std.fs.max_path_bytes]u8 = undefined; + const PATH = global.environ().getPosix("PATH") orelse "/usr/local/bin:/bin/:/usr/bin"; + var it = std.mem.tokenizeScalar(u8, PATH, ':'); + var err: posix.system.E = .NOENT; + var seen_eacces = false; + + while (it.next()) |search_path| { + const path_len = search_path.len + file_slice.len + 1; + if (path_expanded_buf.len < path_len + 1) break :execve .NAMETOOLONG; + @memcpy(path_expanded_buf[0..search_path.len], search_path); + path_expanded_buf[search_path.len] = '/'; + @memcpy(path_expanded_buf[search_path.len + 1 ..][0..file_slice.len], file_slice); + path_expanded_buf[path_len] = 0; + const full_path = path_expanded_buf[0..path_len :0].ptr; + // Replace here, switch on error (any error means that replace + // failed, but we might need to retry). + err = posix.errno(posix.system.execve(full_path, argsZ, envp)); + switch (err) { + .ACCES => seen_eacces = true, + .NOENT, .NOTDIR => {}, + else => break :execve err, + } + } + + if (seen_eacces) break :execve .ACCES; + break :execve err; + }; // 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(global.io(), &stderr_buf); const stderr = &stderr_writer.interface; switch (err) { - error.FileNotFound => stderr.print( + posix.system.E.NOENT => stderr.print( \\Requested executable not found. Please verify the command is on \\the PATH and try again. \\ @@ -243,11 +277,11 @@ fn startPosix(self: *Command, arena: Allocator) !void { .{}, ) catch {}, - else => stderr.print( - \\exec syscall failed with unexpected error: {} + else => |e| stderr.print( + \\exec syscall failed with unexpected error: E{s} \\ , - .{err}, + .{@tagName(e)}, ) catch {}, } stderr.flush() catch {}; @@ -257,6 +291,17 @@ fn startPosix(self: *Command, arena: Allocator) !void { return error.ExecFailedInChild; } +/// Wrapper for the raw fork syscall. This preserves the error handling from +/// the std.posix wrapper that was removed in Zig 0.16. +fn fork() !posix.pid_t { + const rc = posix.system.fork(); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .AGAIN, .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } +} + fn startWindows(self: *Command, arena: Allocator) !void { const cwd_w = if (self.cwd) |cwd| try std.unicode.utf8ToUtf16LeAllocZ(arena, cwd) else null; @@ -275,15 +320,37 @@ fn startWindows(self: *Command, arena: Allocator) !void { const env_w = if (self.env) |env_map| try createWindowsEnvBlock(arena, env_map) else null; const any_null_fd = self.stdin == null or self.stdout == null or self.stderr == null; - const null_fd = if (any_null_fd) try windows.OpenFile( - &[_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, - .{ - .access_mask = windows.GENERIC_READ | windows.SYNCHRONIZE, - .share_access = windows.FILE_SHARE_READ, - .creation = windows.OPEN_EXISTING, - }, - ) else null; - defer if (null_fd) |fd| posix.close(fd); + const null_fd = if (any_null_fd) null_fd: { + // path = "\Device\Null" + const path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; + var path_unicode_string: windows.UNICODE_STRING = .init(&path); + var attrs: windows.OBJECT_ATTRIBUTES = .{ .ObjectName = &path_unicode_string }; + + var fd: windows.HANDLE = undefined; + var io_status: windows.IO_STATUS_BLOCK = undefined; // unused + const result = windows.exp.ntdll.NtCreateFile( + &fd, + .{ .GENERIC = .{ .READ = true }, .STANDARD = .{ .SYNCHRONIZE = true } }, + &attrs, + &io_status, + null, + windows.FILE_ATTRIBUTE_NORMAL, + windows.FILE_SHARE_READ, + windows.OPEN_EXISTING, + windows.FILE_NON_DIRECTORY_FILE, + null, + 0, + ); + + if (result != .SUCCESS) { + return windows.unexpectedStatus(result); + } + + break :null_fd fd; + } else null; + defer { + if (null_fd) |fd| _ = windows.exp.kernel32.CloseHandle(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, @@ -304,17 +371,17 @@ fn startWindows(self: *Command, arena: Allocator) !void { 1, 0, &attribute_list_size, - ) == 0) return windows.unexpectedError(windows.kernel32.GetLastError()); + ) == windows.FALSE) return windows.unexpectedError(windows.GetLastError()); if (windows.exp.kernel32.UpdateProcThreadAttribute( attribute_list_buf.ptr, 0, - windows.exp.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, + windows.PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pseudo_console, - @sizeOf(windows.exp.HPCON), + @sizeOf(windows.HPCON), null, null, - ) == 0) return windows.unexpectedError(windows.kernel32.GetLastError()); + ) == windows.FALSE) return windows.unexpectedError(windows.GetLastError()); break :b .{ attribute_list_buf.ptr, null, null, null }; } else b: { @@ -324,9 +391,9 @@ fn startWindows(self: *Command, arena: Allocator) !void { break :b .{ null, stdin, stdout, stderr }; }; - var startup_info_ex = windows.exp.STARTUPINFOEX{ + var startup_info_ex = windows.STARTUPINFOEX{ .StartupInfo = .{ - .cb = if (attribute_list != null) @sizeOf(windows.exp.STARTUPINFOEX) else @sizeOf(windows.STARTUPINFOW), + .cb = if (attribute_list != null) @sizeOf(windows.STARTUPINFOEX) else @sizeOf(windows.STARTUPINFOW), .hStdError = stderr, .hStdOutput = stdout, .hStdInput = stdin, @@ -348,8 +415,8 @@ fn startWindows(self: *Command, arena: Allocator) !void { .lpAttributeList = attribute_list, }; - var flags: windows.DWORD = windows.exp.CREATE_UNICODE_ENVIRONMENT; - if (attribute_list != null) flags |= windows.exp.EXTENDED_STARTUPINFO_PRESENT; + var flags: windows.DWORD = windows.CREATE_UNICODE_ENVIRONMENT; + if (attribute_list != null) flags |= windows.EXTENDED_STARTUPINFO_PRESENT; var process_information: windows.PROCESS_INFORMATION = undefined; if (windows.exp.kernel32.CreateProcessW( @@ -363,21 +430,18 @@ fn startWindows(self: *Command, arena: Allocator) !void { if (cwd_w) |w| w.ptr else null, @ptrCast(&startup_info_ex.StartupInfo), &process_information, - ) == 0) return windows.unexpectedError(windows.kernel32.GetLastError()); + ) == windows.FALSE) return windows.unexpectedError(windows.GetLastError()); self.pid = process_information.hProcess; } fn setupFd(src: File.Handle, target: i32) !void { - switch (builtin.os.tag) { - .linux => { - // We use dup3 so that we can clear CLO_ON_EXEC. We do NOT want this - // file descriptor to be closed on exec since we're exactly exec-ing after - // this. + const PosixCall = struct { + fn f(func: anytype, args: anytype) !usize { while (true) { - const rc = linux.dup3(src, target, 0); + const rc = @call(.auto, func, args); switch (posix.errno(rc)) { - .SUCCESS => break, + .SUCCESS => return @intCast(rc), .INTR => continue, .AGAIN, .ACCES => return error.Locked, .BADF => unreachable, @@ -391,16 +455,28 @@ fn setupFd(src: File.Handle, target: i32) !void { else => |err| return posix.unexpectedErrno(err), } } + } + }; + + switch (builtin.os.tag) { + .linux => { + // We use dup3 so that we can clear CLO_ON_EXEC. We do NOT want this + // file descriptor to be closed on exec since we're exactly exec-ing after + // this. + _ = try PosixCall.f(linux.dup3, .{ src, target, 0 }); }, .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 PosixCall.f(posix.system.fcntl, .{ src, posix.F.GETFD }); if (flags & posix.FD_CLOEXEC != 0) { - _ = try posix.fcntl(src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC)); + _ = try PosixCall.f( + posix.system.fcntl, + .{ src, posix.F.SETFD, flags & ~@as(u32, posix.FD_CLOEXEC) }, + ); } - try posix.dup2(src, target); + _ = try PosixCall.f(posix.system.dup2, .{ src, target }); }, else => @compileError("unsupported platform"), } @@ -411,21 +487,29 @@ pub fn wait(self: Command, block: bool) !Exit { if (comptime builtin.os.tag == .windows) { // Block until the process exits. This returns immediately if the // process already exited. - const result = windows.kernel32.WaitForSingleObject(self.pid.?, windows.INFINITE); + // + // NOTE: We can use the pid directly as posix.system.pid_t is still an + // alias for a handle under Windows. We might want to keep an eye on if + // this changes, though. + const result = windows.exp.kernel32.WaitForSingleObject(self.pid.?, windows.INFINITE); if (result == windows.WAIT_FAILED) { - return windows.unexpectedError(windows.kernel32.GetLastError()); + return windows.unexpectedError(windows.GetLastError()); } var exit_code: windows.DWORD = undefined; - const has_code = windows.kernel32.GetExitCodeProcess(self.pid.?, &exit_code) != 0; + const has_code = windows.exp.kernel32.GetExitCodeProcess(self.pid.?, &exit_code) != windows.FALSE; if (!has_code) { - return windows.unexpectedError(windows.kernel32.GetLastError()); + return windows.unexpectedError(windows.GetLastError()); } return .{ .Exited = exit_code }; } - const res = if (block) posix.waitpid(self.pid.?, 0) else res: { + const status: u32 = if (block) wait_block: { + var status: if (builtin.link_libc) c_int else u32 = undefined; + _ = try waitPid(self.pid.?, &status, 0); + break :wait_block @bitCast(status); + } else wait_nohang: { // 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,12 +518,32 @@ 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); - if (res.pid != 0) break :res res; + var status: if (builtin.link_libc) c_int else u32 = undefined; + const pid = try waitPid(self.pid.?, &status, posix.system.W.NOHANG); + if (pid != 0) break :wait_nohang @bitCast(status); } }; - return .init(res.status); + return .init(status); +} + +/// Wrapper for the raw waitpid syscall. Status is only initialized on success; +/// interrupted waits are retried and all other errors are propagated. +fn waitPid( + pid: posix.pid_t, + status: *if (builtin.link_libc) c_int else u32, + flags: u32, +) !posix.pid_t { + while (true) { + const rc = posix.system.waitpid(pid, status, @intCast(flags)); + switch (posix.errno(rc)) { + .SUCCESS => return @intCast(rc), + .INTR => continue, + .CHILD => return error.NoChildProcess, + .INVAL => return error.InvalidWaitOptions, + else => |err| return posix.unexpectedErrno(err), + } + } } /// Sets command->data to data. @@ -587,7 +691,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 +742,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,27 +801,31 @@ 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( + if (windows.exp.kernel32.SetHandleInformation( file.handle, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT, - ); + ) == windows.FALSE) { + return windows.unexpectedError(windows.GetLastError()); + } } 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( + if (windows.exp.kernel32.SetHandleInformation( file.handle, windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT, - ); + ) == windows.FALSE) { + return windows.unexpectedError(windows.GetLastError()); + } } return file; @@ -726,8 +834,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 +864,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 +878,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 +914,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 +933,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 +965,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 +995,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 +1027,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 2c4edc497..32e91bfa7 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -20,7 +20,7 @@ const builtin = @import("builtin"); const assert = @import("quirks.zig").inlineAssert; const Allocator = std.mem.Allocator; const ArenaAllocator = std.heap.ArenaAllocator; -const global_state = &@import("global.zig").state; +const global = @import("global.zig"); const oni = @import("oniguruma"); const crash = @import("crash/main.zig"); const unicode = @import("unicode/main.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 @@ -559,8 +559,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 @@ -581,7 +581,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 = global.io() }; + const rng = rng_impl.interface(); + break :candidate rng.int(u64); + }; if (candidate == 0) continue; break :id candidate; } @@ -631,13 +635,12 @@ pub fn init( var env = rt_surface.defaultTermioEnv() catch |err| env: { // 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); + break :env global.environMap() catch 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( @@ -654,7 +657,7 @@ pub fn init( .shell_integration_features = config.@"shell-integration-features", .cursor_blink = config.@"cursor-style-blink", .working_directory = if (config.@"working-directory") |wd| wd.value() else null, - .resources_dir = global_state.resources_dir.host(), + .resources_dir = global.resourcesDir().host(), .term = config.term, .rt_pre_exec_info = .init(config), .rt_post_fork_info = .init(config), @@ -717,7 +720,7 @@ pub fn init( rendererpkg.Thread.threadMain, .{&self.renderer_thread}, ); - self.renderer_thr.setName("renderer") catch {}; + self.renderer_thr.setName(global.io(), "renderer") catch {}; // Start our IO thread self.io_thr = try std.Thread.spawn( @@ -725,7 +728,7 @@ pub fn init( termio.Thread.threadMain, .{ &self.io_thread, &self.io }, ); - self.io_thr.setName("io") catch {}; + self.io_thr.setName(global.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, @@ -900,14 +903,14 @@ 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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); assert(self.renderer_state.inspector == null); self.renderer_state.inspector = self.inspector; } // Notify our components we have an inspector active - _ = self.renderer_thread.mailbox.push(.{ .inspector = true }, .{ .forever = {} }); + _ = self.renderer_thread.mailbox.push(global.io(), .{ .inspector = true }, .{ .forever = {} }); self.queueIo(.{ .inspector = true }, .unlocked); } @@ -917,14 +920,14 @@ 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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); assert(self.renderer_state.inspector != null); self.renderer_state.inspector = null; } // Notify our components we have deactivated inspector - _ = self.renderer_thread.mailbox.push(.{ .inspector = false }, .{ .forever = {} }); + _ = self.renderer_thread.mailbox.push(global.io(), .{ .inspector = false }, .{ .forever = {} }); self.queueIo(.{ .inspector = false }, .unlocked); // Deinit the inspector @@ -948,8 +951,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); break :true !self.io.terminal.cursorIsAtPrompt(); }, }; @@ -1096,9 +1099,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(global.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( @@ -1126,15 +1129,22 @@ pub fn handleMessage(self: *Surface, msg: Message) !void { }, .start_command => { - self.command_timer = try .now(); + self.command_timer = .now(global.io(), .awake); }, .stop_command => |v| timer: { - const end: std.time.Instant = try .now(); + const end: std.Io.Timestamp = .now(global.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( @@ -1185,8 +1195,8 @@ fn selectionScrollTick(self: *Surface) !void { const pos_vp = self.posToViewport(pos.x, pos.y); // We need our locked state for the remainder - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t: *terminal.Terminal = self.renderer_state.terminal; const selection = self.mouse.selection_gesture.autoscrollTick(t, .{ @@ -1276,8 +1286,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t: *terminal.Terminal = self.renderer_state.terminal; t.carriageReturn(); t.linefeed() catch break :terminal; @@ -1316,8 +1326,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t: *terminal.Terminal = self.renderer_state.terminal; // No matter what move the cursor back to the column 0. @@ -1383,8 +1393,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // If our password input state is unchanged then we don't // waste time doing anything more. @@ -1436,6 +1446,7 @@ fn searchCallback_( for (matches) |*m| m.* = try m.clone(alloc); _ = self.renderer_thread.mailbox.push( + global.io(), .{ .search_viewport_matches = .{ .arena = arena, .matches = matches, @@ -1454,6 +1465,7 @@ fn searchCallback_( const match = try sel.highlight.clone(alloc); _ = self.renderer_thread.mailbox.push( + global.io(), .{ .search_selected_match = .{ .arena = arena, .match = match, @@ -1469,6 +1481,7 @@ fn searchCallback_( } else { // Reset our selected match _ = self.renderer_thread.mailbox.push( + global.io(), .{ .search_selected_match = null }, .forever, ); @@ -1493,10 +1506,12 @@ fn searchCallback_( // When we quit, tell our renderer to reset any search state. .quit => { _ = self.renderer_thread.mailbox.push( + global.io(), .{ .search_selected_match = null }, .forever, ); _ = self.renderer_thread.mailbox.push( + global.io(), .{ .search_viewport_matches = .{ .arena = .init(self.alloc), .matches = &.{}, @@ -1539,8 +1554,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); self.renderer_state.mouse.mods = self.mouseModsWithCapture(self.mouse.mods); // We use the clear screen dirty flag to force a rebuild of all @@ -1786,7 +1801,7 @@ pub fn updateConfig( termio_config_ptr.* = try termio.Termio.DerivedConfig.init(self.alloc, config); errdefer termio_config_ptr.deinit(); - _ = self.renderer_thread.mailbox.push(renderer_message, .{ .forever = {} }); + _ = self.renderer_thread.mailbox.push(global.io(), renderer_message, .{ .forever = {} }); self.queueIo(.{ .change_config = .{ .alloc = self.alloc, @@ -1907,8 +1922,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); return try self.dumpTextLocked(alloc, sel); } @@ -2033,15 +2048,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(global.io()); + defer self.renderer_state.mutex.unlock(global.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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const sel = self.io.terminal.screens.active.selection orelse return null; return try self.io.terminal.screens.active.selectionString(alloc, .{ .sel = sel, @@ -2056,8 +2071,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const terminal_pwd = self.io.terminal.getPwd() orelse return null; return try alloc.dupe(u8, terminal_pwd); } @@ -2074,7 +2089,7 @@ fn resolvePathForOpening( const resolved = try std.fs.path.resolve(self.alloc, &.{ terminal_pwd, path }); - std.fs.accessAbsolute(resolved, .{}) catch { + std.Io.Dir.accessAbsolute(global.io(), resolved, .{}) catch { self.alloc.free(resolved); return null; }; @@ -2088,10 +2103,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(global.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(global.io()); // TODO: need to handle when scrolling and the cursor is not // in the visible portion of the screen. @@ -2440,7 +2455,7 @@ pub fn setFontSize(self: *Surface, size: font.face.DesiredSize) !void { // Notify our render thread of the new font stack. The renderer // MUST accept the new font grid and deref the old. - _ = self.renderer_thread.mailbox.push(.{ + _ = self.renderer_thread.mailbox.push(global.io(), .{ .font_grid = .{ .grid = font_grid, .set = &self.app.font_grid_set, @@ -2530,8 +2545,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // We clear our selection when ANY OF: // 1. We have an existing preedit @@ -2566,7 +2581,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); @@ -2697,8 +2712,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); if (self.io.terminal.modes.get(.disable_keyboard)) return .consumed; } @@ -2728,8 +2743,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); self.mouseRefreshLinks( pos, self.posToViewport(pos.x, pos.y), @@ -2821,8 +2836,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); if (self.config.selection_clear_on_typing or event.key == .escape) @@ -3253,8 +3268,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t = &self.io.terminal; var opts: input.key_encode.Options = .fromTerminal(t); @@ -3295,7 +3310,7 @@ pub fn occlusionCallback(self: *Surface, visible: bool) !void { crash.sentry.thread_state = self.crashThreadState(); defer crash.sentry.thread_state = null; - _ = self.renderer_thread.mailbox.push(.{ + _ = self.renderer_thread.mailbox.push(global.io(), .{ .visible = visible, }, .{ .forever = {} }); try self.queueRender(); @@ -3315,7 +3330,7 @@ pub fn focusCallback(self: *Surface, focused: bool) !void { self.focused = focused; // Notify our render thread of the new state - _ = self.renderer_thread.mailbox.push(.{ + _ = self.renderer_thread.mailbox.push(global.io(), .{ .focus = focused, }, .{ .forever = {} }); @@ -3382,9 +3397,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(global.io()); self.io.terminal.flags.focused = focused; - self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.unlock(global.io()); self.queueIo(.{ .focused = focused }, .unlocked); } } @@ -3516,8 +3531,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(global.io()); + defer self.renderer_state.mutex.unlock(global.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 @@ -3717,8 +3732,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(global.io()); + defer if (lock) self.renderer_state.mutex.unlock(global.io()); // If the terminal explicitly requests it then we always allow it // since we processed never/always at this point. @@ -3739,8 +3754,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); return self.io.terminal.flags.mouse_event != .none; } @@ -3796,21 +3811,12 @@ 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 click_time = self.mouse.selection_gesture.left_click_time orelse - break :extend_selection; - const since = now.since(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 click_time = self.mouse.selection_gesture.left_click_time orelse + break :extend_selection; + const since = click_time.untilNow(global.io(), .awake); + if (since.toNanoseconds() <= self.config.mouse_interval) { + // Click interval very short, we may be increasing + // click counts so we don't extend the selection. break :extend_selection; } @@ -3821,8 +3827,8 @@ pub fn mouseButtonCallback( } if (button == .left and action == .release) { - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // The selection gesture tracks whether a press became a drag by // comparing the release cell to the original press cell. Resolve the @@ -3897,8 +3903,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(global.io()); + defer self.renderer_state.mutex.unlock(global.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. @@ -3937,8 +3943,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t: *terminal.Terminal = self.renderer_state.terminal; const screen: *terminal.Screen = self.renderer_state.terminal.screens.active; @@ -3962,12 +3968,8 @@ pub fn mouseButtonCallback( break :pin pin; }; - const time = std.time.Instant.now() catch |err| time: { - log.err("error reading time, mouse multi-click won't work err={}", .{err}); - break :time null; - }; var press_selection = try self.mouse.selection_gesture.press(t, .{ - .time = time, + .time = std.Io.Timestamp.now(global.io(), .awake), .pin = pin, .xpos = pos.x, .ypos = pos.y, @@ -4047,8 +4049,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // Get our viewport pin const screen: *terminal.Screen = self.renderer_state.terminal.screens.active; @@ -4119,8 +4121,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(global.io()); + defer self.renderer_state.mutex.lockUncancelable(global.io()); _ = try self.startClipboardRequest(.standard, .paste); // We don't need to clear selection because we didn't have @@ -4134,8 +4136,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(global.io()); + defer self.renderer_state.mutex.lockUncancelable(global.io()); _ = try self.startClipboardRequest(.standard, .paste); }, } @@ -4428,7 +4430,6 @@ fn openUrl( // apprts to handle this themselves. log.warn("apprt did not handle open URL action, falling back to default opener", .{}); try internal_os.open( - self.alloc, action.kind, action.url, ); @@ -4474,8 +4475,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const sel = self.mouse.selection_gesture.deepPress( self.renderer_state.terminal, @@ -4540,8 +4541,8 @@ pub fn cursorPosCallback( try self.queueRender(); } - self.renderer_state.mutex.lock(); - defer self.renderer_state.mutex.unlock(); + self.renderer_state.mutex.lockUncancelable(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // No mouse point so we don't highlight links self.renderer_state.mouse.point = null; @@ -4567,8 +4568,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // Update our mouse state. We set this to null initially because we only // want to set it when we're not selecting or doing any other mouse @@ -4824,8 +4825,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); self.scrollToBottom() catch |err| { log.warn("error scrolling to bottom err={}", .{err}); }; @@ -4851,8 +4852,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); self.scrollToBottom() catch |err| { log.warn("error scrolling to bottom err={}", .{err}); }; @@ -4864,8 +4865,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); // With the lock held, we must scroll to the bottom. // We always scroll to the bottom for these inputs. @@ -4884,8 +4885,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); self.renderer_state.terminal.fullReset(); }, @@ -4955,7 +4956,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(global.io(), "search") catch {}; break :init s; }; @@ -4968,6 +4969,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } _ = s.state.mailbox.push( + global.io(), .{ .change_needle = try .init( self.alloc, text, @@ -4980,6 +4982,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .navigate_search => |nav| { const s: *Search = if (self.search) |*s| s else return false; _ = s.state.mailbox.push( + global.io(), .{ .select = switch (nav) { .next => .next, .previous => .prev, @@ -4990,8 +4993,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); if (self.io.terminal.screens.active.selection) |sel| { try self.copySelectionToClipboards( @@ -5022,8 +5025,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); if (try self.linkAtPos(pos)) |link_info| { const url_text = switch (link_info.action) { .open => url_text: { @@ -5168,8 +5171,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); if (self.io.terminal.screens.active_key == .alternate) return false; } @@ -5192,8 +5195,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const t: *terminal.Terminal = self.renderer_state.terminal; t.screens.active.scroll(.{ .row = n }); } @@ -5203,8 +5206,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(global.io()); + defer self.renderer_state.mutex.unlock(global.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 }); @@ -5442,8 +5445,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const sel = self.io.terminal.screens.active.selectAll(); if (sel) |s| { @@ -5564,7 +5567,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool .main => @panic("crash binding action, crashing intentionally"), .render => { - _ = self.renderer_thread.mailbox.push(.{ .crash = {} }, .{ .forever = {} }); + _ = self.renderer_thread.mailbox.push(global.io(), .{ .crash = {} }, .{ .forever = {} }); self.queueRender() catch |err| { // Not a big deal if this fails. log.warn("failed to notify renderer of crash message err={}", .{err}); @@ -5575,8 +5578,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const screen: *terminal.Screen = self.io.terminal.screens.active; const sel = if (screen.selection) |*sel| sel else { @@ -5672,23 +5675,24 @@ fn writeScreenFile( // Open our scrollback file var file = try tmp_dir.dir.createFile( + global.io(), filename, switch (builtin.os.tag) { .windows => .{}, - else => .{ .mode = 0o600 }, + else => .{ .permissions = .fromMode(0o600) }, }, ); - defer file.close(); + defer file.close(global.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(global.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(global.io()); + defer self.renderer_state.mutex.unlock(global.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 @@ -5751,7 +5755,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( + global.io(), + filename, + &path_buf, + )]; switch (write_screen.action) { .copy => { @@ -5849,8 +5857,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(global.io()); + defer self.renderer_state.mutex.unlock(global.io()); const opts: input.paste.Options = .fromTerminal(&self.io.terminal); // If we have paste protection enabled, we detect unsafe pastes and return @@ -5970,12 +5978,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(global.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; } @@ -5992,7 +6000,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 4728d73ce..23bff13d7 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -8,6 +8,7 @@ const renderer = @import("../renderer.zig"); const terminal = @import("../terminal/main.zig"); const CoreSurface = @import("../Surface.zig"); const lib = @import("../lib/main.zig"); +const compat_testing = @import("../lib/compat/testing.zig"); /// The target for an action. This is generally the thing that had focus /// while the action was made but the concept of "focus" is not guaranteed @@ -424,8 +425,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)); @@ -434,19 +438,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 @@ -713,7 +710,7 @@ pub const SetTitle = struct { pub fn format( value: @This(), comptime _: []const u8, - _: std.fmt.FormatOptions, + _: std.fmt.Options, writer: *std.Io.Writer, ) !void { try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.title }); @@ -737,7 +734,7 @@ pub const Pwd = struct { pub fn format( value: @This(), comptime _: []const u8, - _: std.fmt.FormatOptions, + _: std.fmt.Options, writer: *std.Io.Writer, ) !void { try writer.print("{s}{{ {s} }}", .{ @typeName(@This()), value.pwd }); @@ -765,7 +762,7 @@ pub const DesktopNotification = struct { pub fn format( value: @This(), comptime _: []const u8, - _: std.fmt.FormatOptions, + _: std.fmt.Options, writer: *std.Io.Writer, ) !void { try writer.print("{s}{{ title: {s}, body: {s} }}", .{ @@ -1008,5 +1005,5 @@ pub const SearchSelected = struct { }; test { - _ = std.testing.refAllDeclsRecursive(@This()); + _ = compat_testing.refAllDeclsRecursive(@This()); } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 7310159cc..1f3b98e24 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -11,6 +11,7 @@ const Allocator = std.mem.Allocator; const objc = @import("objc"); const apprt = @import("../apprt.zig"); const font = @import("../font/main.zig"); +const global = @import("../global.zig"); const input = @import("../input.zig"); const internal_os = @import("../os/main.zig"); const renderer = @import("../renderer.zig"); @@ -333,7 +334,7 @@ pub const App = struct { _: apprt.ipc.Target, comptime action: apprt.ipc.Action.Key, _: apprt.ipc.Action.Value(action), - ) (Allocator.Error || std.posix.WriteError || apprt.ipc.Errors)!bool { + ) (Allocator.Error || apprt.ipc.Errors)!bool { switch (action) { .new_window => return false, .toggle_quick_terminal => return false, @@ -372,7 +373,7 @@ pub const Platform = union(PlatformTag) { /// Initialize a Platform a tag and configuration from the C ABI. pub fn init(tag_int: c_int, c_platform: C) !Platform { - const tag = try std.meta.intToEnum(PlatformTag, tag_int); + const tag = std.enums.fromInt(PlatformTag, tag_int) orelse return error.InvalidEnumTag; return switch (tag) { .macos => if (MacOS != void) macos: { const config = c_platform.macos; @@ -490,16 +491,16 @@ pub const Surface = struct { if (opts.working_directory) |c_wd| { const wd = std.mem.sliceTo(c_wd, 0); if (wd.len > 0) wd: { - var dir = std.fs.openDirAbsolute(wd, .{}) catch |err| { + var dir = std.Io.Dir.openDirAbsolute(global.io(), wd, .{}) catch |err| { log.warn( "error opening requested working directory dir={s} err={}", .{ wd, err }, ); break :wd; }; - defer dir.close(); + defer dir.close(global.io()); - const stat = dir.stat() catch |err| { + const stat = dir.stat(global.io()) catch |err| { log.warn( "failed to stat requested working directory dir={s} err={}", .{ wd, err }, @@ -950,32 +951,32 @@ pub const Surface = struct { }; } - pub fn defaultTermioEnv(self: *const Surface) !std.process.EnvMap { - const alloc = self.app.core_app.alloc; - var env = try internal_os.getEnvMap(alloc); + pub fn defaultTermioEnv(self: *const Surface) !std.process.Environ.Map { + _ = self; + var env = try global.environMap(); errdefer env.deinit(); if (comptime builtin.target.os.tag.isDarwin()) { if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) { - env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS"); - env.remove("__XPC_DYLD_LIBRARY_PATH"); - env.remove("DYLD_FRAMEWORK_PATH"); - env.remove("DYLD_INSERT_LIBRARIES"); - env.remove("DYLD_LIBRARY_PATH"); - env.remove("LD_LIBRARY_PATH"); - env.remove("SECURITYSESSIONID"); - env.remove("XPC_SERVICE_NAME"); + _ = env.orderedRemove("__XCODE_BUILT_PRODUCTS_DIR_PATHS"); + _ = env.orderedRemove("__XPC_DYLD_LIBRARY_PATH"); + _ = env.orderedRemove("DYLD_FRAMEWORK_PATH"); + _ = env.orderedRemove("DYLD_INSERT_LIBRARIES"); + _ = env.orderedRemove("DYLD_LIBRARY_PATH"); + _ = env.orderedRemove("LD_LIBRARY_PATH"); + _ = env.orderedRemove("SECURITYSESSIONID"); + _ = env.orderedRemove("XPC_SERVICE_NAME"); } // Remove this so that running `ghostty` within Ghostty works. - env.remove("GHOSTTY_MAC_LAUNCH_SOURCE"); + _ = env.orderedRemove("GHOSTTY_MAC_LAUNCH_SOURCE"); // If we were launched from the desktop then we want to // remove the LANGUAGE env var so that we don't inherit // our translation settings for Ghostty. If we aren't from // the desktop then we didn't set our LANGUAGE var so we // don't need to remove it. - if (internal_os.launchedFromDesktop()) env.remove("LANGUAGE"); + if (internal_os.launchedFromDesktop()) _ = env.orderedRemove("LANGUAGE"); } return env; @@ -1000,7 +1001,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, @@ -1229,9 +1230,9 @@ 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(global.io(), .awake); io.DeltaTime = if (self.instant) |prev| delta: { - const since_ns: f64 = @floatFromInt(now.since(prev)); + const since_ns: f64 = @floatFromInt(prev.durationTo(now).toNanoseconds()); 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); @@ -1242,8 +1243,6 @@ pub const Inspector = struct { // C API pub const CAPI = struct { - const global = &@import("../global.zig").state; - /// This is the same as Surface.KeyEvent but this is the raw C API version. const KeyEvent = extern struct { action: input.Action, @@ -1300,7 +1299,7 @@ pub const CAPI = struct { pub fn deinit(self: *Text) void { if (self.text) |ptr| { - global.alloc.free(ptr[0..self.text_len :0]); + global.alloc().free(ptr[0..self.text_len :0]); } } }; @@ -1410,12 +1409,12 @@ pub const CAPI = struct { opts: *const apprt.runtime.App.Options, config: *const Config, ) !*App { - const core_app = try CoreApp.create(global.alloc); + const core_app = try CoreApp.create(global.alloc()); errdefer core_app.destroy(); // Create our runtime app - var app = try global.alloc.create(App); - errdefer global.alloc.destroy(app); + var app = try global.alloc().create(App); + errdefer global.alloc().destroy(app); try app.init(core_app, config, opts.*); errdefer app.terminate(); @@ -1438,7 +1437,7 @@ pub const CAPI = struct { export fn ghostty_app_free(v: *App) void { const core_app = v.core_app; v.terminate(); - global.alloc.destroy(v); + global.alloc().destroy(v); core_app.destroy(); } @@ -1520,13 +1519,7 @@ pub const CAPI = struct { /// Update the color scheme of the app. export fn ghostty_app_set_color_scheme(v: *App, scheme_raw: c_int) void { - const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch { - log.warn( - "invalid color scheme to ghostty_surface_set_color_scheme value={}", - .{scheme_raw}, - ); - return; - }; + const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return; v.core_app.colorSchemeEvent(v, scheme) catch |err| { log.err("error setting color scheme err={}", .{err}); @@ -1612,8 +1605,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(global.io()); + defer core_surface.renderer_state.mutex.unlock(global.io()); // If we don't have a selection, do nothing. const core_sel = core_surface.io.terminal.screens.active.selection orelse return false; @@ -1632,8 +1625,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(global.io()); + defer surface.core_surface.renderer_state.mutex.unlock(global.io()); const core_sel = sel.core( surface.core_surface.renderer_state.terminal.screens.active, @@ -1651,7 +1644,7 @@ pub const CAPI = struct { // Get our text directly from the core surface. const text = core_surface.dumpTextLocked( - global.alloc, + global.alloc(), core_sel, ) catch |err| { log.warn("error reading text err={}", .{err}); @@ -1730,14 +1723,7 @@ pub const CAPI = struct { /// Update the color scheme of the surface. export fn ghostty_surface_set_color_scheme(surface: *Surface, scheme_raw: c_int) void { - const scheme = std.meta.intToEnum(apprt.ColorScheme, scheme_raw) catch { - log.warn( - "invalid color scheme to ghostty_surface_set_color_scheme value={}", - .{scheme_raw}, - ); - return; - }; - + const scheme = std.enums.fromInt(apprt.ColorScheme, scheme_raw) orelse return; surface.colorSchemeCallback(scheme); } @@ -1891,17 +1877,7 @@ pub const CAPI = struct { stage_raw: u32, pressure: f64, ) void { - const stage = std.meta.intToEnum( - input.MousePressureStage, - stage_raw, - ) catch { - log.warn( - "invalid mouse pressure stage value={}", - .{stage_raw}, - ); - return; - }; - + const stage = std.enums.fromInt(input.MousePressureStage, stage_raw) orelse return; surface.mousePressureCallback(stage, pressure); } @@ -2134,6 +2110,7 @@ pub const CAPI = struct { export fn ghostty_surface_set_display_id(ptr: *Surface, display_id: u32) void { const surface = &ptr.core_surface; _ = surface.renderer_thread.mailbox.push( + global.io(), .{ .macos_display_id = display_id }, .{ .forever = {} }, ); @@ -2157,8 +2134,8 @@ pub const CAPI = struct { // read the font face. It should not be deferred since // we're loading the primary face. const grid = ptr.core_surface.renderer.font_grid; - grid.lock.lockShared(); - defer grid.lock.unlockShared(); + grid.lock.lockSharedUncancelable(global.io()); + defer grid.lock.unlockShared(global.io()); const collection = &grid.resolver.collection; const face = collection.getFace(.{}) catch return null; @@ -2195,8 +2172,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(global.io()); + defer surface.renderer_state.mutex.unlock(global.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 a627086eb..8243d8e39 100644 --- a/src/apprt/gtk/class/application.zig +++ b/src/apprt/gtk/class/application.zig @@ -1,3 +1,4 @@ +const builtin = @import("builtin"); const std = @import("std"); const assert = @import("../../../quirks.zig").inlineAssert; const Allocator = std.mem.Allocator; @@ -10,16 +11,17 @@ const gtk = @import("gtk"); const build_config = @import("../../../build_config.zig"); const build_info = @import("../build/info.zig"); -const state = &@import("../../../global.zig").state; +const global = @import("../../../global.zig"); 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"); const systemd = @import("../../../os/systemd.zig"); const terminal = @import("../../../terminal/main.zig"); -const xev = @import("../../../global.zig").xev; +const xev = global.xev; const Binding = @import("../../../input.zig").Binding; const CoreConfig = configpkg.Config; const CoreSurface = @import("../../../Surface.zig"); @@ -45,6 +47,8 @@ const OpenURI = @import("../portal.zig").OpenURI; const log = std.log.scoped(.gtk_ghostty_application); +extern "c" fn setenv(name: ?[*]const u8, value: ?[*]const u8, overwrite: c_int) c_int; + /// Function used to funnel GLib/GObject/GTK log messages into Zig's logging /// system rather than just getting dumped directly to stderr. fn glibLogWriterFunction( @@ -271,15 +275,17 @@ pub const Application = extern struct { }; defer config.deinit(); + // Set the old language, const saved_language: ?[:0]const u8 = saved_language: { const old_language = old_language: { - const result = (internal_os.getenv(alloc, "LANG") catch break :old_language null) orelse break :old_language null; - defer result.deinit(alloc); - break :old_language alloc.dupeZ(u8, result.value) catch break :old_language null; + const lang = global.environ().getPosix("LANG") orelse break :old_language null; + break :old_language alloc.dupeSentinel(u8, @ptrCast(lang), 0) catch null; }; - - if (config.language) |language| _ = internal_os.setenv("LANG", language); - + if (config.language) |language| { + // Override LANG if we need to (sync global environs if so) + _ = setenv("LANG", @ptrCast(language), 1); + global.syncEnviron(); + } break :saved_language old_language; }; @@ -294,7 +300,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 +344,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 @@ -1063,7 +1069,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 { @@ -1087,7 +1097,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( + global.io(), + path, + .{}, + ) catch |err| { if (err != error.FileNotFound or !optional) { log.warn( "error opening gtk-custom-css file {s}: {}", @@ -1096,12 +1110,14 @@ pub const Application = extern struct { } continue; }; - defer file.close(); + defer file.close(global.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) { @@ -1111,6 +1127,7 @@ pub const Application = extern struct { }, else => |e| return e, }; + defer alloc.free(contents); const bytes = glib.Bytes.new(contents.ptr, contents.len); @@ -1401,7 +1418,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, ); @@ -1461,7 +1478,7 @@ pub const Application = extern struct { // Queue a new window const priv = self.private(); - _ = priv.core_app.mailbox.push(.{ + _ = priv.core_app.mailbox.push(global.io(), .{ .new_window = .{}, }, .{ .forever = {} }); @@ -1813,7 +1830,7 @@ pub const Application = extern struct { _: ?*glib.Variant, self: *Self, ) callconv(.c) void { - _ = self.core().mailbox.push(.open_config, .forever); + _ = self.core().mailbox.push(global.io(), .open_config, .forever); } fn actionPresentSurface( @@ -1839,6 +1856,7 @@ pub const Application = extern struct { const surface = self.core().findSurfaceByID(surface_id) orelse return; _ = self.core().mailbox.push( + global.io(), .{ .surface_message = .{ .surface = surface, @@ -1866,11 +1884,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 @@ -1894,11 +1909,11 @@ pub const Application = extern struct { }; pub fn openUrlFallback(self: *Application, kind: apprt.action.OpenUrl.Kind, url: []const u8) void { + _ = self; // Fallback to the minimal cross-platform way of opening a URL. // This is always a safe fallback and enables for example Windows // to open URLs (GTK on Windows via WSL is a thing). internal_os.open( - self.allocator(), kind, url, ) catch |err| log.warn("unable to open url: {}", .{err}); @@ -2846,7 +2861,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 { @@ -2863,7 +2878,7 @@ fn setGtkEnv(config: *const CoreConfig) error{NoSpaceLeft}!void { } = .{ // `gtk-opengl-debug` dumps logs directly to stderr so both must be true // to enable OpenGL debugging. - .opengl = state.logging.stderr and config.@"gtk-opengl-debug", + .opengl = global.logging().stderr and config.@"gtk-opengl-debug", }; var gdk_disable: struct { @@ -2915,8 +2930,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)) { @@ -2926,15 +2940,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]); + _ = setenv("GDK_DEBUG", @ptrCast(value[0 .. value.len - 1 :0]), 1); } { 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)) { @@ -2944,10 +2957,13 @@ 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]); + _ = setenv("GDK_DISABLE", @ptrCast(value[0 .. value.len - 1 :0]), 1); } + + // Sync environ after altering system env + global.syncEnviron(); } fn findActiveWindow(data: ?*const anyopaque, _: ?*const anyopaque) callconv(.c) c_int { 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 28987c259..dd44afbdd 100644 --- a/src/apprt/gtk/class/global_shortcuts.zig +++ b/src/apprt/gtk/class/global_shortcuts.zig @@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator; const gio = @import("gio"); const glib = @import("glib"); const gobject = @import("gobject"); +const global = @import("../../../global.zig"); const Binding = @import("../../../input.zig").Binding; const key = @import("../key.zig"); @@ -630,6 +631,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 = global.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..50247fbf1 100644 --- a/src/apprt/gtk/class/imgui_widget.zig +++ b/src/apprt/gtk/class/imgui_widget.zig @@ -7,6 +7,7 @@ const adw = @import("adw"); const gdk = @import("gdk"); const gobject = @import("gobject"); const gtk = @import("gtk"); +const global = @import("../../../global.zig"); const input = @import("../../../input.zig"); const gresource = @import("../build/gresource.zig"); @@ -61,13 +62,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 +141,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(global.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 +300,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(global.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 +459,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(global.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 54825ce05..3a950a6aa 100644 --- a/src/apprt/gtk/class/surface.zig +++ b/src/apprt/gtk/class/surface.zig @@ -36,6 +36,7 @@ const Window = @import("window.zig").Window; const InspectorWindow = @import("inspector_window.zig").InspectorWindow; const i18n = @import("../../../os/i18n.zig"); const media = @import("../media.zig"); +const global = @import("../../../global.zig"); const log = std.log.scoped(.gtk_ghostty_surface); @@ -1589,32 +1590,35 @@ 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); + var env = if (internal_os.isFlatpak()) + std.process.Environ.Map.init(alloc) + else + try global.environMap(); errdefer env.deinit(); 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. @@ -1641,7 +1645,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{ @@ -1708,7 +1712,7 @@ pub const Surface = extern struct { item.key, item.value, ); - for (env_to_remove.items) |key| _ = env_map.remove(key); + for (env_to_remove.items) |key| _ = env_map.orderedRemove(key); } pub fn clipboardRequest( 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..669047833 100644 --- a/src/apprt/gtk/ipc/DBus.zig +++ b/src/apprt/gtk/ipc/DBus.zig @@ -6,6 +6,7 @@ const Allocator = std.mem.Allocator; const gio = @import("gio"); const glib = @import("glib"); +const global = @import("../../../global.zig"); const apprt = @import("../../../apprt.zig"); const ApprtApp = @import("../App.zig"); @@ -28,10 +29,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(global.io(), &buf); const stderr = &stderr_writer.interface; // Get the appropriate bus name and object path for contacting the @@ -133,7 +136,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(global.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 7883bf372..3e6a19c33 100644 --- a/src/apprt/gtk/media.zig +++ b/src/apprt/gtk/media.zig @@ -7,10 +7,15 @@ const gio = @import("gio"); const glib = @import("glib"); const gobject = @import("gobject"); const gtk = @import("gtk"); +const global = @import("../../global.zig"); 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( + global.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..11c3356d7 100644 --- a/src/apprt/gtk/portal.zig +++ b/src/apprt/gtk/portal.zig @@ -1,6 +1,7 @@ const std = @import("std"); const gio = @import("gio"); +const global = @import("../../global.zig"); const Allocator = std.mem.Allocator; @@ -11,7 +12,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 = global.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..401914825 100644 --- a/src/apprt/gtk/portal/OpenURI.zig +++ b/src/apprt/gtk/portal/OpenURI.zig @@ -9,6 +9,7 @@ const assert = std.debug.assert; const gio = @import("gio"); const glib = @import("glib"); const gobject = @import("gobject"); +const global = @import("../../../global.zig"); const App = @import("../App.zig"); const portal = @import("../portal.zig"); @@ -23,7 +24,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 +79,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 +124,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(global.io()); + defer self.mutex.unlock(global.io()); if (!self.alive) return; self.alive = false; @@ -157,8 +158,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(global.io()); + defer self.mutex.unlock(global.io()); // Create an entry that is used to track the results of the D-Bus method // call. @@ -166,7 +167,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(global.io(), .awake), .token = token, .kind = value.kind, .uri = try alloc.dupeZ(u8, value.url), @@ -235,8 +236,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(global.io()); + defer self.mutex.unlock(global.io()); if (!self.alive) return null; @@ -422,8 +423,8 @@ fn requestCallback( return; } - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(global.io()); + defer self.mutex.unlock(global.io()); if (!self.alive) return; @@ -461,8 +462,8 @@ fn responseReceived( return; }; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(global.io()); + defer self.mutex.unlock(global.io()); if (!self.alive) return; @@ -534,22 +535,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(global.io()); + defer self.mutex.unlock(global.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( + global.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..d9b576fad 100644 --- a/src/apprt/gtk/post_fork.zig +++ b/src/apprt/gtk/post_fork.zig @@ -2,6 +2,7 @@ const std = @import("std"); const gio = @import("gio"); const glib = @import("glib"); +const global = @import("../../global.zig"); const log = std.log.scoped(.gtk_post_fork); @@ -85,12 +86,10 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void { return; }; - const start = std.time.Instant.now() catch unreachable; + const start: std.Io.Timestamp = .now(global.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(global.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 +115,6 @@ pub fn postFork(cmd: *Command) Command.PostForkError!void { } } - std.Thread.sleep(25 * std.time.ns_per_ms); + std.Io.sleep(global.io(), .fromMilliseconds(25), .awake) catch unreachable; } } diff --git a/src/apprt/gtk/pre_exec.zig b/src/apprt/gtk/pre_exec.zig index 6f6a9ed51..a4eda4620 100644 --- a/src/apprt/gtk/pre_exec.zig +++ b/src/apprt/gtk/pre_exec.zig @@ -3,6 +3,7 @@ const std = @import("std"); const log = std.log.scoped(.gtk_pre_exec); const configpkg = @import("../../config.zig"); +const global = @import("../../global.zig"); const internal_os = @import("../../os/main.zig"); const Command = @import("../../Command.zig"); @@ -47,12 +48,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(global.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(global.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 +73,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(global.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 dda794ae1..ade44da19 100644 --- a/src/apprt/ipc.zig +++ b/src/apprt/ipc.zig @@ -126,8 +126,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)); @@ -135,20 +139,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/ApcParser.zig b/src/benchmark/ApcParser.zig index 73b7c57a2..8b5f883fd 100644 --- a/src/benchmark/ApcParser.zig +++ b/src/benchmark/ApcParser.zig @@ -12,6 +12,7 @@ const Allocator = std.mem.Allocator; const terminalpkg = @import("../terminal/main.zig"); const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); +const global = @import("../global.zig"); const log = std.log.scoped(.@"apc-parser-bench"); @@ -19,7 +20,7 @@ opts: Options, 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 data to read as a filepath. If this is "-" then @@ -103,7 +104,7 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { fn teardown(ptr: *anyopaque) void { const self: *ApcParser = @ptrCast(@alignCast(ptr)); if (self.data_f) |f| { - f.close(); + f.close(global.io()); self.data_f = null; } } @@ -114,7 +115,7 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { const f = self.data_f orelse return; var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + var f_reader = f.reader(global.io(), &read_buf); const r = &f_reader.interface; // This buffer size matches the read buffer size used by the diff --git a/src/benchmark/Benchmark.zig b/src/benchmark/Benchmark.zig index 41c3695d4..d45ba181c 100644 --- a/src/benchmark/Benchmark.zig +++ b/src/benchmark/Benchmark.zig @@ -6,6 +6,7 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const macos = @import("macos"); const build_config = @import("../build_config.zig"); +const global = @import("../global.zig"); ptr: *anyopaque, vtable: VTable, @@ -64,21 +65,23 @@ pub fn run( signpost.log.release(); }; - const start = std.time.Instant.now() catch return error.BenchmarkFailed; + const start: std.Io.Timestamp = .now(global.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(global.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/CApi.zig b/src/benchmark/CApi.zig index 3bef8b269..c98406713 100644 --- a/src/benchmark/CApi.zig +++ b/src/benchmark/CApi.zig @@ -1,6 +1,6 @@ const std = @import("std"); const cli = @import("cli.zig"); -const state = &@import("../global.zig").state; +const global = @import("../global.zig"); const log = std.log.scoped(.benchmark); @@ -19,7 +19,7 @@ export fn ghostty_benchmark_cli( }; cli.mainAction( - state.alloc, + global.alloc(), action, .{ .string = std.mem.sliceTo(args, 0) }, ) catch |err| { diff --git a/src/benchmark/CodepointWidth.zig b/src/benchmark/CodepointWidth.zig index 30d3f91e7..d0c6b8089 100644 --- a/src/benchmark/CodepointWidth.zig +++ b/src/benchmark/CodepointWidth.zig @@ -14,13 +14,14 @@ const options = @import("options.zig"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const simd = @import("../simd/main.zig"); const table = @import("../unicode/main.zig").table; +const global = @import("../global.zig"); 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 +94,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(global.io()); self.data_f = null; } } @@ -114,7 +115,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(global.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -141,7 +142,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(global.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -173,7 +174,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(global.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..8e5236fe1 100644 --- a/src/benchmark/GraphemeBreak.zig +++ b/src/benchmark/GraphemeBreak.zig @@ -11,13 +11,14 @@ const options = @import("options.zig"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const unicode = @import("../unicode/main.zig"); const uucode = @import("uucode"); +const global = @import("../global.zig"); 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 +83,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(global.io()); self.data_f = null; } } @@ -92,7 +93,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(global.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -115,7 +116,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(global.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..c99bc9ca2 100644 --- a/src/benchmark/IsSymbol.zig +++ b/src/benchmark/IsSymbol.zig @@ -11,13 +11,14 @@ const options = @import("options.zig"); const UTF8Decoder = @import("../terminal/UTF8Decoder.zig"); const uucode = @import("uucode"); const symbols_table = @import("../unicode/symbols_table.zig").table; +const global = @import("../global.zig"); 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 +81,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(global.io()); self.data_f = null; } } @@ -90,7 +91,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(global.io(), &read_buf); var r = &f_reader.interface; var d: UTF8Decoder = .{}; @@ -117,7 +118,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(global.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..9b21c37ab 100644 --- a/src/benchmark/OscParser.zig +++ b/src/benchmark/OscParser.zig @@ -9,11 +9,12 @@ const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); const Parser = @import("../terminal/osc.zig").Parser; const log = std.log.scoped(.@"osc-parser-bench"); +const global = @import("../global.zig"); 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 +71,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(global.io()); self.data_f = null; } } @@ -80,7 +81,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(global.io(), &read_buf); var osc_buf: [4096]u8 align(std.atomic.cache_line) = undefined; while (true) { diff --git a/src/benchmark/PageCompression.zig b/src/benchmark/PageCompression.zig index 21153659e..23ecf0bf2 100644 --- a/src/benchmark/PageCompression.zig +++ b/src/benchmark/PageCompression.zig @@ -67,6 +67,8 @@ const options = @import("options.zig"); const compress = @import("../terminal/compress.zig"); const CompressedPage = compress.Page; const lz4 = compress.lz4; +const compat_file = @import("../lib/compat/file.zig"); +const global = @import("../global.zig"); const log = std.log.scoped(.@"page-compression-bench"); @@ -210,9 +212,9 @@ fn setupData(self: *PageCompression) !void { return error.InvalidRetainedPages; const data_file = try options.dataFile(self.opts.data) orelse return; - defer data_file.close(); + defer data_file.close(global.io()); - self.data = try data_file.readToEndAlloc(self.alloc, max_data_size); + self.data = try compat_file.readToEndAlloc(data_file, self.alloc, max_data_size); errdefer { self.alloc.free(self.data); self.data = &.{}; diff --git a/src/benchmark/ScreenClone.zig b/src/benchmark/ScreenClone.zig index 5176ce26c..9b2012cc1 100644 --- a/src/benchmark/ScreenClone.zig +++ b/src/benchmark/ScreenClone.zig @@ -11,6 +11,7 @@ const terminalpkg = @import("../terminal/main.zig"); const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); const Terminal = terminalpkg.Terminal; +const global = @import("../global.zig"); const log = std.log.scoped(.@"terminal-stream-bench"); @@ -79,7 +80,7 @@ pub fn create( ptr.* = .{ .opts = opts, - .terminal = try .init(alloc, .{ + .terminal = try .init(global.io(), alloc, .{ .rows = opts.@"terminal-rows", .cols = opts.@"terminal-cols", }), @@ -122,7 +123,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}); @@ -133,7 +134,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(global.io(), &read_buf); const r = &f_reader.interface; var buf: [4096]u8 = undefined; @@ -171,6 +172,7 @@ fn stepClone(ptr: *anyopaque) Benchmark.Error!void { for (0..1000) |_| { const s: *terminalpkg.Screen = self.terminal.screens.active; const copy = s.clone( + s.io, s.alloc, .{ .viewport = .{} }, null, diff --git a/src/benchmark/ScrollbackCompression.zig b/src/benchmark/ScrollbackCompression.zig index 40ec20f48..3968427b4 100644 --- a/src/benchmark/ScrollbackCompression.zig +++ b/src/benchmark/ScrollbackCompression.zig @@ -74,6 +74,7 @@ const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); const PageList = terminalpkg.PageList; const Terminal = terminalpkg.Terminal; +const global = @import("../global.zig"); const log = std.log.scoped(.@"scrollback-compression-bench"); @@ -134,7 +135,7 @@ pub fn create( ptr.* = .{ .opts = opts, - .terminal = try .init(alloc, .{ + .terminal = try .init(global.io(), alloc, .{ .rows = opts.@"terminal-rows", .cols = opts.@"terminal-cols", .max_scrollback = opts.@"max-scrollback", @@ -182,13 +183,13 @@ fn setup(ptr: *anyopaque) Benchmark.Error!void { /// terminal-stream benchmark. Parser and file IO costs remain in setup. fn loadCorpus(self: *ScrollbackCompression) !void { const data_file = try options.dataFile(self.opts.data) orelse return; - defer data_file.close(); + defer data_file.close(global.io()); var stream = self.terminal.vtStream(); defer stream.deinit(); var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined; - var file_reader = data_file.reader(&read_buf); + var file_reader = data_file.reader(global.io(), &read_buf); const reader = &file_reader.interface; var buf: [64 * 1024]u8 = undefined; diff --git a/src/benchmark/TerminalParser.zig b/src/benchmark/TerminalParser.zig index 78c933121..d393301c3 100644 --- a/src/benchmark/TerminalParser.zig +++ b/src/benchmark/TerminalParser.zig @@ -7,13 +7,14 @@ const Allocator = std.mem.Allocator; const terminalpkg = @import("../terminal/main.zig"); const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); +const global = @import("../global.zig"); 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 +62,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(global.io()); self.data_f = null; } } @@ -76,7 +77,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(global.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 a5302005b..a521c5b7a 100644 --- a/src/benchmark/TerminalStream.zig +++ b/src/benchmark/TerminalStream.zig @@ -19,6 +19,7 @@ const Benchmark = @import("Benchmark.zig"); const options = @import("options.zig"); const Terminal = terminalpkg.Terminal; const Stream = terminalpkg.TerminalStream; +const global = @import("../global.zig"); const log = std.log.scoped(.@"terminal-stream-bench"); @@ -27,7 +28,7 @@ terminal: Terminal, 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 @@ -54,7 +55,7 @@ pub fn create( ptr.* = .{ .opts = opts, - .terminal = try .init(alloc, .{ + .terminal = try .init(global.io(), alloc, .{ .rows = opts.@"terminal-rows", .cols = opts.@"terminal-cols", }), @@ -97,7 +98,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(global.io()); self.data_f = null; } } @@ -112,8 +113,10 @@ fn step(ptr: *anyopaque) Benchmark.Error!void { // aren't currently IO bound. const f = self.data_f orelse return; - var read_buf: [64 * 1024]u8 align(std.atomic.cache_line) = undefined; - var f_reader = f.reader(&read_buf); + // Unbuffered: readSliceShort below reads directly into `buf`, + // avoiding a per-chunk memcpy through an intermediate reader + // buffer that would pollute the measurement. + var f_reader = f.reader(global.io(), &.{}); const r = &f_reader.interface; // This buffer size matches the read buffer size used by the diff --git a/src/benchmark/cli.zig b/src/benchmark/cli.zig index dc4d7b8f4..19db37a3d 100644 --- a/src/benchmark/cli.zig +++ b/src/benchmark/cli.zig @@ -1,6 +1,7 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const cli = @import("../cli.zig"); +const global = @import("../global.zig"); /// The available actions for the CLI. This is the list of available /// benchmarks. View docs for each individual one in the predictably @@ -44,19 +45,20 @@ pub const Action = enum { }; /// An entrypoint for the benchmark CLI. -pub fn main() !void { +pub fn main(minimal: std.process.Init.Minimal) !void { + try global.init(.{ .tool = minimal }); const alloc = std.heap.c_allocator; - const action_ = try cli.action.detectArgs(Action, alloc); + const action_ = try cli.action.detectArgs(Action, alloc, minimal.args); const action = action_ orelse return error.NoAction; - try mainAction(alloc, action, .cli); + try mainAction(alloc, action, .{ .cli = minimal.args }); } /// Arguments that can be passed to the benchmark. pub const Args = union(enum) { /// The arguments passed to the CLI via argc/argv. - cli, + cli: std.process.Args, - /// Simple string arguments, parsed via std.process.ArgIteratorGeneral. + /// Simple string arguments, parsed via ArgIteratorGeneral. string: []const u8, }; @@ -83,13 +85,13 @@ fn mainActionImpl( var opts: Options = .{}; defer if (@hasDecl(Options, "deinit")) opts.deinit(); switch (args) { - .cli => { - var iter = try cli.args.argsIterator(alloc); + .cli => |process_args| { + var iter = try cli.args.argsIterator(alloc, process_args); defer iter.deinit(); try cli.args.parse(Options, alloc, &opts, &iter); }, .string => |str| { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, str, ); diff --git a/src/benchmark/options.zig b/src/benchmark/options.zig index 049e80f48..9322818d4 100644 --- a/src/benchmark/options.zig +++ b/src/benchmark/options.zig @@ -1,20 +1,21 @@ //! This file contains helpers for CLI options. const std = @import("std"); +const global = @import("../global.zig"); /// Returns the data file for the given path in a way that is consistent /// 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(global.io(), path, .{}); + errdefer file.close(global.io()); return file; } diff --git a/src/build/Config.zig b/src/build/Config.zig index 0a9947317..43db20971 100644 --- a/src/build/Config.zig +++ b/src/build/Config.zig @@ -43,7 +43,7 @@ lib_version: std.SemanticVersion = .{ .major = 0, .minor = 0, .patch = 0 }, /// Binary properties pie: bool = false, strip: bool = false, -patch_rpath: ?[]const u8 = null, +patchelf: ?PatchElf = null, /// Artifacts flatpak: bool = false, @@ -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, @@ -312,24 +312,38 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf //--------------------------------------------------------------- // Binary Properties - // On NixOS, the built binary from `zig build` needs to patch the rpath - // into the built binary for it to be portable across the NixOS system - // it was built for. We default this to true if we can detect we're in - // a Nix shell and have LD_LIBRARY_PATH set. - config.patch_rpath = b.option( - []const u8, - "patch-rpath", - "Inject the LD_LIBRARY_PATH as the rpath in the built binary. " ++ - "This defaults to LD_LIBRARY_PATH if we're in a Nix shell environment on NixOS.", - ) orelse patch_rpath: { - // We only do the patching if we're targeting our own CPU and its Linux. - if (!(target.result.os.tag == .linux) or !target.query.isNativeCpu()) break :patch_rpath null; - - // If we're in a nix shell we default to doing this. - // Note: we purposely never deinit envmap because we leak the strings - if (env.get("IN_NIX_SHELL") == null) break :patch_rpath null; - break :patch_rpath env.get("LD_LIBRARY_PATH"); - }; + // On NixOS, the built binary from `zig build` needs to patch the interp + // and rpath into the built binary for it to be portable across the NixOS + // system it was built for. We default this to true if we can detect we're + // in a Nix shell. + // + // Note that this option is only available on Linux and if building + // natively. + // + // NOTE: Zig does now have an option to pass in the dynamic linker + // (-Ddynamic-linker), but it does seem to have some issues in 0.16.0 that + // may be fixed in 0.17.0. We may want to revisit this afterwards; although + // I'm not too sure if that helps to clean up rpath, this may just be the + // better option. See https://codeberg.org/ziglang/zig/issues/31760. + if ((target.result.os.tag == .linux) and target.query.isNativeCpu()) { + const in_nix_shell = env.get("IN_NIX_SHELL") != null; + if (b.option( + bool, + "patchelf", + "Patch interpreter and rpath in the built binary (default if IN_NIX_SHELL is set)", + ) orelse in_nix_shell) { + var patchelf: PatchElf = .{}; + if (b.findProgram(&.{"ld.so"}, &.{})) |ld_so| { + patchelf.interp = std.Io.Dir.realPathFileAbsoluteAlloc(b.graph.io, ld_so, b.allocator) catch null; + } else |_| {} + if (env.get("LD_LIBRARY_PATH")) |ld_library_path| { + patchelf.rpath = if (ld_library_path.len > 0) ld_library_path else null; + } + if (patchelf.interp != null or patchelf.rpath != null) { + config.patchelf = patchelf; + } + } + } config.pie = b.option( bool, @@ -401,7 +415,7 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf if (system_package) break :emit_docs true; // We only default to true if we can find pandoc. - const path = expandPath(b.allocator, "pandoc") catch + const path = expandPath(b.graph.io, b.allocator, &b.graph.environ_map, "pandoc") catch break :emit_docs false; defer if (path) |p| b.allocator.free(p); break :emit_docs path != null; @@ -450,7 +464,7 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf if (config.emit_lib_vt) { // In lib-vt mode default to whether xcodebuild is available, // since xcodebuild is required to produce the XCFramework. - const path = expandPath(b.allocator, "xcodebuild") catch + const path = expandPath(b.graph.io, b.allocator, &b.graph.environ_map, "xcodebuild") catch break :emit_xcfw false; defer if (path) |p| b.allocator.free(p); break :emit_xcfw path != null; @@ -522,6 +536,27 @@ pub fn init(b: *std.Build, appVersion: []const u8, libVersion: []const u8) !Conf return config; } +const PatchElf = struct { + interp: ?[]const u8 = null, + rpath: ?[]const u8 = null, +}; + +/// Add a patchelf step for the supplied `artifact`, depending on the supplied +/// `step`, if `patchelf` is present in the config. +pub fn addPatchElf(self: *const Config, artifact: *std.Build.Step.Compile, step: *std.Build.Step) void { + const b = artifact.step.owner; + std.debug.assert(b == step.owner); + + if (self.patchelf) |patchelf| { + const run = std.Build.Step.Run.create(b, "patchelf"); + run.addArgs(&.{"patchelf"}); + if (patchelf.interp) |interp| run.addArgs(&.{ "--set-interpreter", interp }); + if (patchelf.rpath) |rpath| run.addArgs(&.{ "--set-rpath", rpath }); + run.addArtifactArg(artifact); + step.dependOn(&run.step); + } +} + /// Configure the build options with our values. pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void { // We need to break these down individual because addOption doesn't @@ -570,7 +605,11 @@ pub fn addOptions(self: *const Config, step: *std.Build.Step.Options) !void { /// Returns the build options for the terminal module. This assumes a /// Ghostty executable being built. Callers should modify this as needed. -pub fn terminalOptions(self: *const Config, artifact: TerminalBuildOptions.Artifact) TerminalBuildOptions { +pub fn terminalOptions( + self: *const Config, + artifact: TerminalBuildOptions.Artifact, + optimize: std.builtin.OptimizeMode, +) TerminalBuildOptions { return .{ .artifact = artifact, .simd = self.simd, @@ -580,7 +619,7 @@ pub fn terminalOptions(self: *const Config, artifact: TerminalBuildOptions.Artif .ghostty => self.version, .lib => self.lib_version, }, - .slow_runtime_safety = switch (self.optimize) { + .slow_runtime_safety = switch (optimize) { .Debug => true, .ReleaseSafe, .ReleaseSmall, @@ -591,7 +630,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 +640,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"), }; } @@ -630,6 +669,17 @@ pub fn fromOptions() Config { }; } +/// Whether release artifacts should omit frame pointers. +/// +/// Stripped release builds omit them in general, but we always keep frame +/// pointers on Apple platforms: the Apple arm64 ABI expects x29 to +/// be a valid frame pointer and Apple's profiling and crash reporting +/// tools rely on it for backtraces. +pub fn omitFramePointer(self: *const Config) bool { + if (self.target.result.os.tag.isDarwin()) return false; + return self.strip; +} + /// Returns the minimum OS version for the given OS tag. This shouldn't /// be used generally, it should only be used for Darwin-based OS currently. pub fn osVersionMin(tag: std.Target.Os.Tag) ?std.Target.Query.OsVersion { 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..22f39b7b3 100644 --- a/src/build/GhosttyExe.zig +++ b/src/build/GhosttyExe.zig @@ -18,7 +18,7 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty .target = cfg.target, .optimize = cfg.optimize, .strip = cfg.strip, - .omit_frame_pointer = cfg.strip, + .omit_frame_pointer = cfg.omitFramePointer(), .unwind_tables = if (cfg.strip) .none else .sync, }), // Crashes on x86_64 self-hosted on 0.15.1 @@ -36,21 +36,14 @@ pub fn init(b: *std.Build, cfg: *const Config, deps: *const SharedDeps) !Ghostty // Check for possible issues try checkNixShell(exe, cfg); - // Patch our rpath if that option is specified. - if (cfg.patch_rpath) |rpath| { - if (rpath.len > 0) { - const run = std.Build.Step.Run.create(b, "patchelf rpath"); - run.addArgs(&.{ "patchelf", "--set-rpath", rpath }); - run.addArtifactArg(exe); - install_step.step.dependOn(&run.step); - } - } + // Add patchelf step if that option is specified. + cfg.addPatchElf(exe, &install_step.step); // OS-specific 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 +78,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..529bb2821 100644 --- a/src/build/GhosttyLib.zig +++ b/src/build/GhosttyLib.zig @@ -1,9 +1,11 @@ const GhosttyLib = @This(); const std = @import("std"); +const builtin = @import("builtin"); const RunStep = std.Build.Step.Run; const CombineArchivesStep = @import("CombineArchivesStep.zig"); const Config = @import("Config.zig"); +const LibsystemOverrideStep = @import("LibsystemOverrideStep.zig"); const SharedDeps = @import("SharedDeps.zig"); const LipoStep = @import("LipoStep.zig"); @@ -27,14 +29,14 @@ pub fn initStatic( .target = deps.config.target, .optimize = deps.config.optimize, .strip = deps.config.strip, - .omit_frame_pointer = deps.config.strip, + .omit_frame_pointer = deps.config.omitFramePointer(), .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. @@ -57,9 +59,19 @@ pub fn initStatic( const combined = CombineArchivesStep.create(b, deps.config.target, "ghostty-internal", lib_list.items); combined.step.dependOn(&lib.step); + // On Darwin, prefer libSystem's libc/libm over the bundled + // compiler-rt for consumers of this archive. See + // libsystem_override.sh for details. This is a no-op elsewhere. + const override = LibsystemOverrideStep.create( + b, + deps.config.target, + combined.output, + "libghostty-internal.a", + ); + return .{ - .step = combined.step, - .output = combined.output, + .step = override.step orelse combined.step, + .output = override.output, // Static libraries cannot have dSYMs because they aren't linked. .dsym = null, @@ -72,6 +84,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, @@ -80,7 +100,7 @@ pub fn initShared( .target = deps.config.target, .optimize = deps.config.optimize, .strip = deps.config.strip, - .omit_frame_pointer = deps.config.strip, + .omit_frame_pointer = deps.config.omitFramePointer(), .unwind_tables = if (deps.config.strip) .none else .sync, }), @@ -99,12 +119,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 +140,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..32ed2aa72 100644 --- a/src/build/GhosttyLibVt.zig +++ b/src/build/GhosttyLibVt.zig @@ -5,6 +5,7 @@ const builtin = @import("builtin"); const assert = std.debug.assert; const RunStep = std.Build.Step.Run; const CombineArchivesStep = @import("CombineArchivesStep.zig"); +const LibsystemOverrideStep = @import("LibsystemOverrideStep.zig"); const Config = @import("Config.zig"); const GhosttyZig = @import("GhosttyZig.zig"); const LipoStep = @import("LipoStep.zig"); @@ -174,7 +175,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)); @@ -290,22 +296,44 @@ fn initLib( const combined = CombineArchivesStep.create(b, target, "ghostty-vt", sources.items); combined.step.dependOn(&lib.step); + // On Darwin, prefer libSystem's libc/libm over the bundled + // compiler-rt for consumers of this archive. See GhosttyLib + // and libsystem_override.sh for details. + const override = LibsystemOverrideStep.create( + b, + target, + combined.output, + "libghostty-vt-fat.a", + ); + return .{ - .step = combined.step, + .step = override.step orelse combined.step, .artifact = &b.addInstallArtifact(lib, .{}).step, .kind = kind, - .output = combined.output, + .output = override.output, .dsym = dsymutil, .pkg_config = if (pcs) |v| v.shared else null, .pkg_config_static = if (pcs) |v| v.static else null, }; } + // Same libSystem preference for the plain (no vendored SIMD) + // static archive; a no-op off Darwin. + const override: LibsystemOverrideStep.Result = if (kind == .static) LibsystemOverrideStep.create( + b, + target, + lib.getEmittedBin(), + "libghostty-vt-static.a", + ) else .{ + .step = null, + .output = lib.getEmittedBin(), + }; + return .{ - .step = &lib.step, + .step = override.step orelse &lib.step, .artifact = &b.addInstallArtifact(lib, .{}).step, .kind = kind, - .output = lib.getEmittedBin(), + .output = override.output, .dsym = dsymutil, .pkg_config = if (pcs) |v| v.shared else null, .pkg_config_static = if (pcs) |v| v.static else null, @@ -438,12 +466,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..4671219e0 100644 --- a/src/build/GhosttyZig.zig +++ b/src/build/GhosttyZig.zig @@ -64,7 +64,7 @@ fn initInner( vt_c_name: []const u8, ) !GhosttyZig { // Terminal module build options - var vt_options = cfg.terminalOptions(.lib); + var vt_options = cfg.terminalOptions(.lib, cfg.optimize); vt_options.artifact = .lib; // We presently don't allow Oniguruma in our Zig module at all. // We should expose this as a build option in the future so we can @@ -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/LibsystemOverrideStep.zig b/src/build/LibsystemOverrideStep.zig new file mode 100644 index 000000000..3e1cafa5e --- /dev/null +++ b/src/build/LibsystemOverrideStep.zig @@ -0,0 +1,51 @@ +//! Wraps a Darwin static archive with the libsystem_override.sh +//! post-processing step so that consumers linking the archive bind +//! well-known libc/libm symbols (memcpy, memmove, memset, cos, sin, +//! ...) to Apple's libSystem instead of the bundled Zig compiler-rt. +//! See src/build/libsystem_override.sh for the full rationale. +//! +//! This applies to every static archive we produce, public +//! (e.g. libghostty-vt) and app-internal alike: the compiler-rt +//! bundle must stay because the few symbols libSystem does not +//! provide (f128 conversions, *q math, sincos*, ___zig_probe_stack) +//! have live references, e.g. from the bundled ubsan runtime and +//! x86_64 stack probing in Debug builds. +//! +//! Note shared libraries can't use either approach: Zig 0.16 links +//! compiler-rt as an eagerly-included object, so its definitions are +//! already bound into the dylib at link time and cannot be repaired +//! post-hoc. That needs an upstream Zig fix. +const std = @import("std"); +const builtin = @import("builtin"); + +pub const Result = struct { + /// The step performing the override, or null if the override + /// doesn't apply to this target/host (in which case `output` is + /// the unmodified input). + step: ?*std.Build.Step, + output: std.Build.LazyPath, +}; + +/// Post-process the given static archive on Darwin. Requires a Darwin +/// host for the Apple toolchain (nmedit); in all other configurations +/// this is a no-op and the input is returned unmodified, which is +/// functional (the bundled compiler-rt symbols are used), just slower. +pub fn create( + b: *std.Build, + target: std.Build.ResolvedTarget, + input: std.Build.LazyPath, + out_name: []const u8, +) Result { + if (!target.result.os.tag.isDarwin() or + comptime !builtin.os.tag.isDarwin()) + { + return .{ .step = null, .output = input }; + } + + const run = b.addSystemCommand(&.{"/bin/sh"}); + run.setName("libsystem override"); + run.addFileArg(b.path("src/build/libsystem_override.sh")); + run.addFileArg(input); + const output = run.addOutputFileArg(out_name); + return .{ .step = &run.step, .output = output }; +} diff --git a/src/build/SharedDeps.zig b/src/build/SharedDeps.zig index b68be92d0..4877686c4 100644 --- a/src/build/SharedDeps.zig +++ b/src/build/SharedDeps.zig @@ -9,6 +9,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 +20,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 +69,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, @@ -133,7 +179,10 @@ pub fn add( step.root_module.addOptions("build_options", self.options); // Every exe needs the terminal options - self.config.terminalOptions(.ghostty).add(b, step.root_module); + self.config.terminalOptions(.ghostty, optimize).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 { @@ -143,11 +192,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 +219,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 +251,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 +276,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 +300,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 +320,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 +334,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 +352,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 +369,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 +393,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 +414,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 +461,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 +480,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 +490,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 +509,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 +536,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 +564,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 +574,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 +585,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 +605,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 +654,38 @@ 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); + + // We need to translate gio headers too + gio_translate: { + // translate-c stuff + const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse + break :gio_translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse + break :gio_translate; + const translated: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("gio_c.h", + \\#include + \\#include + ), + .target = target, + .optimize = optimize, + .link_system_libs = &.{ + .{ .name = "gio-2.0", .options = dynamic_link_opts }, + }, + }); + step.root_module.addImport("gio_c", translated.mod); + } + } switch (self.config.app_runtime) { .none => {}, @@ -643,12 +728,44 @@ fn addGtkNg( step.root_module.addImport(name, gobject.module(module)); } } + gtk_adw_translate: { + // translate-c stuff + const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse break :gtk_adw_translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :gtk_adw_translate; + const Translator = translate_c.Translator; - step.linkSystemLibrary2("gtk4", dynamic_link_opts); - step.linkSystemLibrary2("libadwaita-1", dynamic_link_opts); + { + // GTK headers + const translated: Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("gtk_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + .link_system_libs = &.{ + .{ .name = "gtk4", .options = dynamic_link_opts }, + }, + }); + step.root_module.addImport("gtk_c", translated.mod); + } + { + // Adwaita headers + const translated: Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("adw_c.h", + \\#include + ), + .target = target, + .optimize = optimize, + .link_system_libs = &.{ + .{ .name = "libadwaita-1", .options = 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 +849,39 @@ 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); } - { + ghostty_resources_translate: { // 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.lazyImport(@import("../../build.zig"), "translate_c") orelse + break :ghostty_resources_translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse + break :ghostty_resources_translate; + const translated: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("c.h", + \\#include + ), + .target = target, + .optimize = optimize, + .link_system_libs = &.{ + .{ .name = "glib-2.0", .options = 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 +1022,25 @@ 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); + adw_translate: { + // Adwaita headers + const translate_c = b.lazyImport(@import("../../build.zig"), "translate_c") orelse break :adw_translate; + const translate_c_dep = b.lazyDependency("translate_c", .{}) orelse break :adw_translate; + const translated: translate_c.Translator = .init(translate_c_dep, .{ + .c_source_file = b.addWriteFiles().add("adw_c.h", + \\#include + ), + .target = b.graph.host, + .optimize = .Debug, + .link_system_libs = &.{ + .{ .name = "libadwaita-1", .options = 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 +1069,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 +1110,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/combine_archives.zig b/src/build/combine_archives.zig index 04ec8e978..3df5ed0e4 100644 --- a/src/build/combine_archives.zig +++ b/src/build/combine_archives.zig @@ -11,11 +11,10 @@ const std = @import("std"); -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 std.process.argsAlloc(alloc); + const args = try init.minimal.args.toSlice(alloc); if (args.len < 4) { std.log.err("usage: combine_archives ", .{}); std.process.exit(1); @@ -37,19 +36,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/libsystem_override.sh b/src/build/libsystem_override.sh new file mode 100755 index 000000000..14ed191b8 --- /dev/null +++ b/src/build/libsystem_override.sh @@ -0,0 +1,123 @@ +#!/bin/sh +# +# Darwin-only: rewrite a static archive so that consumers linking it bind +# libc/libm symbols to Apple's libSystem instead of the bundled Zig +# compiler-rt. This uses the Apple toolchain (xcrun, nmedit) and libSystem +# semantics, so it must run on a Darwin host for a Darwin target. +# +# Background: our static library bundles Zig's compiler-rt, which defines +# strong global implementations of libc/libm functions (memcpy, memmove, +# memset, cos, sin, ...). When a consumer (e.g. the macOS app via the +# XCFramework) links this archive, ld64 resolves those symbols from the +# archive instead of libSystem, silently replacing Apple's highly +# optimized implementations (_platform_memmove and friends, vectorized +# libm) with compiler-rt's generic ones. We measured Zig 0.16 (LLVM 21) +# compiler-rt memmove costing several percent of PTY throughput on +# scroll-region workloads versus libSystem's. +# +# The mechanism is to localize (make non-external) the libSystem-provided +# symbols defined by the compiler_rt.o archive member. This keeps +# compiler_rt.o functional for the intrinsics that libSystem does NOT +# provide (e.g. the f128 conversions ___extenddftf2/___extendxftf2, *q +# math, sincos*) while letting every other archive member bind the +# well-known libc/libm symbols to libSystem at final link. +# +# usage: libsystem_override.sh +set -eu + +in="$1" +out="$2" + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +cp -f "$in" "$out" +chmod u+w "$out" + +# The symbols to prefer from libSystem. Everything listed here is a +# stable macOS export (verified against libSystem's link surface). +# Notably NOT listed (not exported by libSystem): the *q (f128) +# variants, sincos/sincosf/sincosl, and all ___-prefixed compiler +# intrinsics except the fortify _chk wrappers below. +cat >"$tmp/localize.txt" <<'EOF' +_bcmp +_memcmp +_memcpy +_memmove +_memset +_strlen +___memcpy_chk +___memmove_chk +___memset_chk +___strcat_chk +___strcpy_chk +_ceil +_ceilf +_ceill +_cos +_cosf +_cosl +_exp +_exp2 +_exp2f +_exp2l +_expf +_expl +_fabs +_fabsf +_fabsl +_floor +_floorf +_floorl +_fma +_fmaf +_fmal +_fmax +_fmaxf +_fmaxl +_fmin +_fminf +_fminl +_fmod +_fmodf +_fmodl +_log +_log10 +_log10f +_log10l +_log2 +_log2f +_log2l +_logf +_logl +_round +_roundf +_roundl +_sin +_sinf +_sinl +_sqrt +_sqrtf +_sqrtl +_tan +_tanf +_tanl +_trunc +_truncf +_truncl +EOF + +cd "$tmp" + +xcrun ar x "$out" compiler_rt.o +chmod 644 compiler_rt.o + +# nmedit takes a keep-list (-s): all current global definitions except +# the ones we want to localize. +xcrun nm -g compiler_rt.o | awk '$2 ~ /^[A-TV-Z]$/ {print $3}' | sort -u >all.txt +sort -u localize.txt >loc.txt +comm -23 all.txt loc.txt >keep.txt +xcrun nmedit -s keep.txt compiler_rt.o + +xcrun ar r "$out" compiler_rt.o +xcrun ranlib "$out" 2>/dev/null || true 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..555734aba 100644 --- a/src/build/wasm_patch_growable_table.zig +++ b/src/build/wasm_patch_growable_table.zig @@ -12,15 +12,13 @@ const std = @import("std"); const testing = std.testing; const Allocator = std.mem.Allocator; -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 init.minimal.args.toSlice(alloc); if (args.len != 3) { std.log.err("usage: wasm_growable_table ", .{}); std.process.exit(1); @@ -30,17 +28,18 @@ pub fn main() !void { // Patch the file. const output: []const u8 = try patchTableGrowable( alloc, - try std.fs.cwd().readFileAlloc( - alloc, + try std.Io.Dir.cwd().readFileAlloc( + init.io, args[1], - std.math.maxInt(usize), + alloc, + .unlimited, ), ); // 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, 0); } /// 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..1523c8858 100644 --- a/src/cli/Pager.zig +++ b/src/cli/Pager.zig @@ -6,29 +6,31 @@ //! 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 global = @import("../global.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. -pub fn init(alloc: Allocator) Pager { - return .{ .child = initPager(alloc) }; +pub fn init() Pager { + return .{ .child = initPager() }; } /// 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(global.io(), buffer); } else { - self.file_writer = std.fs.File.stdout().writer(buffer); + self.file_writer = std.Io.File.stdout().writer(global.io(), buffer); } return &self.file_writer.interface; } @@ -40,45 +42,44 @@ 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(global.io()); child.stdin = null; } - _ = child.wait() catch {}; + _ = child.wait(global.io()) catch {}; } self.* = undefined; } -fn initPager(alloc: Allocator) ?std.process.Child { - const stdout_file: std.fs.File = .stdout(); - if (!stdout_file.isTty()) return null; +fn initPager() ?std.process.Child { + const stdout_file: std.Io.File = .stdout(); + if (!(stdout_file.isTty(global.io()) catch return null)) return null; + + var env = global.environMap() 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; - - child.spawn() catch return null; - return child; + return std.process.spawn(global.io(), .{ + .argv = &.{cmd.?}, + .stdin = .pipe, + .stdout = .inherit, + .stderr = .inherit, + }) catch null; } test "pager: non-tty" { - var pager: Pager = .init(std.testing.allocator); + var pager: Pager = .init(); defer pager.deinit(); try std.testing.expect(pager.child == null); } diff --git a/src/cli/action.zig b/src/cli/action.zig index 41173a9f1..6cedaa096 100644 --- a/src/cli/action.zig +++ b/src/cli/action.zig @@ -11,8 +11,8 @@ 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); +pub fn detectArgs(comptime E: type, alloc: Allocator, args: std.process.Args) !?E { + var iter = try args.iterateAllocator(alloc); defer iter.deinit(); return try detectIter(E, &iter); } @@ -83,7 +83,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 std.process.Args.IteratorGeneral(.{}).init( alloc, "+foo", ); @@ -97,7 +97,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 std.process.Args.IteratorGeneral(.{}).init( alloc, "+invalid", ); @@ -113,7 +113,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 std.process.Args.IteratorGeneral(.{}).init( alloc, "+foo +bar", ); @@ -129,7 +129,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 std.process.Args.IteratorGeneral(.{}).init( alloc, "--some-flag", ); @@ -154,7 +154,7 @@ test "detect special case action" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "--special +bar", ); @@ -164,7 +164,7 @@ test "detect special case action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "+bar --special", ); @@ -174,7 +174,7 @@ test "detect special case action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "+bar", ); @@ -200,7 +200,7 @@ test "detect special case fallback" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "--special", ); @@ -210,7 +210,7 @@ test "detect special case fallback" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "+bar --special", ); @@ -220,7 +220,7 @@ test "detect special case fallback" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "--special +bar", ); @@ -246,7 +246,7 @@ test "detect special case abort_if_no_action" { }; { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "-e", ); @@ -256,7 +256,7 @@ test "detect special case abort_if_no_action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "+foo -e", ); @@ -266,7 +266,7 @@ test "detect special case abort_if_no_action" { } { - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( alloc, "-e +bar", ); diff --git a/src/cli/args.zig b/src/cli/args.zig index bd5060d69..d73277fda 100644 --- a/src/cli/args.zig +++ b/src/cli/args.zig @@ -489,18 +489,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 +672,7 @@ test "parse: simple" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a=42 --b --b-f=false", ); @@ -689,7 +684,7 @@ test "parse: simple" { try testing.expect(!data.@"b-f"); // Reparsing works - var iter2 = try std.process.ArgIteratorGeneral(.{}).init( + var iter2 = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a=84", ); @@ -711,7 +706,7 @@ test "parse: quoted value" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a=\"42\" --b=\"hello!\"", ); @@ -731,7 +726,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 std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a= --b=", ); @@ -750,7 +745,7 @@ test "parse: positional arguments are invalid" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a=84 what", ); @@ -774,7 +769,7 @@ test "parse: diagnostic tracking" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--what --a=42", ); @@ -858,7 +853,7 @@ test "parse: compatibility handler" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--a=yuh", ); @@ -884,7 +879,7 @@ test "parse: compatibility renamed" { } = .{}; defer if (data._arena) |arena| arena.deinit(); - var iter = try std.process.ArgIteratorGeneral(.{}).init( + var iter = try std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--old=true --b=true", ); @@ -1363,8 +1358,11 @@ pub fn ArgsIterator(comptime Iterator: type) type { } /// Create an args iterator for the process args. This will skip argv0. -pub fn argsIterator(alloc_gpa: Allocator) internal_os.args.ArgIterator.InitError!ArgsIterator(internal_os.args.ArgIterator) { - var iter = try internal_os.args.iterator(alloc_gpa); +pub fn argsIterator( + alloc_gpa: Allocator, + args: std.process.Args, +) std.process.Args.Iterator.InitError!ArgsIterator(std.process.Args.Iterator) { + var iter: std.process.Args.Iterator = try .initAllocator(args, alloc_gpa); errdefer iter.deinit(); _ = iter.next(); // skip argv0 return .{ .iterator = iter }; @@ -1373,7 +1371,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 std.process.Args.IteratorGeneral(.{}).init( testing.allocator, "--what +list-things --a=42", ); diff --git a/src/cli/boo.zig b/src/cli/boo.zig index 2834eadbd..a33d56ae5 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 global = @import("../global.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 global.environMap(); + defer env_map.deinit(); + // Disable on non-desktop systems. switch (builtin.os.tag) { .windows, .macos, .linux, .freebsd => {}, @@ -183,7 +187,7 @@ pub fn run(gpa: Allocator) !u8 { defer opts.deinit(); { - var iter = try args.argsIterator(gpa); + var iter = try args.argsIterator(gpa, global.args()); defer iter.deinit(); try args.parse(Options, gpa, &opts, &iter); } @@ -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(global.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..2c0484e96 100644 --- a/src/cli/crash_report.zig +++ b/src/cli/crash_report.zig @@ -4,6 +4,7 @@ const args = @import("args.zig"); const Action = @import("ghostty.zig").Action; const Config = @import("../config.zig").Config; const crash = @import("../crash/main.zig"); +const global = @import("../global.zig"); pub const Options = struct { pub fn deinit(self: Options) void { @@ -33,14 +34,14 @@ pub fn run(alloc_gpa: Allocator) !u8 { defer opts.deinit(); { - var iter = try args.argsIterator(alloc_gpa); + var iter = try args.argsIterator(alloc_gpa, global.args()); defer iter.deinit(); try args.parse(Options, alloc_gpa, &opts, &iter); } 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(global.io(), &buffer); const stdout = &stdout_writer.interface; const result = runInner(alloc, &stdout_file, stdout); @@ -50,7 +51,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 +67,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(global.io())) { try stdout.writeAll("No crash reports! 👻\n"); } return 0; @@ -76,7 +77,7 @@ fn runInner( for (reports.items) |report| { var buf: [128]u8 = undefined; - const now = std.time.nanoTimestamp(); + const now = std.Io.Timestamp.now(global.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..9662ea444 100644 --- a/src/cli/edit_config.zig +++ b/src/cli/edit_config.zig @@ -7,6 +7,7 @@ const Action = @import("ghostty.zig").Action; const configpkg = @import("../config.zig"); const internal_os = @import("../os/main.zig"); const Config = configpkg.Config; +const global = @import("../global.zig"); pub const Options = struct { pub fn deinit(self: Options) void { @@ -48,14 +49,14 @@ 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(global.io(), &buffer); const stderr = &stderr_writer.interface; var opts: Options = .{}; defer opts.deinit(); { - var iter = try args.argsIterator(alloc); + var iter = try args.argsIterator(alloc, global.args()); defer iter.deinit(); try args.parse(Options, alloc, &opts, &iter); } @@ -67,6 +68,12 @@ pub fn run(alloc: Allocator) !u8 { } fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { + // We require libc because we want to use std.c.environ for envp + // and not have to build that ourselves. We can remove this + // limitation later but Ghostty already heavily requires libc + // so this is not a big deal. + comptime assert(builtin.link_libc); + // We load the configuration once because that will write our // default configuration files to disk. We don't use the config. var config = try Config.load(alloc); @@ -91,22 +98,19 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { } // Get our editor - const get_env_: ?internal_os.GetEnvResult = env: { + const editor = env: { // VISUAL vs. EDITOR: https://unix.stackexchange.com/questions/4859/visual-vs-editor-what-s-the-difference - if (try internal_os.getenv(alloc, "VISUAL")) |v| { - if (v.value.len > 0) break :env v; - v.deinit(alloc); + if (try global.environ().containsUnempty(alloc, "VISUAL")) { + break :env try global.environ().getAlloc(alloc, "VISUAL"); } - if (try internal_os.getenv(alloc, "EDITOR")) |v| { - if (v.value.len > 0) break :env v; - v.deinit(alloc); + if (try global.environ().containsUnempty(alloc, "EDITOR")) { + break :env try global.environ().getAlloc(alloc, "EDITOR"); } - break :env null; + break :env ""; }; - defer if (get_env_) |v| v.deinit(alloc); - const editor: []const u8 = if (get_env_) |v| v.value else ""; + defer alloc.free(editor); // If we don't have `$EDITOR` set then we can't do anything // but we can still print a helpful message. @@ -136,8 +140,9 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { return 1; } + // Build the command 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); @@ -152,21 +157,18 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { }; defer alloc.free(command); - // We require libc because we want to use std.c.environ for envp - // and not have to build that ourselves. We can remove this - // limitation later but Ghostty already heavily requires libc - // so this is not a big deal. - comptime assert(builtin.link_libc); - - const err = std.posix.execvpeZ( + // Run/replace process (using execve) + const argv = [_:null]?[*:0]const u8{ "/bin/sh", - &.{ "/bin/sh", "-c", command }, - std.c.environ, - ); + "-c", + command.ptr, + }; + const envp = std.c.environ; + const err = std.posix.errno(std.posix.system.execve(argv[0].?, &argv, envp)); // If we reached this point then exec failed. try stderr.print( - \\Failed to execute the editor. Error code={}. + \\Failed to execute the editor (E{s}). \\ \\This is usually due to the executable path not existing, invalid \\permissions, or the shell environment not being set up @@ -175,6 +177,6 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 { \\Editor: {s} \\Path: {s} \\ - , .{ err, editor, path }); + , .{ @tagName(err), editor, path }); return 1; } diff --git a/src/cli/explain_config.zig b/src/cli/explain_config.zig index 4f034afef..6f03256d6 100644 --- a/src/cli/explain_config.zig +++ b/src/cli/explain_config.zig @@ -7,6 +7,7 @@ const Config = @import("../config/Config.zig"); const ConfigKey = @import("../config/key.zig").Key; const KeybindAction = @import("../input/Binding.zig").Action; const Pager = @import("Pager.zig"); +const global = @import("../global.zig"); pub const Options = struct { /// The config option to explain. For example: @@ -51,7 +52,7 @@ pub fn run(alloc: Allocator) !u8 { var positional: ?[]const u8 = null; var no_pager: bool = false; - var iter = try args.argsIterator(alloc); + var iter = try args.argsIterator(alloc, global.args()); defer iter.deinit(); defer if (option_name) |s| alloc.free(s); defer if (keybind_name) |s| alloc.free(s); @@ -75,9 +76,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(global.io(), &buffer); try stderr_writer.interface.writeAll("Usage: ghostty +explain-config