mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
Use platform-specific cache paths and respect XDG_CACHE_HOME
(#3458)
## Description: Use proper platform-specific methods to determine cache directory paths: 1. First check `XDG_CACHE_HOME` environment variable 2. On macOS: - Use `NSFileManager.URLForDirectory` to get system cache path (`~/Library/Caches`) - Use bundle ID (`com.mitchellh.ghostty`) as base directory to follow macOS conventions 3. Fall back to XDG spec defaults for other platforms (`~/.cache`) ## Changes: - Extract common NSFileManager path lookup logic into `makeCommonPath` helper - Use `NSFileManager.URLForDirectory` to get proper macOS cache directory - Use bundle ID for macOS cache directory to match system conventions and `appSupportDir` behavior - Follows the [same pattern as our configuration file path](https://ghostty.org/docs/config#macos-specific-path-(macos-only):) (`~/Library/Application Support/com.mitchellh.ghostty/config`) - Aligns with `appSupportDir` implementation which already uses bundle ID - Add tests to verify path construction for different platforms ## Questions for reviewers: 1. Should we add migration logic for existing cache files? 2. Or should we document this as a breaking change and let users manually clean up? ## Testing: 1. Added unit tests for path construction: - macOS: verifies `~/Library/Caches/com.mitchellh.ghostty` path - Linux: verifies `~/.cache/ghostty` path (XDG spec) 2. Verified tests pass with `zig build test` Fixes #3202
This commit is contained in:
@ -101,7 +101,23 @@ fn initThread(gpa: Allocator) !void {
|
|||||||
sentry.c.sentry_options_set_before_send(opts, beforeSend, null);
|
sentry.c.sentry_options_set_before_send(opts, beforeSend, null);
|
||||||
|
|
||||||
// Determine the Sentry cache directory.
|
// Determine the Sentry cache directory.
|
||||||
const cache_dir = try internal_os.xdg.cache(alloc, .{ .subdir = "ghostty/sentry" });
|
const cache_dir = cache_dir: {
|
||||||
|
// On macOS, we prefer to use the NSCachesDirectory value to be
|
||||||
|
// a more idiomatic macOS application. But if XDG env vars are set
|
||||||
|
// we will respect them.
|
||||||
|
if (comptime builtin.os.tag == .macos) macos: {
|
||||||
|
if (std.posix.getenv("XDG_CACHE_HOME") != null) break :macos;
|
||||||
|
break :cache_dir try internal_os.macos.cacheDir(
|
||||||
|
alloc,
|
||||||
|
"sentry",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :cache_dir try internal_os.xdg.cache(
|
||||||
|
alloc,
|
||||||
|
.{ .subdir = "ghostty/sentry" },
|
||||||
|
);
|
||||||
|
};
|
||||||
sentry.c.sentry_options_set_database_path_n(
|
sentry.c.sentry_options_set_database_path_n(
|
||||||
opts,
|
opts,
|
||||||
cache_dir.ptr,
|
cache_dir.ptr,
|
||||||
|
119
src/os/macos.zig
119
src/os/macos.zig
@ -25,41 +25,26 @@ pub fn appSupportDir(
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
sub_path: []const u8,
|
sub_path: []const u8,
|
||||||
) AppSupportDirError![]const u8 {
|
) AppSupportDirError![]const u8 {
|
||||||
comptime assert(builtin.target.isDarwin());
|
return try commonDir(
|
||||||
|
alloc,
|
||||||
const NSFileManager = objc.getClass("NSFileManager").?;
|
.NSApplicationSupportDirectory,
|
||||||
const manager = NSFileManager.msgSend(
|
&.{ build_config.bundle_id, sub_path },
|
||||||
objc.Object,
|
|
||||||
objc.sel("defaultManager"),
|
|
||||||
.{},
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const url = manager.msgSend(
|
pub const CacheDirError = Allocator.Error || error{AppleAPIFailed};
|
||||||
objc.Object,
|
|
||||||
objc.sel("URLForDirectory:inDomain:appropriateForURL:create:error:"),
|
/// Return the path to the system cache directory with the given sub path joined.
|
||||||
.{
|
/// This allocates the result using the given allocator.
|
||||||
NSSearchPathDirectory.NSApplicationSupportDirectory,
|
pub fn cacheDir(
|
||||||
NSSearchPathDomainMask.NSUserDomainMask,
|
alloc: Allocator,
|
||||||
@as(?*anyopaque, null),
|
sub_path: []const u8,
|
||||||
true,
|
) CacheDirError![]const u8 {
|
||||||
@as(?*anyopaque, null),
|
return try commonDir(
|
||||||
},
|
alloc,
|
||||||
|
.NSCachesDirectory,
|
||||||
|
&.{ build_config.bundle_id, sub_path },
|
||||||
);
|
);
|
||||||
|
|
||||||
// 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,
|
|
||||||
build_config.bundle_id,
|
|
||||||
sub_path,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SetQosClassError = error{
|
pub const SetQosClassError = error{
|
||||||
@ -110,9 +95,79 @@ pub const NSOperatingSystemVersion = extern struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const NSSearchPathDirectory = enum(c_ulong) {
|
pub const NSSearchPathDirectory = enum(c_ulong) {
|
||||||
|
NSCachesDirectory = 13,
|
||||||
NSApplicationSupportDirectory = 14,
|
NSApplicationSupportDirectory = 14,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NSSearchPathDomainMask = enum(c_ulong) {
|
pub const NSSearchPathDomainMask = enum(c_ulong) {
|
||||||
NSUserDomainMask = 1,
|
NSUserDomainMask = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn commonDir(
|
||||||
|
alloc: Allocator,
|
||||||
|
directory: NSSearchPathDirectory,
|
||||||
|
sub_paths: []const []const u8,
|
||||||
|
) (error{AppleAPIFailed} || Allocator.Error)![]const 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);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, cache_path, build_config.bundle_id) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with subdir
|
||||||
|
{
|
||||||
|
const cache_path = try cacheDir(alloc, "test");
|
||||||
|
defer alloc.free(cache_path);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, cache_path, "Caches") != null);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, cache_path, build_config.bundle_id) != null);
|
||||||
|
|
||||||
|
const bundle_path = try std.fmt.allocPrint(alloc, "{s}/test", .{build_config.bundle_id});
|
||||||
|
defer alloc.free(bundle_path);
|
||||||
|
try testing.expect(std.mem.indexOf(u8, cache_path, bundle_path) != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -143,6 +143,32 @@ test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "cache directory paths" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const mock_home = "/Users/test";
|
||||||
|
|
||||||
|
// Test when XDG_CACHE_HOME is not set
|
||||||
|
{
|
||||||
|
// Test base path
|
||||||
|
{
|
||||||
|
const cache_path = try cache(alloc, .{ .home = mock_home });
|
||||||
|
defer alloc.free(cache_path);
|
||||||
|
try testing.expectEqualStrings("/Users/test/.cache", 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/.cache/ghostty", cache_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test parseTerminalExec {
|
test parseTerminalExec {
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user