| ... | ... | @@ -68,8 +68,7 @@ use_latest_commit: bool, |
| 68 | 68 | // Above this are fields provided as inputs to `run`. |
| 69 | 69 | // Below this are fields populated by `run`. |
| 70 | 70 | |
| 71 | | /// This will either be relative to `global_cache`, or to the build root of |
| 72 | | /// the root package. |
| 71 | /// Relative to the build root of the root package. |
| 73 | 72 | package_root: Cache.Path, |
| 74 | 73 | error_bundle: ErrorBundle.Wip, |
| 75 | 74 | manifest: ?Manifest, |
| ... | ... | @@ -115,6 +114,9 @@ pub const JobQueue = struct { |
| 115 | 114 | http_client: *std.http.Client, |
| 116 | 115 | group: Io.Group = .init, |
| 117 | 116 | global_cache: Cache.Directory, |
| 117 | local_cache: Cache.Path, |
| 118 | /// Path to "zig-pkg" inside the package in which the user ran `zig build`. |
| 119 | root_pkg_path: Cache.Path, |
| 118 | 120 | /// If true then, no fetching occurs, and: |
| 119 | 121 | /// * The `global_cache` directory is assumed to be the direct parent |
| 120 | 122 | /// directory of on-disk packages rather than having the "p/" directory |
| ... | ... | @@ -325,11 +327,12 @@ pub const RunError = error{ |
| 325 | 327 | }; |
| 326 | 328 | |
| 327 | 329 | pub fn run(f: *Fetch) RunError!void { |
| 328 | | const io = f.job_queue.io; |
| 330 | const job_queue = f.job_queue; |
| 331 | const io = job_queue.io; |
| 329 | 332 | const eb = &f.error_bundle; |
| 330 | 333 | const arena = f.arena.allocator(); |
| 331 | 334 | const gpa = f.arena.child_allocator; |
| 332 | | const cache_root = f.job_queue.global_cache; |
| 335 | const local_cache_root = job_queue.local_cache; |
| 333 | 336 | |
| 334 | 337 | try eb.init(gpa); |
| 335 | 338 | |
| ... | ... | @@ -350,13 +353,13 @@ pub fn run(f: *Fetch) RunError!void { |
| 350 | 353 | ); |
| 351 | 354 | // Packages fetched by URL may not use relative paths to escape outside the |
| 352 | 355 | // fetched package directory from within the package cache. |
| 353 | | if (pkg_root.root_dir.eql(cache_root)) { |
| 356 | if (pkg_root.root_dir.eql(local_cache_root.root_dir)) { |
| 354 | 357 | // `parent_package_root.sub_path` contains a path like this: |
| 355 | 358 | // "p/$hash", or |
| 356 | 359 | // "p/$hash/foo", with possibly more directories after "foo". |
| 357 | 360 | // We want to fail unless the resolved relative path has a |
| 358 | 361 | // prefix of "p/$hash/". |
| 359 | | const prefix_len: usize = if (f.job_queue.read_only) 0 else "p/".len; |
| 362 | const prefix_len: usize = if (job_queue.read_only) 0 else "p/".len; |
| 360 | 363 | const parent_sub_path = f.parent_package_root.sub_path; |
| 361 | 364 | const end = find_end: { |
| 362 | 365 | if (parent_sub_path.len > prefix_len) { |
| ... | ... | @@ -379,7 +382,7 @@ pub fn run(f: *Fetch) RunError!void { |
| 379 | 382 | f.package_root = pkg_root; |
| 380 | 383 | try loadManifest(f, pkg_root); |
| 381 | 384 | if (!f.has_build_zig) try checkBuildFileExistence(f); |
| 382 | | if (!f.job_queue.recursive) return; |
| 385 | if (!job_queue.recursive) return; |
| 383 | 386 | return queueJobsForDeps(f); |
| 384 | 387 | }, |
| 385 | 388 | .remote => |remote| remote, |
| ... | ... | @@ -411,51 +414,39 @@ pub fn run(f: *Fetch) RunError!void { |
| 411 | 414 | }; |
| 412 | 415 | |
| 413 | 416 | if (remote.hash) |expected_hash| { |
| 414 | | var prefixed_pkg_sub_path_buffer: [Package.Hash.max_len + 2]u8 = undefined; |
| 415 | | prefixed_pkg_sub_path_buffer[0] = 'p'; |
| 416 | | prefixed_pkg_sub_path_buffer[1] = fs.path.sep; |
| 417 | | const hash_slice = expected_hash.toSlice(); |
| 418 | | @memcpy(prefixed_pkg_sub_path_buffer[2..][0..hash_slice.len], hash_slice); |
| 419 | | const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len]; |
| 420 | | const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0; |
| 421 | | const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..]; |
| 422 | | if (cache_root.handle.access(io, pkg_sub_path, .{})) |_| { |
| 417 | const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice()); |
| 418 | if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { |
| 423 | 419 | assert(f.lazy_status != .unavailable); |
| 424 | | f.package_root = .{ |
| 425 | | .root_dir = cache_root, |
| 426 | | .sub_path = try arena.dupe(u8, pkg_sub_path), |
| 427 | | }; |
| 420 | f.package_root = package_root; |
| 428 | 421 | try loadManifest(f, f.package_root); |
| 429 | 422 | try checkBuildFileExistence(f); |
| 430 | | if (!f.job_queue.recursive) return; |
| 423 | if (!job_queue.recursive) return; |
| 431 | 424 | return queueJobsForDeps(f); |
| 432 | 425 | } else |err| switch (err) { |
| 433 | 426 | error.FileNotFound => { |
| 434 | 427 | switch (f.lazy_status) { |
| 435 | 428 | .eager => {}, |
| 436 | | .available => if (!f.job_queue.unlazy_set.contains(expected_hash)) { |
| 429 | .available => if (!job_queue.unlazy_set.contains(expected_hash)) { |
| 437 | 430 | f.lazy_status = .unavailable; |
| 438 | 431 | return; |
| 439 | 432 | }, |
| 440 | 433 | .unavailable => unreachable, |
| 441 | 434 | } |
| 442 | | if (f.job_queue.read_only) return f.fail( |
| 435 | if (job_queue.read_only) return f.fail( |
| 443 | 436 | f.name_tok, |
| 444 | | try eb.printString("package not found at '{f}{s}'", .{ |
| 445 | | cache_root, pkg_sub_path, |
| 446 | | }), |
| 437 | try eb.printString("package not found at '{f}'", .{package_root}), |
| 447 | 438 | ); |
| 448 | 439 | }, |
| 449 | 440 | else => |e| { |
| 450 | 441 | try eb.addRootErrorMessage(.{ |
| 451 | | .msg = try eb.printString("unable to open global package cache directory '{f}{s}': {s}", .{ |
| 452 | | cache_root, pkg_sub_path, @errorName(e), |
| 442 | .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{ |
| 443 | package_root, e, |
| 453 | 444 | }), |
| 454 | 445 | }); |
| 455 | 446 | return error.FetchFailed; |
| 456 | 447 | }, |
| 457 | 448 | } |
| 458 | | } else if (f.job_queue.read_only) { |
| 449 | } else if (job_queue.read_only) { |
| 459 | 450 | try eb.addRootErrorMessage(.{ |
| 460 | 451 | .msg = try eb.addString("dependency is missing hash field"), |
| 461 | 452 | .src_loc = try f.srcLoc(f.location_tok), |
| ... | ... | @@ -467,7 +458,7 @@ pub fn run(f: *Fetch) RunError!void { |
| 467 | 458 | |
| 468 | 459 | const uri = std.Uri.parse(remote.url) catch |err| return f.fail( |
| 469 | 460 | f.location_tok, |
| 470 | | try eb.printString("invalid URI: {s}", .{@errorName(err)}), |
| 461 | try eb.printString("invalid URI: {t}", .{err}), |
| 471 | 462 | ); |
| 472 | 463 | var buffer: [init_resource_buffer_size]u8 = undefined; |
| 473 | 464 | var resource: Resource = undefined; |
| ... | ... | @@ -487,29 +478,30 @@ fn runResource( |
| 487 | 478 | resource: *Resource, |
| 488 | 479 | remote_hash: ?Package.Hash, |
| 489 | 480 | ) RunError!void { |
| 490 | | const io = f.job_queue.io; |
| 481 | const job_queue = f.job_queue; |
| 482 | const io = job_queue.io; |
| 491 | 483 | defer resource.deinit(io); |
| 492 | 484 | const arena = f.arena.allocator(); |
| 493 | 485 | const eb = &f.error_bundle; |
| 494 | 486 | const s = fs.path.sep_str; |
| 495 | | const cache_root = f.job_queue.global_cache; |
| 487 | const local_cache_root = job_queue.local_cache; |
| 496 | 488 | const rand_int = r: { |
| 497 | 489 | var x: u64 = undefined; |
| 498 | 490 | io.random(@ptrCast(&x)); |
| 499 | 491 | break :r x; |
| 500 | 492 | }; |
| 501 | 493 | const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int); |
| 494 | const tmp_directory_path = try local_cache_root.join(arena, tmp_dir_sub_path); |
| 502 | 495 | |
| 503 | 496 | const package_sub_path = blk: { |
| 504 | | const tmp_directory_path = try cache_root.join(arena, &.{tmp_dir_sub_path}); |
| 505 | 497 | var tmp_directory: Cache.Directory = .{ |
| 506 | | .path = tmp_directory_path, |
| 498 | .path = tmp_directory_path.sub_path, |
| 507 | 499 | .handle = handle: { |
| 508 | | const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ |
| 500 | const dir = tmp_directory_path.root_dir.handle.createDirPathOpen(io, tmp_directory_path.sub_path, .{ |
| 509 | 501 | .open_options = .{ .iterate = true }, |
| 510 | 502 | }) catch |err| { |
| 511 | 503 | try eb.addRootErrorMessage(.{ |
| 512 | | .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{ |
| 504 | .msg = try eb.printString("unable to create temporary directory '{f}': {t}", .{ |
| 513 | 505 | tmp_directory_path, err, |
| 514 | 506 | }), |
| 515 | 507 | }); |
| ... | ... | @@ -545,36 +537,33 @@ fn runResource( |
| 545 | 537 | // directory. |
| 546 | 538 | f.computed_hash = try computeHash(f, pkg_path, filter); |
| 547 | 539 | |
| 548 | | break :blk if (unpack_result.root_dir.len > 0) |
| 549 | | try fs.path.join(arena, &.{ tmp_dir_sub_path, unpack_result.root_dir }) |
| 550 | | else |
| 551 | | tmp_dir_sub_path; |
| 540 | if (unpack_result.root_dir.len > 0) |
| 541 | break :blk try tmp_directory_path.join(arena, unpack_result.root_dir); |
| 542 | |
| 543 | break :blk tmp_directory_path; |
| 552 | 544 | }; |
| 553 | 545 | |
| 554 | 546 | const computed_package_hash = computedPackageHash(f); |
| 555 | 547 | |
| 556 | | // Rename the temporary directory into the global zig package cache |
| 557 | | // directory. If the hash already exists, delete the temporary directory |
| 558 | | // and leave the zig package cache directory untouched as it may be in use |
| 559 | | // by the system. This is done even if the hash is invalid, in case the |
| 560 | | // package with the different hash is used in the future. |
| 561 | | |
| 562 | | f.package_root = .{ |
| 563 | | .root_dir = cache_root, |
| 564 | | .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}), |
| 565 | | }; |
| 566 | | renameTmpIntoCache(io, cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| { |
| 567 | | const src = try cache_root.join(arena, &.{tmp_dir_sub_path}); |
| 568 | | const dest = try cache_root.join(arena, &.{f.package_root.sub_path}); |
| 548 | // Rename the temporary directory into the local zig package directory. If |
| 549 | // the hash already exists, delete the temporary directory and leave the |
| 550 | // zig package directory untouched as it may be in use. This is done even |
| 551 | // if the hash is invalid, in case the package with the different hash is |
| 552 | // used in the future. |
| 553 | f.package_root = try job_queue.root_pkg_path.join(arena, computed_package_hash.toSlice()); |
| 554 | renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| { |
| 569 | 555 | try eb.addRootErrorMessage(.{ .msg = try eb.printString( |
| 570 | | "unable to rename temporary directory '{s}' into package cache directory '{s}': {s}", |
| 571 | | .{ src, dest, @errorName(err) }, |
| 556 | "unable to rename temporary directory {f} into package cache directory {f}: {t}", |
| 557 | .{ package_sub_path, f.package_root, err }, |
| 572 | 558 | ) }); |
| 573 | 559 | return error.FetchFailed; |
| 574 | 560 | }; |
| 575 | 561 | // Remove temporary directory root if not already renamed to global cache. |
| 576 | | if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) { |
| 577 | | cache_root.handle.deleteDir(io, tmp_dir_sub_path) catch {}; |
| 562 | if (!package_sub_path.eql(tmp_directory_path)) { |
| 563 | tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) { |
| 564 | error.Canceled => |e| return e, |
| 565 | else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }), |
| 566 | }; |
| 578 | 567 | } |
| 579 | 568 | |
| 580 | 569 | // Validate the computed hash against the expected hash. If invalid, this |
| ... | ... | @@ -614,7 +603,7 @@ fn runResource( |
| 614 | 603 | |
| 615 | 604 | // Spawn a new fetch job for each dependency in the manifest file. Use |
| 616 | 605 | // a mutex and a hash map so that redundant jobs do not get queued up. |
| 617 | | if (!f.job_queue.recursive) return; |
| 606 | if (!job_queue.recursive) return; |
| 618 | 607 | return queueJobsForDeps(f); |
| 619 | 608 | } |
| 620 | 609 | |
| ... | ... | @@ -641,8 +630,8 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void { |
| 641 | 630 | error.FileNotFound => {}, |
| 642 | 631 | else => |e| { |
| 643 | 632 | try eb.addRootErrorMessage(.{ |
| 644 | | .msg = try eb.printString("unable to access '{f}{s}': {s}", .{ |
| 645 | | f.package_root, Package.build_zig_basename, @errorName(e), |
| 633 | .msg = try eb.printString("unable to access '{f}{s}': {t}", .{ |
| 634 | f.package_root, Package.build_zig_basename, e, |
| 646 | 635 | }), |
| 647 | 636 | }); |
| 648 | 637 | return error.FetchFailed; |
| ... | ... | @@ -667,9 +656,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { |
| 667 | 656 | else => |e| { |
| 668 | 657 | const file_path = try pkg_root.join(arena, Manifest.basename); |
| 669 | 658 | try eb.addRootErrorMessage(.{ |
| 670 | | .msg = try eb.printString("unable to load package manifest '{f}': {s}", .{ |
| 671 | | file_path, @errorName(e), |
| 672 | | }), |
| 659 | .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ file_path, e }), |
| 673 | 660 | }); |
| 674 | 661 | return error.FetchFailed; |
| 675 | 662 | }, |
| ... | ... | @@ -1453,14 +1440,20 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void |
| 1453 | 1440 | } |
| 1454 | 1441 | } |
| 1455 | 1442 | |
| 1456 | | pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { |
| 1457 | | assert(dest_dir_sub_path[1] == fs.path.sep); |
| 1443 | pub fn renameTmpIntoCache(io: Io, tmp_path: Cache.Path, dest_path: Cache.Path) !void { |
| 1458 | 1444 | var handled_missing_dir = false; |
| 1459 | 1445 | while (true) { |
| 1460 | | cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) { |
| 1446 | Io.Dir.rename( |
| 1447 | tmp_path.root_dir.handle, |
| 1448 | tmp_path.sub_path, |
| 1449 | dest_path.root_dir.handle, |
| 1450 | dest_path.sub_path, |
| 1451 | io, |
| 1452 | ) catch |err| switch (err) { |
| 1461 | 1453 | error.FileNotFound => { |
| 1462 | 1454 | if (handled_missing_dir) return err; |
| 1463 | | cache_dir.createDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) { |
| 1455 | const parent_sub_path = Io.Dir.path.dirname(dest_path.sub_path).?; |
| 1456 | dest_path.root_dir.handle.createDir(io, parent_sub_path, .default_dir) catch |er| switch (er) { |
| 1464 | 1457 | error.PathAlreadyExists => handled_missing_dir = true, |
| 1465 | 1458 | else => |e| return e, |
| 1466 | 1459 | }; |
| ... | ... | @@ -1468,9 +1461,11 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u |
| 1468 | 1461 | }, |
| 1469 | 1462 | error.DirNotEmpty, error.AccessDenied => { |
| 1470 | 1463 | // Package has been already downloaded and may already be in use on the system. |
| 1471 | | cache_dir.deleteTree(io, tmp_dir_sub_path) catch { |
| 1464 | tmp_path.root_dir.handle.deleteTree(io, tmp_path.sub_path) catch |er| switch (er) { |
| 1465 | error.Canceled => |e| return e, |
| 1472 | 1466 | // Garbage files leftover in zig-cache/tmp/ is, as they say |
| 1473 | 1467 | // on Star Trek, "operating within normal parameters". |
| 1468 | else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }), |
| 1474 | 1469 | }; |
| 1475 | 1470 | }, |
| 1476 | 1471 | else => |e| return e, |
| ... | ... | @@ -2244,6 +2239,7 @@ fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { |
| 2244 | 2239 | const TestFetchBuilder = struct { |
| 2245 | 2240 | http_client: std.http.Client, |
| 2246 | 2241 | global_cache_directory: Cache.Directory, |
| 2242 | local_cache_path: Cache.Path, |
| 2247 | 2243 | job_queue: Fetch.JobQueue, |
| 2248 | 2244 | fetch: Fetch, |
| 2249 | 2245 | |
| ... | ... | @@ -2254,15 +2250,25 @@ const TestFetchBuilder = struct { |
| 2254 | 2250 | cache_parent_dir: std.Io.Dir, |
| 2255 | 2251 | path_or_url: []const u8, |
| 2256 | 2252 | ) !*Fetch { |
| 2257 | | const cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); |
| 2253 | const global_cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); |
| 2254 | const package_root_dir = try cache_parent_dir.createDirPathOpen(io, "local-project-root", .{}); |
| 2258 | 2255 | |
| 2259 | 2256 | self.http_client = .{ .allocator = allocator, .io = io }; |
| 2260 | | self.global_cache_directory = .{ .handle = cache_dir, .path = null }; |
| 2257 | self.global_cache_directory = .{ .handle = global_cache_dir, .path = "zig-global-cache" }; |
| 2258 | self.local_cache_path = .{ |
| 2259 | .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, |
| 2260 | .sub_path = ".zig-cache", |
| 2261 | }; |
| 2261 | 2262 | |
| 2262 | 2263 | self.job_queue = .{ |
| 2263 | 2264 | .io = io, |
| 2264 | 2265 | .http_client = &self.http_client, |
| 2265 | 2266 | .global_cache = self.global_cache_directory, |
| 2267 | .local_cache = self.local_cache_path, |
| 2268 | .root_pkg_path = .{ |
| 2269 | .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, |
| 2270 | .sub_path = "zig-pkg", |
| 2271 | }, |
| 2266 | 2272 | .recursive = false, |
| 2267 | 2273 | .read_only = false, |
| 2268 | 2274 | .debug_hash = false, |
| ... | ... | @@ -2276,7 +2282,7 @@ const TestFetchBuilder = struct { |
| 2276 | 2282 | .hash_tok = .none, |
| 2277 | 2283 | .name_tok = 0, |
| 2278 | 2284 | .lazy_status = .eager, |
| 2279 | | .parent_package_root = Cache.Path{ .root_dir = Cache.Directory{ .handle = cache_dir, .path = null } }, |
| 2285 | .parent_package_root = .{ .root_dir = .{ .handle = package_root_dir, .path = null } }, |
| 2280 | 2286 | .parent_manifest_ast = null, |
| 2281 | 2287 | .prog_node = std.Progress.Node.none, |
| 2282 | 2288 | .job_queue = &self.job_queue, |