authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-10 16:26:27-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-11 15:39:49-08:00
loga0f2e6a29f4d5c084a248d24b25fae9f30707001
tree3c65602e4baebd6e11eb797d86039ac256993bcc
parent876ab99f5c462e93296ef0b2f642ac2243acb31c

Package: complete the package-fetching logic


2 files changed, 109 insertions(+), 43 deletions(-)

src/Package.zig+104-41
......@@ -6,6 +6,7 @@ const mem = std.mem;
66const Allocator = mem.Allocator;
77const assert = std.debug.assert;
88const Hash = std.crypto.hash.sha2.Sha256;
9const log = std.log.scoped(.package);
910
1011const Compilation = @import("Compilation.zig");
1112const Module = @import("Module.zig");
......@@ -128,6 +129,9 @@ pub fn addAndAdopt(parent: *Package, gpa: Allocator, name: []const u8, child: *P
128129 return parent.add(gpa, name, child);
129130}
130131
132pub const build_zig_basename = "build.zig";
133pub const ini_basename = build_zig_basename ++ ".ini";
134
131135pub fn fetchAndAddDependencies(
132136 pkg: *Package,
133137 thread_pool: *ThreadPool,
......@@ -138,7 +142,7 @@ pub fn fetchAndAddDependencies(
138142) !void {
139143 const max_bytes = 10 * 1024 * 1024;
140144 const gpa = thread_pool.allocator;
141 const build_zig_ini = directory.handle.readFileAlloc(gpa, "build.zig.ini", max_bytes) catch |err| switch (err) {
145 const build_zig_ini = directory.handle.readFileAlloc(gpa, ini_basename, max_bytes) catch |err| switch (err) {
142146 error.FileNotFound => {
143147 // Handle the same as no dependencies.
144148 return;
......@@ -154,7 +158,7 @@ pub fn fetchAndAddDependencies(
154158 var line_it = mem.split(u8, dep, "\n");
155159 var opt_id: ?[]const u8 = null;
156160 var opt_url: ?[]const u8 = null;
157 var expected_hash: ?[Hash.digest_length]u8 = null;
161 var expected_hash: ?[]const u8 = null;
158162 while (line_it.next()) |kv| {
159163 const eq_pos = mem.indexOfScalar(u8, kv, '=') orelse continue;
160164 const key = kv[0..eq_pos];
......@@ -164,8 +168,7 @@ pub fn fetchAndAddDependencies(
164168 } else if (mem.eql(u8, key, "url")) {
165169 opt_url = value;
166170 } else if (mem.eql(u8, key, "hash")) {
167 @panic("TODO parse hex digits of value into expected_hash");
168 //expected_hash = value;
171 expected_hash = value;
169172 } else {
170173 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(key.ptr) - @ptrToInt(ini.bytes.ptr));
171174 std.log.warn("{s}/{s}:{d}:{d} unrecognized key: '{s}'", .{
......@@ -208,6 +211,8 @@ pub fn fetchAndAddDependencies(
208211 global_cache_directory,
209212 url,
210213 expected_hash,
214 ini,
215 directory,
211216 );
212217
213218 try sub_pkg.fetchAndAddDependencies(
......@@ -229,17 +234,48 @@ fn fetchAndUnpack(
229234 http_client: *std.http.Client,
230235 global_cache_directory: Compilation.Directory,
231236 url: []const u8,
232 expected_hash: ?[Hash.digest_length]u8,
237 expected_hash: ?[]const u8,
238 ini: std.Ini,
239 comp_directory: Compilation.Directory,
233240) !*Package {
234241 const gpa = http_client.allocator;
242 const s = fs.path.sep_str;
235243
236244 // Check if the expected_hash is already present in the global package
237245 // cache, and thereby avoid both fetching and unpacking.
238 const s = fs.path.sep_str;
239 if (expected_hash) |h| {
240 const pkg_dir_sub_path = "p" ++ s ++ hexDigest(h);
241 _ = pkg_dir_sub_path;
242 @panic("TODO check the p dir for the package");
246 if (expected_hash) |h| cached: {
247 if (h.len != 2 * Hash.digest_length) {
248 return reportError(
249 ini,
250 comp_directory,
251 h.ptr,
252 "wrong hash size. expected: {d}, found: {d}",
253 .{ Hash.digest_length, h.len },
254 );
255 }
256 const hex_digest = h[0 .. 2 * Hash.digest_length];
257 const pkg_dir_sub_path = "p" ++ s ++ hex_digest;
258 var pkg_dir = global_cache_directory.handle.openDir(pkg_dir_sub_path, .{}) catch |err| switch (err) {
259 error.FileNotFound => break :cached,
260 else => |e| return e,
261 };
262 errdefer pkg_dir.close();
263
264 const ptr = try gpa.create(Package);
265 errdefer gpa.destroy(ptr);
266
267 const owned_src_path = try gpa.dupe(u8, build_zig_basename);
268 errdefer gpa.free(owned_src_path);
269
270 ptr.* = .{
271 .root_src_directory = .{
272 .path = try global_cache_directory.join(gpa, &.{pkg_dir_sub_path}),
273 .handle = pkg_dir,
274 },
275 .root_src_directory_owned = true,
276 .root_src_path = owned_src_path,
277 };
278 return ptr;
243279 }
244280
245281 const uri = try std.Uri.parse(url);
......@@ -277,9 +313,13 @@ fn fetchAndUnpack(
277313 .strip_components = 1,
278314 });
279315 } else {
280 // TODO: show the build.zig.ini file and line number
281 std.log.err("{s}: unknown package extension for path '{s}'", .{ url, uri.path });
282 return error.UnknownPackageExtension;
316 return reportError(
317 ini,
318 comp_directory,
319 uri.path.ptr,
320 "unknown file extension for path '{s}'",
321 .{uri.path},
322 );
283323 }
284324
285325 // TODO: delete files not included in the package prior to computing the package hash.
......@@ -287,24 +327,13 @@ fn fetchAndUnpack(
287327 // apply those rules directly to the filesystem right here. This ensures that files
288328 // not protected by the hash are not present on the file system.
289329
290 const actual_hash = try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle });
291
292 if (expected_hash) |h| {
293 if (!mem.eql(u8, &h, &actual_hash)) {
294 // TODO: show the build.zig.ini file and line number
295 std.log.err("{s}: hash mismatch: expected: {s}, actual: {s}", .{
296 url, h, actual_hash,
297 });
298 return error.PackageHashMismatch;
299 }
300 }
301
302 break :a actual_hash;
330 break :a try computePackageHash(thread_pool, .{ .dir = tmp_directory.handle });
303331 };
304332
333 const pkg_dir_sub_path = "p" ++ s ++ hexDigest(actual_hash);
334
305335 {
306336 // Rename the temporary directory into the global package cache.
307 const pkg_dir_sub_path = "p" ++ s ++ hexDigest(actual_hash);
308337 var handled_missing_dir = false;
309338 while (true) {
310339 global_cache_directory.handle.rename(tmp_dir_sub_path, pkg_dir_sub_path) catch |err| switch (err) {
......@@ -316,27 +345,60 @@ fn fetchAndUnpack(
316345 };
317346 continue;
318347 },
348 error.PathAlreadyExists => {
349 // Package has been already downloaded and may already be in use on the system.
350 global_cache_directory.handle.deleteTree(tmp_dir_sub_path) catch |del_err| {
351 std.log.warn("unable to delete temp directory: {s}", .{@errorName(del_err)});
352 };
353 },
319354 else => |e| return e,
320355 };
321356 break;
322357 }
323358 }
324359
325 if (expected_hash == null) {
326 // TODO: show the build.zig.ini file and line number
327 std.log.err("{s}: missing hash:\nhash={s}", .{
328 url, std.fmt.fmtSliceHexLower(&actual_hash),
329 });
330 return error.PackageDependencyMissingHash;
360 if (expected_hash) |h| {
361 const actual_hex = hexDigest(actual_hash);
362 if (!mem.eql(u8, h, &actual_hex)) {
363 return reportError(
364 ini,
365 comp_directory,
366 h.ptr,
367 "hash mismatch: expected: {s}, found: {s}",
368 .{ h, actual_hex },
369 );
370 }
371 } else {
372 return reportError(
373 ini,
374 comp_directory,
375 url.ptr,
376 "url field is missing corresponding hash field: hash={s}",
377 .{std.fmt.fmtSliceHexLower(&actual_hash)},
378 );
331379 }
332380
333 @panic("TODO create package and set root_src_directory");
334 //return create(gpa, root_src
335 //gpa: Allocator,
336 ///// Null indicates the current working directory
337 //root_src_dir_path: ?[]const u8,
338 ///// Relative to root_src_dir_path
339 //root_src_path: []const u8,
381 return createWithDir(gpa, global_cache_directory, pkg_dir_sub_path, build_zig_basename);
382}
383
384fn reportError(
385 ini: std.Ini,
386 comp_directory: Compilation.Directory,
387 src_ptr: [*]const u8,
388 comptime fmt_string: []const u8,
389 fmt_args: anytype,
390) error{PackageFetchFailed} {
391 const loc = std.zig.findLineColumn(ini.bytes, @ptrToInt(src_ptr) - @ptrToInt(ini.bytes.ptr));
392 if (comp_directory.path) |p| {
393 std.debug.print("{s}{c}{s}:{d}:{d}: error: " ++ fmt_string ++ "\n", .{
394 p, fs.path.sep, ini_basename, loc.line + 1, loc.column + 1,
395 } ++ fmt_args);
396 } else {
397 std.debug.print("{s}:{d}:{d}: error: " ++ fmt_string ++ "\n", .{
398 ini_basename, loc.line + 1, loc.column + 1,
399 } ++ fmt_args);
400 }
401 return error.PackageFetchFailed;
340402}
341403
342404const HashedFile = struct {
......@@ -389,9 +451,10 @@ fn computePackageHash(
389451 .hash = undefined, // to be populated by the worker
390452 .failure = undefined, // to be populated by the worker
391453 };
392
393454 wait_group.start();
394455 try thread_pool.spawn(workerHashFile, .{ pkg_dir.dir, hashed_file, &wait_group });
456
457 try all_files.append(hashed_file);
395458 }
396459 }
397460
src/main.zig+5-2
......@@ -4088,13 +4088,16 @@ pub fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !voi
40884088 defer http_client.deinit();
40894089 try http_client.rescanRootCertificates();
40904090
4091 try main_pkg.fetchAndAddDependencies(
4091 main_pkg.fetchAndAddDependencies(
40924092 &thread_pool,
40934093 &http_client,
40944094 build_directory,
40954095 global_cache_directory,
40964096 local_cache_directory,
4097 );
4097 ) catch |err| switch (err) {
4098 error.PackageFetchFailed => process.exit(1),
4099 else => |e| return e,
4100 };
40984101 }
40994102
41004103 const comp = Compilation.create(gpa, .{