authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-18 20:34:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-05-25 18:54:35-07:00
logdb7ceada15b007747bfd433e6635324edcec918e
tree9f435ee5831fbe1ccefc760e5bf6d20599180bc6
parent398ea7e492b7016dcb6cebfe6656876002f5bf73

Maker: implement Step.WriteFile


3 files changed, 235 insertions(+), 151 deletions(-)

lib/compiler/Maker/Step.zig+14-21
...@@ -26,6 +26,7 @@ pub const ObjCopy = @import("Step/ObjCopy.zig");...@@ -26,6 +26,7 @@ pub const ObjCopy = @import("Step/ObjCopy.zig");
26pub const Options = @import("Step/Options.zig");26pub const Options = @import("Step/Options.zig");
27pub const Run = @import("Step/Run.zig");27pub const Run = @import("Step/Run.zig");
28pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");28pub const UpdateSourceFiles = @import("Step/UpdateSourceFiles.zig");
29pub const WriteFile = @import("Step/WriteFile.zig");
2930
30/// Avoid false sharing.31/// Avoid false sharing.
31_: void align(std.atomic.cache_line) = {},32_: void align(std.atomic.cache_line) = {},
...@@ -87,7 +88,7 @@ pub const Extended = union(enum) {...@@ -87,7 +88,7 @@ pub const Extended = union(enum) {
87 top_level: TopLevel,88 top_level: TopLevel,
88 translate_c: Todo,89 translate_c: Todo,
89 update_source_files: UpdateSourceFiles,90 update_source_files: UpdateSourceFiles,
90 write_file: Todo,91 write_file: WriteFile,
9192
92 pub fn init(tag: Configuration.Step.Tag) Extended {93 pub fn init(tag: Configuration.Step.Tag) Extended {
93 return switch (tag) {94 return switch (tag) {
...@@ -119,9 +120,10 @@ pub const Extended = union(enum) {...@@ -119,9 +120,10 @@ pub const Extended = union(enum) {
119 progress_node: std.Progress.Node,120 progress_node: std.Progress.Node,
120 ) Step.ExtendedMakeError!void {121 ) Step.ExtendedMakeError!void {
121 _ = todo;122 _ = todo;
122 _ = maker;
123 _ = progress_node;123 _ = progress_node;
124 std.debug.panic("TODO implement another step type (index {d})", .{step_index});124 const conf = &maker.scanned_config.configuration;
125 const conf_step = step_index.ptr(conf);
126 std.debug.panic("TODO implement another step type: {s}", .{conf_step.name.slice(conf)});
125 }127 }
126 };128 };
127129
...@@ -807,19 +809,17 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La...@@ -807,19 +809,17 @@ pub fn addWatchInput(step: *Step, maker: *Maker, arena: Allocator, lazy_file: La
807/// Paths derived from this directory should also be manually added via809/// Paths derived from this directory should also be manually added via
808/// `addDirectoryWatchInputFromPath` if and only if this function returns810/// `addDirectoryWatchInputFromPath` if and only if this function returns
809/// `true`.811/// `true`.
810pub fn addDirectoryWatchInput(step: *Step, lazy_directory: LazyPath) Allocator.Error!bool {812pub fn addDirectoryWatchInput(step: *Step, maker: *Maker, lazy_directory: LazyPath) Allocator.Error!bool {
811 switch (lazy_directory) {813 switch (lazy_directory) {
812 .src_path => |src_path| try addDirectoryWatchInputFromBuilder(step, src_path.owner, src_path.sub_path),814 .source_path => |source_path| {
813 .dependency => |d| try addDirectoryWatchInputFromBuilder(step, d.dependency.builder, d.sub_path),815 const conf = &maker.scanned_config.configuration;
814 .cwd_relative => |path_string| {816 const graph = maker.graph;
815 try addDirectoryWatchInputFromPath(step, .{817 const arena = graph.arena; // TODO don't leak into the process arena
816 .root_dir = .{818 const sub_path = source_path.sub_path.slice(conf);
817 .path = null,819 const pkg_path = try maker.packagePath(arena, source_path.owner, sub_path);
818 .handle = .cwd(),820 try addDirectoryWatchInputFromPath(step, maker, pkg_path);
819 },
820 .sub_path = path_string,
821 });
822 },821 },
822 .relative => |relative| try addDirectoryWatchInputFromPath(step, maker, maker.relativePath(relative)),
823 // Nothing to watch because this dependency edge is modeled instead via `dependants`.823 // Nothing to watch because this dependency edge is modeled instead via `dependants`.
824 .generated => return false,824 .generated => return false,
825 }825 }
...@@ -838,13 +838,6 @@ pub fn addDirectoryWatchInputFromPath(step: *Step, maker: *Maker, path: Path) !v...@@ -838,13 +838,6 @@ pub fn addDirectoryWatchInputFromPath(step: *Step, maker: *Maker, path: Path) !v
838 return addWatchInputFromPath(step, maker, path, ".");838 return addWatchInputFromPath(step, maker, path, ".");
839}839}
840840
841fn addDirectoryWatchInputFromBuilder(step: *Step, package: Package, sub_path: []const u8) !void {
842 return addDirectoryWatchInputFromPath(step, .{
843 .root_dir = package.build_root,
844 .sub_path = sub_path,
845 });
846}
847
848fn addWatchInputPath(step: *Step, maker: *Maker, path: Path) Allocator.Error!void {841fn addWatchInputPath(step: *Step, maker: *Maker, path: Path) Allocator.Error!void {
849 return addWatchInputFromPath(step, maker, .{842 return addWatchInputFromPath(step, maker, .{
850 .root_dir = path.root_dir,843 .root_dir = path.root_dir,
lib/compiler/Maker/Step/UpdateSourceFiles.zig+7-5
...@@ -32,6 +32,8 @@ pub fn make(...@@ -32,6 +32,8 @@ pub fn make(
3232
33 progress_node.setEstimatedTotalItems(conf_usf.embeds.slice.len + conf_usf.copies.slice.len);33 progress_node.setEstimatedTotalItems(conf_usf.embeds.slice.len + conf_usf.copies.slice.len);
3434
35 step.clearWatchInputs(maker);
36
35 for (conf_usf.embeds.slice) |*embed| {37 for (conf_usf.embeds.slice) |*embed| {
36 const dest_path: Path = .{38 const dest_path: Path = .{
37 .root_dir = build_root,39 .root_dir = build_root,
...@@ -43,12 +45,12 @@ pub fn make(...@@ -43,12 +45,12 @@ pub fn make(
43 .sub_path = dirname,45 .sub_path = dirname,
44 };46 };
45 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|47 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
46 return step.fail(maker, "failed to create path {f}: {t}", .{ dirname_path, err });48 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
47 }49 }
48 dest_path.root_dir.handle.writeFile(io, .{50 dest_path.root_dir.handle.writeFile(io, .{
49 .sub_path = dest_path.sub_path,51 .sub_path = dest_path.sub_path,
50 .data = embed.contents.slice(conf),52 .data = embed.contents.slice(conf),
51 }) catch |err| return step.fail(maker, "failed to write file {f}: {t}", .{ dest_path, err });53 }) catch |err| return step.fail(maker, "failed writing file {f}: {t}", .{ dest_path, err });
52 any_miss = true;54 any_miss = true;
53 progress_node.completeOne();55 progress_node.completeOne();
54 }56 }
...@@ -64,11 +66,11 @@ pub fn make(...@@ -64,11 +66,11 @@ pub fn make(
64 .sub_path = dirname,66 .sub_path = dirname,
65 };67 };
66 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|68 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
67 return step.fail(maker, "failed to create path {f}: {t}", .{ dirname_path, err });69 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
68 }70 }
69 const src_lazy_path = copy.src_file.get(conf);71 const src_lazy_path = copy.src_file.get(conf);
70 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);72 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
71 if (!step.inputs.populated()) try step.addWatchInput(maker, arena, src_lazy_path);73 try step.addWatchInput(maker, arena, src_lazy_path);
7274
73 const prev_status = source_path.root_dir.handle.updateFile(75 const prev_status = source_path.root_dir.handle.updateFile(
74 io,76 io,
...@@ -76,7 +78,7 @@ pub fn make(...@@ -76,7 +78,7 @@ pub fn make(
76 dest_path.root_dir.handle,78 dest_path.root_dir.handle,
77 dest_path.sub_path,79 dest_path.sub_path,
78 .{},80 .{},
79 ) catch |err| return step.fail(maker, "unable to update file from {f} to {f}: {t}", .{81 ) catch |err| return step.fail(maker, "failed updating file from {f} to {f}: {t}", .{
80 source_path, dest_path, err,82 source_path, dest_path, err,
81 });83 });
82 any_miss = any_miss or prev_status == .stale;84 any_miss = any_miss or prev_status == .stale;
lib/compiler/Maker/Step/WriteFile.zig+214-125
...@@ -1,205 +1,294 @@...@@ -1,205 +1,294 @@
1fn make(step: *Step, options: Step.MakeOptions) !void {1const WriteFile = @This();
2 _ = options;2
3 const b = step.owner;3const std = @import("std");
4 const graph = b.graph;4const Io = std.Io;
5const assert = std.debug.assert;
6const Path = std.Build.Cache.Path;
7const allocPrint = std.fmt.allocPrint;
8const Configuration = std.Build.Configuration;
9
10const Step = @import("../Step.zig");
11const Maker = @import("../../Maker.zig");
12
13pub fn make(
14 wf: *WriteFile,
15 step_index: Configuration.Step.Index,
16 maker: *Maker,
17 progress_node: std.Progress.Node,
18) Step.ExtendedMakeError!void {
19 _ = wf;
20 const graph = maker.graph;
21 const gpa = maker.gpa;
22 const arena = maker.graph.arena; // TODO don't leak into process arena
5 const io = graph.io;23 const io = graph.io;
6 const arena = b.allocator;24 const step = maker.stepByIndex(step_index);
7 const gpa = graph.cache.gpa;25 const conf = &maker.scanned_config.configuration;
8 const write_file: *WriteFile = @fieldParentPtr("step", step);26 const conf_step = step_index.ptr(conf);
27 const conf_wf = conf_step.extended.get(conf.extra).write_file;
28 const cache_root = graph.local_cache_root;
29 const directories = conf_wf.directories.slice;
930
10 const open_dir_cache = try arena.alloc(Io.Dir, write_file.directories.items.len);31 const open_dir_cache = try arena.alloc(Io.Dir, directories.len);
11 var open_dirs_count: usize = 0;32 var open_dirs_count: u32 = 0;
12 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);33 defer Io.Dir.closeMany(io, open_dir_cache[0..open_dirs_count]);
1334
14 switch (write_file.mode) {35 // Doesn't yet include contents of directories.
15 .whole_cached => {36 var total_items: usize = conf_wf.embeds.slice.len + conf_wf.copies.slice.len + conf_wf.directories.slice.len;
16 step.clearWatchInputs();37 progress_node.setEstimatedTotalItems(total_items);
1738
18 // The cache is used here not really as a way to speed things up - because writing39 switch (conf_wf.flags.mode) {
19 // the data to a file would probably be very fast - but as a way to find a canonical40 .whole_cached => {
20 // location to put build artifacts.41 step.clearWatchInputs(maker);
2142
22 // If, for example, a hard-coded path was used as the location to put WriteFile43 // The cache is used here primarily as a way to find a canonical
23 // files, then two WriteFiles executing in parallel might clobber each other.44 // location to put build artifacts without parallel step execution
45 // clobbering each other.
2446
25 var man = b.graph.cache.obtain();47 var man = graph.cache.obtain();
26 defer man.deinit();48 defer man.deinit();
2749
28 for (write_file.files.items) |file| {50 for (conf_wf.embeds.slice) |*embed| {
29 man.hash.addBytes(file.sub_path);51 man.hash.addBytes(embed.sub_path.slice(conf));
3052 man.hash.addBytes(embed.contents.slice(conf));
31 switch (file.contents) {53 }
32 .bytes => |bytes| {54
33 man.hash.addBytes(bytes);55 for (conf_wf.copies.slice) |*copy| {
34 },56 man.hash.addBytes(copy.sub_path.slice(conf));
35 .copy => |lazy_path| {57 const src_lazy_path = copy.src_file.get(conf);
36 const path = lazy_path.getPath3(b, step);58 const source_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
37 _ = try man.addFilePath(path, null);59 _ = try man.addFilePath(source_path, null);
38 try step.addWatchInput(lazy_path);60 try step.addWatchInput(maker, arena, src_lazy_path);
39 },
40 }
41 }61 }
4262
43 for (write_file.directories.items, open_dir_cache) |dir, *open_dir_cache_elem| {63 for (directories, open_dir_cache) |conf_dir, *opened_dir| {
44 man.hash.addBytes(dir.sub_path);64 const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{};
45 for (dir.options.exclude_extensions) |ext| man.hash.addBytes(ext);65 const include_extensions = conf_dir.include_extensions.slice(conf);
46 if (dir.options.include_extensions) |incs| for (incs) |inc| man.hash.addBytes(inc);66
67 man.hash.addBytes(conf_dir.sub_path.slice(conf));
68 for (exclude_extensions) |ext| man.hash.addBytes(ext.slice(conf));
69 if (include_extensions) |includes| for (includes) |inc| {
70 man.hash.addBytes(inc.slice(conf));
71 };
4772
48 const need_derived_inputs = try step.addDirectoryWatchInput(dir.source);73 const src_lazy_path = conf_dir.src_path.get(conf);
49 const src_dir_path = dir.source.getPath3(b, step);74 const need_derived_inputs = try step.addDirectoryWatchInput(maker, src_lazy_path);
75 const src_dir_path = try maker.resolveLazyPath(arena, src_lazy_path, step_index);
5076
51 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {77 var src_dir = src_dir_path.root_dir.handle.openDir(io, src_dir_path.subPathOrDot(), .{ .iterate = true }) catch |err| {
52 return step.fail("unable to open source directory '{f}': {s}", .{78 return step.fail(maker, "failed opening source directory {f}: {t}", .{ src_dir_path, err });
53 src_dir_path, @errorName(err),
54 });
55 };79 };
56 open_dir_cache_elem.* = src_dir;80 opened_dir.* = src_dir;
57 open_dirs_count += 1;81 open_dirs_count += 1;
5882
59 var it = try src_dir.walk(gpa);83 var it = try src_dir.walk(gpa);
60 defer it.deinit();84 defer it.deinit();
61 while (try it.next(io)) |entry| {85 while (it.next(io) catch |err| switch (err) {
62 if (!dir.options.pathIncluded(entry.path)) continue;86 error.Canceled, error.OutOfMemory => |e| return e,
87 else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }),
88 }) |entry| {
89 if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue;
6390
64 switch (entry.kind) {91 switch (entry.kind) {
65 .directory => {92 .directory => {
66 if (need_derived_inputs) {93 if (need_derived_inputs) {
67 const entry_path = try src_dir_path.join(arena, entry.path);94 const entry_path = try src_dir_path.join(arena, entry.path);
68 try step.addDirectoryWatchInputFromPath(entry_path);95 try step.addDirectoryWatchInputFromPath(maker, entry_path);
69 }96 }
70 },97 },
71 .file => {98 .file => {
72 const entry_path = try src_dir_path.join(arena, entry.path);99 const entry_path = try src_dir_path.join(arena, entry.path);
73 _ = try man.addFilePath(entry_path, null);100 _ = try man.addFilePath(entry_path, null);
101 total_items += 1;
74 },102 },
75 else => continue,103 else => continue,
76 }104 }
77 }105 }
78 }106 }
79107
80 if (try step.cacheHit(&man)) {108 if (try step.cacheHit(maker, &man)) {
81 const digest = man.final();109 const digest = man.final();
82 write_file.generated_directory.path = try b.cache_root.join(arena, &.{ "o", &digest });110 maker.generatedPath(conf_wf.generated_directory).* = .{
111 .root_dir = cache_root,
112 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
113 };
83 assert(step.result_cached);114 assert(step.result_cached);
84 return;115 return;
85 }116 }
86117
87 const digest = man.final();118 const digest = man.final();
88 const cache_path = "o" ++ Dir.path.sep_str ++ digest;119 const out_path: Path = .{
89120 .root_dir = cache_root,
90 write_file.generated_directory.path = try b.cache_root.join(arena, &.{cache_path});121 .sub_path = try Io.Dir.path.join(arena, &.{ "o", &digest }),
122 };
91123
92 try operate(write_file, open_dir_cache, .{124 progress_node.setEstimatedTotalItems(total_items);
93 .root_dir = b.cache_root,125 try operate(maker, step_index, open_dir_cache, out_path, progress_node);
94 .sub_path = cache_path,126 try step.writeManifest(maker, &man);
95 });
96127
97 try step.writeManifest(&man);128 maker.generatedPath(conf_wf.generated_directory).* = out_path;
98 },129 },
99 .tmp => {130 .tmp => {
100 step.result_cached = false;131 step.result_cached = false;
101132
102 var rand_int: u64 = undefined;133 var rand_int: u64 = undefined;
103 io.random(@ptrCast(&rand_int));134 io.random(@ptrCast(&rand_int));
104 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);135 const hex_digest = std.fmt.hex(rand_int);
105136
106 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});137 const out_path: Path = .{
138 .root_dir = cache_root,
139 .sub_path = try Io.Dir.path.join(arena, &.{ "tmp", &hex_digest }),
140 };
107141
108 try operate(write_file, open_dir_cache, .{142 try operate(maker, step_index, open_dir_cache, out_path, progress_node);
109 .root_dir = b.cache_root,143
110 .sub_path = tmp_dir_sub_path,144 maker.generatedPath(conf_wf.generated_directory).* = out_path;
111 });
112 },145 },
113 .mutate => |lp| {146 .mutate => {
114 step.result_cached = false;147 step.result_cached = false;
115 const root_path = try lp.getPath4(b, step);148 const root_path = try maker.resolveLazyPathIndex(arena, conf_wf.mutate_path.value.?, step_index);
116 write_file.generated_directory.path = try root_path.toString(arena);149 try operate(maker, step_index, open_dir_cache, root_path, progress_node);
117 try operate(write_file, open_dir_cache, root_path);150 maker.generatedPath(conf_wf.generated_directory).* = root_path;
118 },151 },
119 }152 }
120}153}
121154
122fn operate(write_file: *WriteFile, open_dir_cache: []const Io.Dir, root_path: std.Build.Cache.Path) !void {155fn operate(
123 const step = &write_file.step;156 maker: *Maker,
124 const b = step.owner;157 step_index: Configuration.Step.Index,
125 const io = b.graph.io;158 open_dir_cache: []const Io.Dir,
126 const gpa = b.graph.cache.gpa;159 root_path: std.Build.Cache.Path,
127 const arena = b.allocator;160 progress_node: std.Progress.Node,
128161) !void {
129 var cache_dir = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err|162 const graph = maker.graph;
130 return step.fail("unable to make path {f}: {t}", .{ root_path, err });163 const gpa = maker.gpa;
131 defer cache_dir.close(io);164 const arena = maker.graph.arena; // TODO don't leak into process arena
132165 const io = graph.io;
133 for (write_file.files.items) |file| {166 const step = maker.stepByIndex(step_index);
134 if (Dir.path.dirname(file.sub_path)) |dirname| {167 const conf = &maker.scanned_config.configuration;
135 cache_dir.createDirPath(io, dirname) catch |err| {168 const conf_step = step_index.ptr(conf);
136 return step.fail("unable to make path '{f}{c}{s}': {t}", .{169 const conf_wf = conf_step.extended.get(conf.extra).write_file;
137 root_path, Dir.path.sep, dirname, err,170
138 });171 const root_directory: std.Build.Cache.Directory = .{
172 .handle = root_path.root_dir.handle.createDirPathOpen(io, root_path.sub_path, .{}) catch |err|
173 return step.fail(maker, "failed creating path {f}: {t}", .{ root_path, err }),
174 .path = try root_path.toString(arena),
175 };
176 defer root_directory.handle.close(io);
177
178 for (conf_wf.embeds.slice) |*embed| {
179 const dest_path: Path = .{
180 .root_dir = root_directory,
181 .sub_path = embed.sub_path.slice(conf),
182 };
183 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
184 const dirname_path: Path = .{
185 .root_dir = root_directory,
186 .sub_path = dirname,
139 };187 };
188 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
189 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
140 }190 }
141 switch (file.contents) {191 dest_path.root_dir.handle.writeFile(io, .{
142 .bytes => |bytes| {192 .sub_path = dest_path.sub_path,
143 cache_dir.writeFile(io, .{ .sub_path = file.sub_path, .data = bytes }) catch |err| {193 .data = embed.contents.slice(conf),
144 return step.fail("unable to write file '{f}{c}{s}': {t}", .{194 }) catch |err| return step.fail(maker, "failed writing contents to file {f}: {t}", .{ dest_path, err });
145 root_path, Dir.path.sep, file.sub_path, err,195 progress_node.completeOne();
146 });196 }
147 };197
148 },198 for (conf_wf.copies.slice) |*copy| {
149 .copy => |file_source| {199 const dest_path: Path = .{
150 const source_path = file_source.getPath2(b, step);200 .root_dir = root_directory,
151 const prev_status = Io.Dir.updateFile(.cwd(), io, source_path, cache_dir, file.sub_path, .{}) catch |err| {201 .sub_path = copy.sub_path.slice(conf),
152 return step.fail("unable to update file from '{s}' to '{f}{c}{s}': {t}", .{202 };
153 source_path, root_path, Dir.path.sep, file.sub_path, err,203 // Rather than passing make_path = true below, this optimizes for the
154 });204 // more common case where the directory does not exist.
155 };205 if (Io.Dir.path.dirname(dest_path.sub_path)) |dirname| {
156 // At this point we already will mark the step as a cache miss.206 const dirname_path: Path = .{
157 // But this is kind of a partial cache hit since individual207 .root_dir = root_directory,
158 // file copies may be avoided. Oh well, this information is208 .sub_path = dirname,
159 // discarded.209 };
160 _ = prev_status;210 dirname_path.root_dir.handle.createDirPath(io, dirname_path.sub_path) catch |err|
161 },211 return step.fail(maker, "failed creating path {f}: {t}", .{ dirname_path, err });
162 }212 }
213 const source_path = try maker.resolveLazyPathIndex(arena, copy.src_file, step_index);
214 Io.Dir.copyFile(
215 source_path.root_dir.handle,
216 source_path.sub_path,
217 dest_path.root_dir.handle,
218 dest_path.sub_path,
219 io,
220 .{},
221 ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{
222 source_path, dest_path, err,
223 });
224 progress_node.completeOne();
163 }225 }
164226
165 for (write_file.directories.items, open_dir_cache) |dir, already_open_dir| {227 for (conf_wf.directories.slice, open_dir_cache) |conf_dir, already_open_dir| {
166 const src_dir_path = dir.source.getPath3(b, step);228 const exclude_extensions = conf_dir.exclude_extensions.slice(conf) orelse &.{};
167 const dest_dirname = dir.sub_path;229 const include_extensions = conf_dir.include_extensions.slice(conf);
168230
169 if (dest_dirname.len != 0) {231 const src_dir_path = try maker.resolveLazyPathIndex(arena, conf_dir.src_path, step_index);
170 cache_dir.createDirPath(io, dest_dirname) catch |err| {232 const dest_dir_path: Path = .{
171 return step.fail("unable to make path '{f}{c}{s}': {t}", .{233 .root_dir = root_directory,
172 root_path, Dir.path.sep, dest_dirname, err,234 .sub_path = conf_dir.sub_path.slice(conf),
173 });235 };
174 };236
237 if (dest_dir_path.sub_path.len != 0) {
238 dest_dir_path.root_dir.handle.createDirPath(io, dest_dir_path.sub_path) catch |err|
239 return step.fail(maker, "failed creating path {f}: {t}", .{ dest_dir_path, err });
175 }240 }
176241
177 var it = try already_open_dir.walk(gpa);242 var it = try already_open_dir.walk(gpa);
178 defer it.deinit();243 defer it.deinit();
179 while (try it.next(io)) |entry| {244 while (it.next(io) catch |err| switch (err) {
180 if (!dir.options.pathIncluded(entry.path)) continue;245 error.Canceled, error.OutOfMemory => |e| return e,
246 else => |e| return step.fail(maker, "failed iterating dir {f}: {t}", .{ src_dir_path, e }),
247 }) |entry| {
248 if (!pathIncluded(conf, exclude_extensions, include_extensions, entry.path)) continue;
181249
182 const src_entry_path = try src_dir_path.join(arena, entry.path);250 const src_entry_path = try src_dir_path.join(arena, entry.path);
183 const dest_path = b.pathJoin(&.{ dest_dirname, entry.path });251 const dest_path = try dest_dir_path.join(arena, entry.path);
184 switch (entry.kind) {252 switch (entry.kind) {
185 .directory => try cache_dir.createDirPath(io, dest_path),253 .directory => dest_path.root_dir.handle.createDirPath(io, dest_path.sub_path) catch |err| {
254 return step.fail(maker, "failed creating path {f}: {t}", .{ dest_path, err });
255 },
186 .file => {256 .file => {
187 const prev_status = Io.Dir.updateFile(257 Io.Dir.copyFile(
188 src_entry_path.root_dir.handle,258 src_entry_path.root_dir.handle,
189 io,
190 src_entry_path.sub_path,259 src_entry_path.sub_path,
191 cache_dir,260 dest_path.root_dir.handle,
192 dest_path,261 dest_path.sub_path,
262 io,
193 .{},263 .{},
194 ) catch |err| {264 ) catch |err| return step.fail(maker, "failed copying file from {f} to {f}: {t}", .{
195 return step.fail("unable to update file from '{f}' to '{f}{c}{s}': {t}", .{265 src_entry_path, dest_path, err,
196 src_entry_path, root_path, Dir.path.sep, dest_path, err,266 });
197 });267 progress_node.completeOne();
198 };
199 _ = prev_status;
200 },268 },
201 else => continue,269 else => continue,
202 }270 }
203 }271 }
204 }272 }
205}273}
274
275fn pathIncluded(
276 conf: *const Configuration,
277 exclude_extensions: []const Configuration.String,
278 include_extensions: ?[]const Configuration.String,
279 path: []const u8,
280) bool {
281 for (exclude_extensions) |ext| {
282 if (std.mem.endsWith(u8, path, ext.slice(conf)))
283 return false;
284 }
285 if (include_extensions) |incs| {
286 for (incs) |inc| {
287 if (std.mem.endsWith(u8, path, inc.slice(conf)))
288 return true;
289 } else {
290 return false;
291 }
292 }
293 return true;
294}