| ... | ... | @@ -80,8 +80,8 @@ max_stdio_size: usize, |
| 80 | 80 | /// the step fails. |
| 81 | 81 | stdio_limit: std.Io.Limit, |
| 82 | 82 | |
| 83 | | captured_stdout: ?*Output, |
| 84 | | captured_stderr: ?*Output, |
| 83 | captured_stdout: ?*CapturedStdIo, |
| 84 | captured_stderr: ?*CapturedStdIo, |
| 85 | 85 | |
| 86 | 86 | dep_output_file: ?*Output, |
| 87 | 87 | |
| ... | ... | @@ -142,6 +142,7 @@ pub const Arg = union(enum) { |
| 142 | 142 | artifact: PrefixedArtifact, |
| 143 | 143 | lazy_path: PrefixedLazyPath, |
| 144 | 144 | decorated_directory: DecoratedLazyPath, |
| 145 | file_content: PrefixedLazyPath, |
| 145 | 146 | bytes: []u8, |
| 146 | 147 | output_file: *Output, |
| 147 | 148 | output_directory: *Output, |
| ... | ... | @@ -169,6 +170,25 @@ pub const Output = struct { |
| 169 | 170 | basename: []const u8, |
| 170 | 171 | }; |
| 171 | 172 | |
| 173 | pub const CapturedStdIo = struct { |
| 174 | output: Output, |
| 175 | trim_whitespace: TrimWhitespace, |
| 176 | |
| 177 | pub const Options = struct { |
| 178 | /// `null` means `stdout`/`stderr`. |
| 179 | basename: ?[]const u8 = null, |
| 180 | /// Does not affect `expectStdOutEqual`/`expectStdErrEqual`. |
| 181 | trim_whitespace: TrimWhitespace = .none, |
| 182 | }; |
| 183 | |
| 184 | pub const TrimWhitespace = enum { |
| 185 | none, |
| 186 | all, |
| 187 | leading, |
| 188 | trailing, |
| 189 | }; |
| 190 | }; |
| 191 | |
| 172 | 192 | pub fn create(owner: *std.Build, name: []const u8) *Run { |
| 173 | 193 | const run = owner.allocator.create(Run) catch @panic("OOM"); |
| 174 | 194 | run.* = .{ |
| ... | ... | @@ -319,6 +339,60 @@ pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) |
| 319 | 339 | lp.addStepDependencies(&run.step); |
| 320 | 340 | } |
| 321 | 341 | |
| 342 | /// Appends the content of an input file to the command line arguments. |
| 343 | /// |
| 344 | /// The child process will see a single argument, even if the file contains whitespace. |
| 345 | /// This means that the entire file content up to EOF is rendered as one contiguous |
| 346 | /// string, including escape sequences. Notably, any (trailing) newlines will show up |
| 347 | /// like this: "hello,\nfile world!\n" |
| 348 | /// |
| 349 | /// Modifications to the source file will be detected as a cache miss in subsequent |
| 350 | /// builds, causing the child process to be re-executed. |
| 351 | /// |
| 352 | /// This function may not be used to supply the first argument of a `Run` step. |
| 353 | /// |
| 354 | /// Related: |
| 355 | /// * `addPrefixedFileContentArg` - same thing but prepends a string to the argument |
| 356 | pub fn addFileContentArg(run: *Run, lp: std.Build.LazyPath) void { |
| 357 | run.addPrefixedFileContentArg("", lp); |
| 358 | } |
| 359 | |
| 360 | /// Appends the content of an input file to the command line arguments prepended with a string. |
| 361 | /// |
| 362 | /// For example, a prefix of "-F" will result in the child process seeing something |
| 363 | /// like this: "-Fmy file content" |
| 364 | /// |
| 365 | /// The child process will see a single argument, even if the prefix and/or the file |
| 366 | /// contain whitespace. |
| 367 | /// This means that the entire file content up to EOF is rendered as one contiguous |
| 368 | /// string, including escape sequences. Notably, any (trailing) newlines will show up |
| 369 | /// like this: "hello,\nfile world!\n" |
| 370 | /// |
| 371 | /// Modifications to the source file will be detected as a cache miss in subsequent |
| 372 | /// builds, causing the child process to be re-executed. |
| 373 | /// |
| 374 | /// This function may not be used to supply the first argument of a `Run` step. |
| 375 | /// |
| 376 | /// Related: |
| 377 | /// * `addFileContentArg` - same thing but without the prefix |
| 378 | pub fn addPrefixedFileContentArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) void { |
| 379 | const b = run.step.owner; |
| 380 | |
| 381 | // Some parts of this step's configure phase API rely on the first argument being somewhat |
| 382 | // transparent/readable, but the content of the file specified by `lp` remains completely |
| 383 | // opaque until its path can be resolved during the make phase. |
| 384 | if (run.argv.items.len == 0) { |
| 385 | @panic("'addFileContentArg'/'addPrefixedFileContentArg' cannot be first argument"); |
| 386 | } |
| 387 | |
| 388 | const prefixed_file_source: PrefixedLazyPath = .{ |
| 389 | .prefix = b.dupe(prefix), |
| 390 | .lazy_path = lp.dupe(b), |
| 391 | }; |
| 392 | run.argv.append(b.allocator, .{ .file_content = prefixed_file_source }) catch @panic("OOM"); |
| 393 | lp.addStepDependencies(&run.step); |
| 394 | } |
| 395 | |
| 322 | 396 | /// Provides a directory path as a command line argument to the command being run. |
| 323 | 397 | /// |
| 324 | 398 | /// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs |
| ... | ... | @@ -469,6 +543,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { |
| 469 | 543 | break :use_wine std.mem.endsWith(u8, p.lazy_path.basename(b, &run.step), ".exe"); |
| 470 | 544 | }, |
| 471 | 545 | .decorated_directory => false, |
| 546 | .file_content => unreachable, // not allowed as first arg |
| 472 | 547 | .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"), |
| 473 | 548 | .output_file, .output_directory => false, |
| 474 | 549 | }; |
| ... | ... | @@ -553,34 +628,42 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { |
| 553 | 628 | } |
| 554 | 629 | } |
| 555 | 630 | |
| 556 | | pub fn captureStdErr(run: *Run) std.Build.LazyPath { |
| 631 | pub fn captureStdErr(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { |
| 557 | 632 | assert(run.stdio != .inherit); |
| 633 | const b = run.step.owner; |
| 558 | 634 | |
| 559 | | if (run.captured_stderr) |output| return .{ .generated = .{ .file = &output.generated_file } }; |
| 635 | if (run.captured_stderr) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| 560 | 636 | |
| 561 | | const output = run.step.owner.allocator.create(Output) catch @panic("OOM"); |
| 562 | | output.* = .{ |
| 563 | | .prefix = "", |
| 564 | | .basename = "stderr", |
| 565 | | .generated_file = .{ .step = &run.step }, |
| 637 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); |
| 638 | captured.* = .{ |
| 639 | .output = .{ |
| 640 | .prefix = "", |
| 641 | .basename = if (options.basename) |basename| b.dupe(basename) else "stderr", |
| 642 | .generated_file = .{ .step = &run.step }, |
| 643 | }, |
| 644 | .trim_whitespace = options.trim_whitespace, |
| 566 | 645 | }; |
| 567 | | run.captured_stderr = output; |
| 568 | | return .{ .generated = .{ .file = &output.generated_file } }; |
| 646 | run.captured_stderr = captured; |
| 647 | return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| 569 | 648 | } |
| 570 | 649 | |
| 571 | | pub fn captureStdOut(run: *Run) std.Build.LazyPath { |
| 650 | pub fn captureStdOut(run: *Run, options: CapturedStdIo.Options) std.Build.LazyPath { |
| 572 | 651 | assert(run.stdio != .inherit); |
| 652 | const b = run.step.owner; |
| 573 | 653 | |
| 574 | | if (run.captured_stdout) |output| return .{ .generated = .{ .file = &output.generated_file } }; |
| 654 | if (run.captured_stdout) |captured| return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| 575 | 655 | |
| 576 | | const output = run.step.owner.allocator.create(Output) catch @panic("OOM"); |
| 577 | | output.* = .{ |
| 578 | | .prefix = "", |
| 579 | | .basename = "stdout", |
| 580 | | .generated_file = .{ .step = &run.step }, |
| 656 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); |
| 657 | captured.* = .{ |
| 658 | .output = .{ |
| 659 | .prefix = "", |
| 660 | .basename = if (options.basename) |basename| b.dupe(basename) else "stdout", |
| 661 | .generated_file = .{ .step = &run.step }, |
| 662 | }, |
| 663 | .trim_whitespace = options.trim_whitespace, |
| 581 | 664 | }; |
| 582 | | run.captured_stdout = output; |
| 583 | | return .{ .generated = .{ .file = &output.generated_file } }; |
| 665 | run.captured_stdout = captured; |
| 666 | return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| 584 | 667 | } |
| 585 | 668 | |
| 586 | 669 | /// Adds an additional input files that, when modified, indicates that this Run |
| ... | ... | @@ -732,6 +815,35 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 732 | 815 | try argv_list.append(resolved_arg); |
| 733 | 816 | man.hash.addBytes(resolved_arg); |
| 734 | 817 | }, |
| 818 | .file_content => |file_plp| { |
| 819 | const file_path = file_plp.lazy_path.getPath3(b, step); |
| 820 | |
| 821 | var result: std.Io.Writer.Allocating = .init(arena); |
| 822 | errdefer result.deinit(); |
| 823 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; |
| 824 | |
| 825 | const file = file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}) catch |err| { |
| 826 | return step.fail( |
| 827 | "unable to open input file '{f}': {t}", |
| 828 | .{ file_path, err }, |
| 829 | ); |
| 830 | }; |
| 831 | defer file.close(); |
| 832 | |
| 833 | var buf: [1024]u8 = undefined; |
| 834 | var file_reader = file.reader(&buf); |
| 835 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { |
| 836 | error.ReadFailed => return step.fail( |
| 837 | "failed to read from '{f}': {t}", |
| 838 | .{ file_path, file_reader.err.? }, |
| 839 | ), |
| 840 | error.WriteFailed => return error.OutOfMemory, |
| 841 | }; |
| 842 | |
| 843 | try argv_list.append(result.written()); |
| 844 | man.hash.addBytes(file_plp.prefix); |
| 845 | _ = try man.addFilePath(file_path, null); |
| 846 | }, |
| 735 | 847 | .artifact => |pa| { |
| 736 | 848 | const artifact = pa.artifact; |
| 737 | 849 | |
| ... | ... | @@ -775,12 +887,14 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 775 | 887 | .none => {}, |
| 776 | 888 | } |
| 777 | 889 | |
| 778 | | if (run.captured_stdout) |output| { |
| 779 | | man.hash.addBytes(output.basename); |
| 890 | if (run.captured_stdout) |captured| { |
| 891 | man.hash.addBytes(captured.output.basename); |
| 892 | man.hash.add(captured.trim_whitespace); |
| 780 | 893 | } |
| 781 | 894 | |
| 782 | | if (run.captured_stderr) |output| { |
| 783 | | man.hash.addBytes(output.basename); |
| 895 | if (run.captured_stderr) |captured| { |
| 896 | man.hash.addBytes(captured.output.basename); |
| 897 | man.hash.add(captured.trim_whitespace); |
| 784 | 898 | } |
| 785 | 899 | |
| 786 | 900 | hashStdIo(&man.hash, run.stdio); |
| ... | ... | @@ -951,7 +1065,7 @@ pub fn rerunInFuzzMode( |
| 951 | 1065 | const step = &run.step; |
| 952 | 1066 | const b = step.owner; |
| 953 | 1067 | const arena = b.allocator; |
| 954 | | var argv_list: std.ArrayListUnmanaged([]const u8) = .empty; |
| 1068 | var argv_list: std.ArrayList([]const u8) = .empty; |
| 955 | 1069 | for (run.argv.items) |arg| { |
| 956 | 1070 | switch (arg) { |
| 957 | 1071 | .bytes => |bytes| { |
| ... | ... | @@ -965,6 +1079,25 @@ pub fn rerunInFuzzMode( |
| 965 | 1079 | const file_path = dd.lazy_path.getPath3(b, step); |
| 966 | 1080 | try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix })); |
| 967 | 1081 | }, |
| 1082 | .file_content => |file_plp| { |
| 1083 | const file_path = file_plp.lazy_path.getPath3(b, step); |
| 1084 | |
| 1085 | var result: std.Io.Writer.Allocating = .init(arena); |
| 1086 | errdefer result.deinit(); |
| 1087 | result.writer.writeAll(file_plp.prefix) catch return error.OutOfMemory; |
| 1088 | |
| 1089 | const file = try file_path.root_dir.handle.openFile(file_path.subPathOrDot(), .{}); |
| 1090 | defer file.close(); |
| 1091 | |
| 1092 | var buf: [1024]u8 = undefined; |
| 1093 | var file_reader = file.reader(&buf); |
| 1094 | _ = file_reader.interface.streamRemaining(&result.writer) catch |err| switch (err) { |
| 1095 | error.ReadFailed => return file_reader.err.?, |
| 1096 | error.WriteFailed => return error.OutOfMemory, |
| 1097 | }; |
| 1098 | |
| 1099 | try argv_list.append(arena, result.written()); |
| 1100 | }, |
| 968 | 1101 | .artifact => |pa| { |
| 969 | 1102 | const artifact = pa.artifact; |
| 970 | 1103 | const file_path: []const u8 = p: { |
| ... | ... | @@ -991,8 +1124,8 @@ pub fn rerunInFuzzMode( |
| 991 | 1124 | fn populateGeneratedPaths( |
| 992 | 1125 | arena: std.mem.Allocator, |
| 993 | 1126 | output_placeholders: []const IndexedOutput, |
| 994 | | captured_stdout: ?*Output, |
| 995 | | captured_stderr: ?*Output, |
| 1127 | captured_stdout: ?*CapturedStdIo, |
| 1128 | captured_stderr: ?*CapturedStdIo, |
| 996 | 1129 | cache_root: Build.Cache.Directory, |
| 997 | 1130 | digest: *const Build.Cache.HexDigest, |
| 998 | 1131 | ) !void { |
| ... | ... | @@ -1002,15 +1135,15 @@ fn populateGeneratedPaths( |
| 1002 | 1135 | }); |
| 1003 | 1136 | } |
| 1004 | 1137 | |
| 1005 | | if (captured_stdout) |output| { |
| 1006 | | output.generated_file.path = try cache_root.join(arena, &.{ |
| 1007 | | "o", digest, output.basename, |
| 1138 | if (captured_stdout) |captured| { |
| 1139 | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1140 | "o", digest, captured.output.basename, |
| 1008 | 1141 | }); |
| 1009 | 1142 | } |
| 1010 | 1143 | |
| 1011 | | if (captured_stderr) |output| { |
| 1012 | | output.generated_file.path = try cache_root.join(arena, &.{ |
| 1013 | | "o", digest, output.basename, |
| 1144 | if (captured_stderr) |captured| { |
| 1145 | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1146 | "o", digest, captured.output.basename, |
| 1014 | 1147 | }); |
| 1015 | 1148 | } |
| 1016 | 1149 | } |
| ... | ... | @@ -1251,7 +1384,7 @@ fn runCommand( |
| 1251 | 1384 | |
| 1252 | 1385 | // Capture stdout and stderr to GeneratedFile objects. |
| 1253 | 1386 | const Stream = struct { |
| 1254 | | captured: ?*Output, |
| 1387 | captured: ?*CapturedStdIo, |
| 1255 | 1388 | bytes: ?[]const u8, |
| 1256 | 1389 | }; |
| 1257 | 1390 | for ([_]Stream{ |
| ... | ... | @@ -1264,10 +1397,10 @@ fn runCommand( |
| 1264 | 1397 | .bytes = result.stdio.stderr, |
| 1265 | 1398 | }, |
| 1266 | 1399 | }) |stream| { |
| 1267 | | if (stream.captured) |output| { |
| 1268 | | const output_components = .{ output_dir_path, output.basename }; |
| 1400 | if (stream.captured) |captured| { |
| 1401 | const output_components = .{ output_dir_path, captured.output.basename }; |
| 1269 | 1402 | const output_path = try b.cache_root.join(arena, &output_components); |
| 1270 | | output.generated_file.path = output_path; |
| 1403 | captured.output.generated_file.path = output_path; |
| 1271 | 1404 | |
| 1272 | 1405 | const sub_path = b.pathJoin(&output_components); |
| 1273 | 1406 | const sub_path_dirname = fs.path.dirname(sub_path).?; |
| ... | ... | @@ -1276,7 +1409,13 @@ fn runCommand( |
| 1276 | 1409 | b.cache_root, sub_path_dirname, @errorName(err), |
| 1277 | 1410 | }); |
| 1278 | 1411 | }; |
| 1279 | | b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = stream.bytes.? }) catch |err| { |
| 1412 | const data = switch (captured.trim_whitespace) { |
| 1413 | .none => stream.bytes.?, |
| 1414 | .all => mem.trim(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1415 | .leading => mem.trimStart(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1416 | .trailing => mem.trimEnd(u8, stream.bytes.?, &std.ascii.whitespace), |
| 1417 | }; |
| 1418 | b.cache_root.handle.writeFile(.{ .sub_path = sub_path, .data = data }) catch |err| { |
| 1280 | 1419 | return step.fail("unable to write file '{f}{s}': {s}", .{ |
| 1281 | 1420 | b.cache_root, sub_path, @errorName(err), |
| 1282 | 1421 | }); |