authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-05 17:36:14-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-05 17:36:14-08:00
logd8171e8a2ee56e76bcd91f187d5ca5664b87bc83
tree15d475879933c9ccce5493222cd0b0b2ca261dba
parent1f65e7cccc3124ce2fa9a5e8107c32367a39ec29

fetch: check global cache for compressed tarball

before remote URL

1 files changed, 51 insertions(+), 21 deletions(-)

src/Package/Fetch.zig+51-21
......@@ -40,6 +40,7 @@ const native_os = builtin.os.tag;
4040const std = @import("std");
4141const Io = std.Io;
4242const fs = std.fs;
43const log = std.log.scoped(.fetch);
4344const assert = std.debug.assert;
4445const ascii = std.ascii;
4546const Allocator = std.mem.Allocator;
......@@ -324,7 +325,7 @@ pub const JobQueue = struct {
324325 error.Canceled => |e| return e,
325326 error.ReadFailed => comptime unreachable,
326327 error.WriteFailed => comptime unreachable,
327 else => |e| std.log.warn("failed caching recompressed tarball to {f}: {t}", .{ dest_path, e }),
328 else => |e| log.warn("failed caching recompressed tarball to {f}: {t}", .{ dest_path, e }),
328329 };
329330 }
330331
......@@ -508,14 +509,14 @@ pub fn run(f: *Fetch) RunError!void {
508509 .path_or_url => |path_or_url| {
509510 if (Io.Dir.cwd().openDir(io, path_or_url, .{ .iterate = true })) |dir| {
510511 var resource: Resource = .{ .dir = dir };
511 return f.runResource(path_or_url, &resource, null);
512 return f.runResource(path_or_url, &resource, null, false);
512513 } else |dir_err| {
513514 var server_header_buffer: [init_resource_buffer_size]u8 = undefined;
514515
515516 const file_err = if (dir_err == error.NotDir) e: {
516517 if (Io.Dir.cwd().openFile(io, path_or_url, .{})) |file| {
517518 var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) };
518 return f.runResource(path_or_url, &resource, null);
519 return f.runResource(path_or_url, &resource, null, false);
519520 } else |err| break :e err;
520521 } else dir_err;
521522
......@@ -527,11 +528,13 @@ pub fn run(f: *Fetch) RunError!void {
527528 };
528529 var resource: Resource = undefined;
529530 try f.initResource(uri, &resource, &server_header_buffer);
530 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null);
531 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null, false);
531532 }
532533 },
533534 };
534535
536 var resource_buffer: [init_resource_buffer_size]u8 = undefined;
537
535538 if (remote.hash) |expected_hash| {
536539 const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice());
537540 if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| {
......@@ -543,19 +546,13 @@ pub fn run(f: *Fetch) RunError!void {
543546 return queueJobsForDeps(f);
544547 } else |err| switch (err) {
545548 error.FileNotFound => {
546 switch (f.lazy_status) {
547 .eager => {},
548 .available => if (!job_queue.unlazy_set.contains(expected_hash)) {
549 f.lazy_status = .unavailable;
550 return;
551 },
552 .unavailable => unreachable,
553 }
549 log.debug("FileNotFound: {f}", .{package_root});
554550 if (job_queue.read_only) return f.fail(
555551 f.name_tok,
556552 try eb.printString("package not found at '{f}'", .{package_root}),
557553 );
558554 },
555 error.Canceled => |e| return e,
559556 else => |e| {
560557 try eb.addRootErrorMessage(.{
561558 .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{
......@@ -565,6 +562,38 @@ pub fn run(f: *Fetch) RunError!void {
565562 return error.FetchFailed;
566563 },
567564 }
565
566 // Check global cache before remote fetch.
567 const cached_tarball_sub_path = try std.fmt.allocPrint(arena, "p/{s}.tar.gz", .{expected_hash.toSlice()});
568 const cached_tarball_path: Cache.Path = .{
569 .root_dir = job_queue.global_cache,
570 .sub_path = cached_tarball_sub_path,
571 };
572 if (cached_tarball_path.root_dir.handle.openFile(io, cached_tarball_path.sub_path, .{})) |file| {
573 log.debug("found global cached tarball {f}", .{cached_tarball_path});
574 var resource: Resource = .{ .file = file.reader(io, &resource_buffer) };
575 return f.runResource(cached_tarball_sub_path, &resource, remote.hash, true);
576 } else |err| switch (err) {
577 error.FileNotFound => log.debug("FileNotFound: {f}", .{cached_tarball_path}),
578 error.Canceled => |e| return e,
579 else => |e| {
580 try eb.addRootErrorMessage(.{
581 .msg = try eb.printString("unable to open globally cached package {f}: {t}", .{
582 cached_tarball_path, e,
583 }),
584 });
585 return error.FetchFailed;
586 },
587 }
588
589 switch (f.lazy_status) {
590 .eager => {},
591 .available => if (!job_queue.unlazy_set.contains(expected_hash)) {
592 f.lazy_status = .unavailable;
593 return;
594 },
595 .unavailable => unreachable,
596 }
568597 } else if (job_queue.read_only) {
569598 try eb.addRootErrorMessage(.{
570599 .msg = try eb.addString("dependency is missing hash field"),
......@@ -574,15 +603,13 @@ pub fn run(f: *Fetch) RunError!void {
574603 }
575604
576605 // Fetch and unpack the remote into a temporary directory.
577
578606 const uri = std.Uri.parse(remote.url) catch |err| return f.fail(
579607 f.location_tok,
580608 try eb.printString("invalid URI: {t}", .{err}),
581609 );
582 var buffer: [init_resource_buffer_size]u8 = undefined;
583610 var resource: Resource = undefined;
584 try f.initResource(uri, &resource, &buffer);
585 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash);
611 try f.initResource(uri, &resource, &resource_buffer);
612 return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash, false);
586613}
587614
588615pub fn deinit(f: *Fetch) void {
......@@ -596,6 +623,7 @@ fn runResource(
596623 uri_path: []const u8,
597624 resource: *Resource,
598625 remote_hash: ?Package.Hash,
626 disable_recompress: bool,
599627) RunError!void {
600628 const job_queue = f.job_queue;
601629 assert(!job_queue.read_only);
......@@ -681,15 +709,17 @@ fn runResource(
681709 return error.FetchFailed;
682710 };
683711
684 // Spin off a task to recompress the tarball, with filtered files deleted, into
685 // the global cache.
686 job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash });
712 if (!disable_recompress) {
713 // Spin off a task to recompress the tarball, with filtered files deleted, into
714 // the global cache.
715 job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash });
716 }
687717
688718 // Remove temporary directory root if not already renamed to global cache.
689719 if (!package_sub_path.eql(tmp_directory_path)) {
690720 tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) {
691721 error.Canceled => |e| return e,
692 else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }),
722 else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }),
693723 };
694724 }
695725
......@@ -1588,7 +1618,7 @@ pub fn renameTmpIntoCache(io: Io, tmp_path: Cache.Path, dest_path: Cache.Path) !
15881618 error.Canceled => |e| return e,
15891619 // Garbage files leftover in zig-cache/tmp/ is, as they say
15901620 // on Star Trek, "operating within normal parameters".
1591 else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }),
1621 else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }),
15921622 };
15931623 },
15941624 else => |e| return e,