authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-01-05 02:01:28-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-01-05 02:19:22-05:00
loga690a5085ddbfb540cf07db146645a9f8a4e92f6
tree267f1c2908cd0125178d85adc41c7c81109d608e
parent14fcfe29817c03c3cac023b045433ea7abe4bd47
signature Commit is signed but in an unrecognized format.

rework and improve some of the zig build steps

* `RunStep` moved to lib/std/build/run.zig and gains ability to compare output and exit code against expected values. Multiple redundant locations in the test harness code are replaced to use `RunStep`. * `WriteFileStep` moved to lib/std/build/write_file.zig and gains ability to write more than one file into the cache directory, for when the files need to be relative to each other. This makes usage of `WriteFileStep` no longer problematic when parallelizing zig build. * Added `CheckFileStep`, which can be used to validate that the output of another step produced a valid file. Multiple redundant locations in the test harness code are replaced to use `CheckFileStep`. * Added `TranslateCStep`. This exposes `zig translate-c` to the build system, which is likely to be rarely useful by most Zig users; however Zig's own test suite uses it both for translate-c tests and for run-translated-c tests. * Refactored ad-hoc code to handle source files coming from multiple kinds of sources, into `std.build.FileSource`. * Added `std.build.Builder.addExecutableFromWriteFileStep`. * Added `std.build.Builder.addExecutableSource`. * Added `std.build.Builder.addWriteFiles`. * Added `std.build.Builder.addTranslateC`. * Added `std.build.LibExeObjStep.addCSourceFileSource`. * Added `std.build.LibExeObjStep.addAssemblyFileFromWriteFileStep`. * Added `std.build.LibExeObjStep.addAssemblyFileSource`. * Exposed `std.fs.base64_encoder`.

10 files changed, 955 insertions(+), 865 deletions(-)

