blob 162faffc (43923B) - Raw
1 const std = @import("../index.zig"); 2 const builtin = @import("builtin"); 3 const Os = builtin.Os; 4 const debug = std.debug; 5 const assert = debug.assert; 6 const mem = std.mem; 7 const fmt = std.fmt; 8 const Allocator = mem.Allocator; 9 const os = std.os; 10 const math = std.math; 11 const posix = os.posix; 12 const windows = os.windows; 13 const cstr = std.cstr; 14 15 pub const sep_windows = '\\'; 16 pub const sep_posix = '/'; 17 pub const sep = if (is_windows) sep_windows else sep_posix; 18 19 pub const delimiter_windows = ';'; 20 pub const delimiter_posix = ':'; 21 pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix; 22 23 const is_windows = builtin.os == builtin.Os.windows; 24 25 pub fn isSep(byte: u8) bool { 26 if (is_windows) { 27 return byte == '/' or byte == '\\'; 28 } else { 29 return byte == '/'; 30 } 31 } 32 33 /// Naively combines a series of paths with the native path seperator. 34 /// Allocates memory for the result, which must be freed by the caller. 35 pub fn join(allocator: &Allocator, paths: ...) ![]u8 { 36 if (is_windows) { 37 return joinWindows(allocator, paths); 38 } else { 39 return joinPosix(allocator, paths); 40 } 41 } 42 43 pub fn joinWindows(allocator: &Allocator, paths: ...) ![]u8 { 44 return mem.join(allocator, sep_windows, paths); 45 } 46 47 pub fn joinPosix(allocator: &Allocator, paths: ...) ![]u8 { 48 return mem.join(allocator, sep_posix, paths); 49 } 50 51 test "os.path.join" { 52 assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b", "c"), "c:\\a\\b\\c")); 53 assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b\\", "c"), "c:\\a\\b\\c")); 54 55 assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\", "a", "b\\", "c"), "c:\\a\\b\\c")); 56 assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\", "b\\", "c"), "c:\\a\\b\\c")); 57 58 assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"), "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig")); 59 60 assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b", "c"), "/a/b/c")); 61 assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b/", "c"), "/a/b/c")); 62 63 assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c")); 64 assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/", "b/", "c"), "/a/b/c")); 65 66 assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"), "/home/andy/dev/zig/build/lib/zig/std/io.zig")); 67 } 68 69 pub fn isAbsolute(path: []const u8) bool { 70 if (is_windows) { 71 return isAbsoluteWindows(path); 72 } else { 73 return isAbsolutePosix(path); 74 } 75 } 76 77 pub fn isAbsoluteWindows(path: []const u8) bool { 78 if (path[0] == '/') 79 return true; 80 81 if (path[0] == '\\') { 82 return true; 83 } 84 if (path.len < 3) { 85 return false; 86 } 87 if (path[1] == ':') { 88 if (path[2] == '/') 89 return true; 90 if (path[2] == '\\') 91 return true; 92 } 93 return false; 94 } 95 96 pub fn isAbsolutePosix(path: []const u8) bool { 97 return path[0] == sep_posix; 98 } 99 100 test "os.path.isAbsoluteWindows" { 101 testIsAbsoluteWindows("/", true); 102 testIsAbsoluteWindows("//", true); 103 testIsAbsoluteWindows("//server", true); 104 testIsAbsoluteWindows("//server/file", true); 105 testIsAbsoluteWindows("\\\\server\\file", true); 106 testIsAbsoluteWindows("\\\\server", true); 107 testIsAbsoluteWindows("\\\\", true); 108 testIsAbsoluteWindows("c", false); 109 testIsAbsoluteWindows("c:", false); 110 testIsAbsoluteWindows("c:\\", true); 111 testIsAbsoluteWindows("c:/", true); 112 testIsAbsoluteWindows("c://", true); 113 testIsAbsoluteWindows("C:/Users/", true); 114 testIsAbsoluteWindows("C:\\Users\\", true); 115 testIsAbsoluteWindows("C:cwd/another", false); 116 testIsAbsoluteWindows("C:cwd\\another", false); 117 testIsAbsoluteWindows("directory/directory", false); 118 testIsAbsoluteWindows("directory\\directory", false); 119 testIsAbsoluteWindows("/usr/local", true); 120 } 121 122 test "os.path.isAbsolutePosix" { 123 testIsAbsolutePosix("/home/foo", true); 124 testIsAbsolutePosix("/home/foo/..", true); 125 testIsAbsolutePosix("bar/", false); 126 testIsAbsolutePosix("./baz", false); 127 } 128 129 fn testIsAbsoluteWindows(path: []const u8, expected_result: bool) void { 130 assert(isAbsoluteWindows(path) == expected_result); 131 } 132 133 fn testIsAbsolutePosix(path: []const u8, expected_result: bool) void { 134 assert(isAbsolutePosix(path) == expected_result); 135 } 136 137 pub const WindowsPath = struct { 138 is_abs: bool, 139 kind: Kind, 140 disk_designator: []const u8, 141 142 pub const Kind = enum { 143 None, 144 Drive, 145 NetworkShare, 146 }; 147 }; 148 149 pub fn windowsParsePath(path: []const u8) WindowsPath { 150 if (path.len >= 2 and path[1] == ':') { 151 return WindowsPath{ 152 .is_abs = isAbsoluteWindows(path), 153 .kind = WindowsPath.Kind.Drive, 154 .disk_designator = path[0..2], 155 }; 156 } 157 if (path.len >= 1 and (path[0] == '/' or path[0] == '\\') and 158 (path.len == 1 or (path[1] != '/' and path[1] != '\\'))) 159 { 160 return WindowsPath{ 161 .is_abs = true, 162 .kind = WindowsPath.Kind.None, 163 .disk_designator = path[0..0], 164 }; 165 } 166 const relative_path = WindowsPath{ 167 .kind = WindowsPath.Kind.None, 168 .disk_designator = []u8{}, 169 .is_abs = false, 170 }; 171 if (path.len < "//a/b".len) { 172 return relative_path; 173 } 174 175 // TODO when I combined these together with `inline for` the compiler crashed 176 { 177 const this_sep = '/'; 178 const two_sep = []u8{ this_sep, this_sep }; 179 if (mem.startsWith(u8, path, two_sep)) { 180 if (path[2] == this_sep) { 181 return relative_path; 182 } 183 184 var it = mem.split(path, []u8{this_sep}); 185 _ = (it.next() ?? return relative_path); 186 _ = (it.next() ?? return relative_path); 187 return WindowsPath{ 188 .is_abs = isAbsoluteWindows(path), 189 .kind = WindowsPath.Kind.NetworkShare, 190 .disk_designator = path[0..it.index], 191 }; 192 } 193 } 194 { 195 const this_sep = '\\'; 196 const two_sep = []u8{ this_sep, this_sep }; 197 if (mem.startsWith(u8, path, two_sep)) { 198 if (path[2] == this_sep) { 199 return relative_path; 200 } 201 202 var it = mem.split(path, []u8{this_sep}); 203 _ = (it.next() ?? return relative_path); 204 _ = (it.next() ?? return relative_path); 205 return WindowsPath{ 206 .is_abs = isAbsoluteWindows(path), 207 .kind = WindowsPath.Kind.NetworkShare, 208 .disk_designator = path[0..it.index], 209 }; 210 } 211 } 212 return relative_path; 213 } 214 215 test "os.path.windowsParsePath" { 216 { 217 const parsed = windowsParsePath("//a/b"); 218 assert(parsed.is_abs); 219 assert(parsed.kind == WindowsPath.Kind.NetworkShare); 220 assert(mem.eql(u8, parsed.disk_designator, "//a/b")); 221 } 222 { 223 const parsed = windowsParsePath("\\\\a\\b"); 224 assert(parsed.is_abs); 225 assert(parsed.kind == WindowsPath.Kind.NetworkShare); 226 assert(mem.eql(u8, parsed.disk_designator, "\\\\a\\b")); 227 } 228 { 229 const parsed = windowsParsePath("\\\\a\\"); 230 assert(!parsed.is_abs); 231 assert(parsed.kind == WindowsPath.Kind.None); 232 assert(mem.eql(u8, parsed.disk_designator, "")); 233 } 234 { 235 const parsed = windowsParsePath("/usr/local"); 236 assert(parsed.is_abs); 237 assert(parsed.kind == WindowsPath.Kind.None); 238 assert(mem.eql(u8, parsed.disk_designator, "")); 239 } 240 { 241 const parsed = windowsParsePath("c:../"); 242 assert(!parsed.is_abs); 243 assert(parsed.kind == WindowsPath.Kind.Drive); 244 assert(mem.eql(u8, parsed.disk_designator, "c:")); 245 } 246 } 247 248 pub fn diskDesignator(path: []const u8) []const u8 { 249 if (is_windows) { 250 return diskDesignatorWindows(path); 251 } else { 252 return ""; 253 } 254 } 255 256 pub fn diskDesignatorWindows(path: []const u8) []const u8 { 257 return windowsParsePath(path).disk_designator; 258 } 259 260 fn networkShareServersEql(ns1: []const u8, ns2: []const u8) bool { 261 const sep1 = ns1[0]; 262 const sep2 = ns2[0]; 263 264 var it1 = mem.split(ns1, []u8{sep1}); 265 var it2 = mem.split(ns2, []u8{sep2}); 266 267 // TODO ASCII is wrong, we actually need full unicode support to compare paths. 268 return asciiEqlIgnoreCase(??it1.next(), ??it2.next()); 269 } 270 271 fn compareDiskDesignators(kind: WindowsPath.Kind, p1: []const u8, p2: []const u8) bool { 272 switch (kind) { 273 WindowsPath.Kind.None => { 274 assert(p1.len == 0); 275 assert(p2.len == 0); 276 return true; 277 }, 278 WindowsPath.Kind.Drive => { 279 return asciiUpper(p1[0]) == asciiUpper(p2[0]); 280 }, 281 WindowsPath.Kind.NetworkShare => { 282 const sep1 = p1[0]; 283 const sep2 = p2[0]; 284 285 var it1 = mem.split(p1, []u8{sep1}); 286 var it2 = mem.split(p2, []u8{sep2}); 287 288 // TODO ASCII is wrong, we actually need full unicode support to compare paths. 289 return asciiEqlIgnoreCase(??it1.next(), ??it2.next()) and asciiEqlIgnoreCase(??it1.next(), ??it2.next()); 290 }, 291 } 292 } 293 294 fn asciiUpper(byte: u8) u8 { 295 return switch (byte) { 296 'a'...'z' => 'A' + (byte - 'a'), 297 else => byte, 298 }; 299 } 300 301 fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool { 302 if (s1.len != s2.len) 303 return false; 304 var i: usize = 0; 305 while (i < s1.len) : (i += 1) { 306 if (asciiUpper(s1[i]) != asciiUpper(s2[i])) 307 return false; 308 } 309 return true; 310 } 311 312 /// Converts the command line arguments into a slice and calls `resolveSlice`. 313 pub fn resolve(allocator: &Allocator, args: ...) ![]u8 { 314 var paths: [args.len][]const u8 = undefined; 315 comptime var arg_i = 0; 316 inline while (arg_i < args.len) : (arg_i += 1) { 317 paths[arg_i] = args[arg_i]; 318 } 319 return resolveSlice(allocator, paths); 320 } 321 322 /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. 323 pub fn resolveSlice(allocator: &Allocator, paths: []const []const u8) ![]u8 { 324 if (is_windows) { 325 return resolveWindows(allocator, paths); 326 } else { 327 return resolvePosix(allocator, paths); 328 } 329 } 330 331 /// This function is like a series of `cd` statements executed one after another. 332 /// It resolves "." and "..". 333 /// The result does not have a trailing path separator. 334 /// If all paths are relative it uses the current working directory as a starting point. 335 /// Each drive has its own current working directory. 336 /// Path separators are canonicalized to '\\' and drives are canonicalized to capital letters. 337 pub fn resolveWindows(allocator: &Allocator, paths: []const []const u8) ![]u8 { 338 if (paths.len == 0) { 339 assert(is_windows); // resolveWindows called on non windows can't use getCwd 340 return os.getCwd(allocator); 341 } 342 343 // determine which disk designator we will result with, if any 344 var result_drive_buf = "_:"; 345 var result_disk_designator: []const u8 = ""; 346 var have_drive_kind = WindowsPath.Kind.None; 347 var have_abs_path = false; 348 var first_index: usize = 0; 349 var max_size: usize = 0; 350 for (paths) |p, i| { 351 const parsed = windowsParsePath(p); 352 if (parsed.is_abs) { 353 have_abs_path = true; 354 first_index = i; 355 max_size = result_disk_designator.len; 356 } 357 switch (parsed.kind) { 358 WindowsPath.Kind.Drive => { 359 result_drive_buf[0] = asciiUpper(parsed.disk_designator[0]); 360 result_disk_designator = result_drive_buf[0..]; 361 have_drive_kind = WindowsPath.Kind.Drive; 362 }, 363 WindowsPath.Kind.NetworkShare => { 364 result_disk_designator = parsed.disk_designator; 365 have_drive_kind = WindowsPath.Kind.NetworkShare; 366 }, 367 WindowsPath.Kind.None => {}, 368 } 369 max_size += p.len + 1; 370 } 371 372 // if we will result with a disk designator, loop again to determine 373 // which is the last time the disk designator is absolutely specified, if any 374 // and count up the max bytes for paths related to this disk designator 375 if (have_drive_kind != WindowsPath.Kind.None) { 376 have_abs_path = false; 377 first_index = 0; 378 max_size = result_disk_designator.len; 379 var correct_disk_designator = false; 380 381 for (paths) |p, i| { 382 const parsed = windowsParsePath(p); 383 if (parsed.kind != WindowsPath.Kind.None) { 384 if (parsed.kind == have_drive_kind) { 385 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); 386 } else { 387 continue; 388 } 389 } 390 if (!correct_disk_designator) { 391 continue; 392 } 393 if (parsed.is_abs) { 394 first_index = i; 395 max_size = result_disk_designator.len; 396 have_abs_path = true; 397 } 398 max_size += p.len + 1; 399 } 400 } 401 402 // Allocate result and fill in the disk designator, calling getCwd if we have to. 403 var result: []u8 = undefined; 404 var result_index: usize = 0; 405 406 if (have_abs_path) { 407 switch (have_drive_kind) { 408 WindowsPath.Kind.Drive => { 409 result = try allocator.alloc(u8, max_size); 410 411 mem.copy(u8, result, result_disk_designator); 412 result_index += result_disk_designator.len; 413 }, 414 WindowsPath.Kind.NetworkShare => { 415 result = try allocator.alloc(u8, max_size); 416 var it = mem.split(paths[first_index], "/\\"); 417 const server_name = ??it.next(); 418 const other_name = ??it.next(); 419 420 result[result_index] = '\\'; 421 result_index += 1; 422 result[result_index] = '\\'; 423 result_index += 1; 424 mem.copy(u8, result[result_index..], server_name); 425 result_index += server_name.len; 426 result[result_index] = '\\'; 427 result_index += 1; 428 mem.copy(u8, result[result_index..], other_name); 429 result_index += other_name.len; 430 431 result_disk_designator = result[0..result_index]; 432 }, 433 WindowsPath.Kind.None => { 434 assert(is_windows); // resolveWindows called on non windows can't use getCwd 435 const cwd = try os.getCwd(allocator); 436 defer allocator.free(cwd); 437 const parsed_cwd = windowsParsePath(cwd); 438 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1); 439 mem.copy(u8, result, parsed_cwd.disk_designator); 440 result_index += parsed_cwd.disk_designator.len; 441 result_disk_designator = result[0..parsed_cwd.disk_designator.len]; 442 if (parsed_cwd.kind == WindowsPath.Kind.Drive) { 443 result[0] = asciiUpper(result[0]); 444 } 445 have_drive_kind = parsed_cwd.kind; 446 }, 447 } 448 } else { 449 assert(is_windows); // resolveWindows called on non windows can't use getCwd 450 // TODO call get cwd for the result_disk_designator instead of the global one 451 const cwd = try os.getCwd(allocator); 452 defer allocator.free(cwd); 453 454 result = try allocator.alloc(u8, max_size + cwd.len + 1); 455 456 mem.copy(u8, result, cwd); 457 result_index += cwd.len; 458 const parsed_cwd = windowsParsePath(result[0..result_index]); 459 result_disk_designator = parsed_cwd.disk_designator; 460 if (parsed_cwd.kind == WindowsPath.Kind.Drive) { 461 result[0] = asciiUpper(result[0]); 462 } 463 have_drive_kind = parsed_cwd.kind; 464 } 465 errdefer allocator.free(result); 466 467 // Now we know the disk designator to use, if any, and what kind it is. And our result 468 // is big enough to append all the paths to. 469 var correct_disk_designator = true; 470 for (paths[first_index..]) |p, i| { 471 const parsed = windowsParsePath(p); 472 473 if (parsed.kind != WindowsPath.Kind.None) { 474 if (parsed.kind == have_drive_kind) { 475 correct_disk_designator = compareDiskDesignators(have_drive_kind, result_disk_designator, parsed.disk_designator); 476 } else { 477 continue; 478 } 479 } 480 if (!correct_disk_designator) { 481 continue; 482 } 483 var it = mem.split(p[parsed.disk_designator.len..], "/\\"); 484 while (it.next()) |component| { 485 if (mem.eql(u8, component, ".")) { 486 continue; 487 } else if (mem.eql(u8, component, "..")) { 488 while (true) { 489 if (result_index == 0 or result_index == result_disk_designator.len) 490 break; 491 result_index -= 1; 492 if (result[result_index] == '\\' or result[result_index] == '/') 493 break; 494 } 495 } else { 496 result[result_index] = sep_windows; 497 result_index += 1; 498 mem.copy(u8, result[result_index..], component); 499 result_index += component.len; 500 } 501 } 502 } 503 504 if (result_index == result_disk_designator.len) { 505 result[result_index] = '\\'; 506 result_index += 1; 507 } 508 509 return result[0..result_index]; 510 } 511 512 /// This function is like a series of `cd` statements executed one after another. 513 /// It resolves "." and "..". 514 /// The result does not have a trailing path separator. 515 /// If all paths are relative it uses the current working directory as a starting point. 516 pub fn resolvePosix(allocator: &Allocator, paths: []const []const u8) ![]u8 { 517 if (paths.len == 0) { 518 assert(!is_windows); // resolvePosix called on windows can't use getCwd 519 return os.getCwd(allocator); 520 } 521 522 var first_index: usize = 0; 523 var have_abs = false; 524 var max_size: usize = 0; 525 for (paths) |p, i| { 526 if (isAbsolutePosix(p)) { 527 first_index = i; 528 have_abs = true; 529 max_size = 0; 530 } 531 max_size += p.len + 1; 532 } 533 534 var result: []u8 = undefined; 535 var result_index: usize = 0; 536 537 if (have_abs) { 538 result = try allocator.alloc(u8, max_size); 539 } else { 540 assert(!is_windows); // resolvePosix called on windows can't use getCwd 541 const cwd = try os.getCwd(allocator); 542 defer allocator.free(cwd); 543 result = try allocator.alloc(u8, max_size + cwd.len + 1); 544 mem.copy(u8, result, cwd); 545 result_index += cwd.len; 546 } 547 errdefer allocator.free(result); 548 549 for (paths[first_index..]) |p, i| { 550 var it = mem.split(p, "/"); 551 while (it.next()) |component| { 552 if (mem.eql(u8, component, ".")) { 553 continue; 554 } else if (mem.eql(u8, component, "..")) { 555 while (true) { 556 if (result_index == 0) 557 break; 558 result_index -= 1; 559 if (result[result_index] == '/') 560 break; 561 } 562 } else { 563 result[result_index] = '/'; 564 result_index += 1; 565 mem.copy(u8, result[result_index..], component); 566 result_index += component.len; 567 } 568 } 569 } 570 571 if (result_index == 0) { 572 result[0] = '/'; 573 result_index += 1; 574 } 575 576 return result[0..result_index]; 577 } 578 579 test "os.path.resolve" { 580 const cwd = try os.getCwd(debug.global_allocator); 581 if (is_windows) { 582 if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) { 583 cwd[0] = asciiUpper(cwd[0]); 584 } 585 assert(mem.eql(u8, testResolveWindows([][]const u8{"."}), cwd)); 586 } else { 587 assert(mem.eql(u8, testResolvePosix([][]const u8{ "a/b/c/", "../../.." }), cwd)); 588 assert(mem.eql(u8, testResolvePosix([][]const u8{"."}), cwd)); 589 } 590 } 591 592 test "os.path.resolveWindows" { 593 if (is_windows) { 594 const cwd = try os.getCwd(debug.global_allocator); 595 const parsed_cwd = windowsParsePath(cwd); 596 { 597 const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }); 598 const expected = try join(debug.global_allocator, parsed_cwd.disk_designator, "usr\\local\\lib\\zig\\std\\array_list.zig"); 599 if (parsed_cwd.kind == WindowsPath.Kind.Drive) { 600 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]); 601 } 602 assert(mem.eql(u8, result, expected)); 603 } 604 { 605 const result = testResolveWindows([][]const u8{ "usr/local", "lib\\zig" }); 606 const expected = try join(debug.global_allocator, cwd, "usr\\local\\lib\\zig"); 607 if (parsed_cwd.kind == WindowsPath.Kind.Drive) { 608 expected[0] = asciiUpper(parsed_cwd.disk_designator[0]); 609 } 610 assert(mem.eql(u8, result, expected)); 611 } 612 } 613 614 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:\\a\\b\\c", "/hi", "ok" }), "C:\\hi\\ok")); 615 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "c:../a" }), "C:\\blah\\a")); 616 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/blah\\blah", "d:/games", "C:../a" }), "C:\\blah\\a")); 617 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "d:\\a/b\\c/d", "\\e.exe" }), "D:\\e.exe")); 618 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/ignore", "c:/some/file" }), "C:\\some\\file")); 619 assert(mem.eql(u8, testResolveWindows([][]const u8{ "d:/ignore", "d:some/dir//" }), "D:\\ignore\\some\\dir")); 620 assert(mem.eql(u8, testResolveWindows([][]const u8{ "//server/share", "..", "relative\\" }), "\\\\server\\share\\relative")); 621 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//" }), "C:\\")); 622 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//dir" }), "C:\\dir")); 623 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server/share" }), "\\\\server\\share\\")); 624 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "//server//share" }), "\\\\server\\share\\")); 625 assert(mem.eql(u8, testResolveWindows([][]const u8{ "c:/", "///some//dir" }), "C:\\some\\dir")); 626 assert(mem.eql(u8, testResolveWindows([][]const u8{ "C:\\foo\\tmp.3\\", "..\\tmp.3\\cycles\\root.js" }), "C:\\foo\\tmp.3\\cycles\\root.js")); 627 } 628 629 test "os.path.resolvePosix" { 630 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c" }), "/a/b/c")); 631 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b", "c", "//d", "e///" }), "/d/e")); 632 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/a/b/c", "..", "../" }), "/a")); 633 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/", "..", ".." }), "/")); 634 assert(mem.eql(u8, testResolvePosix([][]const u8{"/a/b/c/"}), "/a/b/c")); 635 636 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "../", "file/" }), "/var/file")); 637 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/var/lib", "/../", "file/" }), "/file")); 638 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/some/dir", ".", "/absolute/" }), "/absolute")); 639 assert(mem.eql(u8, testResolvePosix([][]const u8{ "/foo/tmp.3/", "../tmp.3/cycles/root.js" }), "/foo/tmp.3/cycles/root.js")); 640 } 641 642 fn testResolveWindows(paths: []const []const u8) []u8 { 643 return resolveWindows(debug.global_allocator, paths) catch unreachable; 644 } 645 646 fn testResolvePosix(paths: []const []const u8) []u8 { 647 return resolvePosix(debug.global_allocator, paths) catch unreachable; 648 } 649 650 /// If the path is a file in the current directory (no directory component) 651 /// then the returned slice has .len = 0. 652 pub fn dirname(path: []const u8) []const u8 { 653 if (is_windows) { 654 return dirnameWindows(path); 655 } else { 656 return dirnamePosix(path); 657 } 658 } 659 660 pub fn dirnameWindows(path: []const u8) []const u8 { 661 if (path.len == 0) 662 return path[0..0]; 663 664 const root_slice = diskDesignatorWindows(path); 665 if (path.len == root_slice.len) 666 return path; 667 668 const have_root_slash = path.len > root_slice.len and (path[root_slice.len] == '/' or path[root_slice.len] == '\\'); 669 670 var end_index: usize = path.len - 1; 671 672 while ((path[end_index] == '/' or path[end_index] == '\\') and end_index > root_slice.len) { 673 if (end_index == 0) 674 return path[0..0]; 675 end_index -= 1; 676 } 677 678 while (path[end_index] != '/' and path[end_index] != '\\' and end_index > root_slice.len) { 679 if (end_index == 0) 680 return path[0..0]; 681 end_index -= 1; 682 } 683 684 if (have_root_slash and end_index == root_slice.len) { 685 end_index += 1; 686 } 687 688 return path[0..end_index]; 689 } 690 691 pub fn dirnamePosix(path: []const u8) []const u8 { 692 if (path.len == 0) 693 return path[0..0]; 694 695 var end_index: usize = path.len - 1; 696 while (path[end_index] == '/') { 697 if (end_index == 0) 698 return path[0..1]; 699 end_index -= 1; 700 } 701 702 while (path[end_index] != '/') { 703 if (end_index == 0) 704 return path[0..0]; 705 end_index -= 1; 706 } 707 708 if (end_index == 0 and path[end_index] == '/') 709 return path[0..1]; 710 711 return path[0..end_index]; 712 } 713 714 test "os.path.dirnamePosix" { 715 testDirnamePosix("/a/b/c", "/a/b"); 716 testDirnamePosix("/a/b/c///", "/a/b"); 717 testDirnamePosix("/a", "/"); 718 testDirnamePosix("/", "/"); 719 testDirnamePosix("////", "/"); 720 testDirnamePosix("", ""); 721 testDirnamePosix("a", ""); 722 testDirnamePosix("a/", ""); 723 testDirnamePosix("a//", ""); 724 } 725 726 test "os.path.dirnameWindows" { 727 testDirnameWindows("c:\\", "c:\\"); 728 testDirnameWindows("c:\\foo", "c:\\"); 729 testDirnameWindows("c:\\foo\\", "c:\\"); 730 testDirnameWindows("c:\\foo\\bar", "c:\\foo"); 731 testDirnameWindows("c:\\foo\\bar\\", "c:\\foo"); 732 testDirnameWindows("c:\\foo\\bar\\baz", "c:\\foo\\bar"); 733 testDirnameWindows("\\", "\\"); 734 testDirnameWindows("\\foo", "\\"); 735 testDirnameWindows("\\foo\\", "\\"); 736 testDirnameWindows("\\foo\\bar", "\\foo"); 737 testDirnameWindows("\\foo\\bar\\", "\\foo"); 738 testDirnameWindows("\\foo\\bar\\baz", "\\foo\\bar"); 739 testDirnameWindows("c:", "c:"); 740 testDirnameWindows("c:foo", "c:"); 741 testDirnameWindows("c:foo\\", "c:"); 742 testDirnameWindows("c:foo\\bar", "c:foo"); 743 testDirnameWindows("c:foo\\bar\\", "c:foo"); 744 testDirnameWindows("c:foo\\bar\\baz", "c:foo\\bar"); 745 testDirnameWindows("file:stream", ""); 746 testDirnameWindows("dir\\file:stream", "dir"); 747 testDirnameWindows("\\\\unc\\share", "\\\\unc\\share"); 748 testDirnameWindows("\\\\unc\\share\\foo", "\\\\unc\\share\\"); 749 testDirnameWindows("\\\\unc\\share\\foo\\", "\\\\unc\\share\\"); 750 testDirnameWindows("\\\\unc\\share\\foo\\bar", "\\\\unc\\share\\foo"); 751 testDirnameWindows("\\\\unc\\share\\foo\\bar\\", "\\\\unc\\share\\foo"); 752 testDirnameWindows("\\\\unc\\share\\foo\\bar\\baz", "\\\\unc\\share\\foo\\bar"); 753 testDirnameWindows("/a/b/", "/a"); 754 testDirnameWindows("/a/b", "/a"); 755 testDirnameWindows("/a", "/"); 756 testDirnameWindows("", ""); 757 testDirnameWindows("/", "/"); 758 testDirnameWindows("////", "/"); 759 testDirnameWindows("foo", ""); 760 } 761 762 fn testDirnamePosix(input: []const u8, expected_output: []const u8) void { 763 assert(mem.eql(u8, dirnamePosix(input), expected_output)); 764 } 765 766 fn testDirnameWindows(input: []const u8, expected_output: []const u8) void { 767 assert(mem.eql(u8, dirnameWindows(input), expected_output)); 768 } 769 770 pub fn basename(path: []const u8) []const u8 { 771 if (is_windows) { 772 return basenameWindows(path); 773 } else { 774 return basenamePosix(path); 775 } 776 } 777 778 pub fn basenamePosix(path: []const u8) []const u8 { 779 if (path.len == 0) 780 return []u8{}; 781 782 var end_index: usize = path.len - 1; 783 while (path[end_index] == '/') { 784 if (end_index == 0) 785 return []u8{}; 786 end_index -= 1; 787 } 788 var start_index: usize = end_index; 789 end_index += 1; 790 while (path[start_index] != '/') { 791 if (start_index == 0) 792 return path[0..end_index]; 793 start_index -= 1; 794 } 795 796 return path[start_index + 1 .. end_index]; 797 } 798 799 pub fn basenameWindows(path: []const u8) []const u8 { 800 if (path.len == 0) 801 return []u8{}; 802 803 var end_index: usize = path.len - 1; 804 while (true) { 805 const byte = path[end_index]; 806 if (byte == '/' or byte == '\\') { 807 if (end_index == 0) 808 return []u8{}; 809 end_index -= 1; 810 continue; 811 } 812 if (byte == ':' and end_index == 1) { 813 return []u8{}; 814 } 815 break; 816 } 817 818 var start_index: usize = end_index; 819 end_index += 1; 820 while (path[start_index] != '/' and path[start_index] != '\\' and 821 !(path[start_index] == ':' and start_index == 1)) 822 { 823 if (start_index == 0) 824 return path[0..end_index]; 825 start_index -= 1; 826 } 827 828 return path[start_index + 1 .. end_index]; 829 } 830 831 test "os.path.basename" { 832 testBasename("", ""); 833 testBasename("/", ""); 834 testBasename("/dir/basename.ext", "basename.ext"); 835 testBasename("/basename.ext", "basename.ext"); 836 testBasename("basename.ext", "basename.ext"); 837 testBasename("basename.ext/", "basename.ext"); 838 testBasename("basename.ext//", "basename.ext"); 839 testBasename("/aaa/bbb", "bbb"); 840 testBasename("/aaa/", "aaa"); 841 testBasename("/aaa/b", "b"); 842 testBasename("/a/b", "b"); 843 testBasename("//a", "a"); 844 845 testBasenamePosix("\\dir\\basename.ext", "\\dir\\basename.ext"); 846 testBasenamePosix("\\basename.ext", "\\basename.ext"); 847 testBasenamePosix("basename.ext", "basename.ext"); 848 testBasenamePosix("basename.ext\\", "basename.ext\\"); 849 testBasenamePosix("basename.ext\\\\", "basename.ext\\\\"); 850 testBasenamePosix("foo", "foo"); 851 852 testBasenameWindows("\\dir\\basename.ext", "basename.ext"); 853 testBasenameWindows("\\basename.ext", "basename.ext"); 854 testBasenameWindows("basename.ext", "basename.ext"); 855 testBasenameWindows("basename.ext\\", "basename.ext"); 856 testBasenameWindows("basename.ext\\\\", "basename.ext"); 857 testBasenameWindows("foo", "foo"); 858 testBasenameWindows("C:", ""); 859 testBasenameWindows("C:.", "."); 860 testBasenameWindows("C:\\", ""); 861 testBasenameWindows("C:\\dir\\base.ext", "base.ext"); 862 testBasenameWindows("C:\\basename.ext", "basename.ext"); 863 testBasenameWindows("C:basename.ext", "basename.ext"); 864 testBasenameWindows("C:basename.ext\\", "basename.ext"); 865 testBasenameWindows("C:basename.ext\\\\", "basename.ext"); 866 testBasenameWindows("C:foo", "foo"); 867 testBasenameWindows("file:stream", "file:stream"); 868 } 869 870 fn testBasename(input: []const u8, expected_output: []const u8) void { 871 assert(mem.eql(u8, basename(input), expected_output)); 872 } 873 874 fn testBasenamePosix(input: []const u8, expected_output: []const u8) void { 875 assert(mem.eql(u8, basenamePosix(input), expected_output)); 876 } 877 878 fn testBasenameWindows(input: []const u8, expected_output: []const u8) void { 879 assert(mem.eql(u8, basenameWindows(input), expected_output)); 880 } 881 882 /// Returns the relative path from `from` to `to`. If `from` and `to` each 883 /// resolve to the same path (after calling `resolve` on each), a zero-length 884 /// string is returned. 885 /// On Windows this canonicalizes the drive to a capital letter and paths to `\\`. 886 pub fn relative(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { 887 if (is_windows) { 888 return relativeWindows(allocator, from, to); 889 } else { 890 return relativePosix(allocator, from, to); 891 } 892 } 893 894 pub fn relativeWindows(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { 895 const resolved_from = try resolveWindows(allocator, [][]const u8{from}); 896 defer allocator.free(resolved_from); 897 898 var clean_up_resolved_to = true; 899 const resolved_to = try resolveWindows(allocator, [][]const u8{to}); 900 defer if (clean_up_resolved_to) allocator.free(resolved_to); 901 902 const parsed_from = windowsParsePath(resolved_from); 903 const parsed_to = windowsParsePath(resolved_to); 904 const result_is_to = x: { 905 if (parsed_from.kind != parsed_to.kind) { 906 break :x true; 907 } else switch (parsed_from.kind) { 908 WindowsPath.Kind.NetworkShare => { 909 break :x !networkShareServersEql(parsed_to.disk_designator, parsed_from.disk_designator); 910 }, 911 WindowsPath.Kind.Drive => { 912 break :x asciiUpper(parsed_from.disk_designator[0]) != asciiUpper(parsed_to.disk_designator[0]); 913 }, 914 else => unreachable, 915 } 916 }; 917 918 if (result_is_to) { 919 clean_up_resolved_to = false; 920 return resolved_to; 921 } 922 923 var from_it = mem.split(resolved_from, "/\\"); 924 var to_it = mem.split(resolved_to, "/\\"); 925 while (true) { 926 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); 927 const to_rest = to_it.rest(); 928 if (to_it.next()) |to_component| { 929 // TODO ASCII is wrong, we actually need full unicode support to compare paths. 930 if (asciiEqlIgnoreCase(from_component, to_component)) 931 continue; 932 } 933 var up_count: usize = 1; 934 while (from_it.next()) |_| { 935 up_count += 1; 936 } 937 const up_index_end = up_count * "..\\".len; 938 const result = try allocator.alloc(u8, up_index_end + to_rest.len); 939 errdefer allocator.free(result); 940 941 var result_index: usize = 0; 942 while (result_index < up_index_end) { 943 result[result_index] = '.'; 944 result_index += 1; 945 result[result_index] = '.'; 946 result_index += 1; 947 result[result_index] = '\\'; 948 result_index += 1; 949 } 950 // shave off the trailing slash 951 result_index -= 1; 952 953 var rest_it = mem.split(to_rest, "/\\"); 954 while (rest_it.next()) |to_component| { 955 result[result_index] = '\\'; 956 result_index += 1; 957 mem.copy(u8, result[result_index..], to_component); 958 result_index += to_component.len; 959 } 960 961 return result[0..result_index]; 962 } 963 964 return []u8{}; 965 } 966 967 pub fn relativePosix(allocator: &Allocator, from: []const u8, to: []const u8) ![]u8 { 968 const resolved_from = try resolvePosix(allocator, [][]const u8{from}); 969 defer allocator.free(resolved_from); 970 971 const resolved_to = try resolvePosix(allocator, [][]const u8{to}); 972 defer allocator.free(resolved_to); 973 974 var from_it = mem.split(resolved_from, "/"); 975 var to_it = mem.split(resolved_to, "/"); 976 while (true) { 977 const from_component = from_it.next() ?? return mem.dupe(allocator, u8, to_it.rest()); 978 const to_rest = to_it.rest(); 979 if (to_it.next()) |to_component| { 980 if (mem.eql(u8, from_component, to_component)) 981 continue; 982 } 983 var up_count: usize = 1; 984 while (from_it.next()) |_| { 985 up_count += 1; 986 } 987 const up_index_end = up_count * "../".len; 988 const result = try allocator.alloc(u8, up_index_end + to_rest.len); 989 errdefer allocator.free(result); 990 991 var result_index: usize = 0; 992 while (result_index < up_index_end) { 993 result[result_index] = '.'; 994 result_index += 1; 995 result[result_index] = '.'; 996 result_index += 1; 997 result[result_index] = '/'; 998 result_index += 1; 999 } 1000 if (to_rest.len == 0) { 1001 // shave off the trailing slash 1002 return result[0 .. result_index - 1]; 1003 } 1004 1005 mem.copy(u8, result[result_index..], to_rest); 1006 return result; 1007 } 1008 1009 return []u8{}; 1010 } 1011 1012 test "os.path.relative" { 1013 testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); 1014 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); 1015 testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); 1016 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/bbbb", ""); 1017 testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa/cccc", "..\\cccc"); 1018 testRelativeWindows("c:/aaaa/", "c:/aaaa/cccc", "cccc"); 1019 testRelativeWindows("c:/", "c:\\aaaa\\bbbb", "aaaa\\bbbb"); 1020 testRelativeWindows("c:/aaaa/bbbb", "d:\\", "D:\\"); 1021 testRelativeWindows("c:/AaAa/bbbb", "c:/aaaa/bbbb", ""); 1022 testRelativeWindows("c:/aaaaa/", "c:/aaaa/cccc", "..\\aaaa\\cccc"); 1023 testRelativeWindows("C:\\foo\\bar\\baz\\quux", "C:\\", "..\\..\\..\\.."); 1024 testRelativeWindows("C:\\foo\\test", "C:\\foo\\test\\bar\\package.json", "bar\\package.json"); 1025 testRelativeWindows("C:\\foo\\bar\\baz-quux", "C:\\foo\\bar\\baz", "..\\baz"); 1026 testRelativeWindows("C:\\foo\\bar\\baz", "C:\\foo\\bar\\baz-quux", "..\\baz-quux"); 1027 testRelativeWindows("\\\\foo\\bar", "\\\\foo\\bar\\baz", "baz"); 1028 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar", ".."); 1029 testRelativeWindows("\\\\foo\\bar\\baz-quux", "\\\\foo\\bar\\baz", "..\\baz"); 1030 testRelativeWindows("\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz-quux", "..\\baz-quux"); 1031 testRelativeWindows("C:\\baz-quux", "C:\\baz", "..\\baz"); 1032 testRelativeWindows("C:\\baz", "C:\\baz-quux", "..\\baz-quux"); 1033 testRelativeWindows("\\\\foo\\baz-quux", "\\\\foo\\baz", "..\\baz"); 1034 testRelativeWindows("\\\\foo\\baz", "\\\\foo\\baz-quux", "..\\baz-quux"); 1035 testRelativeWindows("C:\\baz", "\\\\foo\\bar\\baz", "\\\\foo\\bar\\baz"); 1036 testRelativeWindows("\\\\foo\\bar\\baz", "C:\\baz", "C:\\baz"); 1037 1038 testRelativePosix("/var/lib", "/var", ".."); 1039 testRelativePosix("/var/lib", "/bin", "../../bin"); 1040 testRelativePosix("/var/lib", "/var/lib", ""); 1041 testRelativePosix("/var/lib", "/var/apache", "../apache"); 1042 testRelativePosix("/var/", "/var/lib", "lib"); 1043 testRelativePosix("/", "/var/lib", "var/lib"); 1044 testRelativePosix("/foo/test", "/foo/test/bar/package.json", "bar/package.json"); 1045 testRelativePosix("/Users/a/web/b/test/mails", "/Users/a/web/b", "../.."); 1046 testRelativePosix("/foo/bar/baz-quux", "/foo/bar/baz", "../baz"); 1047 testRelativePosix("/foo/bar/baz", "/foo/bar/baz-quux", "../baz-quux"); 1048 testRelativePosix("/baz-quux", "/baz", "../baz"); 1049 testRelativePosix("/baz", "/baz-quux", "../baz-quux"); 1050 } 1051 1052 fn testRelativePosix(from: []const u8, to: []const u8, expected_output: []const u8) void { 1053 const result = relativePosix(debug.global_allocator, from, to) catch unreachable; 1054 assert(mem.eql(u8, result, expected_output)); 1055 } 1056 1057 fn testRelativeWindows(from: []const u8, to: []const u8, expected_output: []const u8) void { 1058 const result = relativeWindows(debug.global_allocator, from, to) catch unreachable; 1059 assert(mem.eql(u8, result, expected_output)); 1060 } 1061 1062 /// Return the canonicalized absolute pathname. 1063 /// Expands all symbolic links and resolves references to `.`, `..`, and 1064 /// extra `/` characters in ::pathname. 1065 /// Caller must deallocate result. 1066 pub fn real(allocator: &Allocator, pathname: []const u8) ![]u8 { 1067 switch (builtin.os) { 1068 Os.windows => { 1069 const pathname_buf = try allocator.alloc(u8, pathname.len + 1); 1070 defer allocator.free(pathname_buf); 1071 1072 mem.copy(u8, pathname_buf, pathname); 1073 pathname_buf[pathname.len] = 0; 1074 1075 const h_file = windows.CreateFileA(pathname_buf.ptr, windows.GENERIC_READ, windows.FILE_SHARE_READ, null, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, null); 1076 if (h_file == windows.INVALID_HANDLE_VALUE) { 1077 const err = windows.GetLastError(); 1078 return switch (err) { 1079 windows.ERROR.FILE_NOT_FOUND => error.FileNotFound, 1080 windows.ERROR.ACCESS_DENIED => error.AccessDenied, 1081 windows.ERROR.FILENAME_EXCED_RANGE => error.NameTooLong, 1082 else => os.unexpectedErrorWindows(err), 1083 }; 1084 } 1085 defer os.close(h_file); 1086 var buf = try allocator.alloc(u8, 256); 1087 errdefer allocator.free(buf); 1088 while (true) { 1089 const buf_len = math.cast(windows.DWORD, buf.len) catch return error.NameTooLong; 1090 const result = windows.GetFinalPathNameByHandleA(h_file, buf.ptr, buf_len, windows.VOLUME_NAME_DOS); 1091 1092 if (result == 0) { 1093 const err = windows.GetLastError(); 1094 return switch (err) { 1095 windows.ERROR.PATH_NOT_FOUND => error.FileNotFound, 1096 windows.ERROR.NOT_ENOUGH_MEMORY => error.OutOfMemory, 1097 windows.ERROR.INVALID_PARAMETER => unreachable, 1098 else => os.unexpectedErrorWindows(err), 1099 }; 1100 } 1101 1102 if (result > buf.len) { 1103 buf = try allocator.realloc(u8, buf, result); 1104 continue; 1105 } 1106 1107 // windows returns \\?\ prepended to the path 1108 // we strip it because nobody wants \\?\ prepended to their path 1109 const final_len = x: { 1110 if (result > 4 and mem.startsWith(u8, buf, "\\\\?\\")) { 1111 var i: usize = 4; 1112 while (i < result) : (i += 1) { 1113 buf[i - 4] = buf[i]; 1114 } 1115 break :x result - 4; 1116 } else { 1117 break :x result; 1118 } 1119 }; 1120 1121 return allocator.shrink(u8, buf, final_len); 1122 } 1123 }, 1124 Os.macosx, Os.ios => { 1125 // TODO instead of calling the libc function here, port the implementation 1126 // to Zig, and then remove the NameTooLong error possibility. 1127 const pathname_buf = try allocator.alloc(u8, pathname.len + 1); 1128 defer allocator.free(pathname_buf); 1129 1130 const result_buf = try allocator.alloc(u8, posix.PATH_MAX); 1131 errdefer allocator.free(result_buf); 1132 1133 mem.copy(u8, pathname_buf, pathname); 1134 pathname_buf[pathname.len] = 0; 1135 1136 const err = posix.getErrno(posix.realpath(pathname_buf.ptr, result_buf.ptr)); 1137 if (err > 0) { 1138 return switch (err) { 1139 posix.EINVAL => unreachable, 1140 posix.EBADF => unreachable, 1141 posix.EFAULT => unreachable, 1142 posix.EACCES => error.AccessDenied, 1143 posix.ENOENT => error.FileNotFound, 1144 posix.ENOTSUP => error.NotSupported, 1145 posix.ENOTDIR => error.NotDir, 1146 posix.ENAMETOOLONG => error.NameTooLong, 1147 posix.ELOOP => error.SymLinkLoop, 1148 posix.EIO => error.InputOutput, 1149 else => os.unexpectedErrorPosix(err), 1150 }; 1151 } 1152 return allocator.shrink(u8, result_buf, cstr.len(result_buf.ptr)); 1153 }, 1154 Os.linux => { 1155 const fd = try os.posixOpen(allocator, pathname, posix.O_PATH | posix.O_NONBLOCK | posix.O_CLOEXEC, 0); 1156 defer os.close(fd); 1157 1158 var buf: ["/proc/self/fd/-2147483648".len]u8 = undefined; 1159 const proc_path = fmt.bufPrint(buf[0..], "/proc/self/fd/{}", fd) catch unreachable; 1160 1161 return os.readLink(allocator, proc_path); 1162 }, 1163 else => @compileError("TODO implement os.path.real for " ++ @tagName(builtin.os)), 1164 } 1165 } 1166 1167 test "os.path.real" { 1168 // at least call it so it gets compiled 1169 _ = real(debug.global_allocator, "some_path"); 1170 }