| ... | ... | @@ -104,6 +104,12 @@ pub const LazyStatus = enum { |
| 104 | 104 | unavailable, |
| 105 | 105 | }; |
| 106 | 106 | |
| 107 | pub const LocalStorage = struct { |
| 108 | cache_root: Cache.Path, |
| 109 | /// Path to "zig-pkg" inside the package in which the user ran `zig build`. |
| 110 | pkg_root: Cache.Path, |
| 111 | }; |
| 112 | |
| 107 | 113 | /// Contains shared state among all `Fetch` tasks. |
| 108 | 114 | pub const JobQueue = struct { |
| 109 | 115 | io: Io, |
| ... | ... | @@ -122,9 +128,8 @@ pub const JobQueue = struct { |
| 122 | 128 | /// This tracks `Fetch` tasks as well as recompression tasks. |
| 123 | 129 | group: Io.Group = .init, |
| 124 | 130 | global_cache: Cache.Directory, |
| 125 | | local_cache: Cache.Path, |
| 126 | | /// Path to "zig-pkg" inside the package in which the user ran `zig build`. |
| 127 | | root_pkg_path: Cache.Path, |
| 131 | /// If `null`, indicates fetch globally only. |
| 132 | local_storage: ?*const LocalStorage, |
| 128 | 133 | /// If true then, no fetching occurs, and: |
| 129 | 134 | /// * The `global_cache` directory is assumed to be the direct parent |
| 130 | 135 | /// directory of on-disk packages rather than having the "p/" directory |
| ... | ... | @@ -341,7 +346,7 @@ pub const JobQueue = struct { |
| 341 | 346 | ); |
| 342 | 347 | } |
| 343 | 348 | |
| 344 | | fn recompress(jq: *JobQueue, package_hash: Package.Hash) Io.Cancelable!void { |
| 349 | fn recompress(jq: *JobQueue, package_hash: Package.Hash, package_root: Cache.Path) Io.Cancelable!void { |
| 345 | 350 | const pkg_hash_slice = package_hash.toSlice(); |
| 346 | 351 | |
| 347 | 352 | const prog_node = jq.prog_node.startFmt(0, "recompress {s}", .{pkg_hash_slice}); |
| ... | ... | @@ -359,7 +364,7 @@ pub const JobQueue = struct { |
| 359 | 364 | defer arena_instance.deinit(); |
| 360 | 365 | const arena = arena_instance.allocator(); |
| 361 | 366 | |
| 362 | | recompressFallible(jq, arena, dest_path, pkg_hash_slice, prog_node) catch |err| switch (err) { |
| 367 | recompressFallible(jq, arena, dest_path, pkg_hash_slice, package_root, prog_node) catch |err| switch (err) { |
| 363 | 368 | error.Canceled => |e| return e, |
| 364 | 369 | error.ReadFailed => comptime unreachable, |
| 365 | 370 | error.WriteFailed => comptime unreachable, |
| ... | ... | @@ -372,6 +377,7 @@ pub const JobQueue = struct { |
| 372 | 377 | arena: Allocator, |
| 373 | 378 | dest_path: Cache.Path, |
| 374 | 379 | pkg_hash_slice: []const u8, |
| 380 | package_root: Cache.Path, |
| 375 | 381 | prog_node: std.Progress.Node, |
| 376 | 382 | ) !void { |
| 377 | 383 | const gpa = jq.http_client.allocator; |
| ... | ... | @@ -386,7 +392,7 @@ pub const JobQueue = struct { |
| 386 | 392 | var scanned_files: std.ArrayList(ScannedFile) = .empty; |
| 387 | 393 | defer scanned_files.deinit(gpa); |
| 388 | 394 | |
| 389 | | var pkg_dir = try jq.root_pkg_path.openDir(io, pkg_hash_slice, .{ .iterate = true }); |
| 395 | var pkg_dir = try package_root.root_dir.handle.openDir(io, package_root.sub_path, .{ .iterate = true }); |
| 390 | 396 | defer pkg_dir.close(io); |
| 391 | 397 | |
| 392 | 398 | { |
| ... | ... | @@ -513,7 +519,6 @@ pub fn run(f: *Fetch) RunError!void { |
| 513 | 519 | const eb = &f.error_bundle; |
| 514 | 520 | const arena = f.arena.allocator(); |
| 515 | 521 | const gpa = f.arena.child_allocator; |
| 516 | | const local_cache_root = job_queue.local_cache; |
| 517 | 522 | |
| 518 | 523 | try eb.init(gpa); |
| 519 | 524 | |
| ... | ... | @@ -534,32 +539,16 @@ pub fn run(f: *Fetch) RunError!void { |
| 534 | 539 | ); |
| 535 | 540 | // Packages fetched by URL may not use relative paths to escape outside the |
| 536 | 541 | // fetched package directory from within the package cache. |
| 537 | | if (pkg_root.root_dir.eql(local_cache_root.root_dir)) { |
| 538 | | // `parent_package_root.sub_path` contains a path like this: |
| 539 | | // "p/$hash", or |
| 540 | | // "p/$hash/foo", with possibly more directories after "foo". |
| 541 | | // We want to fail unless the resolved relative path has a |
| 542 | | // prefix of "p/$hash/". |
| 543 | | const prefix_len: usize = if (job_queue.read_only) 0 else "p/".len; |
| 544 | | const parent_sub_path = f.parent_package_root.sub_path; |
| 545 | | const end = find_end: { |
| 546 | | if (parent_sub_path.len > prefix_len) { |
| 547 | | // Use `isSep` instead of `indexOfScalarPos` to account for |
| 548 | | // Windows accepting both `\` and `/` as path separators. |
| 549 | | for (parent_sub_path[prefix_len..], prefix_len..) |c, i| { |
| 550 | | if (std.fs.path.isSep(c)) break :find_end i; |
| 551 | | } |
| 552 | | } |
| 553 | | break :find_end parent_sub_path.len; |
| 554 | | }; |
| 555 | | const expected_prefix = parent_sub_path[0..end]; |
| 556 | | if (!std.mem.startsWith(u8, pkg_root.sub_path, expected_prefix)) { |
| 557 | | return f.fail( |
| 558 | | f.location_tok, |
| 559 | | try eb.printString("dependency path outside project: '{f}'", .{pkg_root}), |
| 560 | | ); |
| 561 | | } |
| 562 | | } |
| 542 | |
| 543 | // This code path is only reachable recursively and the sub_path |
| 544 | // will already have been resolved to no longer have extra ".." or |
| 545 | // "." components. |
| 546 | assert(job_queue.local_storage != null); |
| 547 | assert(pkg_root.root_dir.eql(f.parent_package_root.root_dir)); |
| 548 | if (!std.mem.startsWith(u8, pkg_root.sub_path, f.parent_package_root.sub_path)) return f.fail( |
| 549 | f.location_tok, |
| 550 | try eb.printString("dependency path outside project: '{f}'", .{pkg_root}), |
| 551 | ); |
| 563 | 552 | f.package_root = pkg_root; |
| 564 | 553 | try loadManifest(f, pkg_root); |
| 565 | 554 | if (!f.has_build_zig) try checkBuildFileExistence(f); |
| ... | ... | @@ -610,31 +599,33 @@ pub fn run(f: *Fetch) RunError!void { |
| 610 | 599 | return queueJobsForDeps(f); |
| 611 | 600 | } |
| 612 | 601 | |
| 613 | | const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice()); |
| 614 | | if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { |
| 615 | | assert(f.lazy_status != .unavailable); |
| 616 | | f.package_root = package_root; |
| 617 | | try loadManifest(f, f.package_root); |
| 618 | | try checkBuildFileExistence(f); |
| 619 | | if (!job_queue.recursive) return; |
| 620 | | return queueJobsForDeps(f); |
| 621 | | } else |err| switch (err) { |
| 622 | | error.FileNotFound => { |
| 623 | | log.debug("FileNotFound: {f}", .{package_root}); |
| 624 | | if (job_queue.read_only and f.lazy_status == .eager) return f.fail( |
| 625 | | f.name_tok, |
| 626 | | try eb.printString("package not found at '{f}'", .{package_root}), |
| 627 | | ); |
| 628 | | }, |
| 629 | | error.Canceled => |e| return e, |
| 630 | | else => |e| { |
| 631 | | try eb.addRootErrorMessage(.{ |
| 632 | | .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{ |
| 633 | | package_root, e, |
| 634 | | }), |
| 635 | | }); |
| 636 | | return error.FetchFailed; |
| 637 | | }, |
| 602 | if (job_queue.local_storage) |ls| { |
| 603 | const package_root = try ls.pkg_root.join(arena, expected_hash.toSlice()); |
| 604 | if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { |
| 605 | assert(f.lazy_status != .unavailable); |
| 606 | f.package_root = package_root; |
| 607 | try loadManifest(f, f.package_root); |
| 608 | try checkBuildFileExistence(f); |
| 609 | if (!job_queue.recursive) return; |
| 610 | return queueJobsForDeps(f); |
| 611 | } else |err| switch (err) { |
| 612 | error.FileNotFound => { |
| 613 | log.debug("FileNotFound: {f}", .{package_root}); |
| 614 | if (job_queue.read_only and f.lazy_status == .eager) return f.fail( |
| 615 | f.name_tok, |
| 616 | try eb.printString("package not found at '{f}'", .{package_root}), |
| 617 | ); |
| 618 | }, |
| 619 | error.Canceled => |e| return e, |
| 620 | else => |e| { |
| 621 | try eb.addRootErrorMessage(.{ |
| 622 | .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{ |
| 623 | package_root, e, |
| 624 | }), |
| 625 | }); |
| 626 | return error.FetchFailed; |
| 627 | }, |
| 628 | } |
| 638 | 629 | } |
| 639 | 630 | |
| 640 | 631 | // Check global cache before remote fetch. |
| ... | ... | @@ -713,7 +704,14 @@ fn runResource( |
| 713 | 704 | break :r x; |
| 714 | 705 | }; |
| 715 | 706 | const tmp_dir_sub_path = ".tmp-" ++ std.fmt.hex(rand_int); |
| 716 | | const tmp_directory_path = try job_queue.root_pkg_path.join(arena, tmp_dir_sub_path); |
| 707 | const tmp_tmp_dir_sub_path = "tmp/" ++ tmp_dir_sub_path; |
| 708 | const tmp_directory_path: Cache.Path = if (job_queue.local_storage) |ls| |
| 709 | try ls.pkg_root.join(arena, tmp_dir_sub_path) |
| 710 | else |
| 711 | .{ |
| 712 | .root_dir = job_queue.global_cache, |
| 713 | .sub_path = tmp_tmp_dir_sub_path, |
| 714 | }; |
| 717 | 715 | |
| 718 | 716 | const package_sub_path = blk: { |
| 719 | 717 | var tmp_directory: Cache.Directory = .{ |
| ... | ... | @@ -772,19 +770,23 @@ fn runResource( |
| 772 | 770 | // zig package directory untouched as it may be in use. This is done even |
| 773 | 771 | // if the hash is invalid, in case the package with the different hash is |
| 774 | 772 | // used in the future. |
| 775 | | f.package_root = try job_queue.root_pkg_path.join(arena, computed_package_hash.toSlice()); |
| 776 | | renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| { |
| 777 | | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| 778 | | "unable to rename temporary directory {f} into package cache directory {f}: {t}", |
| 779 | | .{ package_sub_path, f.package_root, err }, |
| 780 | | ) }); |
| 781 | | return error.FetchFailed; |
| 782 | | }; |
| 773 | if (job_queue.local_storage) |ls| { |
| 774 | f.package_root = try ls.pkg_root.join(arena, computed_package_hash.toSlice()); |
| 775 | renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| { |
| 776 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| 777 | "unable to rename temporary directory {f} into package cache directory {f}: {t}", |
| 778 | .{ package_sub_path, f.package_root, err }, |
| 779 | ) }); |
| 780 | return error.FetchFailed; |
| 781 | }; |
| 782 | } else { |
| 783 | f.package_root = tmp_directory_path; |
| 784 | } |
| 783 | 785 | |
| 784 | 786 | if (!disable_recompress) { |
| 785 | 787 | // Spin off a task to recompress the tarball, with filtered files deleted, into |
| 786 | 788 | // the global cache. |
| 787 | | job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash }); |
| 789 | job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash, f.package_root }); |
| 788 | 790 | } |
| 789 | 791 | |
| 790 | 792 | // Remove temporary directory root if not already renamed to global cache. |