Consumes resource, even if an error is returned.
fn runResource(
f: *Fetch,
uri_path: []const u8,
resource: *Resource,
remote_hash: ?Package.Hash,
disable_recompress: bool,
) RunError!void
fn runResource(
f: *Fetch,
uri_path: []const u8,
resource: *Resource,
remote_hash: ?Package.Hash,
disable_recompress: bool,
) RunError!void {
const job_queue = f.job_queue;
assert(!job_queue.read_only);
const io = job_queue.io;
defer resource.deinit(io);
const arena = f.arena.allocator();
const eb = &f.error_bundle;
const rand_int = r: {
var x: u64 = undefined;
io.random(@ptrCast(&x));
break :r x;
};
const tmp_dir_sub_path = ".tmp-" ++ std.fmt.hex(rand_int);
const tmp_tmp_dir_sub_path = "tmp/" ++ tmp_dir_sub_path;
const tmp_directory_path: Path = if (job_queue.local_storage) |ls|
try ls.pkg_root.join(arena, tmp_dir_sub_path)
else
.{
.root_dir = job_queue.global_cache,
.sub_path = tmp_tmp_dir_sub_path,
};
const package_sub_path = blk: {
var tmp_directory: Directory = .{
.path = tmp_directory_path.sub_path,
.handle = handle: {
const dir = tmp_directory_path.root_dir.handle.createDirPathOpen(io, tmp_directory_path.sub_path, .{
.open_options = .{ .iterate = true },
}) catch |err| {
try eb.addRootErrorMessage(.{
.msg = try eb.printString("unable to create temporary directory '{f}': {t}", .{
tmp_directory_path, err,
}),
});
return error.FetchFailed;
};
break :handle dir;
},
};
defer tmp_directory.handle.close(io);
// Fetch and unpack a resource into a temporary directory.
var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory);
const pkg_path: Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir };
// Load, parse, and validate the unpacked build.zig.zon file. It is allowed
// for the file to be missing, in which case this fetched package is
// considered to be a "naked" package.
try loadManifest(f, pkg_path);
const filter: Filter = .{
.include_paths = if (f.have_manifest) f.manifest.paths else .{},
};
// Ignore errors that were excluded by manifest, such as failure to
// create symlinks that weren't supposed to be included anyway.
try unpack_result.validate(f, filter);
// Apply the manifest's inclusion rules to the temporary directory by
// deleting excluded files.
// Empty directories have already been omitted by `unpackResource`.
// Compute the package hash based on the remaining files in the temporary
// directory.
f.computed_hash = try computeHash(f, pkg_path, filter);
if (unpack_result.root_dir.len > 0)
break :blk try tmp_directory_path.join(arena, unpack_result.root_dir);
break :blk tmp_directory_path;
};
const computed_package_hash = computedPackageHash(f);
// Rename the temporary directory into the local zig package directory. If
// the hash already exists, delete the temporary directory and leave the
// zig package directory untouched as it may be in use. This is done even
// if the hash is invalid, in case the package with the different hash is
// used in the future.
if (job_queue.local_storage) |ls| {
f.package_root = try ls.pkg_root.join(arena, computed_package_hash.toSlice());
renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| {
try eb.addRootErrorMessage(.{ .msg = try eb.printString(
"failed to rename temporary directory {f} into package cache directory {f}: {t}",
.{ package_sub_path, f.package_root, err },
) });
return error.FetchFailed;
};
} else {
f.package_root = tmp_directory_path;
}
f.remote_package_root = f.package_root;
if (!disable_recompress) {
// Spin off a task to recompress the tarball, with filtered files deleted, into
// the global cache.
job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash, f.package_root });
}
// Remove temporary directory root if not already renamed to global cache.
if (!package_sub_path.eql(tmp_directory_path)) {
tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }),
};
}
// Validate the computed hash against the expected hash. If invalid, this
// job is done.
if (remote_hash) |declared_hash| {
const hash_tok = f.hash_tok.unwrap().?;
if (!computed_package_hash.eql(&declared_hash)) {
return f.fail(hash_tok, try eb.printString(
"hash mismatch: manifest declares {s} but the fetched package has {s}",
.{ declared_hash.toSlice(), computed_package_hash.toSlice() },
));
}
} else if (!f.omit_missing_hash_error) {
const notes_len = 1;
try eb.addRootErrorMessage(.{
.msg = try eb.addString("dependency is missing hash field"),
.src_loc = try f.srcLoc(f.location_tok),
.notes_len = notes_len,
});
const notes_start = try eb.reserveNotes(notes_len);
eb.extra.items[notes_start] = @backingInt(try eb.addErrorMessage(.{
.msg = try eb.printString("expected .hash = {q},", .{computed_package_hash.toSlice()}),
}));
return error.FetchFailed;
}
// Spawn a new fetch job for each dependency in the manifest file. Use
// a mutex and a hash map so that redundant jobs do not get queued up.
if (!job_queue.recursive) return;
return queueJobsForDeps(f);
}