lib/std/build.zig+132-192
...@@ -17,6 +17,10 @@ const fmt_lib = std.fmt;...@@ -17,6 +17,10 @@ const fmt_lib = std.fmt;
17const File = std.fs.File;17const File = std.fs.File;
1818
19pub const FmtStep = @import("build/fmt.zig").FmtStep;19pub const FmtStep = @import("build/fmt.zig").FmtStep;
20pub const TranslateCStep = @import("build/translate_c.zig").TranslateCStep;
21pub const WriteFileStep = @import("build/write_file.zig").WriteFileStep;
22pub const RunStep = @import("build/run.zig").RunStep;
23pub const CheckFileStep = @import("build/check_file.zig").CheckFileStep;
2024
21pub const Builder = struct {25pub const Builder = struct {
22 install_tls: TopLevelStep,26 install_tls: TopLevelStep,
...@@ -203,23 +207,53 @@ pub const Builder = struct {...@@ -203,23 +207,53 @@ pub const Builder = struct {
203 }207 }
204208
205 pub fn addExecutable(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {209 pub fn addExecutable(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
210 return LibExeObjStep.createExecutable(
211 self,
212 name,
213 if (root_src) |p| FileSource{ .path = p } else null,
214 false,
215 );
216 }
217
218 pub fn addExecutableFromWriteFileStep(
219 self: *Builder,
220 name: []const u8,
221 wfs: *WriteFileStep,
222 basename: []const u8,
223 ) *LibExeObjStep {
224 return LibExeObjStep.createExecutable(self, name, @as(FileSource, .{
225 .write_file = .{
226 .step = wfs,
227 .basename = basename,
228 },
229 }), false);
230 }
231
232 pub fn addExecutableSource(
233 self: *Builder,
234 name: []const u8,
235 root_src: ?FileSource,
236 ) *LibExeObjStep {
206 return LibExeObjStep.createExecutable(self, name, root_src, false);237 return LibExeObjStep.createExecutable(self, name, root_src, false);
207 }238 }
208239
209 pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {240 pub fn addObject(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
210 return LibExeObjStep.createObject(self, name, root_src);241 const root_src_param = if (root_src) |p| @as(FileSource, .{ .path = p }) else null;
242 return LibExeObjStep.createObject(self, name, root_src_param);
211 }243 }
212244
213 pub fn addSharedLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8, ver: Version) *LibExeObjStep {245 pub fn addSharedLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8, ver: Version) *LibExeObjStep {
214 return LibExeObjStep.createSharedLibrary(self, name, root_src, ver);246 const root_src_param = if (root_src) |p| @as(FileSource, .{ .path = p }) else null;
247 return LibExeObjStep.createSharedLibrary(self, name, root_src_param, ver);
215 }248 }
216249
217 pub fn addStaticLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {250 pub fn addStaticLibrary(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {
218 return LibExeObjStep.createStaticLibrary(self, name, root_src);251 const root_src_param = if (root_src) |p| @as(FileSource, .{ .path = p }) else null;
252 return LibExeObjStep.createStaticLibrary(self, name, root_src_param);
219 }253 }
220254
221 pub fn addTest(self: *Builder, root_src: []const u8) *LibExeObjStep {255 pub fn addTest(self: *Builder, root_src: []const u8) *LibExeObjStep {
222 return LibExeObjStep.createTest(self, "test", root_src);256 return LibExeObjStep.createTest(self, "test", .{ .path = root_src });
223 }257 }
224258
225 pub fn addAssemble(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep {259 pub fn addAssemble(self: *Builder, name: []const u8, src: []const u8) *LibExeObjStep {
...@@ -256,8 +290,14 @@ pub const Builder = struct {...@@ -256,8 +290,14 @@ pub const Builder = struct {
256 }290 }
257291
258 pub fn addWriteFile(self: *Builder, file_path: []const u8, data: []const u8) *WriteFileStep {292 pub fn addWriteFile(self: *Builder, file_path: []const u8, data: []const u8) *WriteFileStep {
293 const write_file_step = self.addWriteFiles();
294 write_file_step.add(file_path, data);
295 return write_file_step;
296 }
297
298 pub fn addWriteFiles(self: *Builder) *WriteFileStep {
259 const write_file_step = self.allocator.create(WriteFileStep) catch unreachable;299 const write_file_step = self.allocator.create(WriteFileStep) catch unreachable;
260 write_file_step.* = WriteFileStep.init(self, file_path, data);300 write_file_step.* = WriteFileStep.init(self);
261 return write_file_step;301 return write_file_step;
262 }302 }
263303
...@@ -278,6 +318,10 @@ pub const Builder = struct {...@@ -278,6 +318,10 @@ pub const Builder = struct {
278 return FmtStep.create(self, paths);318 return FmtStep.create(self, paths);
279 }319 }
280320
321 pub fn addTranslateC(self: *Builder, source: FileSource) *TranslateCStep {
322 return TranslateCStep.create(self, source);
323 }
324
281 pub fn version(self: *const Builder, major: u32, minor: u32, patch: u32) Version {325 pub fn version(self: *const Builder, major: u32, minor: u32, patch: u32) Version {
282 return Version{326 return Version{
283 .major = major,327 .major = major,
...@@ -1002,7 +1046,7 @@ const Pkg = struct {...@@ -1002,7 +1046,7 @@ const Pkg = struct {
1002};1046};
10031047
1004const CSourceFile = struct {1048const CSourceFile = struct {
1005 source_path: []const u8,1049 source: FileSource,
1006 args: []const []const u8,1050 args: []const []const u8,
1007};1051};
10081052
...@@ -1015,6 +1059,33 @@ fn isLibCLibrary(name: []const u8) bool {...@@ -1015,6 +1059,33 @@ fn isLibCLibrary(name: []const u8) bool {
1015 return false;1059 return false;
1016}1060}
10171061
1062pub const FileSource = union(enum) {
1063 /// Relative to build root
1064 path: []const u8,
1065 write_file: struct {
1066 step: *WriteFileStep,
1067 basename: []const u8,
1068 },
1069 translate_c: *TranslateCStep,
1070
1071 pub fn addStepDependencies(self: FileSource, step: *Step) void {
1072 switch (self) {
1073 .path => {},
1074 .write_file => |wf| step.dependOn(&wf.step.step),
1075 .translate_c => |tc| step.dependOn(&tc.step),
1076 }
1077 }
1078
1079 /// Should only be called during make()
1080 pub fn getPath(self: FileSource, builder: *Builder) []const u8 {
1081 return switch (self) {
1082 .path => |p| builder.pathFromRoot(p),
1083 .write_file => |wf| wf.step.getOutputPath(wf.basename),
1084 .translate_c => |tc| tc.getOutputPath(),
1085 };
1086 }
1087};
1088
1018pub const LibExeObjStep = struct {1089pub const LibExeObjStep = struct {
1019 step: Step,1090 step: Step,
1020 builder: *Builder,1091 builder: *Builder,
...@@ -1047,7 +1118,7 @@ pub const LibExeObjStep = struct {...@@ -1047,7 +1118,7 @@ pub const LibExeObjStep = struct {
1047 filter: ?[]const u8,1118 filter: ?[]const u8,
1048 single_threaded: bool,1119 single_threaded: bool,
10491120
1050 root_src: ?[]const u8,1121 root_src: ?FileSource,
1051 out_h_filename: []const u8,1122 out_h_filename: []const u8,
1052 out_lib_filename: []const u8,1123 out_lib_filename: []const u8,
1053 out_pdb_filename: []const u8,1124 out_pdb_filename: []const u8,
...@@ -1099,7 +1170,7 @@ pub const LibExeObjStep = struct {...@@ -1099,7 +1170,7 @@ pub const LibExeObjStep = struct {
1099 StaticPath: []const u8,1170 StaticPath: []const u8,
1100 OtherStep: *LibExeObjStep,1171 OtherStep: *LibExeObjStep,
1101 SystemLib: []const u8,1172 SystemLib: []const u8,
1102 AssemblyFile: []const u8,1173 AssemblyFile: FileSource,
1103 CSourceFile: *CSourceFile,1174 CSourceFile: *CSourceFile,
1104 };1175 };
11051176
...@@ -1116,37 +1187,44 @@ pub const LibExeObjStep = struct {...@@ -1116,37 +1187,44 @@ pub const LibExeObjStep = struct {
1116 Test,1187 Test,
1117 };1188 };
11181189
1119 pub fn createSharedLibrary(builder: *Builder, name: []const u8, root_src: ?[]const u8, ver: Version) *LibExeObjStep {1190 pub fn createSharedLibrary(builder: *Builder, name: []const u8, root_src: ?FileSource, ver: Version) *LibExeObjStep {
1120 const self = builder.allocator.create(LibExeObjStep) catch unreachable;1191 const self = builder.allocator.create(LibExeObjStep) catch unreachable;
1121 self.* = initExtraArgs(builder, name, root_src, Kind.Lib, true, ver);1192 self.* = initExtraArgs(builder, name, root_src, Kind.Lib, true, ver);
1122 return self;1193 return self;
1123 }1194 }
11241195
1125 pub fn createStaticLibrary(builder: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {1196 pub fn createStaticLibrary(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep {
1126 const self = builder.allocator.create(LibExeObjStep) catch unreachable;1197 const self = builder.allocator.create(LibExeObjStep) catch unreachable;
1127 self.* = initExtraArgs(builder, name, root_src, Kind.Lib, false, builder.version(0, 0, 0));1198 self.* = initExtraArgs(builder, name, root_src, Kind.Lib, false, builder.version(0, 0, 0));
1128 return self;1199 return self;
1129 }1200 }
11301201
1131 pub fn createObject(builder: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep {1202 pub fn createObject(builder: *Builder, name: []const u8, root_src: ?FileSource) *LibExeObjStep {
1132 const self = builder.allocator.create(LibExeObjStep) catch unreachable;1203 const self = builder.allocator.create(LibExeObjStep) catch unreachable;
1133 self.* = initExtraArgs(builder, name, root_src, Kind.Obj, false, builder.version(0, 0, 0));1204 self.* = initExtraArgs(builder, name, root_src, Kind.Obj, false, builder.version(0, 0, 0));
1134 return self;1205 return self;
1135 }1206 }
11361207
1137 pub fn createExecutable(builder: *Builder, name: []const u8, root_src: ?[]const u8, is_dynamic: bool) *LibExeObjStep {1208 pub fn createExecutable(builder: *Builder, name: []const u8, root_src: ?FileSource, is_dynamic: bool) *LibExeObjStep {
1138 const self = builder.allocator.create(LibExeObjStep) catch unreachable;1209 const self = builder.allocator.create(LibExeObjStep) catch unreachable;
1139 self.* = initExtraArgs(builder, name, root_src, Kind.Exe, is_dynamic, builder.version(0, 0, 0));1210 self.* = initExtraArgs(builder, name, root_src, Kind.Exe, is_dynamic, builder.version(0, 0, 0));
1140 return self;1211 return self;
1141 }1212 }
11421213
1143 pub fn createTest(builder: *Builder, name: []const u8, root_src: []const u8) *LibExeObjStep {1214 pub fn createTest(builder: *Builder, name: []const u8, root_src: FileSource) *LibExeObjStep {
1144 const self = builder.allocator.create(LibExeObjStep) catch unreachable;1215 const self = builder.allocator.create(LibExeObjStep) catch unreachable;
1145 self.* = initExtraArgs(builder, name, root_src, Kind.Test, false, builder.version(0, 0, 0));1216 self.* = initExtraArgs(builder, name, root_src, Kind.Test, false, builder.version(0, 0, 0));
1146 return self;1217 return self;
1147 }1218 }
11481219
1149 fn initExtraArgs(builder: *Builder, name: []const u8, root_src: ?[]const u8, kind: Kind, is_dynamic: bool, ver: Version) LibExeObjStep {1220 fn initExtraArgs(
1221 builder: *Builder,
1222 name: []const u8,
1223 root_src: ?FileSource,
1224 kind: Kind,
1225 is_dynamic: bool,
1226 ver: Version,
1227 ) LibExeObjStep {
1150 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {1228 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
1151 panic("invalid name: '{}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});1229 panic("invalid name: '{}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});
1152 }1230 }
...@@ -1196,6 +1274,7 @@ pub const LibExeObjStep = struct {...@@ -1196,6 +1274,7 @@ pub const LibExeObjStep = struct {
1196 .install_step = null,1274 .install_step = null,
1197 };1275 };
1198 self.computeOutFileNames();1276 self.computeOutFileNames();
1277 if (root_src) |rs| rs.addStepDependencies(&self.step);
1199 return self;1278 return self;
1200 }1279 }
12011280
...@@ -1486,15 +1565,22 @@ pub const LibExeObjStep = struct {...@@ -1486,15 +1565,22 @@ pub const LibExeObjStep = struct {
1486 }1565 }
14871566
1488 pub fn addCSourceFile(self: *LibExeObjStep, file: []const u8, args: []const []const u8) void {1567 pub fn addCSourceFile(self: *LibExeObjStep, file: []const u8, args: []const []const u8) void {
1568 self.addCSourceFileSource(.{
1569 .args = args,
1570 .source = .{ .path = file },
1571 });
1572 }
1573
1574 pub fn addCSourceFileSource(self: *LibExeObjStep, source: CSourceFile) void {
1489 const c_source_file = self.builder.allocator.create(CSourceFile) catch unreachable;1575 const c_source_file = self.builder.allocator.create(CSourceFile) catch unreachable;
1490 const args_copy = self.builder.allocator.alloc([]u8, args.len) catch unreachable;1576
1491 for (args) |arg, i| {1577 const args_copy = self.builder.allocator.alloc([]u8, source.args.len) catch unreachable;
1578 for (source.args) |arg, i| {
1492 args_copy[i] = self.builder.dupe(arg);1579 args_copy[i] = self.builder.dupe(arg);
1493 }1580 }
1494 c_source_file.* = CSourceFile{1581
1495 .source_path = self.builder.dupe(file),1582 c_source_file.* = source;
1496 .args = args_copy,1583 c_source_file.args = args_copy;
1497 };
1498 self.link_objects.append(LinkObject{ .CSourceFile = c_source_file }) catch unreachable;1584 self.link_objects.append(LinkObject{ .CSourceFile = c_source_file }) catch unreachable;
1499 }1585 }
15001586
...@@ -1571,6 +1657,20 @@ pub const LibExeObjStep = struct {...@@ -1571,6 +1657,20 @@ pub const LibExeObjStep = struct {
1571 self.link_objects.append(LinkObject{ .AssemblyFile = self.builder.dupe(path) }) catch unreachable;1657 self.link_objects.append(LinkObject{ .AssemblyFile = self.builder.dupe(path) }) catch unreachable;
1572 }1658 }
15731659
1660 pub fn addAssemblyFileFromWriteFileStep(self: *LibExeObjStep, wfs: *WriteFileStep, basename: []const u8) void {
1661 self.addAssemblyFileSource(.{
1662 .write_file = .{
1663 .step = wfs,
1664 .basename = self.builder.dupe(basename),
1665 },
1666 });
1667 }
1668
1669 pub fn addAssemblyFileSource(self: *LibExeObjStep, source: FileSource) void {
1670 self.link_objects.append(LinkObject{ .AssemblyFile = source }) catch unreachable;
1671 source.addStepDependencies(&self.step);
1672 }
1673
1574 pub fn addObjectFile(self: *LibExeObjStep, path: []const u8) void {1674 pub fn addObjectFile(self: *LibExeObjStep, path: []const u8) void {
1575 self.link_objects.append(LinkObject{ .StaticPath = self.builder.dupe(path) }) catch unreachable;1675 self.link_objects.append(LinkObject{ .StaticPath = self.builder.dupe(path) }) catch unreachable;
1576 }1676 }
...@@ -1698,25 +1798,23 @@ pub const LibExeObjStep = struct {...@@ -1698,25 +1798,23 @@ pub const LibExeObjStep = struct {
1698 };1798 };
1699 zig_args.append(cmd) catch unreachable;1799 zig_args.append(cmd) catch unreachable;
17001800
1701 if (self.root_src) |root_src| {1801 if (self.root_src) |root_src| try zig_args.append(root_src.getPath(builder));
1702 zig_args.append(builder.pathFromRoot(root_src)) catch unreachable;
1703 }
17041802
1705 for (self.link_objects.toSlice()) |link_object| {1803 for (self.link_objects.toSlice()) |link_object| {
1706 switch (link_object) {1804 switch (link_object) {
1707 LinkObject.StaticPath => |static_path| {1805 .StaticPath => |static_path| {
1708 try zig_args.append("--object");1806 try zig_args.append("--object");
1709 try zig_args.append(builder.pathFromRoot(static_path));1807 try zig_args.append(builder.pathFromRoot(static_path));
1710 },1808 },
17111809
1712 LinkObject.OtherStep => |other| switch (other.kind) {1810 .OtherStep => |other| switch (other.kind) {
1713 LibExeObjStep.Kind.Exe => unreachable,1811 .Exe => unreachable,
1714 LibExeObjStep.Kind.Test => unreachable,1812 .Test => unreachable,
1715 LibExeObjStep.Kind.Obj => {1813 .Obj => {
1716 try zig_args.append("--object");1814 try zig_args.append("--object");
1717 try zig_args.append(other.getOutputPath());1815 try zig_args.append(other.getOutputPath());
1718 },1816 },
1719 LibExeObjStep.Kind.Lib => {1817 .Lib => {
1720 if (!other.is_dynamic or self.target.isWindows()) {1818 if (!other.is_dynamic or self.target.isWindows()) {
1721 try zig_args.append("--object");1819 try zig_args.append("--object");
1722 try zig_args.append(other.getOutputLibPath());1820 try zig_args.append(other.getOutputLibPath());
...@@ -1732,20 +1830,20 @@ pub const LibExeObjStep = struct {...@@ -1732,20 +1830,20 @@ pub const LibExeObjStep = struct {
1732 }1830 }
1733 },1831 },
1734 },1832 },
1735 LinkObject.SystemLib => |name| {1833 .SystemLib => |name| {
1736 try zig_args.append("--library");1834 try zig_args.append("--library");
1737 try zig_args.append(name);1835 try zig_args.append(name);
1738 },1836 },
1739 LinkObject.AssemblyFile => |asm_file| {1837 .AssemblyFile => |asm_file| {
1740 try zig_args.append("--c-source");1838 try zig_args.append("--c-source");
1741 try zig_args.append(builder.pathFromRoot(asm_file));1839 try zig_args.append(asm_file.getPath(builder));
1742 },1840 },
1743 LinkObject.CSourceFile => |c_source_file| {1841 .CSourceFile => |c_source_file| {
1744 try zig_args.append("--c-source");1842 try zig_args.append("--c-source");
1745 for (c_source_file.args) |arg| {1843 for (c_source_file.args) |arg| {
1746 try zig_args.append(arg);1844 try zig_args.append(arg);
1747 }1845 }
1748 try zig_args.append(self.builder.pathFromRoot(c_source_file.source_path));1846 try zig_args.append(c_source_file.source.getPath(builder));
1749 },1847 },
1750 }1848 }
1751 }1849 }
...@@ -2041,134 +2139,6 @@ pub const LibExeObjStep = struct {...@@ -2041,134 +2139,6 @@ pub const LibExeObjStep = struct {
2041 }2139 }
2042};2140};
20432141
2044pub const RunStep = struct {
2045 step: Step,
2046 builder: *Builder,
2047
2048 /// See also addArg and addArgs to modifying this directly
2049 argv: ArrayList(Arg),
2050
2051 /// Set this to modify the current working directory
2052 cwd: ?[]const u8,
2053
2054 /// Override this field to modify the environment, or use setEnvironmentVariable
2055 env_map: ?*BufMap,
2056
2057 pub const Arg = union(enum) {
2058 Artifact: *LibExeObjStep,
2059 Bytes: []u8,
2060 };
2061
2062 pub fn create(builder: *Builder, name: []const u8) *RunStep {
2063 const self = builder.allocator.create(RunStep) catch unreachable;
2064 self.* = RunStep{
2065 .builder = builder,
2066 .step = Step.init(name, builder.allocator, make),
2067 .argv = ArrayList(Arg).init(builder.allocator),
2068 .cwd = null,
2069 .env_map = null,
2070 };
2071 return self;
2072 }
2073
2074 pub fn addArtifactArg(self: *RunStep, artifact: *LibExeObjStep) void {
2075 self.argv.append(Arg{ .Artifact = artifact }) catch unreachable;
2076 self.step.dependOn(&artifact.step);
2077 }
2078
2079 pub fn addArg(self: *RunStep, arg: []const u8) void {
2080 self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable;
2081 }
2082
2083 pub fn addArgs(self: *RunStep, args: []const []const u8) void {
2084 for (args) |arg| {
2085 self.addArg(arg);
2086 }
2087 }
2088
2089 pub fn clearEnvironment(self: *RunStep) void {
2090 const new_env_map = self.builder.allocator.create(BufMap) catch unreachable;
2091 new_env_map.* = BufMap.init(self.builder.allocator);
2092 self.env_map = new_env_map;
2093 }
2094
2095 pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
2096 const env_map = self.getEnvMap();
2097
2098 var key: []const u8 = undefined;
2099 var prev_path: ?[]const u8 = undefined;
2100 if (builtin.os == .windows) {
2101 key = "Path";
2102 prev_path = env_map.get(key);
2103 if (prev_path == null) {
2104 key = "PATH";
2105 prev_path = env_map.get(key);
2106 }
2107 } else {
2108 key = "PATH";
2109 prev_path = env_map.get(key);
2110 }
2111
2112 if (prev_path) |pp| {
2113 const new_path = self.builder.fmt("{}" ++ [1]u8{fs.path.delimiter} ++ "{}", .{ pp, search_path });
2114 env_map.set(key, new_path) catch unreachable;
2115 } else {
2116 env_map.set(key, search_path) catch unreachable;
2117 }
2118 }
2119
2120 pub fn getEnvMap(self: *RunStep) *BufMap {
2121 return self.env_map orelse {
2122 const env_map = self.builder.allocator.create(BufMap) catch unreachable;
2123 env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable;
2124 self.env_map = env_map;
2125 return env_map;
2126 };
2127 }
2128
2129 pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
2130 const env_map = self.getEnvMap();
2131 env_map.set(key, value) catch unreachable;
2132 }
2133
2134 fn make(step: *Step) !void {
2135 const self = @fieldParentPtr(RunStep, "step", step);
2136
2137 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
2138
2139 var argv = ArrayList([]const u8).init(self.builder.allocator);
2140 for (self.argv.toSlice()) |arg| {
2141 switch (arg) {
2142 Arg.Bytes => |bytes| try argv.append(bytes),
2143 Arg.Artifact => |artifact| {
2144 if (artifact.target.isWindows()) {
2145 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
2146 self.addPathForDynLibs(artifact);
2147 }
2148 const executable_path = artifact.installed_path orelse artifact.getOutputPath();
2149 try argv.append(executable_path);
2150 },
2151 }
2152 }
2153
2154 return self.builder.spawnChildEnvMap(cwd, self.env_map orelse self.builder.env_map, argv.toSliceConst());
2155 }
2156
2157 fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void {
2158 for (artifact.link_objects.toSliceConst()) |link_object| {
2159 switch (link_object) {
2160 LibExeObjStep.LinkObject.OtherStep => |other| {
2161 if (other.target.isWindows() and other.isDynamicLibrary()) {
2162 self.addPathDir(fs.path.dirname(other.getOutputPath()).?);
2163 self.addPathForDynLibs(other);
2164 }
2165 },
2166 else => {},
2167 }
2168 }
2169 }
2170};
2171
2172const InstallArtifactStep = struct {2142const InstallArtifactStep = struct {
2173 step: Step,2143 step: Step,
2174 builder: *Builder,2144 builder: *Builder,
...@@ -2321,36 +2291,6 @@ pub const InstallDirStep = struct {...@@ -2321,36 +2291,6 @@ pub const InstallDirStep = struct {
2321 }2291 }
2322};2292};
23232293
2324pub const WriteFileStep = struct {
2325 step: Step,
2326 builder: *Builder,
2327 file_path: []const u8,
2328 data: []const u8,
2329
2330 pub fn init(builder: *Builder, file_path: []const u8, data: []const u8) WriteFileStep {
2331 return WriteFileStep{
2332 .builder = builder,
2333 .step = Step.init(builder.fmt("writefile {}", .{file_path}), builder.allocator, make),
2334 .file_path = file_path,
2335 .data = data,
2336 };
2337 }
2338
2339 fn make(step: *Step) !void {
2340 const self = @fieldParentPtr(WriteFileStep, "step", step);
2341 const full_path = self.builder.pathFromRoot(self.file_path);
2342 const full_path_dir = fs.path.dirname(full_path) orelse ".";
2343 fs.makePath(self.builder.allocator, full_path_dir) catch |err| {
2344 warn("unable to make path {}: {}\n", .{ full_path_dir, @errorName(err) });
2345 return err;
2346 };
2347 io.writeFile(full_path, self.data) catch |err| {
2348 warn("unable to write {}: {}\n", .{ full_path, @errorName(err) });
2349 return err;
2350 };
2351 }
2352};
2353
2354pub const LogStep = struct {2294pub const LogStep = struct {
2355 step: Step,2295 step: Step,
2356 builder: *Builder,2296 builder: *Builder,
lib/std/build/check_file.zig created+52
...@@ -0,0 +1,52 @@
1const std = @import("../std.zig");
2const build = std.build;
3const Step = build.Step;
4const Builder = build.Builder;
5const fs = std.fs;
6const mem = std.mem;
7const warn = std.debug.warn;
8
9pub const CheckFileStep = struct {
10 step: Step,
11 builder: *Builder,
12 expected_matches: []const []const u8,
13 source: build.FileSource,
14 max_bytes: usize = 20 * 1024 * 1024,
15
16 pub fn create(
17 builder: *Builder,
18 source: build.FileSource,
19 expected_matches: []const []const u8,
20 ) *CheckFileStep {
21 const self = builder.allocator.create(CheckFileStep) catch unreachable;
22 self.* = CheckFileStep{
23 .builder = builder,
24 .step = Step.init("CheckFile", builder.allocator, make),
25 .source = source,
26 .expected_matches = expected_matches,
27 };
28 self.source.addStepDependencies(&self.step);
29 return self;
30 }
31
32 fn make(step: *Step) !void {
33 const self = @fieldParentPtr(CheckFileStep, "step", step);
34
35 const src_path = self.source.getPath(self.builder);
36 const contents = try fs.cwd().readFileAlloc(self.builder.allocator, src_path, self.max_bytes);
37
38 for (self.expected_matches) |expected_match| {
39 if (mem.indexOf(u8, contents, expected_match) == null) {
40 warn(
41 \\
42 \\========= Expected to find: ===================
43 \\{}
44 \\========= But file does not contain it: =======
45 \\{}
46 \\
47 , .{ expected_match, contents });
48 return error.TestFailed;
49 }
50 }
51 }
52};
lib/std/build/run.zig created+302
...@@ -0,0 +1,302 @@
1const std = @import("../std.zig");
2const builtin = std.builtin;
3const build = std.build;
4const Step = build.Step;
5const Builder = build.Builder;
6const LibExeObjStep = build.LibExeObjStep;
7const fs = std.fs;
8const mem = std.mem;
9const process = std.process;
10const ArrayList = std.ArrayList;
11const BufMap = std.BufMap;
12const Buffer = std.Buffer;
13const warn = std.debug.warn;
14
15const max_stdout_size = 1 * 1024 * 1024; // 1 MiB
16
17pub const RunStep = struct {
18 step: Step,
19 builder: *Builder,
20
21 /// See also addArg and addArgs to modifying this directly
22 argv: ArrayList(Arg),
23
24 /// Set this to modify the current working directory
25 cwd: ?[]const u8,
26
27 /// Override this field to modify the environment, or use setEnvironmentVariable
28 env_map: ?*BufMap,
29
30 stdout_action: StdIoAction = .inherit,
31 stderr_action: StdIoAction = .inherit,
32
33 expected_exit_code: u8 = 0,
34
35 pub const StdIoAction = union(enum) {
36 inherit,
37 ignore,
38 expect_exact: []const u8,
39 expect_matches: []const []const u8,
40 };
41
42 pub const Arg = union(enum) {
43 Artifact: *LibExeObjStep,
44 Bytes: []u8,
45 };
46
47 pub fn create(builder: *Builder, name: []const u8) *RunStep {
48 const self = builder.allocator.create(RunStep) catch unreachable;
49 self.* = RunStep{
50 .builder = builder,
51 .step = Step.init(name, builder.allocator, make),
52 .argv = ArrayList(Arg).init(builder.allocator),
53 .cwd = null,
54 .env_map = null,
55 };
56 return self;
57 }
58
59 pub fn addArtifactArg(self: *RunStep, artifact: *LibExeObjStep) void {
60 self.argv.append(Arg{ .Artifact = artifact }) catch unreachable;
61 self.step.dependOn(&artifact.step);
62 }
63
64 pub fn addArg(self: *RunStep, arg: []const u8) void {
65 self.argv.append(Arg{ .Bytes = self.builder.dupe(arg) }) catch unreachable;
66 }
67
68 pub fn addArgs(self: *RunStep, args: []const []const u8) void {
69 for (args) |arg| {
70 self.addArg(arg);
71 }
72 }
73
74 pub fn clearEnvironment(self: *RunStep) void {
75 const new_env_map = self.builder.allocator.create(BufMap) catch unreachable;
76 new_env_map.* = BufMap.init(self.builder.allocator);
77 self.env_map = new_env_map;
78 }
79
80 pub fn addPathDir(self: *RunStep, search_path: []const u8) void {
81 const env_map = self.getEnvMap();
82
83 var key: []const u8 = undefined;
84 var prev_path: ?[]const u8 = undefined;
85 if (builtin.os == .windows) {
86 key = "Path";
87 prev_path = env_map.get(key);
88 if (prev_path == null) {
89 key = "PATH";
90 prev_path = env_map.get(key);
91 }
92 } else {
93 key = "PATH";
94 prev_path = env_map.get(key);
95 }
96
97 if (prev_path) |pp| {
98 const new_path = self.builder.fmt("{}" ++ [1]u8{fs.path.delimiter} ++ "{}", .{ pp, search_path });
99 env_map.set(key, new_path) catch unreachable;
100 } else {
101 env_map.set(key, search_path) catch unreachable;
102 }
103 }
104
105 pub fn getEnvMap(self: *RunStep) *BufMap {
106 return self.env_map orelse {
107 const env_map = self.builder.allocator.create(BufMap) catch unreachable;
108 env_map.* = process.getEnvMap(self.builder.allocator) catch unreachable;
109 self.env_map = env_map;
110 return env_map;
111 };
112 }
113
114 pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void {
115 const env_map = self.getEnvMap();
116 env_map.set(key, value) catch unreachable;
117 }
118
119 pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void {
120 self.stderr_action = .{ .expect_exact = bytes };
121 }
122
123 pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void {
124 self.stdout_action = .{ .expect_exact = bytes };
125 }
126
127 fn stdIoActionToBehavior(action: StdIoAction) std.ChildProcess.StdIo {
128 return switch (action) {
129 .ignore => .Ignore,
130 .inherit => .Inherit,
131 .expect_exact, .expect_matches => .Pipe,
132 };
133 }
134
135 fn make(step: *Step) !void {
136 const self = @fieldParentPtr(RunStep, "step", step);
137
138 const cwd = if (self.cwd) |cwd| self.builder.pathFromRoot(cwd) else self.builder.build_root;
139
140 var argv_list = ArrayList([]const u8).init(self.builder.allocator);
141 for (self.argv.toSlice()) |arg| {
142 switch (arg) {
143 Arg.Bytes => |bytes| try argv_list.append(bytes),
144 Arg.Artifact => |artifact| {
145 if (artifact.target.isWindows()) {
146 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
147 self.addPathForDynLibs(artifact);
148 }
149 const executable_path = artifact.installed_path orelse artifact.getOutputPath();
150 try argv_list.append(executable_path);
151 },
152 }
153 }
154
155 const argv = argv_list.toSliceConst();
156
157 const child = std.ChildProcess.init(argv, self.builder.allocator) catch unreachable;
158 defer child.deinit();
159
160 child.cwd = cwd;
161 child.env_map = self.env_map orelse self.builder.env_map;
162
163 child.stdin_behavior = .Ignore;
164 child.stdout_behavior = stdIoActionToBehavior(self.stdout_action);
165 child.stderr_behavior = stdIoActionToBehavior(self.stderr_action);
166
167 child.spawn() catch |err| {
168 warn("Unable to spawn {}: {}\n", .{ argv[0], @errorName(err) });
169 return err;
170 };
171
172 var stdout = Buffer.initNull(self.builder.allocator);
173 var stderr = Buffer.initNull(self.builder.allocator);
174
175 // TODO need to poll to read these streams to prevent a deadlock (or rely on evented I/O).
176
177 switch (self.stdout_action) {
178 .expect_exact, .expect_matches => {
179 var stdout_file_in_stream = child.stdout.?.inStream();
180 stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable;
181 },
182 .inherit, .ignore => {},
183 }
184
185 switch (self.stdout_action) {
186 .expect_exact, .expect_matches => {
187 var stderr_file_in_stream = child.stderr.?.inStream();
188 stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable;
189 },
190 .inherit, .ignore => {},
191 }
192
193 const term = child.wait() catch |err| {
194 warn("Unable to spawn {}: {}\n", .{ argv[0], @errorName(err) });
195 return err;
196 };
197
198 switch (term) {
199 .Exited => |code| {
200 if (code != self.expected_exit_code) {
201 warn("The following command exited with error code {} (expected {}):\n", .{
202 code,
203 self.expected_exit_code,
204 });
205 printCmd(cwd, argv);
206 return error.UncleanExit;
207 }
208 },
209 else => {
210 warn("The following command terminated unexpectedly:\n", .{});
211 printCmd(cwd, argv);
212 return error.UncleanExit;
213 },
214 }
215
216 switch (self.stderr_action) {
217 .inherit, .ignore => {},
218 .expect_exact => |expected_bytes| {
219 if (!mem.eql(u8, expected_bytes, stderr.toSliceConst())) {
220 warn(
221 \\
222 \\========= Expected this stderr: =========
223 \\{}
224 \\========= But found: ====================
225 \\{}
226 \\
227 , .{ expected_bytes, stderr.toSliceConst() });
228 printCmd(cwd, argv);
229 return error.TestFailed;
230 }
231 },
232 .expect_matches => |matches| for (matches) |match| {
233 if (mem.indexOf(u8, stderr.toSliceConst(), match) == null) {
234 warn(
235 \\
236 \\========= Expected to find in stderr: =========
237 \\{}
238 \\========= But stderr does not contain it: =====
239 \\{}
240 \\
241 , .{ match, stderr.toSliceConst() });
242 printCmd(cwd, argv);
243 return error.TestFailed;
244 }
245 },
246 }
247
248 switch (self.stdout_action) {
249 .inherit, .ignore => {},
250 .expect_exact => |expected_bytes| {
251 if (!mem.eql(u8, expected_bytes, stdout.toSliceConst())) {
252 warn(
253 \\
254 \\========= Expected this stdout: =========
255 \\{}
256 \\========= But found: ====================
257 \\{}
258 \\
259 , .{ expected_bytes, stdout.toSliceConst() });
260 printCmd(cwd, argv);
261 return error.TestFailed;
262 }
263 },
264 .expect_matches => |matches| for (matches) |match| {
265 if (mem.indexOf(u8, stdout.toSliceConst(), match) == null) {
266 warn(
267 \\
268 \\========= Expected to find in stdout: =========
269 \\{}
270 \\========= But stdout does not contain it: =====
271 \\{}
272 \\
273 , .{ match, stdout.toSliceConst() });
274 printCmd(cwd, argv);
275 return error.TestFailed;
276 }
277 },
278 }
279 }
280
281 fn printCmd(cwd: ?[]const u8, argv: []const []const u8) void {
282 if (cwd) |yes_cwd| warn("cd {} && ", .{yes_cwd});
283 for (argv) |arg| {
284 warn("{} ", .{arg});
285 }
286 warn("\n", .{});
287 }
288
289 fn addPathForDynLibs(self: *RunStep, artifact: *LibExeObjStep) void {
290 for (artifact.link_objects.toSliceConst()) |link_object| {
291 switch (link_object) {
292 .OtherStep => |other| {
293 if (other.target.isWindows() and other.isDynamicLibrary()) {
294 self.addPathDir(fs.path.dirname(other.getOutputPath()).?);
295 self.addPathForDynLibs(other);
296 }
297 },
298 else => {},
299 }
300 }
301 }
302};
lib/std/build/translate_c.zig created+73
...@@ -0,0 +1,73 @@
1const std = @import("../std.zig");
2const build = std.build;
3const Step = build.Step;
4const Builder = build.Builder;
5const WriteFileStep = build.WriteFileStep;
6const LibExeObjStep = build.LibExeObjStep;
7const CheckFileStep = build.CheckFileStep;
8const fs = std.fs;
9const mem = std.mem;
10
11pub const TranslateCStep = struct {
12 step: Step,
13 builder: *Builder,
14 source: build.FileSource,
15 output_dir: ?[]const u8,
16 out_basename: []const u8,
17
18 pub fn create(builder: *Builder, source: build.FileSource) *TranslateCStep {
19 const self = builder.allocator.create(TranslateCStep) catch unreachable;
20 self.* = TranslateCStep{
21 .step = Step.init("zig translate-c", builder.allocator, make),
22 .builder = builder,
23 .source = source,
24 .output_dir = null,
25 .out_basename = undefined,
26 };
27 source.addStepDependencies(&self.step);
28 return self;
29 }
30
31 /// Unless setOutputDir was called, this function must be called only in
32 /// the make step, from a step that has declared a dependency on this one.
33 /// To run an executable built with zig build, use `run`, or create an install step and invoke it.
34 pub fn getOutputPath(self: *TranslateCStep) []const u8 {
35 return fs.path.join(
36 self.builder.allocator,
37 &[_][]const u8{ self.output_dir.?, self.out_basename },
38 ) catch unreachable;
39 }
40
41 /// Creates a step to build an executable from the translated source.
42 pub fn addExecutable(self: *TranslateCStep) *LibExeObjStep {
43 return self.builder.addExecutableSource("translated_c", @as(build.FileSource, .{ .translate_c = self }));
44 }
45
46 pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep {
47 return CheckFileStep.create(self.builder, .{ .translate_c = self }, expected_matches);
48 }
49
50 fn make(step: *Step) !void {
51 const self = @fieldParentPtr(TranslateCStep, "step", step);
52
53 const argv = [_][]const u8{
54 self.builder.zig_exe,
55 "translate-c",
56 "-lc",
57 "--cache",
58 "on",
59 self.source.getPath(self.builder),
60 };
61
62 const output_path_nl = try self.builder.exec(&argv);
63 const output_path = mem.trimRight(u8, output_path_nl, "\r\n");
64
65 self.out_basename = fs.path.basename(output_path);
66 if (self.output_dir) |output_dir| {
67 const full_dest = try fs.path.join(self.builder.allocator, &[_][]const u8{ output_dir, self.out_basename });
68 try self.builder.updateFile(output_path, full_dest);
69 } else {
70 self.output_dir = fs.path.dirname(output_path).?;
71 }
72 }
73};
lib/std/build/write_file.zig created+94
...@@ -0,0 +1,94 @@
1const std = @import("../std.zig");
2const build = @import("../build.zig");
3const Step = build.Step;
4const Builder = build.Builder;
5const fs = std.fs;
6const warn = std.debug.warn;
7const ArrayList = std.ArrayList;
8
9pub const WriteFileStep = struct {
10 step: Step,
11 builder: *Builder,
12 output_dir: []const u8,
13 files: ArrayList(File),
14
15 pub const File = struct {
16 basename: []const u8,
17 bytes: []const u8,
18 };
19
20 pub fn init(builder: *Builder) WriteFileStep {
21 return WriteFileStep{
22 .builder = builder,
23 .step = Step.init("writefile", builder.allocator, make),
24 .files = ArrayList(File).init(builder.allocator),
25 .output_dir = undefined,
26 };
27 }
28
29 pub fn add(self: *WriteFileStep, basename: []const u8, bytes: []const u8) void {
30 self.files.append(.{ .basename = basename, .bytes = bytes }) catch unreachable;
31 }
32
33 /// Unless setOutputDir was called, this function must be called only in
34 /// the make step, from a step that has declared a dependency on this one.
35 /// To run an executable built with zig build, use `run`, or create an install step and invoke it.
36 pub fn getOutputPath(self: *WriteFileStep, basename: []const u8) []const u8 {
37 return fs.path.join(
38 self.builder.allocator,
39 &[_][]const u8{ self.output_dir, basename },
40 ) catch unreachable;
41 }
42
43 fn make(step: *Step) !void {
44 const self = @fieldParentPtr(WriteFileStep, "step", step);
45
46 // The cache is used here not really as a way to speed things up - because writing
47 // the data to a file would probably be very fast - but as a way to find a canonical
48 // location to put build artifacts.
49
50 // If, for example, a hard-coded path was used as the location to put WriteFileStep
51 // files, then two WriteFileSteps executing in parallel might clobber each other.
52
53 // TODO port the cache system from stage1 to zig std lib. Until then we use blake2b
54 // directly and construct the path, and no "cache hit" detection happens; the files
55 // are always written.
56 var hash = std.crypto.Blake2b384.init();
57
58 // Random bytes to make WriteFileStep unique. Refresh this with
59 // new random bytes when WriteFileStep implementation is modified
60 // in a non-backwards-compatible way.
61 hash.update("eagVR1dYXoE7ARDP");
62 for (self.files.toSliceConst()) |file| {
63 hash.update(file.basename);
64 hash.update(file.bytes);
65 hash.update("|");
66 }
67 var digest: [48]u8 = undefined;
68 hash.final(&digest);
69 var hash_basename: [64]u8 = undefined;
70 fs.base64_encoder.encode(&hash_basename, &digest);
71 self.output_dir = try fs.path.join(self.builder.allocator, &[_][]const u8{
72 self.builder.cache_root,
73 "o",
74 &hash_basename,
75 });
76 // TODO replace with something like fs.makePathAndOpenDir
77 fs.makePath(self.builder.allocator, self.output_dir) catch |err| {
78 warn("unable to make path {}: {}\n", .{ self.output_dir, @errorName(err) });
79 return err;
80 };
81 var dir = try fs.cwd().openDirTraverse(self.output_dir);
82 defer dir.close();
83 for (self.files.toSliceConst()) |file| {
84 dir.writeFile(file.basename, file.bytes) catch |err| {
85 warn("unable to write {} into {}: {}\n", .{
86 file.basename,
87 self.output_dir,
88 @errorName(err),
89 });
90 return err;
91 };
92 }
93 }
94};
lib/std/fs.zig+8-5
...@@ -37,8 +37,11 @@ pub const MAX_PATH_BYTES = switch (builtin.os) {...@@ -37,8 +37,11 @@ pub const MAX_PATH_BYTES = switch (builtin.os) {
37 else => @compileError("Unsupported OS"),37 else => @compileError("Unsupported OS"),
38};38};
3939
40// here we replace the standard +/ with -_ so that it can be used in a file name40/// Base64, replacing the standard `+/` with `-_` so that it can be used in a file name on any filesystem.
41const b64_fs_encoder = base64.Base64Encoder.init("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", base64.standard_pad_char);41pub const base64_encoder = base64.Base64Encoder.init(
42 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
43 base64.standard_pad_char,
44);
4245
43/// TODO remove the allocator requirement from this API46/// TODO remove the allocator requirement from this API
44pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {47pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
...@@ -58,7 +61,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:...@@ -58,7 +61,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
58 tmp_path[dirname.len] = path.sep;61 tmp_path[dirname.len] = path.sep;
59 while (true) {62 while (true) {
60 try crypto.randomBytes(rand_buf[0..]);63 try crypto.randomBytes(rand_buf[0..]);
61 b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);64 base64_encoder.encode(tmp_path[dirname.len + 1 ..], &rand_buf);
6265
63 if (symLink(existing_path, tmp_path)) {66 if (symLink(existing_path, tmp_path)) {
64 return rename(tmp_path, new_path);67 return rename(tmp_path, new_path);
...@@ -227,10 +230,10 @@ pub const AtomicFile = struct {...@@ -227,10 +230,10 @@ pub const AtomicFile = struct {
227230
228 while (true) {231 while (true) {
229 try crypto.randomBytes(rand_buf[0..]);232 try crypto.randomBytes(rand_buf[0..]);
230 b64_fs_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);233 base64_encoder.encode(tmp_path_slice[dirname_component_len..tmp_path_len], &rand_buf);
231234
232 const file = my_cwd.createFileC(235 const file = my_cwd.createFileC(
233 tmp_path_slice, 236 tmp_path_slice,
234 .{ .mode = mode, .exclusive = true },237 .{ .mode = mode, .exclusive = true },
235 ) catch |err| switch (err) {238 ) catch |err| switch (err) {
236 error.PathAlreadyExists => continue,239 error.PathAlreadyExists => continue,
test/src/compare_output.zig created+165
...@@ -0,0 +1,165 @@
1// This is the implementation of the test harness.
2// For the actual test cases, see test/compare_output.zig.
3const std = @import("std");
4const builtin = std.builtin;
5const build = std.build;
6const ArrayList = std.ArrayList;
7const fmt = std.fmt;
8const mem = std.mem;
9const fs = std.fs;
10const warn = std.debug.warn;
11const Mode = builtin.Mode;
12
13pub const CompareOutputContext = struct {
14 b: *build.Builder,
15 step: *build.Step,
16 test_index: usize,
17 test_filter: ?[]const u8,
18 modes: []const Mode,
19
20 const Special = enum {
21 None,
22 Asm,
23 RuntimeSafety,
24 };
25
26 const TestCase = struct {
27 name: []const u8,
28 sources: ArrayList(SourceFile),
29 expected_output: []const u8,
30 link_libc: bool,
31 special: Special,
32 cli_args: []const []const u8,
33
34 const SourceFile = struct {
35 filename: []const u8,
36 source: []const u8,
37 };
38
39 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
40 self.sources.append(SourceFile{
41 .filename = filename,
42 .source = source,
43 }) catch unreachable;
44 }
45
46 pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void {
47 self.cli_args = args;
48 }
49 };
50
51 pub fn createExtra(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase {
52 var tc = TestCase{
53 .name = name,
54 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
55 .expected_output = expected_output,
56 .link_libc = false,
57 .special = special,
58 .cli_args = &[_][]const u8{},
59 };
60 const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
61 tc.addSourceFile(root_src_name, source);
62 return tc;
63 }
64
65 pub fn create(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) TestCase {
66 return createExtra(self, name, source, expected_output, Special.None);
67 }
68
69 pub fn addC(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
70 var tc = self.create(name, source, expected_output);
71 tc.link_libc = true;
72 self.addCase(tc);
73 }
74
75 pub fn add(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
76 const tc = self.create(name, source, expected_output);
77 self.addCase(tc);
78 }
79
80 pub fn addAsm(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
81 const tc = self.createExtra(name, source, expected_output, Special.Asm);
82 self.addCase(tc);
83 }
84
85 pub fn addRuntimeSafety(self: *CompareOutputContext, name: []const u8, source: []const u8) void {
86 const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety);
87 self.addCase(tc);
88 }
89
90 pub fn addCase(self: *CompareOutputContext, case: TestCase) void {
91 const b = self.b;
92
93 const write_src = b.addWriteFiles();
94 for (case.sources.toSliceConst()) |src_file| {
95 write_src.add(src_file.filename, src_file.source);
96 }
97
98 switch (case.special) {
99 Special.Asm => {
100 const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {}", .{
101 case.name,
102 }) catch unreachable;
103 if (self.test_filter) |filter| {
104 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
105 }
106
107 const exe = b.addExecutable("test", null);
108 exe.addAssemblyFileFromWriteFileStep(write_src, case.sources.toSliceConst()[0].filename);
109
110 const run = exe.run();
111 run.addArgs(case.cli_args);
112 run.expectStdErrEqual("");
113 run.expectStdOutEqual(case.expected_output);
114
115 self.step.dependOn(&run.step);
116 },
117 Special.None => {
118 for (self.modes) |mode| {
119 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", .{
120 "compare-output",
121 case.name,
122 @tagName(mode),
123 }) catch unreachable;
124 if (self.test_filter) |filter| {
125 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
126 }
127
128 const basename = case.sources.toSliceConst()[0].filename;
129 const exe = b.addExecutableFromWriteFileStep("test", write_src, basename);
130 exe.setBuildMode(mode);
131 if (case.link_libc) {
132 exe.linkSystemLibrary("c");
133 }
134
135 const run = exe.run();
136 run.addArgs(case.cli_args);
137 run.expectStdErrEqual("");
138 run.expectStdOutEqual(case.expected_output);
139
140 self.step.dependOn(&run.step);
141 }
142 },
143 Special.RuntimeSafety => {
144 const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {}", .{case.name}) catch unreachable;
145 if (self.test_filter) |filter| {
146 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
147 }
148
149 const basename = case.sources.toSliceConst()[0].filename;
150 const exe = b.addExecutableFromWriteFileStep("test", write_src, basename);
151 if (case.link_libc) {
152 exe.linkSystemLibrary("c");
153 }
154
155 const run = exe.run();
156 run.addArgs(case.cli_args);
157 run.stderr_action = .ignore;
158 run.stdout_action = .ignore;
159 run.expected_exit_code = 126;
160
161 self.step.dependOn(&run.step);
162 },
163 }
164 }
165};
test/src/run_translated_c.zig+18-94
...@@ -33,89 +33,6 @@ pub const RunTranslatedCContext = struct {...@@ -33,89 +33,6 @@ pub const RunTranslatedCContext = struct {
33 }33 }
34 };34 };
3535
36 const DoEverythingStep = struct {
37 step: build.Step,
38 context: *RunTranslatedCContext,
39 name: []const u8,
40 case: *const TestCase,
41 test_index: usize,
42
43 pub fn create(
44 context: *RunTranslatedCContext,
45 name: []const u8,
46 case: *const TestCase,
47 ) *DoEverythingStep {
48 const allocator = context.b.allocator;
49 const ptr = allocator.create(DoEverythingStep) catch unreachable;
50 ptr.* = DoEverythingStep{
51 .context = context,
52 .name = name,
53 .case = case,
54 .test_index = context.test_index,
55 .step = build.Step.init("RunTranslatedC", allocator, make),
56 };
57 context.test_index += 1;
58 return ptr;
59 }
60
61 fn make(step: *build.Step) !void {
62 const self = @fieldParentPtr(DoEverythingStep, "step", step);
63 const b = self.context.b;
64
65 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name });
66 // translate from c to zig
67 const translated_c_code = blk: {
68 var zig_args = ArrayList([]const u8).init(b.allocator);
69 defer zig_args.deinit();
70
71 const rel_c_filename = try fs.path.join(b.allocator, &[_][]const u8{
72 b.cache_root,
73 self.case.sources.toSliceConst()[0].filename,
74 });
75
76 try zig_args.append(b.zig_exe);
77 try zig_args.append("translate-c");
78 try zig_args.append("-lc");
79 try zig_args.append(b.pathFromRoot(rel_c_filename));
80
81 break :blk try b.exec(zig_args.toSliceConst());
82 };
83
84 // write stdout to a file
85
86 const translated_c_path = try fs.path.join(b.allocator,
87 &[_][]const u8{ b.cache_root, "translated_c.zig" });
88 try fs.cwd().writeFile(translated_c_path, translated_c_code);
89
90 // zig run the result
91 const run_stdout = blk: {
92 var zig_args = ArrayList([]const u8).init(b.allocator);
93 defer zig_args.deinit();
94
95 try zig_args.append(b.zig_exe);
96 try zig_args.append("-lc");
97 try zig_args.append("run");
98 try zig_args.append(translated_c_path);
99
100 break :blk try b.exec(zig_args.toSliceConst());
101 };
102 // compare stdout
103 if (!mem.eql(u8, self.case.expected_stdout, run_stdout)) {
104 warn(
105 \\
106 \\========= Expected this output: =========
107 \\{}
108 \\========= But found: ====================
109 \\{}
110 \\
111 , .{ self.case.expected_stdout, run_stdout });
112 return error.TestFailed;
113 }
114
115 warn("OK\n", .{});
116 }
117 };
118
119 pub fn create(36 pub fn create(
120 self: *RunTranslatedCContext,37 self: *RunTranslatedCContext,
121 allow_warnings: bool,38 allow_warnings: bool,
...@@ -159,22 +76,29 @@ pub const RunTranslatedCContext = struct {...@@ -159,22 +76,29 @@ pub const RunTranslatedCContext = struct {
159 pub fn addCase(self: *RunTranslatedCContext, case: *const TestCase) void {76 pub fn addCase(self: *RunTranslatedCContext, case: *const TestCase) void {
160 const b = self.b;77 const b = self.b;
16178
162 const annotated_case_name = fmt.allocPrint(self.b.allocator, "run-translated-c {}", .{ case.name }) catch unreachable;79 const annotated_case_name = fmt.allocPrint(self.b.allocator, "run-translated-c {}", .{case.name}) catch unreachable;
163 if (self.test_filter) |filter| {80 if (self.test_filter) |filter| {
164 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;81 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
165 }82 }
16683
167 const do_everything_step = DoEverythingStep.create(self, annotated_case_name, case);84 const write_src = b.addWriteFiles();
168 self.step.dependOn(&do_everything_step.step);
169
170 for (case.sources.toSliceConst()) |src_file| {85 for (case.sources.toSliceConst()) |src_file| {
171 const expanded_src_path = fs.path.join(86 write_src.add(src_file.filename, src_file.source);
172 b.allocator,87 }
173 &[_][]const u8{ b.cache_root, src_file.filename },88 const translate_c = b.addTranslateC(.{
174 ) catch unreachable;89 .write_file = .{
175 const write_src = b.addWriteFile(expanded_src_path, src_file.source);90 .step = write_src,
176 do_everything_step.step.dependOn(&write_src.step);91 .basename = case.sources.toSliceConst()[0].filename,
92 },
93 });
94 const exe = translate_c.addExecutable();
95 exe.linkLibC();
96 const run = exe.run();
97 if (!case.allow_warnings) {
98 run.expectStdErrEqual("");
177 }99 }
100 run.expectStdOutEqual(case.expected_stdout);
101
102 self.step.dependOn(&run.step);
178 }103 }
179};104};
180
test/src/translate_c.zig created+109
...@@ -0,0 +1,109 @@
1// This is the implementation of the test harness.
2// For the actual test cases, see test/translate_c.zig.
3const std = @import("std");
4const build = std.build;
5const ArrayList = std.ArrayList;
6const fmt = std.fmt;
7const mem = std.mem;
8const fs = std.fs;
9const warn = std.debug.warn;
10
11pub const TranslateCContext = struct {
12 b: *build.Builder,
13 step: *build.Step,
14 test_index: usize,
15 test_filter: ?[]const u8,
16
17 const TestCase = struct {
18 name: []const u8,
19 sources: ArrayList(SourceFile),
20 expected_lines: ArrayList([]const u8),
21 allow_warnings: bool,
22
23 const SourceFile = struct {
24 filename: []const u8,
25 source: []const u8,
26 };
27
28 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
29 self.sources.append(SourceFile{
30 .filename = filename,
31 .source = source,
32 }) catch unreachable;
33 }
34
35 pub fn addExpectedLine(self: *TestCase, text: []const u8) void {
36 self.expected_lines.append(text) catch unreachable;
37 }
38 };
39
40 pub fn create(
41 self: *TranslateCContext,
42 allow_warnings: bool,
43 filename: []const u8,
44 name: []const u8,
45 source: []const u8,
46 expected_lines: []const []const u8,
47 ) *TestCase {
48 const tc = self.b.allocator.create(TestCase) catch unreachable;
49 tc.* = TestCase{
50 .name = name,
51 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
52 .expected_lines = ArrayList([]const u8).init(self.b.allocator),
53 .allow_warnings = allow_warnings,
54 };
55
56 tc.addSourceFile(filename, source);
57 var arg_i: usize = 0;
58 while (arg_i < expected_lines.len) : (arg_i += 1) {
59 tc.addExpectedLine(expected_lines[arg_i]);
60 }
61 return tc;
62 }
63
64 pub fn add(
65 self: *TranslateCContext,
66 name: []const u8,
67 source: []const u8,
68 expected_lines: []const []const u8,
69 ) void {
70 const tc = self.create(false, "source.h", name, source, expected_lines);
71 self.addCase(tc);
72 }
73
74 pub fn addAllowWarnings(
75 self: *TranslateCContext,
76 name: []const u8,
77 source: []const u8,
78 expected_lines: []const []const u8,
79 ) void {
80 const tc = self.create(true, "source.h", name, source, expected_lines);
81 self.addCase(tc);
82 }
83
84 pub fn addCase(self: *TranslateCContext, case: *const TestCase) void {
85 const b = self.b;
86
87 const translate_c_cmd = "translate-c";
88 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {}", .{ translate_c_cmd, case.name }) catch unreachable;
89 if (self.test_filter) |filter| {
90 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
91 }
92
93 const write_src = b.addWriteFiles();
94 for (case.sources.toSliceConst()) |src_file| {
95 write_src.add(src_file.filename, src_file.source);
96 }
97
98 const translate_c = b.addTranslateC(.{
99 .write_file = .{
100 .step = write_src,
101 .basename = case.sources.toSliceConst()[0].filename,
102 },
103 });
104
105 const check_file = translate_c.addCheckFile(case.expected_lines.toSliceConst());
106
107 self.step.dependOn(&check_file.step);
108 }
109};
test/tests.zig+2-574
...@@ -26,7 +26,9 @@ const run_translated_c = @import("run_translated_c.zig");...@@ -26,7 +26,9 @@ const run_translated_c = @import("run_translated_c.zig");
26const gen_h = @import("gen_h.zig");26const gen_h = @import("gen_h.zig");
2727
28// Implementations28// Implementations
29pub const TranslateCContext = @import("src/translate_c.zig").TranslateCContext;
29pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext;30pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext;
31pub const CompareOutputContext = @import("src/compare_output.zig").CompareOutputContext;
3032
31const TestTarget = struct {33const TestTarget = struct {
32 target: Target = .Native,34 target: Target = .Native,
...@@ -498,356 +500,6 @@ pub fn addPkgTests(...@@ -498,356 +500,6 @@ pub fn addPkgTests(
498 return step;500 return step;
499}501}
500502
501pub const CompareOutputContext = struct {
502 b: *build.Builder,
503 step: *build.Step,
504 test_index: usize,
505 test_filter: ?[]const u8,
506 modes: []const Mode,
507
508 const Special = enum {
509 None,
510 Asm,
511 RuntimeSafety,
512 };
513
514 const TestCase = struct {
515 name: []const u8,
516 sources: ArrayList(SourceFile),
517 expected_output: []const u8,
518 link_libc: bool,
519 special: Special,
520 cli_args: []const []const u8,
521
522 const SourceFile = struct {
523 filename: []const u8,
524 source: []const u8,
525 };
526
527 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
528 self.sources.append(SourceFile{
529 .filename = filename,
530 .source = source,
531 }) catch unreachable;
532 }
533
534 pub fn setCommandLineArgs(self: *TestCase, args: []const []const u8) void {
535 self.cli_args = args;
536 }
537 };
538
539 const RunCompareOutputStep = struct {
540 step: build.Step,
541 context: *CompareOutputContext,
542 exe: *LibExeObjStep,
543 name: []const u8,
544 expected_output: []const u8,
545 test_index: usize,
546 cli_args: []const []const u8,
547
548 pub fn create(
549 context: *CompareOutputContext,
550 exe: *LibExeObjStep,
551 name: []const u8,
552 expected_output: []const u8,
553 cli_args: []const []const u8,
554 ) *RunCompareOutputStep {
555 const allocator = context.b.allocator;
556 const ptr = allocator.create(RunCompareOutputStep) catch unreachable;
557 ptr.* = RunCompareOutputStep{
558 .context = context,
559 .exe = exe,
560 .name = name,
561 .expected_output = expected_output,
562 .test_index = context.test_index,
563 .step = build.Step.init("RunCompareOutput", allocator, make),
564 .cli_args = cli_args,
565 };
566 ptr.step.dependOn(&exe.step);
567 context.test_index += 1;
568 return ptr;
569 }
570
571 fn make(step: *build.Step) !void {
572 const self = @fieldParentPtr(RunCompareOutputStep, "step", step);
573 const b = self.context.b;
574
575 const full_exe_path = self.exe.getOutputPath();
576 var args = ArrayList([]const u8).init(b.allocator);
577 defer args.deinit();
578
579 args.append(full_exe_path) catch unreachable;
580 for (self.cli_args) |arg| {
581 args.append(arg) catch unreachable;
582 }
583
584 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name });
585
586 const child = std.ChildProcess.init(args.toSliceConst(), b.allocator) catch unreachable;
587 defer child.deinit();
588
589 child.stdin_behavior = .Ignore;
590 child.stdout_behavior = .Pipe;
591 child.stderr_behavior = .Pipe;
592 child.env_map = b.env_map;
593
594 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", .{ full_exe_path, @errorName(err) });
595
596 var stdout = Buffer.initNull(b.allocator);
597 var stderr = Buffer.initNull(b.allocator);
598
599 var stdout_file_in_stream = child.stdout.?.inStream();
600 var stderr_file_in_stream = child.stderr.?.inStream();
601
602 stdout_file_in_stream.stream.readAllBuffer(&stdout, max_stdout_size) catch unreachable;
603 stderr_file_in_stream.stream.readAllBuffer(&stderr, max_stdout_size) catch unreachable;
604
605 const term = child.wait() catch |err| {
606 debug.panic("Unable to spawn {}: {}\n", .{ full_exe_path, @errorName(err) });
607 };
608 switch (term) {
609 .Exited => |code| {
610 if (code != 0) {
611 warn("Process {} exited with error code {}\n", .{ full_exe_path, code });
612 printInvocation(args.toSliceConst());
613 return error.TestFailed;
614 }
615 },
616 else => {
617 warn("Process {} terminated unexpectedly\n", .{full_exe_path});
618 printInvocation(args.toSliceConst());
619 return error.TestFailed;
620 },
621 }
622
623 if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
624 warn(
625 \\
626 \\========= Expected this output: =========
627 \\{}
628 \\========= But found: ====================
629 \\{}
630 \\
631 , .{ self.expected_output, stdout.toSliceConst() });
632 return error.TestFailed;
633 }
634 warn("OK\n", .{});
635 }
636 };
637
638 const RuntimeSafetyRunStep = struct {
639 step: build.Step,
640 context: *CompareOutputContext,
641 exe: *LibExeObjStep,
642 name: []const u8,
643 test_index: usize,
644
645 pub fn create(context: *CompareOutputContext, exe: *LibExeObjStep, name: []const u8) *RuntimeSafetyRunStep {
646 const allocator = context.b.allocator;
647 const ptr = allocator.create(RuntimeSafetyRunStep) catch unreachable;
648 ptr.* = RuntimeSafetyRunStep{
649 .context = context,
650 .exe = exe,
651 .name = name,
652 .test_index = context.test_index,
653 .step = build.Step.init("RuntimeSafetyRun", allocator, make),
654 };
655 ptr.step.dependOn(&exe.step);
656 context.test_index += 1;
657 return ptr;
658 }
659
660 fn make(step: *build.Step) !void {
661 const self = @fieldParentPtr(RuntimeSafetyRunStep, "step", step);
662 const b = self.context.b;
663
664 const full_exe_path = self.exe.getOutputPath();
665
666 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name });
667
668 const child = std.ChildProcess.init(&[_][]const u8{full_exe_path}, b.allocator) catch unreachable;
669 defer child.deinit();
670
671 child.env_map = b.env_map;
672 child.stdin_behavior = .Ignore;
673 child.stdout_behavior = .Ignore;
674 child.stderr_behavior = .Ignore;
675
676 const term = child.spawnAndWait() catch |err| {
677 debug.panic("Unable to spawn {}: {}\n", .{ full_exe_path, @errorName(err) });
678 };
679
680 const expected_exit_code: u32 = 126;
681 switch (term) {
682 .Exited => |code| {
683 if (code != expected_exit_code) {
684 warn("\nProgram expected to exit with code {} but exited with code {}\n", .{
685 expected_exit_code, code,
686 });
687 return error.TestFailed;
688 }
689 },
690 .Signal => |sig| {
691 warn("\nProgram expected to exit with code {} but instead signaled {}\n", .{
692 expected_exit_code, sig,
693 });
694 return error.TestFailed;
695 },
696 else => {
697 warn("\nProgram expected to exit with code {} but exited in an unexpected way\n", .{
698 expected_exit_code,
699 });
700 return error.TestFailed;
701 },
702 }
703
704 warn("OK\n", .{});
705 }
706 };
707
708 pub fn createExtra(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8, special: Special) TestCase {
709 var tc = TestCase{
710 .name = name,
711 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
712 .expected_output = expected_output,
713 .link_libc = false,
714 .special = special,
715 .cli_args = &[_][]const u8{},
716 };
717 const root_src_name = if (special == Special.Asm) "source.s" else "source.zig";
718 tc.addSourceFile(root_src_name, source);
719 return tc;
720 }
721
722 pub fn create(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) TestCase {
723 return createExtra(self, name, source, expected_output, Special.None);
724 }
725
726 pub fn addC(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
727 var tc = self.create(name, source, expected_output);
728 tc.link_libc = true;
729 self.addCase(tc);
730 }
731
732 pub fn add(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
733 const tc = self.create(name, source, expected_output);
734 self.addCase(tc);
735 }
736
737 pub fn addAsm(self: *CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) void {
738 const tc = self.createExtra(name, source, expected_output, Special.Asm);
739 self.addCase(tc);
740 }
741
742 pub fn addRuntimeSafety(self: *CompareOutputContext, name: []const u8, source: []const u8) void {
743 const tc = self.createExtra(name, source, undefined, Special.RuntimeSafety);
744 self.addCase(tc);
745 }
746
747 pub fn addCase(self: *CompareOutputContext, case: TestCase) void {
748 const b = self.b;
749
750 const root_src = fs.path.join(
751 b.allocator,
752 &[_][]const u8{ b.cache_root, case.sources.items[0].filename },
753 ) catch unreachable;
754
755 switch (case.special) {
756 Special.Asm => {
757 const annotated_case_name = fmt.allocPrint(self.b.allocator, "assemble-and-link {}", .{
758 case.name,
759 }) catch unreachable;
760 if (self.test_filter) |filter| {
761 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
762 }
763
764 const exe = b.addExecutable("test", null);
765 exe.addAssemblyFile(root_src);
766
767 for (case.sources.toSliceConst()) |src_file| {
768 const expanded_src_path = fs.path.join(
769 b.allocator,
770 &[_][]const u8{ b.cache_root, src_file.filename },
771 ) catch unreachable;
772 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
773 exe.step.dependOn(&write_src.step);
774 }
775
776 const run_and_cmp_output = RunCompareOutputStep.create(
777 self,
778 exe,
779 annotated_case_name,
780 case.expected_output,
781 case.cli_args,
782 );
783
784 self.step.dependOn(&run_and_cmp_output.step);
785 },
786 Special.None => {
787 for (self.modes) |mode| {
788 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {} ({})", .{
789 "compare-output",
790 case.name,
791 @tagName(mode),
792 }) catch unreachable;
793 if (self.test_filter) |filter| {
794 if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
795 }
796
797 const exe = b.addExecutable("test", root_src);
798 exe.setBuildMode(mode);
799 if (case.link_libc) {
800 exe.linkSystemLibrary("c");
801 }
802
803 for (case.sources.toSliceConst()) |src_file| {
804 const expanded_src_path = fs.path.join(
805 b.allocator,
806 &[_][]const u8{ b.cache_root, src_file.filename },
807 ) catch unreachable;
808 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
809 exe.step.dependOn(&write_src.step);
810 }
811
812 const run_and_cmp_output = RunCompareOutputStep.create(
813 self,
814 exe,
815 annotated_case_name,
816 case.expected_output,
817 case.cli_args,
818 );
819
820 self.step.dependOn(&run_and_cmp_output.step);
821 }
822 },
823 Special.RuntimeSafety => {
824 const annotated_case_name = fmt.allocPrint(self.b.allocator, "safety {}", .{case.name}) catch unreachable;
825 if (self.test_filter) |filter| {
826 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
827 }
828
829 const exe = b.addExecutable("test", root_src);
830 if (case.link_libc) {
831 exe.linkSystemLibrary("c");
832 }
833
834 for (case.sources.toSliceConst()) |src_file| {
835 const expanded_src_path = fs.path.join(
836 b.allocator,
837 &[_][]const u8{ b.cache_root, src_file.filename },
838 ) catch unreachable;
839 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
840 exe.step.dependOn(&write_src.step);
841 }
842
843 const run_and_cmp_output = RuntimeSafetyRunStep.create(self, exe, annotated_case_name);
844
845 self.step.dependOn(&run_and_cmp_output.step);
846 },
847 }
848 }
849};
850
851pub const StackTracesContext = struct {503pub const StackTracesContext = struct {
852 b: *build.Builder,504 b: *build.Builder,
853 step: *build.Step,505 step: *build.Step,
...@@ -1430,230 +1082,6 @@ pub const StandaloneContext = struct {...@@ -1430,230 +1082,6 @@ pub const StandaloneContext = struct {
1430 }1082 }
1431};1083};
14321084
1433pub const TranslateCContext = struct {
1434 b: *build.Builder,
1435 step: *build.Step,
1436 test_index: usize,
1437 test_filter: ?[]const u8,
1438
1439 const TestCase = struct {
1440 name: []const u8,
1441 sources: ArrayList(SourceFile),
1442 expected_lines: ArrayList([]const u8),
1443 allow_warnings: bool,
1444
1445 const SourceFile = struct {
1446 filename: []const u8,
1447 source: []const u8,
1448 };
1449
1450 pub fn addSourceFile(self: *TestCase, filename: []const u8, source: []const u8) void {
1451 self.sources.append(SourceFile{
1452 .filename = filename,
1453 .source = source,
1454 }) catch unreachable;
1455 }
1456
1457 pub fn addExpectedLine(self: *TestCase, text: []const u8) void {
1458 self.expected_lines.append(text) catch unreachable;
1459 }
1460 };
1461
1462 const TranslateCCmpOutputStep = struct {
1463 step: build.Step,
1464 context: *TranslateCContext,
1465 name: []const u8,
1466 test_index: usize,
1467 case: *const TestCase,
1468
1469 pub fn create(context: *TranslateCContext, name: []const u8, case: *const TestCase) *TranslateCCmpOutputStep {
1470 const allocator = context.b.allocator;
1471 const ptr = allocator.create(TranslateCCmpOutputStep) catch unreachable;
1472 ptr.* = TranslateCCmpOutputStep{
1473 .step = build.Step.init("ParseCCmpOutput", allocator, make),
1474 .context = context,
1475 .name = name,
1476 .test_index = context.test_index,
1477 .case = case,
1478 };
1479
1480 context.test_index += 1;
1481 return ptr;
1482 }
1483
1484 fn make(step: *build.Step) !void {
1485 const self = @fieldParentPtr(TranslateCCmpOutputStep, "step", step);
1486 const b = self.context.b;
1487
1488 const root_src = fs.path.join(
1489 b.allocator,
1490 &[_][]const u8{ b.cache_root, self.case.sources.items[0].filename },
1491 ) catch unreachable;
1492
1493 var zig_args = ArrayList([]const u8).init(b.allocator);
1494 zig_args.append(b.zig_exe) catch unreachable;
1495
1496 const translate_c_cmd = "translate-c";
1497 zig_args.append(translate_c_cmd) catch unreachable;
1498 zig_args.append(b.pathFromRoot(root_src)) catch unreachable;
1499
1500 warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name });
1501
1502 if (b.verbose) {
1503 printInvocation(zig_args.toSliceConst());
1504 }
1505
1506 const child = std.ChildProcess.init(zig_args.toSliceConst(), b.allocator) catch unreachable;
1507 defer child.deinit();
1508
1509 child.env_map = b.env_map;
1510 child.stdin_behavior = .Ignore;
1511 child.stdout_behavior = .Pipe;
1512 child.stderr_behavior = .Pipe;
1513
1514 child.spawn() catch |err| debug.panic("Unable to spawn {}: {}\n", .{
1515 zig_args.toSliceConst()[0],
1516 @errorName(err),
1517 });
1518
1519 var stdout_buf = Buffer.initNull(b.allocator);
1520 var stderr_buf = Buffer.initNull(b.allocator);
1521
1522 var stdout_file_in_stream = child.stdout.?.inStream();
1523 var stderr_file_in_stream = child.stderr.?.inStream();
1524
1525 stdout_file_in_stream.stream.readAllBuffer(&stdout_buf, max_stdout_size) catch unreachable;
1526 stderr_file_in_stream.stream.readAllBuffer(&stderr_buf, max_stdout_size) catch unreachable;
1527
1528 const term = child.wait() catch |err| {
1529 debug.panic("Unable to spawn {}: {}\n", .{ zig_args.toSliceConst()[0], @errorName(err) });
1530 };
1531 switch (term) {
1532 .Exited => |code| {
1533 if (code != 0) {
1534 warn("Compilation failed with exit code {}\n{}\n", .{ code, stderr_buf.toSliceConst() });
1535 printInvocation(zig_args.toSliceConst());
1536 return error.TestFailed;
1537 }
1538 },
1539 .Signal => |code| {
1540 warn("Compilation failed with signal {}\n", .{code});
1541 printInvocation(zig_args.toSliceConst());
1542 return error.TestFailed;
1543 },
1544 else => {
1545 warn("Compilation terminated unexpectedly\n", .{});
1546 printInvocation(zig_args.toSliceConst());
1547 return error.TestFailed;
1548 },
1549 }
1550
1551 const stdout = stdout_buf.toSliceConst();
1552 const stderr = stderr_buf.toSliceConst();
1553
1554 if (stderr.len != 0 and !self.case.allow_warnings) {
1555 warn(
1556 \\====== translate-c emitted warnings: =======
1557 \\{}
1558 \\============================================
1559 \\
1560 , .{stderr});
1561 printInvocation(zig_args.toSliceConst());
1562 return error.TestFailed;
1563 }
1564
1565 for (self.case.expected_lines.toSliceConst()) |expected_line| {
1566 if (mem.indexOf(u8, stdout, expected_line) == null) {
1567 warn(
1568 \\
1569 \\========= Expected this output: ================
1570 \\{}
1571 \\========= But found: ===========================
1572 \\{}
1573 \\
1574 , .{ expected_line, stdout });
1575 printInvocation(zig_args.toSliceConst());
1576 return error.TestFailed;
1577 }
1578 }
1579 warn("OK\n", .{});
1580 }
1581 };
1582
1583 fn printInvocation(args: []const []const u8) void {
1584 for (args) |arg| {
1585 warn("{} ", .{arg});
1586 }
1587 warn("\n", .{});
1588 }
1589
1590 pub fn create(
1591 self: *TranslateCContext,
1592 allow_warnings: bool,
1593 filename: []const u8,
1594 name: []const u8,
1595 source: []const u8,
1596 expected_lines: []const []const u8,
1597 ) *TestCase {
1598 const tc = self.b.allocator.create(TestCase) catch unreachable;
1599 tc.* = TestCase{
1600 .name = name,
1601 .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
1602 .expected_lines = ArrayList([]const u8).init(self.b.allocator),
1603 .allow_warnings = allow_warnings,
1604 };
1605
1606 tc.addSourceFile(filename, source);
1607 var arg_i: usize = 0;
1608 while (arg_i < expected_lines.len) : (arg_i += 1) {
1609 tc.addExpectedLine(expected_lines[arg_i]);
1610 }
1611 return tc;
1612 }
1613
1614 pub fn add(
1615 self: *TranslateCContext,
1616 name: []const u8,
1617 source: []const u8,
1618 expected_lines: []const []const u8,
1619 ) void {
1620 const tc = self.create(false, "source.h", name, source, expected_lines);
1621 self.addCase(tc);
1622 }
1623
1624 pub fn addAllowWarnings(
1625 self: *TranslateCContext,
1626 name: []const u8,
1627 source: []const u8,
1628 expected_lines: []const []const u8,
1629 ) void {
1630 const tc = self.create(true, "source.h", name, source, expected_lines);
1631 self.addCase(tc);
1632 }
1633
1634 pub fn addCase(self: *TranslateCContext, case: *const TestCase) void {
1635 const b = self.b;
1636
1637 const translate_c_cmd = "translate-c";
1638 const annotated_case_name = fmt.allocPrint(self.b.allocator, "{} {}", .{ translate_c_cmd, case.name }) catch unreachable;
1639 if (self.test_filter) |filter| {
1640 if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
1641 }
1642
1643 const translate_c_and_cmp = TranslateCCmpOutputStep.create(self, annotated_case_name, case);
1644 self.step.dependOn(&translate_c_and_cmp.step);
1645
1646 for (case.sources.toSliceConst()) |src_file| {
1647 const expanded_src_path = fs.path.join(
1648 b.allocator,
1649 &[_][]const u8{ b.cache_root, src_file.filename },
1650 ) catch unreachable;
1651 const write_src = b.addWriteFile(expanded_src_path, src_file.source);
1652 translate_c_and_cmp.step.dependOn(&write_src.step);
1653 }
1654 }
1655};
1656
1657pub const GenHContext = struct {1085pub const GenHContext = struct {
1658 b: *build.Builder,1086 b: *build.Builder,
1659 step: *build.Step,1087 step: *build.Step,