| 1 | b: *Build, |
| 2 | step: *Step, |
| 3 | optimize: std.builtin.Optimize, |
| 4 | target: std.Build.ResolvedTarget, |
| 5 | target_desc: []const u8, |
| 6 | use_llvm: bool, |
| 7 | use_lld: bool, |
| 8 | link_libc: bool, |
| 9 | test_filters: []const []const u8, |
| 10 | update_step: ?*Step.UpdateSourceFiles, |
| 11 | updated_snapshots: std.array_hash_map.String(void), |
| 12 | max_rss: usize, |
| 13 | |
| 14 | pub fn includeTest(self: *Link, prefix: []const u8) ?Case { |
| 15 | if (for (self.test_filters) |filter| { |
| 16 | if (std.mem.find(u8, prefix, filter)) |_| break false; |
| 17 | } else self.test_filters.len > 0) return null; |
| 18 | |
| 19 | return .{ |
| 20 | .ctx = self, |
| 21 | .prefix = prefix, |
| 22 | }; |
| 23 | } |
| 24 | |
| 25 | pub fn sourcePath(self: *const Link, sub_path: []const u8) std.Build.LazyPath { |
| 26 | return self.b.path(self.b.pathJoin(&.{ "test/link", sub_path })); |
| 27 | } |
| 28 | |
| 29 | pub const Case = struct { |
| 30 | ctx: *Link, |
| 31 | prefix: []const u8, |
| 32 | |
| 33 | fn resolveName(self: *const Case, overlay: *const OverlayOptions) []const u8 { |
| 34 | if (!overlay.name_prefix and !overlay.name_target) |
| 35 | return overlay.name; |
| 36 | |
| 37 | if (overlay.name_prefix == overlay.name_target) |
| 38 | return self.ctx.b.fmt("{s}-{s}-{s}", .{ self.prefix, overlay.name, self.ctx.target_desc }) |
| 39 | else if (overlay.name_prefix) |
| 40 | return self.ctx.b.fmt("{s}-{s}", .{ self.prefix, overlay.name }) |
| 41 | else |
| 42 | return self.ctx.b.fmt("{s}-{s}", .{ overlay.name, self.ctx.target_desc }); |
| 43 | } |
| 44 | |
| 45 | pub fn addLibrary( |
| 46 | self: *const Case, |
| 47 | linkage: std.builtin.LinkMode, |
| 48 | overlay: OverlayOptions, |
| 49 | ) *Step.Compile { |
| 50 | return self.ctx.b.addLibrary(.{ |
| 51 | .linkage = linkage, |
| 52 | .name = self.resolveName(&overlay), |
| 53 | .root_module = self.ctx.createModule(overlay), |
| 54 | .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, |
| 55 | .use_lld = overlay.use_lld orelse self.ctx.use_lld, |
| 56 | }); |
| 57 | } |
| 58 | |
| 59 | pub fn addExecutable( |
| 60 | self: *const Case, |
| 61 | overlay: OverlayOptions, |
| 62 | ) *Step.Compile { |
| 63 | return self.ctx.b.addExecutable(.{ |
| 64 | .name = self.resolveName(&overlay), |
| 65 | .root_module = self.ctx.createModule(overlay), |
| 66 | .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, |
| 67 | .use_lld = overlay.use_lld orelse self.ctx.use_lld, |
| 68 | }); |
| 69 | } |
| 70 | |
| 71 | pub fn addRunArtifact( |
| 72 | self: *const Case, |
| 73 | exe: *Step.Compile, |
| 74 | ) *Step.Run { |
| 75 | const run_step = self.ctx.b.addRunArtifact(exe); |
| 76 | run_step.skip_foreign_checks = true; |
| 77 | self.ctx.step.dependOn(&run_step.step); |
| 78 | return run_step; |
| 79 | } |
| 80 | |
| 81 | pub fn addObject( |
| 82 | self: *const Case, |
| 83 | overlay: OverlayOptions, |
| 84 | ) *Step.Compile { |
| 85 | return self.ctx.b.addObject(.{ |
| 86 | .name = self.resolveName(&overlay), |
| 87 | .root_module = self.ctx.createModule(overlay), |
| 88 | .use_llvm = overlay.use_llvm orelse self.ctx.use_llvm, |
| 89 | .use_lld = overlay.use_lld orelse self.ctx.use_lld, |
| 90 | }); |
| 91 | } |
| 92 | |
| 93 | pub fn expectLinkErrors( |
| 94 | self: *const Case, |
| 95 | comp: *Step.Compile, |
| 96 | expected_errors: Step.Compile.ExpectedCompileErrors, |
| 97 | ) void { |
| 98 | comp.expect_errors = expected_errors; |
| 99 | const bin_file = comp.getEmittedBin(); |
| 100 | bin_file.addStepDependencies(self.ctx.step); |
| 101 | } |
| 102 | |
| 103 | const SnapshotScope = struct { |
| 104 | /// If a test case has multiple verifyObjdump calls, `opt_sub_name` should |
| 105 | /// be used to differentiate them. |
| 106 | sub_name: ?[]const u8 = null, |
| 107 | arch: bool = false, |
| 108 | os: bool = false, |
| 109 | abi: bool = false, |
| 110 | optimize: bool = false, |
| 111 | use_llvm: bool = false, |
| 112 | use_lld: bool = false, |
| 113 | link_libc: bool = false, |
| 114 | }; |
| 115 | |
| 116 | /// Verify the results of a `zig objdump` call against a snapshot, which |
| 117 | /// contains the expected output. Snapshots alias between all build |
| 118 | /// configurations by default, but by specifying fields in `scope`, |
| 119 | /// unique snapshot names are generated for each value of that field. |
| 120 | pub fn verifyObjdump( |
| 121 | self: *const Case, |
| 122 | file: Build.LazyPath, |
| 123 | args: []const []const u8, |
| 124 | scope: SnapshotScope, |
| 125 | ) void { |
| 126 | const ctx = self.ctx; |
| 127 | const snapshot_name = self.snapshotName(scope) catch @panic("OOM"); |
| 128 | const snapshot_sub_path = ctx.b.pathJoin(&.{ "test/link/snapshots/", snapshot_name }); |
| 129 | |
| 130 | // Many tests may read the same snapshot, so only use the first one to update. |
| 131 | // If there are differences in output, they will show up on the next test run. |
| 132 | if (ctx.update_step != null) { |
| 133 | const gop = ctx.updated_snapshots.getOrPut(ctx.b.allocator, snapshot_sub_path) catch @panic("OOM"); |
| 134 | if (gop.found_existing) return; |
| 135 | } |
| 136 | |
| 137 | const run_step = Step.Run.create(ctx.b, ctx.b.fmt( |
| 138 | "objdump {s} {s}", |
| 139 | .{ snapshot_name, ctx.target_desc }, |
| 140 | )); |
| 141 | run_step.addArgs(&.{ ctx.b.graph.zig_exe, "objdump" }); |
| 142 | run_step.addFileArg(file); |
| 143 | run_step.addArgs(args); |
| 144 | run_step.addCheck(.{ .expect_term = .{ .exited = 0 } }); |
| 145 | |
| 146 | if (ctx.update_step) |update_step| { |
| 147 | // Workaround for the build system not realizing objdump itself has changed |
| 148 | run_step.has_side_effects = true; |
| 149 | |
| 150 | const snapshot_update_path = run_step.captureStdOut(.{}); |
| 151 | update_step.addCopyFileToSource(snapshot_update_path, snapshot_sub_path); |
| 152 | } else { |
| 153 | run_step.addCheck(.{ .expect_stdout_snapshot = ctx.b.path(snapshot_sub_path) }); |
| 154 | } |
| 155 | |
| 156 | ctx.step.dependOn(&run_step.step); |
| 157 | } |
| 158 | |
| 159 | fn snapshotName( |
| 160 | self: *const Case, |
| 161 | scope: SnapshotScope, |
| 162 | ) ![]const u8 { |
| 163 | const ctx = self.ctx; |
| 164 | var snapshot_name: std.Io.Writer.Allocating = .init(ctx.b.allocator); |
| 165 | const w = &snapshot_name.writer; |
| 166 | |
| 167 | try w.writeAll(self.prefix); |
| 168 | var sep: u8 = '.'; |
| 169 | |
| 170 | if (try snapshotNameInner(w, scope.sub_name != null, &sep)) |
| 171 | try w.writeAll(scope.sub_name.?); |
| 172 | if (try snapshotNameInner(w, scope.arch, &sep)) |
| 173 | try w.print("{t}", .{ctx.target.result.cpu.arch}); |
| 174 | if (try snapshotNameInner(w, scope.os, &sep)) |
| 175 | try w.print("{t}", .{ctx.target.result.os.tag}); |
| 176 | if (try snapshotNameInner(w, scope.abi, &sep)) |
| 177 | try w.print("{t}", .{ctx.target.result.abi}); |
| 178 | if (try snapshotNameInner(w, scope.optimize, &sep)) |
| 179 | try w.print("{t}", .{ctx.optimize}); |
| 180 | if (try snapshotNameInner(w, scope.use_llvm, &sep)) |
| 181 | try w.writeAll(if (ctx.use_llvm) "llvm" else "no-llvm"); |
| 182 | if (try snapshotNameInner(w, scope.use_lld, &sep)) |
| 183 | try w.writeAll(if (ctx.use_lld) "lld" else "no-lld"); |
| 184 | if (try snapshotNameInner(w, scope.link_libc, &sep)) |
| 185 | try w.writeAll(if (ctx.link_libc) "libc" else "no-libc"); |
| 186 | |
| 187 | if (sep == '-') sep = '.'; |
| 188 | try w.writeByte(sep); |
| 189 | try w.writeAll("dmp"); |
| 190 | |
| 191 | return try snapshot_name.toOwnedSlice(); |
| 192 | } |
| 193 | |
| 194 | fn snapshotNameInner(w: *std.Io.Writer, cond: bool, sep: *u8) !bool { |
| 195 | if (cond) { |
| 196 | try w.writeByte(sep.*); |
| 197 | sep.* = '-'; |
| 198 | } |
| 199 | |
| 200 | return cond; |
| 201 | } |
| 202 | }; |
| 203 | |
| 204 | fn createModule(self: *const Link, overlay: OverlayOptions) *Build.Module { |
| 205 | const write_files = self.b.addWriteFiles(); |
| 206 | |
| 207 | const mod = self.b.createModule(.{ |
| 208 | .target = self.target, |
| 209 | .optimize = self.optimize, |
| 210 | .root_source_file = overlay.zig_source_file orelse rsf: { |
| 211 | const bytes = overlay.zig_source_bytes orelse break :rsf null; |
| 212 | const name = self.b.fmt("{s}.zig", .{overlay.name}); |
| 213 | break :rsf write_files.add(name, bytes); |
| 214 | }, |
| 215 | .link_libc = self.link_libc, // TODO: Should this be in overlay instead? |
| 216 | .pic = overlay.pic, |
| 217 | .strip = overlay.strip, |
| 218 | }); |
| 219 | |
| 220 | if (overlay.objcpp_source_bytes) |bytes| { |
| 221 | mod.addCSourceFile(.{ |
| 222 | .file = write_files.add("a.mm", bytes), |
| 223 | .flags = overlay.objcpp_source_flags, |
| 224 | }); |
| 225 | } |
| 226 | if (overlay.objc_source_bytes) |bytes| { |
| 227 | mod.addCSourceFile(.{ |
| 228 | .file = write_files.add("a.m", bytes), |
| 229 | .flags = overlay.objc_source_flags, |
| 230 | }); |
| 231 | } |
| 232 | if (overlay.cpp_source_bytes) |bytes| { |
| 233 | mod.addCSourceFile(.{ |
| 234 | .file = write_files.add("a.cpp", bytes), |
| 235 | .flags = overlay.cpp_source_flags, |
| 236 | }); |
| 237 | } |
| 238 | if (overlay.c_source_bytes) |bytes| { |
| 239 | mod.addCSourceFile(.{ |
| 240 | .file = write_files.add("a.c", bytes), |
| 241 | .flags = overlay.c_source_flags, |
| 242 | }); |
| 243 | } |
| 244 | if (overlay.asm_source_bytes) |bytes| { |
| 245 | mod.addAssemblyFile(write_files.add("a.s", bytes)); |
| 246 | } |
| 247 | |
| 248 | return mod; |
| 249 | } |
| 250 | |
| 251 | const OverlayOptions = struct { |
| 252 | name: []const u8, |
| 253 | /// Prefix the name with the test case prefix. |
| 254 | /// Unset if names with specific lengths are needed. |
| 255 | name_prefix: bool = true, |
| 256 | /// Prefix the name with `target_desc`. |
| 257 | /// Can be unset when the snapshot needs to contain the name, |
| 258 | /// so that snapshots can alias between targets. |
| 259 | name_target: bool = true, |
| 260 | asm_source_bytes: ?[]const u8 = null, |
| 261 | c_source_bytes: ?[]const u8 = null, |
| 262 | c_source_flags: []const []const u8 = &.{}, |
| 263 | cpp_source_bytes: ?[]const u8 = null, |
| 264 | cpp_source_flags: []const []const u8 = &.{}, |
| 265 | objc_source_bytes: ?[]const u8 = null, |
| 266 | objc_source_flags: []const []const u8 = &.{}, |
| 267 | objcpp_source_bytes: ?[]const u8 = null, |
| 268 | objcpp_source_flags: []const []const u8 = &.{}, |
| 269 | zig_source_bytes: ?[]const u8 = null, |
| 270 | zig_source_file: ?std.Build.LazyPath = null, |
| 271 | pic: ?bool = null, |
| 272 | strip: ?bool = null, |
| 273 | use_llvm: ?bool = null, |
| 274 | use_lld: ?bool = null, |
| 275 | }; |
| 276 | |
| 277 | const std = @import("std"); |
| 278 | const Build = std.Build; |
| 279 | const Step = Build.Step; |
| 280 | |
| 281 | const Link = @This(); |