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,326 @@ 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
+ 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