diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 8fe1627dbc..e0cfc9b0a4 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1249,6 +1249,9 @@ fn computeHash( var all_files = std.ArrayList(*HashedFile).init(gpa); defer all_files.deinit(); + var all_deletions = std.ArrayList(*DeletedFile).init(gpa); + defer all_deletions.deinit(); + var walker = try @as(fs.IterableDir, .{ .dir = tmp_directory.handle }).walk(gpa); defer walker.deinit(); @@ -1267,10 +1270,25 @@ fn computeHash( ) }); return error.FetchFailed; }) |entry| { - _ = filter; // TODO: apply filter rules here + if (entry.kind == .directory) continue; + + if (!filter.includePath(entry.path)) { + // Delete instead of including in hash calculation. + const deleted_file = try arena.create(DeletedFile); + deleted_file.* = .{ + .fs_path = try arena.dupe(u8, entry.path), + .failure = undefined, // to be populated by the worker + }; + wait_group.start(); + try thread_pool.spawn(workerDeleteFile, .{ + tmp_directory.handle, deleted_file, &wait_group, + }); + try all_deletions.append(deleted_file); + continue; + } const kind: HashedFile.Kind = switch (entry.kind) { - .directory => continue, + .directory => unreachable, .file => .file, .sym_link => .sym_link, else => return f.fail(f.location_tok, try eb.printString( @@ -1295,7 +1313,6 @@ fn computeHash( try thread_pool.spawn(workerHashFile, .{ tmp_directory.handle, hashed_file, &wait_group, }); - try all_files.append(hashed_file); } } @@ -1315,6 +1332,17 @@ fn computeHash( }; hasher.update(&hashed_file.hash); } + for (all_deletions.items) |deleted_file| { + deleted_file.failure catch |err| { + any_failures = true; + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("failed to delete excluded path '{s}' from package: {s}", .{ + deleted_file.fs_path, @errorName(err), + }), + }); + }; + } + if (any_failures) return error.FetchFailed; return hasher.finalResult(); } @@ -1324,6 +1352,11 @@ fn workerHashFile(dir: fs.Dir, hashed_file: *HashedFile, wg: *WaitGroup) void { hashed_file.failure = hashFileFallible(dir, hashed_file); } +fn workerDeleteFile(dir: fs.Dir, deleted_file: *DeletedFile, wg: *WaitGroup) void { + defer wg.finish(); + deleted_file.failure = deleteFileFallible(dir, deleted_file); +} + fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void { var buf: [8000]u8 = undefined; var hasher = Manifest.Hash.init(.{}); @@ -1347,6 +1380,20 @@ fn hashFileFallible(dir: fs.Dir, hashed_file: *HashedFile) HashedFile.Error!void hasher.final(&hashed_file.hash); } +fn deleteFileFallible(dir: fs.Dir, deleted_file: *DeletedFile) DeletedFile.Error!void { + try dir.deleteFile(deleted_file.fs_path); + // In case the file was the last remaining file in the parent directory, attempt to + // remove the parent directory. + var opt_parent = fs.path.dirname(deleted_file.fs_path); + while (opt_parent) |parent| : (opt_parent = fs.path.dirname(parent)) { + dir.deleteDir(parent) catch |err| switch (err) { + error.DirNotEmpty => return, + error.FileNotFound => return, + else => |e| return e, + }; + } +} + fn isExecutable(file: fs.File) !bool { if (builtin.os.tag == .windows) { // TODO check the ACL on Windows. @@ -1360,6 +1407,15 @@ fn isExecutable(file: fs.File) !bool { } } +const DeletedFile = struct { + fs_path: []const u8, + failure: Error!void, + + const Error = + fs.Dir.DeleteFileError || + fs.Dir.DeleteDirError; +}; + const HashedFile = struct { fs_path: []const u8, normalized_path: []const u8, @@ -1402,7 +1458,7 @@ fn normalizePath(arena: Allocator, fs_path: []const u8) ![]const u8 { const Filter = struct { include_paths: std.StringArrayHashMapUnmanaged(void) = .{}, - /// sub_path is relative to the tarball root. + /// sub_path is relative to the package root. pub fn includePath(self: Filter, sub_path: []const u8) bool { if (self.include_paths.count() == 0) return true; if (self.include_paths.contains("")) return true; @@ -1410,7 +1466,7 @@ const Filter = struct { // Check if any included paths are parent directories of sub_path. var dirname = sub_path; - while (std.fs.path.dirname(sub_path)) |next_dirname| { + while (std.fs.path.dirname(dirname)) |next_dirname| { if (self.include_paths.contains(sub_path)) return true; dirname = next_dirname; } diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index de1870ea75..c1b1cdfb4f 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -318,7 +318,10 @@ const Parse = struct { for (array_init.ast.elements) |elem_node| { const path_string = try parseString(p, elem_node); - try p.paths.put(p.gpa, path_string, {}); + // This is normalized so that it can be used in string comparisons + // against file system paths. + const normalized = try std.fs.path.resolve(p.arena, &.{path_string}); + try p.paths.put(p.gpa, normalized, {}); } }