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" );
1
5
const std = @import ("std" );
6
+ const testing = std .testing ;
7
+ const Allocator = std .mem .Allocator ;
2
8
const base = @import ("base.zig" );
3
9
const canonicalize = @import ("check/canonicalize.zig" );
4
10
@@ -19,3 +25,381 @@ pub fn getPackageRootAbsDir(url_data: Package.Url, gpa: std.mem.Allocator) []con
19
25
20
26
@panic ("not implemented" );
21
27
}
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
+ const SaveCacheFileError = error {
172
+ FailedToMakeCacheFolder ,
173
+ FailedToMakeCacheFile ,
174
+ FailedToWriteToCacheFile ,
175
+ };
176
+
177
+ fn saveCacheFile (file_cache_path : []const u8 , file_binary : []const u8 ) SaveCacheFileError ! void {
178
+ const cwd = std .fs .cwd ();
179
+ const roc_cache_folder = std .fs .path .dirname (file_cache_path );
180
+ if (roc_cache_folder ) | x | {
181
+ cwd .makePath (x ) catch {
182
+ return error .FailedToMakeCacheFolder ;
183
+ };
184
+ }
185
+ const file = cwd .createFile (file_cache_path , .{}) catch {
186
+ return SaveCacheFileError .FailedToMakeCacheFile ;
187
+ };
188
+ file .writeAll (file_binary ) catch {
189
+ return SaveCacheFileError .FailedToWriteToCacheFile ;
190
+ };
191
+ }
192
+
193
+ fn saveToCacheInternal (
194
+ // SKILL_ISSUE: force my formatter to put the arguments in a vertical line
195
+ allocator : Allocator ,
196
+ roc_cache_file : CacheFileData ,
197
+ comptime save : fn (file_cache_path : []const u8 , file_binary : []const u8 ) SaveCacheFileError ! void ,
198
+ ) ! void {
199
+ const file_cache_path = switch (roc_cache_file ) {
200
+ .compiler = > | value | try getCompilerPath (allocator , value .version_name ),
201
+ else = > @panic ("FIX ME!!!!!!" ),
202
+ };
203
+
204
+ defer allocator .free (file_cache_path );
205
+
206
+ const file_binary = switch (roc_cache_file ) {
207
+ .compiler = > | value | value .binary ,
208
+ else = > @panic ("FIX ME!!!!!!" ),
209
+ };
210
+
211
+ try save (file_cache_path , file_binary );
212
+ }
213
+
214
+ pub fn saveToCache (allocator : Allocator , roc_cache_file : CacheFileData ) ! void {
215
+ try saveToCacheInternal (allocator , roc_cache_file , saveCacheFile );
216
+ }
217
+
218
+ const GetCacheFileError = error {
219
+ AccessDeniedToCache ,
220
+ Unexpected ,
221
+ OutOfMemory ,
222
+ };
223
+
224
+ fn getCacheFile (allocator : Allocator , file_cache_path : []const u8 ) GetCacheFileError ! ? []const u8 {
225
+ const File = std .fs .File ;
226
+ const OpenError = File .OpenError ;
227
+ const file = std .fs .cwd ().openFile (file_cache_path , .{ .mode = .read_only }) catch | e | {
228
+ return switch (e ) {
229
+ OpenError .FileNotFound = > null ,
230
+ OpenError .AccessDenied = > GetCacheFileError .AccessDeniedToCache ,
231
+ OpenError .NameTooLong = > null ,
232
+ else = > GetCacheFileError .Unexpected ,
233
+ };
234
+ };
235
+ defer file .close ();
236
+
237
+ const GetSeekPosError = File .GetSeekPosError ;
238
+ const file_size = file .getEndPos () catch | e | {
239
+ return switch (e ) {
240
+ GetSeekPosError .AccessDenied = > GetCacheFileError .AccessDeniedToCache ,
241
+ else = > GetCacheFileError .Unexpected ,
242
+ };
243
+ };
244
+ const buf = try allocator .alloc (u8 , file_size );
245
+
246
+ const ReadError = File .ReadError ;
247
+ _ = file .readAll (buf ) catch | e | {
248
+ return switch (e ) {
249
+ ReadError .AccessDenied = > GetCacheFileError .AccessDeniedToCache ,
250
+ else = > GetCacheFileError .Unexpected ,
251
+ };
252
+ };
253
+ return buf ;
254
+ }
255
+
256
+ fn tryToGetCacheFileInternal (allocator : Allocator , cache_file_query : CacheFileQuery ) ! ? []const u8 {
257
+ const file_cache_path = switch (cache_file_query ) {
258
+ .compiler = > | value | try getCompilerPath (allocator , value .version_name ),
259
+ else = > @panic ("FIX ME!!!!!!" ),
260
+ };
261
+ defer allocator .free (file_cache_path );
262
+ return try getCacheFile (allocator , file_cache_path );
263
+ }
264
+
265
+ pub fn tryToGetCacheFile (allocator : Allocator , cache_file_query : CacheFileQuery ) ! ? []const u8 {
266
+ return try tryToGetCacheFileInternal (allocator , cache_file_query );
267
+ }
268
+
269
+ fn create_test_cache_folder (allocator : Allocator ) ! testing.TmpDir {
270
+ const tmp_dir = testing .tmpDir (.{});
271
+ const path = try tmp_dir .dir .realpathAlloc (allocator , "." );
272
+ defer allocator .free (path );
273
+ try setEnvVar (allocator , xdg_cache_home_var_name , path );
274
+ return tmp_dir ;
275
+ }
276
+
277
+ pub fn create_save_stub (allocator : Allocator ) type {
278
+ return struct {
279
+ const Self = @This ();
280
+ var file_cache_path_list = std .ArrayList ([]const u8 ).init (allocator );
281
+ var file_binary_list = std .ArrayList ([]const u8 ).init (allocator );
282
+
283
+ fn deinit () void {
284
+ for (Self .file_cache_path_list .items ) | path | {
285
+ allocator .free (path );
286
+ }
287
+ for (Self .file_binary_list .items ) | binary | {
288
+ allocator .free (binary );
289
+ }
290
+
291
+ Self .file_cache_path_list .deinit ();
292
+ Self .file_binary_list .deinit ();
293
+ }
294
+
295
+ fn closure (file_cache_path : []const u8 , file_binary : []const u8 ) SaveCacheFileError ! void {
296
+ const file_cache_path_copy = allocator .dupe (u8 , file_cache_path ) catch {
297
+ @panic ("Failed to copy cache path string in test" );
298
+ };
299
+ Self .file_cache_path_list .append (file_cache_path_copy ) catch {
300
+ @panic ("Failed to append to file_cache_path_list in test" );
301
+ };
302
+ const file_binary_copy = allocator .dupe (u8 , file_binary ) catch {
303
+ @panic ("Failed to copy cache path string in test" );
304
+ };
305
+ Self .file_binary_list .append (file_binary_copy ) catch {
306
+ @panic ("Failed to append to file_binary_list in test" );
307
+ };
308
+ }
309
+
310
+ fn assert_save_called_with_correct_arguments (file_cache_path : []const u8 , file_binary : []const u8 ) ! void {
311
+ try testing .expectEqual (1 , Self .file_cache_path_list .items .len );
312
+ try testing .expectEqual (1 , Self .file_binary_list .items .len );
313
+ try testing .expectEqualStrings (file_cache_path , Self .file_cache_path_list .items [0 ]);
314
+ try testing .expectEqualStrings (file_binary , Self .file_binary_list .items [0 ]);
315
+ }
316
+ };
317
+ }
318
+
319
+ fn get_home_directory (allocator : Allocator ) ! []u8 {
320
+ return {
321
+ switch (target_os ) {
322
+ .macos , .linux = > {
323
+ return try std .process .getEnvVarOwned (allocator , "HOME" );
324
+ },
325
+ .windows = > {
326
+ return "%APPDATA%" ;
327
+ },
328
+ else = > {
329
+ @compileError ("\" " ++ @tagName (target_os ) ++ "\" is not supported in cache.zig" );
330
+ },
331
+ }
332
+ };
333
+ }
334
+
335
+ test "Should save to XDG_CACHE_HOME if it is set for compiler cache" {
336
+ const allocator = testing .allocator ;
337
+ var tmp_dir = try create_test_cache_folder (allocator );
338
+ defer tmp_dir .cleanup ();
339
+ defer {
340
+ unsetEnvVar (xdg_cache_home_var_name ) catch {
341
+ @panic ("failed to unset " ++ xdg_cache_home_var_name );
342
+ };
343
+ }
344
+ const cache_file = CacheFileData.Compiler {
345
+ .version_name = "1.0.0" ,
346
+ .binary = "A roc compiler binary" ,
347
+ };
348
+ try saveToCache (allocator , CacheFileData { .compiler = cache_file });
349
+
350
+ const binary = try tryToGetCacheFile (allocator , CacheFileQuery { .compiler = CacheFileQuery.Compiler {
351
+ .version_name = cache_file .version_name ,
352
+ } }) orelse @panic ("Binary file should exist" );
353
+ defer allocator .free (binary );
354
+
355
+ try testing .expectEqual (cache_file .binary .len , binary .len );
356
+ try testing .expectEqualStrings (cache_file .binary , binary );
357
+ }
358
+
359
+ test "Should save to .cache folder if XDG_CACHE_HOME is not set for compiler cache" {
360
+ const allocator = testing .allocator ;
361
+ const home_dir = try get_home_directory (allocator );
362
+ defer allocator .free (home_dir );
363
+
364
+ const cache_file = CacheFileData.Compiler {
365
+ .version_name = "1.0.0" ,
366
+ .binary = "A roc compiler binary" ,
367
+ };
368
+
369
+ const save_stub_context = create_save_stub (allocator );
370
+ defer save_stub_context .deinit ();
371
+
372
+ try saveToCacheInternal (allocator , CacheFileData { .compiler = cache_file }, save_stub_context .closure );
373
+
374
+ const expected_binary = cache_file .binary ;
375
+ const expected_file_cache_path = try std .fs .path .join (allocator , &[_ ][]const u8 {
376
+ home_dir ,
377
+ ".cache" ,
378
+ "roc" ,
379
+ "compiler" ,
380
+ cache_file .version_name ,
381
+ });
382
+ defer allocator .free (expected_file_cache_path );
383
+ try save_stub_context .assert_save_called_with_correct_arguments (expected_file_cache_path , expected_binary );
384
+ }
385
+
386
+ test "Should return if there is no cache file" {
387
+ const allocator = testing .allocator ;
388
+ const home_dir = try get_home_directory (allocator );
389
+ defer allocator .free (home_dir );
390
+ const fake_path = try std .fs .path .join (allocator , &[_ ][]const u8 {
391
+ home_dir ,
392
+ "fake-cache" ,
393
+ });
394
+ defer allocator .free (fake_path );
395
+ try setEnvVar (allocator , xdg_cache_home_var_name , fake_path );
396
+ const cache_file_query = CacheFileQuery.Compiler {
397
+ .version_name = "1.0.0" ,
398
+ };
399
+ const binary = try tryToGetCacheFile (allocator , CacheFileQuery { .compiler = cache_file_query });
400
+ if (binary ) | b | {
401
+ defer allocator .free (b );
402
+ }
403
+
404
+ try testing .expectEqual (null , binary );
405
+ }
0 commit comments