diff --git a/src/crash/sentry.zig b/src/crash/sentry.zig index 14f2e484c4..fba20067d8 100644 --- a/src/crash/sentry.zig +++ b/src/crash/sentry.zig @@ -101,7 +101,10 @@ fn initThread(gpa: Allocator) !void { sentry.c.sentry_options_set_before_send(opts, beforeSend, null); // Determine the Sentry cache directory. - const cache_dir = try internal_os.xdg.cache(alloc, .{ .subdir = "ghostty/sentry" }); + const cache_dir = if (builtin.os.tag == .macos) + try internal_os.macos.cacheDir(alloc, "ghostty/sentry") + else + try internal_os.xdg.cache(alloc, .{ .subdir = "ghostty/sentry" }); sentry.c.sentry_options_set_database_path_n( opts, cache_dir.ptr, diff --git a/src/os/macos.zig b/src/os/macos.zig index 53dfd17199..83c0a693d8 100644 --- a/src/os/macos.zig +++ b/src/os/macos.zig @@ -25,43 +25,23 @@ pub fn appSupportDir( alloc: Allocator, sub_path: []const u8, ) AppSupportDirError![]u8 { - comptime assert(builtin.target.isDarwin()); - - const NSFileManager = objc.getClass("NSFileManager").?; - const manager = NSFileManager.msgSend( - objc.Object, - objc.sel("defaultManager"), - .{}, - ); - - const url = manager.msgSend( - objc.Object, - objc.sel("URLForDirectory:inDomain:appropriateForURL:create:error:"), - .{ - NSSearchPathDirectory.NSApplicationSupportDirectory, - NSSearchPathDomainMask.NSUserDomainMask, - @as(?*anyopaque, null), - true, - @as(?*anyopaque, null), - }, - ); - - // I don't think this is possible but just in case. - if (url.value == null) return error.AppleAPIFailed; - - // Get the UTF-8 string from the URL. - const path = url.getProperty(objc.Object, "path"); - const c_str = path.getProperty(?[*:0]const u8, "UTF8String") orelse - return error.AppleAPIFailed; - const app_support_dir = std.mem.sliceTo(c_str, 0); - - return try std.fs.path.join(alloc, &.{ - app_support_dir, + return try makeCommonPath(alloc, .NSApplicationSupportDirectory, &.{ build_config.bundle_id, sub_path, }); } +pub const CacheDirError = Allocator.Error || error{AppleAPIFailed}; + +/// Return the path to the system cache directory with the given sub path joined. +/// This allocates the result using the given allocator. +pub fn cacheDir( + alloc: Allocator, + sub_path: []const u8, +) CacheDirError![]u8 { + return try makeCommonPath(alloc, .NSCachesDirectory, &.{sub_path}); +} + pub const SetQosClassError = error{ // The thread can't have its QoS class changed usually because // a different pthread API was called that makes it an invalid @@ -110,9 +90,74 @@ pub const NSOperatingSystemVersion = extern struct { }; pub const NSSearchPathDirectory = enum(c_ulong) { + NSCachesDirectory = 13, NSApplicationSupportDirectory = 14, }; pub const NSSearchPathDomainMask = enum(c_ulong) { NSUserDomainMask = 1, }; + +fn makeCommonPath( + alloc: Allocator, + directory: NSSearchPathDirectory, + sub_paths: []const []const u8, +) (error{AppleAPIFailed} || Allocator.Error)![]u8 { + comptime assert(builtin.target.isDarwin()); + + const NSFileManager = objc.getClass("NSFileManager").?; + const manager = NSFileManager.msgSend( + objc.Object, + objc.sel("defaultManager"), + .{}, + ); + + const url = manager.msgSend( + objc.Object, + objc.sel("URLForDirectory:inDomain:appropriateForURL:create:error:"), + .{ + directory, + NSSearchPathDomainMask.NSUserDomainMask, + @as(?*anyopaque, null), + true, + @as(?*anyopaque, null), + }, + ); + + if (url.value == null) return error.AppleAPIFailed; + + const path = url.getProperty(objc.Object, "path"); + const c_str = path.getProperty(?[*:0]const u8, "UTF8String") orelse + return error.AppleAPIFailed; + const base_dir = std.mem.sliceTo(c_str, 0); + + // Create a new array with base_dir as the first element + var paths = try alloc.alloc([]const u8, sub_paths.len + 1); + paths[0] = base_dir; + @memcpy(paths[1..], sub_paths); + defer alloc.free(paths); + + return try std.fs.path.join(alloc, paths); +} + +test "cacheDir paths" { + if (!builtin.target.isDarwin()) return; + + const testing = std.testing; + const alloc = testing.allocator; + + // Test base path + { + const cache_path = try cacheDir(alloc, ""); + defer alloc.free(cache_path); + // We don't test the exact path since it comes from NSFileManager + try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null); + } + + // Test with subdir + { + const cache_path = try cacheDir(alloc, "ghostty"); + defer alloc.free(cache_path); + try testing.expect(std.mem.indexOf(u8, cache_path, "Caches/ghostty") != null); + } +} diff --git a/src/os/xdg.zig b/src/os/xdg.zig index 1d60374efc..5984e31ea5 100644 --- a/src/os/xdg.zig +++ b/src/os/xdg.zig @@ -7,6 +7,7 @@ const assert = std.debug.assert; const Allocator = std.mem.Allocator; const posix = std.posix; const homedir = @import("homedir.zig"); +const macos = @import("macos.zig"); pub const Options = struct { /// Subdirectories to join to the base. This avoids extra allocations @@ -30,18 +31,9 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 { /// Get the XDG cache directory. The returned value is allocated. pub fn cache(alloc: Allocator, opts: Options) ![]u8 { - // On macOS we should use ~/Library/Caches instead of ~/.cache - if (builtin.os.tag == .macos) { - // Get our home dir if not provided - const home = if (opts.home) |h| h else blk: { - var buf: [1024]u8 = undefined; - break :blk try homedir.home(&buf) orelse return error.NoHomeDir; - }; - + if (posix.getenv("XDG_CACHE_HOME")) |env| { return try std.fs.path.join(alloc, &[_][]const u8{ - home, - "Library", - "Caches", + env, opts.subdir orelse "", }); } @@ -164,28 +156,8 @@ test "cache directory paths" { const alloc = testing.allocator; const mock_home = "/Users/test"; - // Test macOS path - if (builtin.os.tag == .macos) { - // Test base path - { - const cache_path = try cache(alloc, .{ .home = mock_home }); - defer alloc.free(cache_path); - try testing.expectEqualStrings("/Users/test/Library/Caches", cache_path); - } - - // Test with subdir - { - const cache_path = try cache(alloc, .{ - .home = mock_home, - .subdir = "ghostty", - }); - defer alloc.free(cache_path); - try testing.expectEqualStrings("/Users/test/Library/Caches/ghostty", cache_path); - } - } - - // Test Linux path (when XDG_CACHE_HOME is not set) - if (builtin.os.tag == .linux) { + // Test when XDG_CACHE_HOME is not set + { // Test base path { const cache_path = try cache(alloc, .{ .home = mock_home });