diff --git a/build.zig b/build.zig index 149eac8..4ca331c 100644 --- a/build.zig +++ b/build.zig @@ -11,6 +11,7 @@ pub fn build(b: *std.Build) void { const build_options = b.addOptions(); build_options.addOption([]const u8, "version", version); build_options.addOption(bool, "download_geoip", download_geoip); + build_options.addOption([]const u8, "cache_dir", resolveCacheDir(b)); const httpz = b.dependency("httpz", .{ .target = target, @@ -170,3 +171,26 @@ fn configureCompilationUnit(compile: *std.Build.Step.Compile, libs: []const *std // option set where `root_module` is created. for (libs) |lib| compile.root_module.linkLibrary(lib); } + +/// Resolves the runtime cache directory, mirroring `Config.load`'s precedence +/// (`WTTR_CACHE_DIR`, else `${XDG_CACHE_HOME:-$HOME/.cache}/wttr`). +/// +/// Tests need this because Zig 0.16 removed process-environment access outside +/// of `main`; build scripts kept it. Without this, `Config.loadForTest` would +/// fall back to `HOME = "/tmp"` and write test artifacts -- including the +/// GeoLite2 database -- into `/tmp`. +fn resolveCacheDir(b: *std.Build) []const u8 { + const env = &b.graph.environ_map; + if (env.get("WTTR_CACHE_DIR")) |dir| return b.dupe(dir); + + const xdg_cache = if (env.get("XDG_CACHE_HOME")) |x| + b.dupe(x) + else if (env.get("HOME")) |home| + b.pathJoin(&.{ home, ".cache" }) + else + // No home directory to work from; keep artifacts inside the build + // cache rather than falling back to a world-writable location. + b.pathFromRoot(".zig-cache"); + + return b.pathJoin(&.{ xdg_cache, "wttr" }); +} diff --git a/src/Config.zig b/src/Config.zig index 1916f77..4f66aec 100644 --- a/src/Config.zig +++ b/src/Config.zig @@ -90,14 +90,22 @@ pub fn load(allocator: std.mem.Allocator, env: *const std.process.Environ.Map) ! }; } -/// Loads configuration from an empty environment, for tests. +/// Loads configuration for tests. /// /// Zig 0.16 removed any way to reach the process environment outside of `main`, -/// and tests are better off not depending on ambient variables anyway: this -/// keeps them reproducible regardless of the developer's shell. +/// so tests cannot resolve `HOME`/`XDG_CACHE_HOME` themselves. Left to an empty +/// environment, `load` would fall back to `HOME = "/tmp"` and scatter test +/// artifacts (including the ~63 MB GeoLite2 database that `-Ddownload-geoip` +/// fetches) into `/tmp`. +/// +/// Instead `build.zig` resolves the cache directory from its own environment -- +/// build scripts still have access -- and passes it through `build_options`, so +/// tests read and write the same location the server uses. Everything else is +/// left unset so tests do not depend on the developer's shell. pub fn loadForTest(allocator: std.mem.Allocator) !Config { var env: std.process.Environ.Map = .init(allocator); defer env.deinit(); + try env.put("WTTR_CACHE_DIR", @import("build_options").cache_dir); return load(allocator, &env); }