| ... | ... | @@ -16,6 +16,7 @@ const allocPrint = std.fmt.allocPrint; |
| 16 | 16 | |
| 17 | 17 | const Step = @import("../Step.zig"); |
| 18 | 18 | const Maker = @import("../../Maker.zig"); |
| 19 | const Fuzz = @import("../../Maker/Fuzz.zig"); |
| 19 | 20 | |
| 20 | 21 | /// If this is a Zig unit test binary, this tracks the names of the unit |
| 21 | 22 | /// tests that are also fuzz tests. Indexes cannot be used as they may |
| ... | ... | @@ -31,6 +32,8 @@ rebuilt_executable: ?Path = null, |
| 31 | 32 | argv: std.ArrayList([]const u8) = .empty, |
| 32 | 33 | /// Persisted to reuse memory on subsequent calls to `make`. |
| 33 | 34 | output_placeholders: std.ArrayList(IndexedOutput) = .empty, |
| 35 | /// Persisted to reuse memory on subsequent calls to `make`. |
| 36 | environ_map: std.process.Environ.Map = .{ .array_hash_map = .empty, .allocator = undefined }, |
| 34 | 37 | |
| 35 | 38 | pub fn make( |
| 36 | 39 | run: *Run, |
| ... | ... | @@ -67,6 +70,8 @@ pub fn make( |
| 67 | 70 | man.hash.add(conf_run.flags.color); |
| 68 | 71 | man.hash.add(conf_run.flags.disable_zig_progress); |
| 69 | 72 | |
| 73 | var dep_file_count: usize = 0; |
| 74 | |
| 70 | 75 | for (conf_run.args.slice) |arg_index| { |
| 71 | 76 | const arg = arg_index.get(conf); |
| 72 | 77 | try argv_list.ensureUnusedCapacity(gpa, 1); |
| ... | ... | @@ -157,6 +162,9 @@ pub fn make( |
| 157 | 162 | man.hash.addBytesZ(basename); |
| 158 | 163 | man.hash.addBytesZ(suffix); |
| 159 | 164 | |
| 165 | man.hash.add(arg.flags.dep_file); |
| 166 | dep_file_count += @intFromBool(arg.flags.dep_file); |
| 167 | |
| 160 | 168 | // Add a placeholder into the argument list because we need the |
| 161 | 169 | // manifest hash to be updated with all arguments before the |
| 162 | 170 | // object directory is computed. |
| ... | ... | @@ -220,145 +228,95 @@ pub fn make( |
| 220 | 228 | |
| 221 | 229 | const has_side_effects = conf_run.flags.has_side_effects; |
| 222 | 230 | |
| 223 | | if (true) @panic("TODO"); |
| 224 | | |
| 225 | 231 | if (!has_side_effects and try step.cacheHitAndWatch(maker, &man)) { |
| 226 | | // cache hit, skip running command |
| 232 | // Cache hit; skip running command. |
| 227 | 233 | const digest = man.final(); |
| 228 | | |
| 229 | | try populateGeneratedPaths( |
| 230 | | arena, |
| 231 | | output_placeholders.items, |
| 232 | | &conf_run, |
| 233 | | cache_root, |
| 234 | | &digest, |
| 235 | | ); |
| 236 | | |
| 234 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 235 | try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest); |
| 237 | 236 | step.result_cached = true; |
| 238 | 237 | return; |
| 239 | 238 | } |
| 240 | 239 | |
| 241 | | const dep_output_file = conf_run.dep_output_file orelse { |
| 242 | | // We already know the final output paths, use them directly. |
| 243 | | const digest = if (has_side_effects) |
| 244 | | man.hash.final() |
| 245 | | else |
| 246 | | man.final(); |
| 247 | | |
| 248 | | try populateGeneratedPaths( |
| 249 | | arena, |
| 250 | | output_placeholders.items, |
| 251 | | &conf_run, |
| 252 | | cache_root, |
| 253 | | &digest, |
| 254 | | ); |
| 255 | | |
| 240 | if (dep_file_count == 0) { |
| 241 | // We already know the final output paths; use them directly. |
| 242 | const digest = if (has_side_effects) man.hash.final() else man.final(); |
| 256 | 243 | const output_dir_path = "o" ++ Dir.path.sep_str ++ &digest; |
| 257 | | for (output_placeholders.items) |placeholder| { |
| 258 | | const output_sub_path = graph.pathJoin(&.{ output_dir_path, placeholder.output.basename }); |
| 259 | | const output_sub_dir_path = switch (placeholder.tag) { |
| 260 | | .output_file => Dir.path.dirname(output_sub_path).?, |
| 261 | | .output_directory => output_sub_path, |
| 262 | | else => unreachable, |
| 263 | | }; |
| 264 | | cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { |
| 265 | | return step.fail(maker, "unable to make path '{f}{s}': {t}", .{ |
| 266 | | cache_root, output_sub_dir_path, err, |
| 267 | | }); |
| 268 | | }; |
| 269 | | const arg_output_path = try convertPathArg(run_index, maker, .{ |
| 270 | | .root_dir = .cwd(), |
| 271 | | .sub_path = placeholder.output.generated_file.getPath(), |
| 272 | | }); |
| 273 | | argv_list.items[placeholder.index] = if (placeholder.output.prefix.len == 0) |
| 274 | | arg_output_path |
| 275 | | else |
| 276 | | try allocPrint(arena, "{s}{s}", .{ placeholder.output.prefix, arg_output_path }); |
| 277 | | } |
| 278 | | |
| 279 | | try runCommand(run, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null); |
| 280 | | if (!has_side_effects) try step.writeManifestAndWatch(&man); |
| 244 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 245 | try populateGeneratedPathsCreateDirs(run, run_index, maker, output_dir_path); |
| 246 | try runCommand(run, run_index, maker, progress_node, argv_list.items, has_side_effects, output_dir_path, null); |
| 247 | if (!has_side_effects) try step.writeManifestAndWatch(maker, &man); |
| 281 | 248 | return; |
| 282 | | }; |
| 249 | } |
| 283 | 250 | |
| 284 | | // We do not know the final output paths yet, use temp paths to run the command. |
| 251 | // We do not know the final output paths yet; use temporary directory to run the command. |
| 285 | 252 | var rand_int: u64 = undefined; |
| 286 | 253 | io.random(@ptrCast(&rand_int)); |
| 287 | 254 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); |
| 288 | 255 | |
| 256 | try populateGeneratedPathsCreateDirs(run, run_index, maker, tmp_dir_path); |
| 257 | try runCommand(run, run_index, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null); |
| 258 | |
| 289 | 259 | for (output_placeholders.items) |placeholder| { |
| 290 | | const output_components = .{ tmp_dir_path, placeholder.output.basename }; |
| 291 | | const output_sub_path = graph.pathJoin(&output_components); |
| 292 | | const output_sub_dir_path = switch (placeholder.tag) { |
| 293 | | .output_file => Dir.path.dirname(output_sub_path).?, |
| 294 | | .output_directory => output_sub_path, |
| 260 | const arg = placeholder.arg_index.get(conf); |
| 261 | switch (arg.flags.tag) { |
| 262 | .output_file => if (arg.flags.dep_file) { |
| 263 | const generated_path = maker.generatedPath(arg.generated.value.?).*; |
| 264 | const result = if (has_side_effects) |
| 265 | man.addDepFile(generated_path.root_dir.handle, generated_path.sub_path) |
| 266 | else |
| 267 | man.addDepFilePost(generated_path.root_dir.handle, generated_path.sub_path); |
| 268 | result catch |err| switch (err) { |
| 269 | error.OutOfMemory, error.Canceled => |e| return e, |
| 270 | else => |e| return step.fail(maker, "failed adding to cache the file {f}: {t}", .{ |
| 271 | generated_path, e, |
| 272 | }), |
| 273 | }; |
| 274 | }, |
| 275 | .output_directory => continue, |
| 295 | 276 | else => unreachable, |
| 296 | | }; |
| 297 | | cache_root.handle.createDirPath(io, output_sub_dir_path) catch |err| { |
| 298 | | return step.fail(maker, "unable to make path '{f}{s}': {t}", .{ |
| 299 | | cache_root, output_sub_dir_path, err, |
| 300 | | }); |
| 301 | | }; |
| 302 | | const raw_output_path: Path = .{ |
| 303 | | .root_dir = cache_root, |
| 304 | | .sub_path = graph.pathJoin(&output_components), |
| 305 | | }; |
| 306 | | placeholder.output.generated_file.path = raw_output_path.toString(arena) catch @panic("OOM"); |
| 307 | | argv_list.items[placeholder.index] = try mem.concat(arena, u8, .{ |
| 308 | | placeholder.output.prefix, |
| 309 | | try convertPathArg(run_index, maker, raw_output_path), |
| 310 | | }); |
| 277 | } |
| 311 | 278 | } |
| 312 | 279 | |
| 313 | | try runCommand(run, maker, progress_node, argv_list.items, has_side_effects, tmp_dir_path, null); |
| 314 | | |
| 315 | | const dep_file_dir = Dir.cwd(); |
| 316 | | const dep_file_basename = dep_output_file.generated_file.getPath2(graph, step); |
| 317 | | if (has_side_effects) |
| 318 | | try man.addDepFile(dep_file_dir, dep_file_basename) |
| 319 | | else |
| 320 | | try man.addDepFilePost(dep_file_dir, dep_file_basename); |
| 321 | | |
| 322 | | const digest = if (has_side_effects) |
| 323 | | man.hash.final() |
| 324 | | else |
| 325 | | man.final(); |
| 280 | const digest = if (has_side_effects) man.hash.final() else man.final(); |
| 326 | 281 | |
| 327 | 282 | const any_output = output_placeholders.items.len > 0 or |
| 328 | | conf_run.captured_stdout != null or conf_run.captured_stderr != null; |
| 283 | conf_run.captured_stdout.value != null or conf_run.captured_stderr.value != null; |
| 329 | 284 | |
| 330 | | // Rename into place |
| 331 | 285 | if (any_output) { |
| 332 | | const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest; |
| 333 | | |
| 334 | | cache_root.handle.rename(tmp_dir_path, cache_root.handle, o_sub_path, io) catch |err| switch (err) { |
| 335 | | Dir.RenameError.DirNotEmpty => { |
| 336 | | cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| { |
| 337 | | return step.fail(maker, "unable to remove dir '{f}'{s}: {t}", .{ |
| 338 | | cache_root, tmp_dir_path, del_err, |
| 339 | | }); |
| 340 | | }; |
| 341 | | cache_root.handle.rename(tmp_dir_path, cache_root.handle, o_sub_path, io) catch |retry_err| { |
| 342 | | return step.fail(maker, "unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ |
| 343 | | cache_root, tmp_dir_path, cache_root, o_sub_path, retry_err, |
| 344 | | }); |
| 345 | | }; |
| 286 | // Rename into place. |
| 287 | const tmp_path: Path = .{ .root_dir = cache_root, .sub_path = tmp_dir_path }; |
| 288 | const dst_path: Path = .{ .root_dir = cache_root, .sub_path = "o" ++ Dir.path.sep_str ++ &digest }; |
| 289 | Dir.rename( |
| 290 | tmp_path.root_dir.handle, |
| 291 | tmp_path.sub_path, |
| 292 | dst_path.root_dir.handle, |
| 293 | dst_path.sub_path, |
| 294 | io, |
| 295 | ) catch |err| switch (err) { |
| 296 | error.DirNotEmpty => { |
| 297 | dst_path.root_dir.handle.deleteTree(io, dst_path.sub_path) catch |del_err| |
| 298 | return step.fail(maker, "failed to remove tree {f}: {t}", .{ dst_path, del_err }); |
| 299 | |
| 300 | Dir.rename( |
| 301 | tmp_path.root_dir.handle, |
| 302 | tmp_path.sub_path, |
| 303 | dst_path.root_dir.handle, |
| 304 | dst_path.sub_path, |
| 305 | io, |
| 306 | ) catch |retry_err| return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ |
| 307 | tmp_path, dst_path, retry_err, |
| 308 | }); |
| 346 | 309 | }, |
| 347 | | else => return step.fail(maker, "unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ |
| 348 | | cache_root, tmp_dir_path, cache_root, o_sub_path, err, |
| 310 | else => return step.fail(maker, "failed to rename directory {f} to {f}: {t}", .{ |
| 311 | tmp_path, dst_path, err, |
| 349 | 312 | }), |
| 350 | 313 | }; |
| 351 | 314 | } |
| 352 | 315 | |
| 353 | | if (!has_side_effects) try step.writeManifestAndWatch(&man); |
| 316 | if (!has_side_effects) try step.writeManifestAndWatch(maker, &man); |
| 354 | 317 | |
| 355 | | try populateGeneratedPaths( |
| 356 | | arena, |
| 357 | | output_placeholders.items, |
| 358 | | &conf_run, |
| 359 | | cache_root, |
| 360 | | &digest, |
| 361 | | ); |
| 318 | try populateGeneratedStdIo(maker, &conf_run, cache_root, &digest); |
| 319 | try populateGeneratedPaths(maker, output_placeholders.items, cache_root, &digest); |
| 362 | 320 | } |
| 363 | 321 | |
| 364 | 322 | /// Reads stdout of a Zig test process until a termination condition is reached: |
| ... | ... | @@ -918,9 +876,12 @@ const FuzzTestRunner = struct { |
| 918 | 876 | } |
| 919 | 877 | |
| 920 | 878 | fn saveCrash(f: *FuzzTestRunner, id: u32, term: process.Child.Term) !void { |
| 879 | const fuzz = f.context.fuzz; |
| 880 | const maker = fuzz.maker; |
| 921 | 881 | const step = &f.run.step; |
| 922 | | const b = step.owner; |
| 923 | | const io = b.graph.io; |
| 882 | const graph = maker.graph; |
| 883 | const io = graph.io; |
| 884 | const cache_root = graph.local_cache_root; |
| 924 | 885 | |
| 925 | 886 | if (f.coverage_id == null) return; |
| 926 | 887 | |
| ... | ... | @@ -938,7 +899,7 @@ const FuzzTestRunner = struct { |
| 938 | 899 | }) { |
| 939 | 900 | const name_prefix = "f" ++ Io.Dir.path.sep_str ++ "in"; |
| 940 | 901 | in_name = std.fmt.bufPrint(&in_name_buf, name_prefix ++ "{x}", .{i}) catch unreachable; |
| 941 | | in_f = b.cache_root.handle.openFile(io, in_name, .{ |
| 902 | in_f = cache_root.handle.openFile(io, in_name, .{ |
| 942 | 903 | .lock = .exclusive, |
| 943 | 904 | .lock_nonblocking = true, |
| 944 | 905 | }) catch |e| switch (e) { |
| ... | ... | @@ -946,7 +907,7 @@ const FuzzTestRunner = struct { |
| 946 | 907 | error.WouldBlock => continue, // Can not be from |
| 947 | 908 | // the crashed instance since it is still locked. |
| 948 | 909 | else => return step.fail("failed to open file '{f}{s}': {t}", .{ |
| 949 | | b.cache_root, in_name, e, |
| 910 | cache_root, in_name, e, |
| 950 | 911 | }), |
| 951 | 912 | }; |
| 952 | 913 | |
| ... | ... | @@ -955,7 +916,7 @@ const FuzzTestRunner = struct { |
| 955 | 916 | in_f.close(io); |
| 956 | 917 | switch (e) { |
| 957 | 918 | error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ |
| 958 | | b.cache_root, in_name, in_r.err.?, |
| 919 | cache_root, in_name, in_r.err.?, |
| 959 | 920 | }), |
| 960 | 921 | error.EndOfStream => continue, |
| 961 | 922 | } |
| ... | ... | @@ -974,10 +935,10 @@ const FuzzTestRunner = struct { |
| 974 | 935 | |
| 975 | 936 | // Save it to a seperate file |
| 976 | 937 | const crash_name = "f" ++ Io.Dir.path.sep_str ++ "crash"; |
| 977 | | const out = b.cache_root.handle.createFile(io, crash_name, .{ |
| 938 | const out = cache_root.handle.createFile(io, crash_name, .{ |
| 978 | 939 | .lock = .exclusive, // Multiple run steps could have found a crash at the same time |
| 979 | 940 | }) catch |e| return step.fail("failed to create file '{f}{s}': {t}", .{ |
| 980 | | b.cache_root, crash_name, e, |
| 941 | cache_root, crash_name, e, |
| 981 | 942 | }); |
| 982 | 943 | defer out.close(io); |
| 983 | 944 | |
| ... | ... | @@ -985,17 +946,17 @@ const FuzzTestRunner = struct { |
| 985 | 946 | var out_w = out.writerStreaming(io, &out_w_buf); |
| 986 | 947 | _ = out_w.interface.sendFileAll(&in_r, .limited(header.len)) catch |e| switch (e) { |
| 987 | 948 | error.ReadFailed => return step.fail("failed to read file '{f}{s}': {t}", .{ |
| 988 | | b.cache_root, in_name, in_r.err.?, |
| 949 | cache_root, in_name, in_r.err.?, |
| 989 | 950 | }), |
| 990 | 951 | error.WriteFailed => return step.fail("failed to write file '{f}{s}': {t}", .{ |
| 991 | | b.cache_root, crash_name, out_w.err.?, |
| 952 | cache_root, crash_name, out_w.err.?, |
| 992 | 953 | }), |
| 993 | 954 | }; |
| 994 | 955 | |
| 995 | 956 | return f.run.step.fail("test '{s}' {f}; input saved to '{f}{s}'", .{ |
| 996 | 957 | f.run.fuzz_tests.items[header.test_i], |
| 997 | 958 | fmtTerm(term), |
| 998 | | b.cache_root, |
| 959 | cache_root, |
| 999 | 960 | crash_name, |
| 1000 | 961 | }); |
| 1001 | 962 | } |
| ... | ... | @@ -1492,54 +1453,85 @@ pub fn rerunInFuzzMode( |
| 1492 | 1453 | const maker = fuzz.maker; |
| 1493 | 1454 | const graph = maker.graph; |
| 1494 | 1455 | const step = &run.step; |
| 1495 | | const b = step.owner; |
| 1496 | 1456 | const io = graph.io; |
| 1497 | | const arena = b.allocator; |
| 1498 | | var argv_list: std.ArrayList([]const u8) = .empty; |
| 1499 | | for (run.argv.items) |arg| { |
| 1500 | | switch (arg) { |
| 1501 | | .bytes => |bytes| { |
| 1502 | | try argv_list.append(arena, bytes); |
| 1457 | const arena = graph.arena; // TODO don't leak into the process arena |
| 1458 | const gpa = maker.gpa; |
| 1459 | const conf = &maker.scanned_config.configuration; |
| 1460 | const conf_step = run_index.ptr(conf); |
| 1461 | const conf_run = conf_step.extended.get(conf.extra).run; |
| 1462 | const argv_list = &run.argv; |
| 1463 | |
| 1464 | argv_list.clearRetainingCapacity(); |
| 1465 | |
| 1466 | for (conf_run.args.slice) |arg_index| { |
| 1467 | const arg = arg_index.get(conf); |
| 1468 | try argv_list.ensureUnusedCapacity(gpa, 1); |
| 1469 | switch (arg.flags.tag) { |
| 1470 | .string => { |
| 1471 | const prefix = arg.prefix.value.?.slice(conf); |
| 1472 | argv_list.appendAssumeCapacity(prefix); |
| 1503 | 1473 | }, |
| 1504 | | .lazy_path => |file| { |
| 1505 | | const file_path = file.lazy_path.getPath3(b, step); |
| 1506 | | try argv_list.append(arena, b.fmt("{s}{s}", .{ file.prefix, convertPathArg(run_index, maker, file_path) })); |
| 1474 | .path_file => { |
| 1475 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| 1476 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; |
| 1477 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); |
| 1478 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ |
| 1479 | prefix, try convertPathArg(run_index, maker, file_path), suffix, |
| 1480 | })); |
| 1507 | 1481 | }, |
| 1508 | | .decorated_directory => |dd| { |
| 1509 | | const file_path = dd.lazy_path.getPath3(b, step); |
| 1510 | | try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, convertPathArg(run_index, maker, file_path), dd.suffix })); |
| 1482 | .path_directory => { |
| 1483 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| 1484 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; |
| 1485 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); |
| 1486 | const resolved_arg = try mem.concat(arena, u8, &.{ |
| 1487 | prefix, try convertPathArg(run_index, maker, file_path), suffix, |
| 1488 | }); |
| 1489 | argv_list.appendAssumeCapacity(resolved_arg); |
| 1511 | 1490 | }, |
| 1512 | | .file_content => |file_plp| { |
| 1513 | | const file_path = file_plp.lazy_path.getPath3(b, step); |
| 1491 | .file_content => { |
| 1492 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| 1493 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; |
| 1494 | const file_path = try maker.resolveLazyPathIndex(arena, arg.path.value.?, run_index); |
| 1514 | 1495 | |
| 1515 | 1496 | var result: std.Io.Writer.Allocating = .init(arena); |
| 1516 | | errdefer result.deinit(); |
| 1517 | | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; |
| 1497 | result.writer.writeAll(prefix) catch return error.OutOfMemory; |
| 1518 | 1498 | |
| 1519 | | const file = try file_path.root_dir.handle.openFile(io, file_path.subPathOrDot(), .{}); |
| 1499 | const file = file_path.root_dir.handle.openFile(io, file_path.sub_path, .{}) catch |err| |
| 1500 | return step.fail(maker, "unable to open input file {f}: {t}", .{ file_path, err }); |
| 1520 | 1501 | defer file.close(io); |
| 1521 | 1502 | |
| 1522 | | var buf: [1024]u8 = undefined; |
| 1523 | | var file_reader = file.reader(io, &buf); |
| 1503 | var file_reader = file.reader(io, &.{}); |
| 1524 | 1504 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { |
| 1525 | | error.ReadFailed => return file_reader.err.?, |
| 1505 | error.ReadFailed => switch (file_reader.err.?) { |
| 1506 | error.Canceled => |e| return e, |
| 1507 | else => |e| return step.fail(maker, "failed to read from {f}: {t}", .{ file_path, e }), |
| 1508 | }, |
| 1526 | 1509 | error.WriteFailed => return error.OutOfMemory, |
| 1527 | 1510 | }; |
| 1511 | result.writer.writeAll(suffix) catch return error.OutOfMemory; |
| 1528 | 1512 | |
| 1529 | | try argv_list.append(arena, result.written()); |
| 1513 | argv_list.appendAssumeCapacity(result.written()); |
| 1530 | 1514 | }, |
| 1531 | | .artifact => |pa| { |
| 1532 | | const artifact = pa.artifact; |
| 1533 | | const file_path: []const u8 = p: { |
| 1534 | | if (artifact == run.producer.?) break :p b.fmt("{f}", .{run.rebuilt_executable.?}); |
| 1535 | | break :p artifact.installed_path orelse artifact.generated_bin.?.path.?; |
| 1536 | | }; |
| 1537 | | try argv_list.append(arena, b.fmt("{s}{s}", .{ |
| 1538 | | pa.prefix, |
| 1539 | | convertPathArg(run_index, maker, .{ .root_dir = .cwd(), .sub_path = file_path }), |
| 1515 | .artifact => { |
| 1516 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| 1517 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; |
| 1518 | const producer_index = arg.producer.value.?; |
| 1519 | const producer_step = producer_index.ptr(conf); |
| 1520 | const producer = producer_step.extended.get(conf.extra).compile; |
| 1521 | const producer_make_comp_step = maker.stepByIndex(producer_index); |
| 1522 | const producer_make_comp = &producer_make_comp_step.extended.compile; |
| 1523 | const file_path: Path = if (producer_index == conf_run.producer.value.?) |
| 1524 | run.rebuilt_executable.? |
| 1525 | else |
| 1526 | producer_make_comp.installed_path orelse |
| 1527 | maker.generatedPath(producer.generated_bin.value.?).*; |
| 1528 | argv_list.appendAssumeCapacity(try mem.concat(arena, u8, &.{ |
| 1529 | prefix, try convertPathArg(run_index, maker, file_path), suffix, |
| 1540 | 1530 | })); |
| 1541 | 1531 | }, |
| 1542 | | .output_file, .output_directory => unreachable, |
| 1532 | .output_file => unreachable, |
| 1533 | .output_directory => unreachable, |
| 1534 | .cli_rest_positionals => unreachable, |
| 1543 | 1535 | } |
| 1544 | 1536 | } |
| 1545 | 1537 | |
| ... | ... | @@ -1552,7 +1544,7 @@ pub fn rerunInFuzzMode( |
| 1552 | 1544 | var rand_int: u64 = undefined; |
| 1553 | 1545 | io.random(@ptrCast(&rand_int)); |
| 1554 | 1546 | const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); |
| 1555 | | try runCommand(run, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{ |
| 1547 | try runCommand(run, run_index, maker, prog_node, argv_list.items, has_side_effects, tmp_dir_path, .{ |
| 1556 | 1548 | .fuzz = fuzz, |
| 1557 | 1549 | }); |
| 1558 | 1550 | } |
| ... | ... | @@ -1560,28 +1552,94 @@ pub fn rerunInFuzzMode( |
| 1560 | 1552 | const CapturedStdIo = void; // TODO get it from Configuration |
| 1561 | 1553 | |
| 1562 | 1554 | fn populateGeneratedPaths( |
| 1563 | | arena: std.mem.Allocator, |
| 1555 | maker: *Maker, |
| 1564 | 1556 | output_placeholders: []const IndexedOutput, |
| 1565 | | conf_run: *const Configuration.Step.Run, |
| 1566 | 1557 | cache_root: Cache.Directory, |
| 1567 | 1558 | digest: *const Cache.HexDigest, |
| 1568 | 1559 | ) !void { |
| 1560 | const conf = &maker.scanned_config.configuration; |
| 1561 | const graph = maker.graph; |
| 1562 | const arena = graph.arena; // TODO don't leak into the process arena |
| 1563 | |
| 1569 | 1564 | for (output_placeholders) |placeholder| { |
| 1570 | | placeholder.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1571 | | "o", digest, placeholder.output.basename, |
| 1572 | | }); |
| 1565 | const arg = placeholder.arg_index.get(conf); |
| 1566 | maker.generatedPath(arg.generated.value.?).* = .{ |
| 1567 | .root_dir = cache_root, |
| 1568 | .sub_path = try Dir.path.join(arena, &.{ |
| 1569 | "o", digest, arg.basename.value.?.slice(conf), |
| 1570 | }), |
| 1571 | }; |
| 1572 | } |
| 1573 | } |
| 1574 | |
| 1575 | fn populateGeneratedPathsCreateDirs( |
| 1576 | run: *Run, |
| 1577 | run_index: Configuration.Step.Index, |
| 1578 | maker: *Maker, |
| 1579 | output_dir_path: []const u8, |
| 1580 | ) !void { |
| 1581 | const step = maker.stepByIndex(run_index); |
| 1582 | const conf = &maker.scanned_config.configuration; |
| 1583 | const graph = maker.graph; |
| 1584 | const io = graph.io; |
| 1585 | const arena = graph.arena; // TODO don't leak into the process arena |
| 1586 | const cache_root = graph.local_cache_root; |
| 1587 | const argv = run.argv.items; |
| 1588 | |
| 1589 | for (run.output_placeholders.items) |placeholder| { |
| 1590 | const arg = placeholder.arg_index.get(conf); |
| 1591 | const prefix = if (arg.prefix.value) |p| p.slice(conf) else ""; |
| 1592 | const suffix = if (arg.suffix.value) |p| p.slice(conf) else ""; |
| 1593 | const basename = arg.basename.value.?.slice(conf); |
| 1594 | |
| 1595 | const generated_path: Path = .{ |
| 1596 | .root_dir = cache_root, |
| 1597 | .sub_path = try Dir.path.join(arena, &.{ output_dir_path, basename }), |
| 1598 | }; |
| 1599 | const create_path: Path = .{ |
| 1600 | .root_dir = cache_root, |
| 1601 | .sub_path = switch (arg.flags.tag) { |
| 1602 | .output_file => Dir.path.dirname(generated_path.sub_path).?, |
| 1603 | .output_directory => generated_path.sub_path, |
| 1604 | else => unreachable, |
| 1605 | }, |
| 1606 | }; |
| 1607 | create_path.root_dir.handle.createDirPath(io, create_path.sub_path) catch |err| |
| 1608 | return step.fail(maker, "unable to make path {f}: {t}", .{ create_path, err }); |
| 1609 | |
| 1610 | maker.generatedPath(arg.generated.value.?).* = generated_path; |
| 1611 | |
| 1612 | const arg_output_path = try convertPathArg(run_index, maker, generated_path); |
| 1613 | argv[placeholder.index] = try mem.concat(arena, u8, &.{ prefix, arg_output_path, suffix }); |
| 1573 | 1614 | } |
| 1615 | } |
| 1616 | |
| 1617 | fn populateGeneratedStdIo( |
| 1618 | maker: *Maker, |
| 1619 | conf_run: *const Configuration.Step.Run, |
| 1620 | cache_root: Cache.Directory, |
| 1621 | digest: *const Cache.HexDigest, |
| 1622 | ) !void { |
| 1623 | const conf = &maker.scanned_config.configuration; |
| 1624 | const graph = maker.graph; |
| 1625 | const arena = graph.arena; // TODO don't leak into the process arena |
| 1574 | 1626 | |
| 1575 | 1627 | if (conf_run.captured_stdout.value) |captured| { |
| 1576 | | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1577 | | "o", digest, captured.output.basename, |
| 1578 | | }); |
| 1628 | maker.generatedPath(captured.generated_file).* = .{ |
| 1629 | .root_dir = cache_root, |
| 1630 | .sub_path = try Dir.path.join(arena, &.{ |
| 1631 | "o", digest, captured.basename.slice(conf), |
| 1632 | }), |
| 1633 | }; |
| 1579 | 1634 | } |
| 1580 | 1635 | |
| 1581 | 1636 | if (conf_run.captured_stderr.value) |captured| { |
| 1582 | | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1583 | | "o", digest, captured.output.basename, |
| 1584 | | }); |
| 1637 | maker.generatedPath(captured.generated_file).* = .{ |
| 1638 | .root_dir = cache_root, |
| 1639 | .sub_path = try Dir.path.join(arena, &.{ |
| 1640 | "o", digest, captured.basename.slice(conf), |
| 1641 | }), |
| 1642 | }; |
| 1585 | 1643 | } |
| 1586 | 1644 | } |
| 1587 | 1645 | |
| ... | ... | @@ -1600,11 +1658,12 @@ fn fmtTerm(term: ?process.Child.Term) std.fmt.Alt(?process.Child.Term, formatTer |
| 1600 | 1658 | } |
| 1601 | 1659 | |
| 1602 | 1660 | const FuzzContext = struct { |
| 1603 | | fuzz: *std.Build.Fuzz, |
| 1661 | fuzz: *Fuzz, |
| 1604 | 1662 | }; |
| 1605 | 1663 | |
| 1606 | 1664 | fn runCommand( |
| 1607 | 1665 | run: *Run, |
| 1666 | run_index: Configuration.Step.Index, |
| 1608 | 1667 | maker: *Maker, |
| 1609 | 1668 | progress_node: std.Progress.Node, |
| 1610 | 1669 | argv: []const []const u8, |
| ... | ... | @@ -1615,30 +1674,45 @@ fn runCommand( |
| 1615 | 1674 | const graph = maker.graph; |
| 1616 | 1675 | const arena = graph.arena; // TODO don't leak into process arena |
| 1617 | 1676 | const gpa = maker.gpa; |
| 1618 | | const step = &run.step; |
| 1619 | | const b = step.owner; |
| 1677 | const step = maker.stepByIndex(run_index); |
| 1620 | 1678 | const io = graph.io; |
| 1679 | const cache_root = graph.local_cache_root; |
| 1680 | const conf = &maker.scanned_config.configuration; |
| 1681 | const conf_step = run_index.ptr(conf); |
| 1682 | const conf_run = conf_step.extended.get(conf.extra).run; |
| 1683 | const environ_map = &run.environ_map; |
| 1621 | 1684 | |
| 1622 | | const cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, step) } else .inherit; |
| 1623 | | |
| 1624 | | try step.handleChildProcUnsupported(); |
| 1625 | | try Step.handleVerbose(step.owner, cwd, run.environ_map, argv); |
| 1685 | const cwd: process.Child.Cwd = if (conf_run.cwd.value) |lazy_cwd| |
| 1686 | .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) } |
| 1687 | else |
| 1688 | .inherit; |
| 1626 | 1689 | |
| 1627 | | const allow_skip = switch (run.stdio) { |
| 1628 | | .check, .zig_test => run.skip_foreign_checks, |
| 1690 | const allow_skip = switch (conf_run.flags.stdio) { |
| 1691 | .check, .zig_test => conf_run.flags.skip_foreign_checks, |
| 1629 | 1692 | else => false, |
| 1630 | 1693 | }; |
| 1631 | 1694 | |
| 1632 | | var interp_argv = std.array_list.Managed([]const u8).init(b.allocator); |
| 1633 | | defer interp_argv.deinit(); |
| 1695 | var interp_argv: std.ArrayList([]const u8) = .empty; |
| 1696 | |
| 1697 | // `environ_map` is initialized with an undefined `allocator` field; lazily |
| 1698 | // initialize it here. |
| 1699 | environ_map.allocator = gpa; |
| 1700 | // In either case we add to this mutatable data structure so that we can |
| 1701 | // tweak the environment below. |
| 1702 | environ_map.clearRetainingCapacity(); |
| 1703 | if (conf_run.environ_map.value) |env_map_index| { |
| 1704 | const conf_env_map = env_map_index.get(conf); |
| 1705 | for (conf_env_map.keys.slice(conf), conf_env_map.values.slice(conf)) |k, v| { |
| 1706 | try environ_map.put(k.slice(conf), v.slice(conf)); |
| 1707 | } |
| 1708 | } else { |
| 1709 | try environ_map.putAll(&graph.environ_map); |
| 1710 | } |
| 1711 | try graph.handleVerbose(cwd, environ_map, argv); |
| 1634 | 1712 | |
| 1635 | | var environ_map: EnvMap = env: { |
| 1636 | | const orig = run.environ_map orelse &graph.environ_map; |
| 1637 | | break :env try orig.clone(gpa); |
| 1638 | | }; |
| 1639 | | defer environ_map.deinit(); |
| 1713 | if (true) @panic("TODO"); |
| 1640 | 1714 | |
| 1641 | | const opt_generic_result = spawnChildAndCollect(run, maker, progress_node, argv, &environ_map, has_side_effects, fuzz_context) catch |err| term: { |
| 1715 | const opt_generic_result = spawnChildAndCollect(run_index, run, maker, progress_node, argv, &environ_map, has_side_effects, fuzz_context) catch |err| term: { |
| 1642 | 1716 | // InvalidExe: cpu arch mismatch |
| 1643 | 1717 | // FileNotFound: can happen with a wrong dynamic linker path |
| 1644 | 1718 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { |
| ... | ... | @@ -1660,7 +1734,7 @@ fn runCommand( |
| 1660 | 1734 | (root_target.isGnuLibC() or (root_target.isMuslLibC() and exe.linkage == .dynamic)); |
| 1661 | 1735 | const other_target = exe.root_module.resolved_target.?.result; |
| 1662 | 1736 | switch (std.zig.system.getExternalExecutor(io, &graph.host.result, &other_target, .{ |
| 1663 | | .qemu_fixes_dl = need_cross_libc and b.libc_runtimes_dir != null, |
| 1737 | .qemu_fixes_dl = need_cross_libc and graph.libc_runtimes_dir != null, |
| 1664 | 1738 | .link_libc = exe.is_linking_libc, |
| 1665 | 1739 | })) { |
| 1666 | 1740 | .native, .rosetta => { |
| ... | ... | @@ -1668,7 +1742,7 @@ fn runCommand( |
| 1668 | 1742 | break :interpret; |
| 1669 | 1743 | }, |
| 1670 | 1744 | .wine => |bin_name| { |
| 1671 | | if (b.enable_wine) { |
| 1745 | if (graph.enable_wine) { |
| 1672 | 1746 | try interp_argv.append(bin_name); |
| 1673 | 1747 | try interp_argv.appendSlice(argv); |
| 1674 | 1748 | |
| ... | ... | @@ -1682,21 +1756,21 @@ fn runCommand( |
| 1682 | 1756 | } |
| 1683 | 1757 | }, |
| 1684 | 1758 | .qemu => |bin_name| { |
| 1685 | | if (b.enable_qemu) { |
| 1759 | if (graph.enable_qemu) { |
| 1686 | 1760 | try interp_argv.append(bin_name); |
| 1687 | 1761 | |
| 1688 | 1762 | if (need_cross_libc) { |
| 1689 | | if (b.libc_runtimes_dir) |dir| { |
| 1763 | if (graph.libc_runtimes_dir) |dir| { |
| 1690 | 1764 | try interp_argv.append("-L"); |
| 1691 | | try interp_argv.append(b.pathJoin(&.{ |
| 1765 | try interp_argv.append(try Dir.path.join(arena, &.{ |
| 1692 | 1766 | dir, |
| 1693 | 1767 | try if (root_target.isGnuLibC()) std.zig.target.glibcRuntimeTriple( |
| 1694 | | b.allocator, |
| 1768 | arena, |
| 1695 | 1769 | root_target.cpu.arch, |
| 1696 | 1770 | root_target.os.tag, |
| 1697 | 1771 | root_target.abi, |
| 1698 | 1772 | ) else if (root_target.isMuslLibC()) std.zig.target.muslRuntimeTriple( |
| 1699 | | b.allocator, |
| 1773 | arena, |
| 1700 | 1774 | root_target.cpu.arch, |
| 1701 | 1775 | root_target.abi, |
| 1702 | 1776 | ) else unreachable, |
| ... | ... | @@ -1708,7 +1782,7 @@ fn runCommand( |
| 1708 | 1782 | } else return failForeign(run, "-fqemu", argv[0], exe); |
| 1709 | 1783 | }, |
| 1710 | 1784 | .darling => |bin_name| { |
| 1711 | | if (b.enable_darling) { |
| 1785 | if (graph.enable_darling) { |
| 1712 | 1786 | try interp_argv.append(bin_name); |
| 1713 | 1787 | try interp_argv.appendSlice(argv); |
| 1714 | 1788 | } else { |
| ... | ... | @@ -1716,7 +1790,7 @@ fn runCommand( |
| 1716 | 1790 | } |
| 1717 | 1791 | }, |
| 1718 | 1792 | .wasmtime => |bin_name| { |
| 1719 | | if (b.enable_wasmtime) { |
| 1793 | if (graph.enable_wasmtime) { |
| 1720 | 1794 | try interp_argv.append(bin_name); |
| 1721 | 1795 | try interp_argv.append("--dir=."); |
| 1722 | 1796 | // Wasmtime doeesn't inherit environment variables from the parent process |
| ... | ... | @@ -1743,8 +1817,8 @@ fn runCommand( |
| 1743 | 1817 | .bad_os_or_cpu => { |
| 1744 | 1818 | if (allow_skip) return error.MakeSkipped; |
| 1745 | 1819 | |
| 1746 | | const host_name = try graph.host.result.zigTriple(b.allocator); |
| 1747 | | const foreign_name = try root_target.zigTriple(b.allocator); |
| 1820 | const host_name = try graph.host.result.zigTriple(arena); |
| 1821 | const foreign_name = try root_target.zigTriple(arena); |
| 1748 | 1822 | |
| 1749 | 1823 | return step.fail(maker, "the host system ({s}) is unable to execute binaries from the target ({s})", .{ |
| 1750 | 1824 | host_name, foreign_name, |
| ... | ... | @@ -1761,7 +1835,7 @@ fn runCommand( |
| 1761 | 1835 | step.result_failed_command = null; |
| 1762 | 1836 | try Step.handleVerbose(step.owner, cwd, run.environ_map, interp_argv.items); |
| 1763 | 1837 | |
| 1764 | | break :term spawnChildAndCollect(run, maker, progress_node, interp_argv.items, &environ_map, has_side_effects, fuzz_context) catch |e| { |
| 1838 | break :term spawnChildAndCollect(run_index, run, maker, progress_node, interp_argv.items, &environ_map, has_side_effects, fuzz_context) catch |e| { |
| 1765 | 1839 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; |
| 1766 | 1840 | if (e == error.MakeFailed) return error.MakeFailed; // error already reported |
| 1767 | 1841 | return step.fail(maker, "unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); |
| ... | ... | @@ -1800,14 +1874,14 @@ fn runCommand( |
| 1800 | 1874 | }) |stream| { |
| 1801 | 1875 | if (stream.captured) |captured| { |
| 1802 | 1876 | const output_components = .{ output_dir_path, captured.output.basename }; |
| 1803 | | const output_path = try b.cache_root.join(arena, &output_components); |
| 1877 | const output_path = try cache_root.join(arena, &output_components); |
| 1804 | 1878 | captured.output.generated_file.path = output_path; |
| 1805 | 1879 | |
| 1806 | | const sub_path = b.pathJoin(&output_components); |
| 1880 | const sub_path = try Dir.path.join(arena, &output_components); |
| 1807 | 1881 | const sub_path_dirname = Dir.path.dirname(sub_path).?; |
| 1808 | | b.cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { |
| 1809 | | return step.fail(maker, "unable to make path '{f}{s}': {s}", .{ |
| 1810 | | b.cache_root, sub_path_dirname, @errorName(err), |
| 1882 | cache_root.handle.createDirPath(io, sub_path_dirname) catch |err| { |
| 1883 | return step.fail(maker, "unable to make path '{f}{s}': {t}", .{ |
| 1884 | cache_root, sub_path_dirname, err, |
| 1811 | 1885 | }); |
| 1812 | 1886 | }; |
| 1813 | 1887 | const data = switch (captured.trim_whitespace) { |
| ... | ... | @@ -1816,9 +1890,9 @@ fn runCommand( |
| 1816 | 1890 | .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1817 | 1891 | .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1818 | 1892 | }; |
| 1819 | | b.cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| { |
| 1820 | | return step.fail(maker, "unable to write file '{f}{s}': {s}", .{ |
| 1821 | | b.cache_root, sub_path, @errorName(err), |
| 1893 | cache_root.handle.writeFile(io, .{ .sub_path = sub_path, .data = data }) catch |err| { |
| 1894 | return step.fail(maker, "unable to write file '{f}{s}': {t}", .{ |
| 1895 | cache_root, sub_path, err, |
| 1822 | 1896 | }); |
| 1823 | 1897 | }; |
| 1824 | 1898 | } |
| ... | ... | @@ -1912,6 +1986,7 @@ const EvalGenericResult = struct { |
| 1912 | 1986 | }; |
| 1913 | 1987 | |
| 1914 | 1988 | fn spawnChildAndCollect( |
| 1989 | run_index: Configuration.Step.Index, |
| 1915 | 1990 | run: *Run, |
| 1916 | 1991 | maker: *Maker, |
| 1917 | 1992 | progress_node: std.Progress.Node, |
| ... | ... | @@ -1920,25 +1995,34 @@ fn spawnChildAndCollect( |
| 1920 | 1995 | has_side_effects: bool, |
| 1921 | 1996 | fuzz_context: ?FuzzContext, |
| 1922 | 1997 | ) !?EvalGenericResult { |
| 1923 | | const b = run.step.owner; |
| 1998 | const step = run.step; |
| 1924 | 1999 | const graph = maker.graph; |
| 1925 | 2000 | const gpa = maker.gpa; |
| 1926 | 2001 | const io = graph.io; |
| 2002 | const arena = graph.arena; // TODO don't leak into process arena |
| 2003 | const conf = &maker.scanned_config.configuration; |
| 2004 | const conf_step = run_index.ptr(conf); |
| 2005 | const conf_run = conf_step.extended.get(conf.extra).run; |
| 1927 | 2006 | |
| 1928 | 2007 | if (fuzz_context != null) { |
| 1929 | 2008 | assert(!has_side_effects); |
| 1930 | 2009 | assert(run.stdio == .zig_test); |
| 1931 | 2010 | } |
| 1932 | 2011 | |
| 1933 | | const child_cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, &run.step) } else .inherit; |
| 2012 | const child_cwd: process.Child.Cwd = if (conf_run.cwd) |lazy_cwd| |
| 2013 | .{ .path = try maker.resolveLazyPathIndexAbs(arena, lazy_cwd, run_index) } |
| 2014 | else |
| 2015 | .inherit; |
| 1934 | 2016 | |
| 1935 | 2017 | // If an error occurs, it's caused by this command: |
| 1936 | | assert(run.step.result_failed_command == null); |
| 1937 | | run.step.result_failed_command = try Step.allocPrintCmd(gpa, child_cwd, .{ |
| 2018 | assert(step.result_failed_command == null); |
| 2019 | step.result_failed_command = try Step.allocPrintCmd(gpa, child_cwd, .{ |
| 1938 | 2020 | .child = environ_map, |
| 1939 | 2021 | .parent = &graph.environ_map, |
| 1940 | 2022 | }, argv); |
| 1941 | 2023 | |
| 2024 | try step.handleChildProcUnsupported(maker); |
| 2025 | |
| 1942 | 2026 | var spawn_options: process.SpawnOptions = .{ |
| 1943 | 2027 | .argv = argv, |
| 1944 | 2028 | .cwd = child_cwd, |
| ... | ... | @@ -1973,7 +2057,7 @@ fn spawnChildAndCollect( |
| 1973 | 2057 | error.Canceled => |e| return e, |
| 1974 | 2058 | else => |e| e, |
| 1975 | 2059 | }; |
| 1976 | | run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); |
| 2060 | step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); |
| 1977 | 2061 | try result; |
| 1978 | 2062 | return null; |
| 1979 | 2063 | } else { |
| ... | ... | @@ -1993,7 +2077,7 @@ fn spawnChildAndCollect( |
| 1993 | 2077 | error.Canceled => |e| return e, |
| 1994 | 2078 | else => |e| e, |
| 1995 | 2079 | }; |
| 1996 | | run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); |
| 2080 | step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); |
| 1997 | 2081 | return try result; |
| 1998 | 2082 | } |
| 1999 | 2083 | } |