| ... | @@ -200,7 +200,7 @@ pub const JobQueue = struct { | ... | @@ -200,7 +200,7 @@ pub const JobQueue = struct { |
| 200 | pub const Location = union(enum) { | 200 | pub const Location = union(enum) { |
| 201 | remote: Remote, | 201 | remote: Remote, |
| 202 | /// A directory found inside the parent package. | 202 | /// A directory found inside the parent package. |
| 203 | relative_path: []const u8, | 203 | relative_path: Package.Path, |
| 204 | /// Recursive Fetch tasks will never use this Location, but it may be | 204 | /// Recursive Fetch tasks will never use this Location, but it may be |
| 205 | /// passed in by the CLI. Indicates the file contents here should be copied | 205 | /// passed in by the CLI. Indicates the file contents here should be copied |
| 206 | /// into the global package cache. It may be a file relative to the cwd or | 206 | /// into the global package cache. It may be a file relative to the cwd or |
| ... | @@ -239,8 +239,8 @@ pub fn run(f: *Fetch) RunError!void { | ... | @@ -239,8 +239,8 @@ pub fn run(f: *Fetch) RunError!void { |
| 239 | // relative path, treat this the same as a cache hit. Otherwise, proceed. | 239 | // relative path, treat this the same as a cache hit. Otherwise, proceed. |
| 240 | | 240 | |
| 241 | const remote = switch (f.location) { | 241 | const remote = switch (f.location) { |
| 242 | .relative_path => |sub_path| { | 242 | .relative_path => |pkg_root| { |
| 243 | if (fs.path.isAbsolute(sub_path)) return f.fail( | 243 | if (fs.path.isAbsolute(pkg_root.sub_path)) return f.fail( |
| 244 | f.location_tok, | 244 | f.location_tok, |
| 245 | try eb.addString("expected path relative to build root; found absolute path"), | 245 | try eb.addString("expected path relative to build root; found absolute path"), |
| 246 | ); | 246 | ); |
| ... | @@ -248,31 +248,19 @@ pub fn run(f: *Fetch) RunError!void { | ... | @@ -248,31 +248,19 @@ pub fn run(f: *Fetch) RunError!void { |
| 248 | f.hash_tok, | 248 | f.hash_tok, |
| 249 | try eb.addString("path-based dependencies are not hashed"), | 249 | try eb.addString("path-based dependencies are not hashed"), |
| 250 | ); | 250 | ); |
| 251 | f.package_root = try f.parent_package_root.resolvePosix(arena, sub_path); | 251 | if (std.mem.startsWith(u8, pkg_root.sub_path, "../")) { |
| 252 | if (std.mem.startsWith(u8, f.package_root.sub_path, "../")) { | | |
| 253 | return f.fail( | 252 | return f.fail( |
| 254 | f.location_tok, | 253 | f.location_tok, |
| 255 | try eb.addString("dependency path outside package"), | 254 | try eb.printString("dependency path outside project: '{}{s}'", .{ |
| | 255 | pkg_root.root_dir, pkg_root.sub_path, |
| | 256 | }), |
| 256 | ); | 257 | ); |
| 257 | } | 258 | } |
| 258 | try loadManifest(f, f.package_root); | 259 | f.package_root = pkg_root; |
| | 260 | try loadManifest(f, pkg_root); |
| 259 | try checkBuildFileExistence(f); | 261 | try checkBuildFileExistence(f); |
| 260 | if (!f.job_queue.recursive) return; | 262 | if (!f.job_queue.recursive) return; |
| 261 | // Package hashes are used as unique identifiers for packages, so | 263 | return queueJobsForDeps(f); |
| 262 | // we still need one for relative paths. | | |
| 263 | const digest = h: { | | |
| 264 | var hasher = Manifest.Hash.init(.{}); | | |
| 265 | // This hash is a tuple of: | | |
| 266 | // * whether it relative to the global cache directory or to the root package | | |
| 267 | // * the relative file path from there to the build root of the package | | |
| 268 | hasher.update(if (f.package_root.root_dir.eql(cache_root)) | | |
| 269 | &package_hash_prefix_cached | | |
| 270 | else | | |
| 271 | &package_hash_prefix_project); | | |
| 272 | hasher.update(f.package_root.sub_path); | | |
| 273 | break :h hasher.finalResult(); | | |
| 274 | }; | | |
| 275 | return queueJobsForDeps(f, Manifest.hexDigest(digest)); | | |
| 276 | }, | 264 | }, |
| 277 | .remote => |remote| remote, | 265 | .remote => |remote| remote, |
| 278 | .path_or_url => |path_or_url| { | 266 | .path_or_url => |path_or_url| { |
| ... | @@ -310,7 +298,7 @@ pub fn run(f: *Fetch) RunError!void { | ... | @@ -310,7 +298,7 @@ pub fn run(f: *Fetch) RunError!void { |
| 310 | try loadManifest(f, f.package_root); | 298 | try loadManifest(f, f.package_root); |
| 311 | try checkBuildFileExistence(f); | 299 | try checkBuildFileExistence(f); |
| 312 | if (!f.job_queue.recursive) return; | 300 | if (!f.job_queue.recursive) return; |
| 313 | return queueJobsForDeps(f, expected_hash); | 301 | return queueJobsForDeps(f); |
| 314 | } else |err| switch (err) { | 302 | } else |err| switch (err) { |
| 315 | error.FileNotFound => {}, | 303 | error.FileNotFound => {}, |
| 316 | else => |e| { | 304 | else => |e| { |
| ... | @@ -450,7 +438,7 @@ fn runResource( | ... | @@ -450,7 +438,7 @@ fn runResource( |
| 450 | // Spawn a new fetch job for each dependency in the manifest file. Use | 438 | // Spawn a new fetch job for each dependency in the manifest file. Use |
| 451 | // a mutex and a hash map so that redundant jobs do not get queued up. | 439 | // a mutex and a hash map so that redundant jobs do not get queued up. |
| 452 | if (!f.job_queue.recursive) return; | 440 | if (!f.job_queue.recursive) return; |
| 453 | return queueJobsForDeps(f, actual_hex); | 441 | return queueJobsForDeps(f); |
| 454 | } | 442 | } |
| 455 | | 443 | |
| 456 | /// `computeHash` gets a free check for the existence of `build.zig`, but when | 444 | /// `computeHash` gets a free check for the existence of `build.zig`, but when |
| ... | @@ -534,27 +522,29 @@ fn loadManifest(f: *Fetch, pkg_root: Package.Path) RunError!void { | ... | @@ -534,27 +522,29 @@ fn loadManifest(f: *Fetch, pkg_root: Package.Path) RunError!void { |
| 534 | } | 522 | } |
| 535 | } | 523 | } |
| 536 | | 524 | |
| 537 | fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void { | 525 | fn queueJobsForDeps(f: *Fetch) RunError!void { |
| 538 | assert(f.job_queue.recursive); | 526 | assert(f.job_queue.recursive); |
| 539 | | 527 | |
| 540 | // If the package does not have a build.zig.zon file then there are no dependencies. | 528 | // If the package does not have a build.zig.zon file then there are no dependencies. |
| 541 | const manifest = f.manifest orelse return; | 529 | const manifest = f.manifest orelse return; |
| 542 | | 530 | |
| 543 | const new_fetches = nf: { | 531 | const new_fetches = nf: { |
| 544 | const deps = manifest.dependencies.values(); | 532 | const parent_arena = f.arena.allocator(); |
| 545 | const gpa = f.arena.child_allocator; | 533 | const gpa = f.arena.child_allocator; |
| | 534 | const cache_root = f.job_queue.global_cache; |
| | 535 | const deps = manifest.dependencies.values(); |
| 546 | // Grab the new tasks into a temporary buffer so we can unlock that mutex | 536 | // Grab the new tasks into a temporary buffer so we can unlock that mutex |
| 547 | // as fast as possible. | 537 | // as fast as possible. |
| 548 | // This overallocates any fetches that get skipped by the `continue` in the | 538 | // This overallocates any fetches that get skipped by the `continue` in the |
| 549 | // loop below. | 539 | // loop below. |
| 550 | const new_fetches = try f.arena.allocator().alloc(Fetch, deps.len); | 540 | const new_fetches = try parent_arena.alloc(Fetch, deps.len); |
| 551 | var new_fetch_index: usize = 0; | 541 | var new_fetch_index: usize = 0; |
| 552 | | 542 | |
| 553 | f.job_queue.mutex.lock(); | 543 | f.job_queue.mutex.lock(); |
| 554 | defer f.job_queue.mutex.unlock(); | 544 | defer f.job_queue.mutex.unlock(); |
| 555 | | 545 | |
| 556 | try f.job_queue.all_fetches.ensureUnusedCapacity(gpa, new_fetches.len); | 546 | try f.job_queue.all_fetches.ensureUnusedCapacity(gpa, new_fetches.len); |
| 557 | try f.job_queue.table.ensureUnusedCapacity(gpa, @intCast(new_fetches.len + 1)); | 547 | try f.job_queue.table.ensureUnusedCapacity(gpa, @intCast(new_fetches.len)); |
| 558 | | 548 | |
| 559 | // There are four cases here: | 549 | // There are four cases here: |
| 560 | // * Correct hash is provided by manifest. | 550 | // * Correct hash is provided by manifest. |
| ... | @@ -564,12 +554,8 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void | ... | @@ -564,12 +554,8 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void |
| 564 | // * Hash is not provided by manifest. | 554 | // * Hash is not provided by manifest. |
| 565 | // - Hash missing error emitted; `queueJobsForDeps` is not called. | 555 | // - Hash missing error emitted; `queueJobsForDeps` is not called. |
| 566 | // * path-based location is used without a hash. | 556 | // * path-based location is used without a hash. |
| 567 | // - We need to add `hash` to the table now. | 557 | // - Hash is added to the table based on the path alone before |
| 568 | switch (f.location) { | 558 | // calling run(); no need to add it again. |
| 569 | .remote => assert(f.job_queue.table.get(hash) == f), | | |
| 570 | .relative_path => f.job_queue.table.putAssumeCapacityNoClobber(hash, f), | | |
| 571 | .path_or_url => unreachable, | | |
| 572 | } | | |
| 573 | | 559 | |
| 574 | for (deps) |dep| { | 560 | for (deps) |dep| { |
| 575 | const new_fetch = &new_fetches[new_fetch_index]; | 561 | const new_fetch = &new_fetches[new_fetch_index]; |
| ... | @@ -586,7 +572,16 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void | ... | @@ -586,7 +572,16 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void |
| 586 | break :h multihash_digest; | 572 | break :h multihash_digest; |
| 587 | }, | 573 | }, |
| 588 | } }, | 574 | } }, |
| 589 | .path => |path| .{ .relative_path = path }, | 575 | .path => |rel_path| l: { |
| | 576 | // This might produce an invalid path, which is checked for |
| | 577 | // at the beginning of run(). |
| | 578 | const new_root = try f.package_root.resolvePosix(parent_arena, rel_path); |
| | 579 | const multihash_digest = relativePathDigest(new_root, cache_root); |
| | 580 | const gop = f.job_queue.table.getOrPutAssumeCapacity(multihash_digest); |
| | 581 | if (gop.found_existing) continue; |
| | 582 | gop.value_ptr.* = new_fetch; |
| | 583 | break :l .{ .relative_path = new_root }; |
| | 584 | }, |
| 590 | }; | 585 | }; |
| 591 | new_fetch_index += 1; | 586 | new_fetch_index += 1; |
| 592 | f.job_queue.all_fetches.appendAssumeCapacity(new_fetch); | 587 | f.job_queue.all_fetches.appendAssumeCapacity(new_fetch); |
| ... | @@ -630,6 +625,22 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void | ... | @@ -630,6 +625,22 @@ fn queueJobsForDeps(f: *Fetch, hash: Manifest.MultiHashHexDigest) RunError!void |
| 630 | } | 625 | } |
| 631 | } | 626 | } |
| 632 | | 627 | |
| | 628 | pub fn relativePathDigest( |
| | 629 | pkg_root: Package.Path, |
| | 630 | cache_root: Cache.Directory, |
| | 631 | ) Manifest.MultiHashHexDigest { |
| | 632 | var hasher = Manifest.Hash.init(.{}); |
| | 633 | // This hash is a tuple of: |
| | 634 | // * whether it relative to the global cache directory or to the root package |
| | 635 | // * the relative file path from there to the build root of the package |
| | 636 | hasher.update(if (pkg_root.root_dir.eql(cache_root)) |
| | 637 | &package_hash_prefix_cached |
| | 638 | else |
| | 639 | &package_hash_prefix_project); |
| | 640 | hasher.update(pkg_root.sub_path); |
| | 641 | return Manifest.hexDigest(hasher.finalResult()); |
| | 642 | } |
| | 643 | |
| 633 | pub fn workerRun(f: *Fetch) void { | 644 | pub fn workerRun(f: *Fetch) void { |
| 634 | defer f.job_queue.wait_group.finish(); | 645 | defer f.job_queue.wait_group.finish(); |
| 635 | run(f) catch |err| switch (err) { | 646 | run(f) catch |err| switch (err) { |