Skip to content

Commit 5f0391b

Browse files
committed
Added cache logic
1 parent ea336d3 commit 5f0391b

File tree

1 file changed

+329
-0
lines changed

1 file changed

+329
-0
lines changed

src/cache.zig

+329
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
// Generally Questions
2+
// How will the compiler/roc symlink be set?
3+
// Spec here https://github.com/roc-lang/roc/issues/7517
4+
const builtin = @import("builtin");
15
const std = @import("std");
6+
const testing = std.testing;
7+
const Allocator = std.mem.Allocator;
28
const base = @import("base.zig");
39
const canonicalize = @import("check/canonicalize.zig");
410

@@ -19,3 +25,326 @@ pub fn getPackageRootAbsDir(url_data: Package.Url, gpa: std.mem.Allocator) []con
1925

2026
@panic("not implemented");
2127
}
28+
29+
pub const CacheFileQuery = union(enum) {
30+
compiler: Compiler,
31+
build,
32+
packages_src,
33+
packages_build,
34+
35+
pub const Compiler = struct {
36+
version_name: []const u8,
37+
};
38+
};
39+
40+
pub const CacheFileData = union(enum) {
41+
compiler: Compiler,
42+
build,
43+
packages_src,
44+
packages_build,
45+
46+
pub const Compiler = struct {
47+
version_name: []const u8,
48+
binary: []const u8,
49+
};
50+
};
51+
52+
const target_os = builtin.target.os.tag;
53+
fn setEnvVar(allocator: Allocator, name: []const u8, value: []const u8) !void {
54+
// SKILL_ISSUE: it's required to use -lc to run the tests because of the
55+
// c library calls. Is there a better way to run a single test file?
56+
// zig test ./src/cache.zig -lc
57+
const c_str = try allocator.dupeZ(u8, value);
58+
defer allocator.free(c_str);
59+
60+
return {
61+
switch (target_os) {
62+
.macos, .linux => {
63+
const c = @cImport({
64+
@cInclude("stdlib.h");
65+
});
66+
if (c.setenv(name.ptr, c_str.ptr, 1) != 0) {
67+
return error.SetEnvFailed;
68+
}
69+
},
70+
//.windows => {
71+
// TODO: figure out windows implementation
72+
//const c = @cImport({
73+
//@cInclude("windows.h");
74+
//});
75+
//if (c.SetEnvironmentVariableA(name.ptr, value.ptr) == 0) {
76+
//return error.SetEnvFailed;
77+
//}
78+
//},
79+
else => {
80+
@compileError("\"" ++ @tagName(target_os) ++ "\" is not supported in cache.zig");
81+
},
82+
}
83+
};
84+
}
85+
86+
pub fn unsetEnvVar(name: []const u8) !void {
87+
// SKILL_ISSUE: it's required to use -lc to run the tests because of the
88+
// c library calls. Is there a better way to run a single test file?
89+
// zig test ./src/cache.zig -lc
90+
return {
91+
switch (target_os) {
92+
.macos, .linux => {
93+
const c = @cImport({
94+
@cInclude("stdlib.h");
95+
});
96+
if (c.unsetenv(name.ptr) != 0) {
97+
return error.UnsetEnvFailed;
98+
}
99+
},
100+
// FIX_FOR_WINDOWS:
101+
//.windows => {
102+
// TODO: figure out windows implementation
103+
//const c = @cImport({
104+
//@cInclude("windows.h");
105+
//});
106+
//if (c.SetEnvironmentVariableA(name.ptr, value.ptr) == 0) {
107+
//return error.SetEnvFailed;
108+
//}
109+
//},
110+
else => {
111+
@compileError("\"" ++ @tagName(target_os) ++ "\" is not supported in cache.zig");
112+
},
113+
}
114+
};
115+
}
116+
117+
fn getDefaultCacheFolder(allocator: Allocator) ![]u8 {
118+
return {
119+
switch (target_os) {
120+
.macos, .linux => {
121+
const home_dir = try std.process.getEnvVarOwned(allocator, "HOME");
122+
defer allocator.free(home_dir);
123+
const default_cache_folder_parts = [_][]const u8{
124+
home_dir,
125+
".cache",
126+
};
127+
const default_cache_folder = try std.fs.path.join(allocator, &default_cache_folder_parts);
128+
return default_cache_folder;
129+
},
130+
.windows => {
131+
const default_cache_folder_parts = [_][]const u8{"%APPDATA%"};
132+
const default_cache_folder = try std.fs.path.join(allocator, &default_cache_folder_parts);
133+
return default_cache_folder;
134+
},
135+
else => {
136+
@compileError("\"" ++ @tagName(target_os) ++ "\" is not supported in cache.zig");
137+
},
138+
}
139+
};
140+
}
141+
142+
const xdg_cache_home_var_name = "XDG_CACHE_HOME";
143+
fn getBaseRocCacheFolder(allocator: Allocator, sub_path: []const u8) ![]u8 {
144+
const xdg_cache_home = std.process.getEnvVarOwned(allocator, xdg_cache_home_var_name) catch null;
145+
defer {
146+
if (xdg_cache_home) |x| {
147+
allocator.free(x);
148+
}
149+
}
150+
const base_dir = if (xdg_cache_home) |x|
151+
try allocator.dupe(u8, x)
152+
else
153+
try getDefaultCacheFolder(allocator);
154+
155+
defer allocator.free(base_dir);
156+
const roc_cache_parts = [_][]const u8{
157+
base_dir,
158+
"roc",
159+
sub_path,
160+
};
161+
return try std.fs.path.join(allocator, &roc_cache_parts);
162+
}
163+
164+
fn getCompilerPath(allocator: Allocator, version_name: []const u8) ![]u8 {
165+
const sub_path_parts = [_][]const u8{ "compiler", version_name };
166+
const sub_path = try std.fs.path.join(allocator, &sub_path_parts);
167+
defer allocator.free(sub_path);
168+
return try getBaseRocCacheFolder(allocator, sub_path);
169+
}
170+
171+
fn saveCacheFile(file_cache_path: []const u8, file_binary: []const u8) !void {
172+
const cwd = std.fs.cwd();
173+
const roc_cache_folder = std.fs.path.dirname(file_cache_path);
174+
if (roc_cache_folder) |x| {
175+
cwd.makePath(x) catch {
176+
return error.FailedToMakeCacheFolder;
177+
};
178+
}
179+
const file = cwd.createFile(file_cache_path, .{}) catch {
180+
return error.FailedToMakeCacheFile;
181+
};
182+
file.writeAll(file_binary) catch {
183+
return error.FailedToWriteToCacheFile;
184+
};
185+
}
186+
187+
pub fn saveToCache(allocator: Allocator, roc_cache_file: CacheFileData) !void {
188+
const file_cache_path = switch (roc_cache_file) {
189+
.compiler => |value| try getCompilerPath(allocator, value.version_name),
190+
else => @panic("FIX ME!!!!!!"),
191+
};
192+
193+
defer allocator.free(file_cache_path);
194+
195+
const file_binary = switch (roc_cache_file) {
196+
.compiler => |value| value.binary,
197+
else => @panic("FIX ME!!!!!!"),
198+
};
199+
200+
try saveCacheFile(file_cache_path, file_binary);
201+
}
202+
203+
fn getCacheFile(allocator: Allocator, file_cache_path: []const u8) !?[]const u8 {
204+
const File = std.fs.File;
205+
const OpenError = File.OpenError;
206+
const file = std.fs.cwd().openFile(file_cache_path, .{ .mode = .read_only }) catch |e| {
207+
return switch (e) {
208+
OpenError.FileNotFound => null,
209+
OpenError.AccessDenied => error.AccessDeniedToCache,
210+
OpenError.NameTooLong => null,
211+
else => error.Unexpected,
212+
};
213+
};
214+
defer file.close();
215+
216+
const GetSeekPosError = File.GetSeekPosError;
217+
const file_size = file.getEndPos() catch |e| {
218+
return switch (e) {
219+
GetSeekPosError.AccessDenied => error.AccessDeniedToCache,
220+
else => error.Unexpected,
221+
};
222+
};
223+
const buf = try allocator.alloc(u8, file_size);
224+
225+
const ReadError = File.ReadError;
226+
_ = file.readAll(buf) catch |e| {
227+
return switch (e) {
228+
ReadError.AccessDenied => error.AccessDeniedToCache,
229+
else => error.Unexpected,
230+
};
231+
};
232+
return buf;
233+
}
234+
235+
fn tryToGetCacheFile(allocator: Allocator, cache_file_query: CacheFileQuery) !?[]const u8 {
236+
const file_cache_path = switch (cache_file_query) {
237+
.compiler => |value| try getCompilerPath(allocator, value.version_name),
238+
else => @panic("FIX ME!!!!!!"),
239+
};
240+
defer allocator.free(file_cache_path);
241+
return try getCacheFile(allocator, file_cache_path);
242+
}
243+
244+
fn create_test_cache_folder(allocator: Allocator) !testing.TmpDir {
245+
const tmp_dir = testing.tmpDir(.{});
246+
const path = try tmp_dir.dir.realpathAlloc(allocator, ".");
247+
defer allocator.free(path);
248+
try setEnvVar(allocator, xdg_cache_home_var_name, path);
249+
return tmp_dir;
250+
}
251+
252+
fn get_home_directory(allocator: Allocator) ![]u8 {
253+
return {
254+
switch (target_os) {
255+
.macos, .linux => {
256+
return try std.process.getEnvVarOwned(allocator, "HOME");
257+
},
258+
.windows => {
259+
return "%APPDATA%";
260+
},
261+
else => {
262+
@compileError("\"" ++ @tagName(target_os) ++ "\" is not supported in cache.zig");
263+
},
264+
}
265+
};
266+
}
267+
268+
// SKILL_ISSUE: this seems like a really hack way to check if a file exists
269+
fn file_exists(path: []const u8) bool {
270+
var fileExists = true;
271+
std.fs.cwd().access(path, .{}) catch |err| {
272+
fileExists = if (err == error.FileNotFound) false else true;
273+
};
274+
return fileExists;
275+
}
276+
277+
test "Should save to XDG_CACHE_HOME if it is set for compiler cache" {
278+
const allocator = testing.allocator;
279+
var tmp_dir = try create_test_cache_folder(allocator);
280+
defer tmp_dir.cleanup();
281+
defer {
282+
unsetEnvVar(xdg_cache_home_var_name) catch {
283+
@panic("failed to unset " ++ xdg_cache_home_var_name);
284+
};
285+
}
286+
const cache_file = CacheFileData.Compiler{
287+
.version_name = "1.0.0",
288+
.binary = "A roc compiler binary",
289+
};
290+
try saveToCache(allocator, CacheFileData{ .compiler = cache_file });
291+
292+
const binary = try tryToGetCacheFile(allocator, CacheFileQuery{ .compiler = CacheFileQuery.Compiler{
293+
.version_name = cache_file.version_name,
294+
} }) orelse @panic("Binary file should exist");
295+
defer allocator.free(binary);
296+
297+
try testing.expectEqual(cache_file.binary.len, binary.len);
298+
try testing.expectEqualStrings(cache_file.binary, binary);
299+
300+
const tmp_dir_path = try tmp_dir.dir.realpathAlloc(allocator, ".");
301+
defer allocator.free(tmp_dir_path);
302+
const expected_cache_dir = try std.fs.path.join(allocator, &[_][]const u8{
303+
tmp_dir_path,
304+
"roc",
305+
"compiler",
306+
cache_file.version_name,
307+
});
308+
defer allocator.free(expected_cache_dir);
309+
310+
try testing.expect(file_exists(expected_cache_dir));
311+
}
312+
313+
test "Should get correct base cache folder" {
314+
const allocator = testing.allocator;
315+
const home_dir = try get_home_directory(allocator);
316+
defer allocator.free(home_dir);
317+
318+
const actual_cache_dir = try getBaseRocCacheFolder(allocator, "compiler");
319+
defer allocator.free(actual_cache_dir);
320+
const expected_cache_dir = try std.fs.path.join(allocator, &[_][]const u8{
321+
home_dir,
322+
".cache",
323+
"roc",
324+
"compiler",
325+
});
326+
defer allocator.free(expected_cache_dir);
327+
328+
try testing.expectEqualStrings(expected_cache_dir, actual_cache_dir);
329+
}
330+
331+
test "Should return if null there is no cache file" {
332+
const allocator = testing.allocator;
333+
const home_dir = try get_home_directory(allocator);
334+
defer allocator.free(home_dir);
335+
const fake_path = try std.fs.path.join(allocator, &[_][]const u8{
336+
home_dir,
337+
"fake-cache",
338+
});
339+
defer allocator.free(fake_path);
340+
try setEnvVar(allocator, xdg_cache_home_var_name, fake_path);
341+
const cache_file_query = CacheFileQuery.Compiler{
342+
.version_name = "1.0.0",
343+
};
344+
const binary = try tryToGetCacheFile(allocator, CacheFileQuery{ .compiler = cache_file_query });
345+
if (binary) |b| {
346+
defer allocator.free(b);
347+
}
348+
349+
try testing.expectEqual(null, binary);
350+
}

0 commit comments

Comments
 (0)