| ... | @@ -80,8 +80,8 @@ max_stdio_size: usize, | ... | @@ -80,8 +80,8 @@ max_stdio_size: usize, |
| 80 | /// the step fails. | 80 | /// the step fails. |
| 81 | stdio_limit: std.Io.Limit, | 81 | stdio_limit: std.Io.Limit, |
| 82 | | 82 | |
| 83 | captured_stdout: ?*Output, | 83 | captured_stdout: ?*CapturedStdIo, |
| 84 | captured_stderr: ?*Output, | 84 | captured_stderr: ?*CapturedStdIo, |
| 85 | | 85 | |
| 86 | dep_output_file: ?*Output, | 86 | dep_output_file: ?*Output, |
| 87 | | 87 | |
| ... | @@ -142,6 +142,7 @@ pub const Arg = union(enum) { | ... | @@ -142,6 +142,7 @@ pub const Arg = union(enum) { |
| 142 | artifact: PrefixedArtifact, | 142 | artifact: PrefixedArtifact, |
| 143 | lazy_path: PrefixedLazyPath, | 143 | lazy_path: PrefixedLazyPath, |
| 144 | decorated_directory: DecoratedLazyPath, | 144 | decorated_directory: DecoratedLazyPath, |
| | 145 | file_content: PrefixedLazyPath, |
| 145 | bytes: []u8, | 146 | bytes: []u8, |
| 146 | output_file: *Output, | 147 | output_file: *Output, |
| 147 | output_directory: *Output, | 148 | output_directory: *Output, |
| ... | @@ -169,6 +170,25 @@ pub const Output = struct { | ... | @@ -169,6 +170,25 @@ pub const Output = struct { |
| 169 | basename: []const u8, | 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 | pub fn create(owner: *std.Build, name: []const u8) *Run { | 192 | pub fn create(owner: *std.Build, name: []const u8) *Run { |
| 173 | const run = owner.allocator.create(Run) catch @panic("OOM"); | 193 | const run = owner.allocator.create(Run) catch @panic("OOM"); |
| 174 | run.* = .{ | 194 | run.* = .{ |
| ... | @@ -319,6 +339,60 @@ pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) | ... | @@ -319,6 +339,60 @@ pub fn addPrefixedFileArg(run: *Run, prefix: []const u8, lp: std.Build.LazyPath) |
| 319 | lp.addStepDependencies(&run.step); | 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 | /// Provides a directory path as a command line argument to the command being run. | 396 | /// Provides a directory path as a command line argument to the command being run. |
| 323 | /// | 397 | /// |
| 324 | /// Returns a `std.Build.LazyPath` which can be used as inputs to other APIs | 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,6 +543,7 @@ pub fn addPathDir(run: *Run, search_path: []const u8) void { |
| 469 | break :use_wine std.mem.endsWith(u8, p.lazy_path.basename(b, &run.step), ".exe"); | 543 | break :use_wine std.mem.endsWith(u8, p.lazy_path.basename(b, &run.step), ".exe"); |
| 470 | }, | 544 | }, |
| 471 | .decorated_directory => false, | 545 | .decorated_directory => false, |
| | 546 | .file_content => unreachable, // not allowed as first arg |
| 472 | .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"), | 547 | .bytes => |bytes| std.mem.endsWith(u8, bytes, ".exe"), |
| 473 | .output_file, .output_directory => false, | 548 | .output_file, .output_directory => false, |
| 474 | }; | 549 | }; |
| ... | @@ -553,34 +628,42 @@ pub fn addCheck(run: *Run, new_check: StdIo.Check) void { | ... | @@ -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 | assert(run.stdio != .inherit); | 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"); | 637 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); |
| 562 | output.* = .{ | 638 | captured.* = .{ |
| 563 | .prefix = "", | 639 | .output = .{ |
| 564 | .basename = "stderr", | 640 | .prefix = "", |
| 565 | .generated_file = .{ .step = &run.step }, | 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; | 646 | run.captured_stderr = captured; |
| 568 | return .{ .generated = .{ .file = &output.generated_file } }; | 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 | assert(run.stdio != .inherit); | 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"); | 656 | const captured = b.allocator.create(CapturedStdIo) catch @panic("OOM"); |
| 577 | output.* = .{ | 657 | captured.* = .{ |
| 578 | .prefix = "", | 658 | .output = .{ |
| 579 | .basename = "stdout", | 659 | .prefix = "", |
| 580 | .generated_file = .{ .step = &run.step }, | 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; | 665 | run.captured_stdout = captured; |
| 583 | return .{ .generated = .{ .file = &output.generated_file } }; | 666 | return .{ .generated = .{ .file = &captured.output.generated_file } }; |
| 584 | } | 667 | } |
| 585 | | 668 | |
| 586 | /// Adds an additional input files that, when modified, indicates that this Run | 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,6 +815,35 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 732 | try argv_list.append(resolved_arg); | 815 | try argv_list.append(resolved_arg); |
| 733 | man.hash.addBytes(resolved_arg); | 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 | .artifact => |pa| { | 847 | .artifact => |pa| { |
| 736 | const artifact = pa.artifact; | 848 | const artifact = pa.artifact; |
| 737 | | 849 | |
| ... | @@ -775,12 +887,14 @@ fn make(step: *Step, options: Step.MakeOptions) !void { | ... | @@ -775,12 +887,14 @@ fn make(step: *Step, options: Step.MakeOptions) !void { |
| 775 | .none => {}, | 887 | .none => {}, |
| 776 | } | 888 | } |
| 777 | | 889 | |
| 778 | if (run.captured_stdout) |output| { | 890 | if (run.captured_stdout) |captured| { |
| 779 | man.hash.addBytes(output.basename); | 891 | man.hash.addBytes(captured.output.basename); |
| | 892 | man.hash.add(captured.trim_whitespace); |
| 780 | } | 893 | } |
| 781 | | 894 | |
| 782 | if (run.captured_stderr) |output| { | 895 | if (run.captured_stderr) |captured| { |
| 783 | man.hash.addBytes(output.basename); | 896 | man.hash.addBytes(captured.output.basename); |
| | 897 | man.hash.add(captured.trim_whitespace); |
| 784 | } | 898 | } |
| 785 | | 899 | |
| 786 | hashStdIo(&man.hash, run.stdio); | 900 | hashStdIo(&man.hash, run.stdio); |
| ... | @@ -951,7 +1065,7 @@ pub fn rerunInFuzzMode( | ... | @@ -951,7 +1065,7 @@ pub fn rerunInFuzzMode( |
| 951 | const step = &run.step; | 1065 | const step = &run.step; |
| 952 | const b = step.owner; | 1066 | const b = step.owner; |
| 953 | const arena = b.allocator; | 1067 | const arena = b.allocator; |
| 954 | var argv_list: std.ArrayListUnmanaged([]const u8) = .empty; | 1068 | var argv_list: std.ArrayList([]const u8) = .empty; |
| 955 | for (run.argv.items) |arg| { | 1069 | for (run.argv.items) |arg| { |
| 956 | switch (arg) { | 1070 | switch (arg) { |
| 957 | .bytes => |bytes| { | 1071 | .bytes => |bytes| { |
| ... | @@ -965,6 +1079,25 @@ pub fn rerunInFuzzMode( | ... | @@ -965,6 +1079,25 @@ pub fn rerunInFuzzMode( |
| 965 | const file_path = dd.lazy_path.getPath3(b, step); | 1079 | const file_path = dd.lazy_path.getPath3(b, step); |
| 966 | try argv_list.append(arena, b.fmt("{s}{s}{s}", .{ dd.prefix, run.convertPathArg(file_path), dd.suffix })); | 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 | .artifact => |pa| { | 1101 | .artifact => |pa| { |
| 969 | const artifact = pa.artifact; | 1102 | const artifact = pa.artifact; |
| 970 | const file_path: []const u8 = p: { | 1103 | const file_path: []const u8 = p: { |
| ... | @@ -991,8 +1124,8 @@ pub fn rerunInFuzzMode( | ... | @@ -991,8 +1124,8 @@ pub fn rerunInFuzzMode( |
| 991 | fn populateGeneratedPaths( | 1124 | fn populateGeneratedPaths( |
| 992 | arena: std.mem.Allocator, | 1125 | arena: std.mem.Allocator, |
| 993 | output_placeholders: []const IndexedOutput, | 1126 | output_placeholders: []const IndexedOutput, |
| 994 | captured_stdout: ?*Output, | 1127 | captured_stdout: ?*CapturedStdIo, |
| 995 | captured_stderr: ?*Output, | 1128 | captured_stderr: ?*CapturedStdIo, |
| 996 | cache_root: Build.Cache.Directory, | 1129 | cache_root: Build.Cache.Directory, |
| 997 | digest: *const Build.Cache.HexDigest, | 1130 | digest: *const Build.Cache.HexDigest, |
| 998 | ) !void { | 1131 | ) !void { |
| ... | @@ -1002,15 +1135,15 @@ fn populateGeneratedPaths( | ... | @@ -1002,15 +1135,15 @@ fn populateGeneratedPaths( |
| 1002 | }); | 1135 | }); |
| 1003 | } | 1136 | } |
| 1004 | | 1137 | |
| 1005 | if (captured_stdout) |output| { | 1138 | if (captured_stdout) |captured| { |
| 1006 | output.generated_file.path = try cache_root.join(arena, &.{ | 1139 | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1007 | "o", digest, output.basename, | 1140 | "o", digest, captured.output.basename, |
| 1008 | }); | 1141 | }); |
| 1009 | } | 1142 | } |
| 1010 | | 1143 | |
| 1011 | if (captured_stderr) |output| { | 1144 | if (captured_stderr) |captured| { |
| 1012 | output.generated_file.path = try cache_root.join(arena, &.{ | 1145 | captured.output.generated_file.path = try cache_root.join(arena, &.{ |
| 1013 | "o", digest, output.basename, | 1146 | "o", digest, captured.output.basename, |
| 1014 | }); | 1147 | }); |
| 1015 | } | 1148 | } |
| 1016 | } | 1149 | } |
| ... | @@ -1251,7 +1384,7 @@ fn runCommand( | ... | @@ -1251,7 +1384,7 @@ fn runCommand( |
| 1251 | | 1384 | |
| 1252 | // Capture stdout and stderr to GeneratedFile objects. | 1385 | // Capture stdout and stderr to GeneratedFile objects. |
| 1253 | const Stream = struct { | 1386 | const Stream = struct { |
| 1254 | captured: ?*Output, | 1387 | captured: ?*CapturedStdIo, |
| 1255 | bytes: ?[]const u8, | 1388 | bytes: ?[]const u8, |
| 1256 | }; | 1389 | }; |
| 1257 | for ([_]Stream{ | 1390 | for ([_]Stream{ |
| ... | @@ -1264,10 +1397,10 @@ fn runCommand( | ... | @@ -1264,10 +1397,10 @@ fn runCommand( |
| 1264 | .bytes = result.stdio.stderr, | 1397 | .bytes = result.stdio.stderr, |
| 1265 | }, | 1398 | }, |
| 1266 | }) |stream| { | 1399 | }) |stream| { |
| 1267 | if (stream.captured) |output| { | 1400 | if (stream.captured) |captured| { |
| 1268 | const output_components = .{ output_dir_path, output.basename }; | 1401 | const output_components = .{ output_dir_path, captured.output.basename }; |
| 1269 | const output_path = try b.cache_root.join(arena, &output_components); | 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 | const sub_path = b.pathJoin(&output_components); | 1405 | const sub_path = b.pathJoin(&output_components); |
| 1273 | const sub_path_dirname = fs.path.dirname(sub_path).?; | 1406 | const sub_path_dirname = fs.path.dirname(sub_path).?; |
| ... | @@ -1276,7 +1409,13 @@ fn runCommand( | ... | @@ -1276,7 +1409,13 @@ fn runCommand( |
| 1276 | b.cache_root, sub_path_dirname, @errorName(err), | 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 | return step.fail("unable to write file '{f}{s}': {s}", .{ | 1419 | return step.fail("unable to write file '{f}{s}': {s}", .{ |
| 1281 | b.cache_root, sub_path, @errorName(err), | 1420 | b.cache_root, sub_path, @errorName(err), |
| 1282 | }); | 1421 | }); |