| author | |
| committer | |
| log | 13eb7251d37759bd47403db304c6120c706fe353 |
| tree | 225d87ef968270968379e2d58b9791b0aa152aa7 |
| parent | 855493bb8b395970921494d3a11ccfeaac30c2dc |
Follow-up actions from #14647
Fixes #1494729 files changed, 6411 insertions(+), 6415 deletions(-)
lib/std/Build.zig+14-14| ... | @@ -29,20 +29,20 @@ pub const Builder = Build; | ... | @@ -29,20 +29,20 @@ pub const Builder = Build; |
| 29 | pub const InstallDirectoryOptions = InstallDirStep.Options; | 29 | pub const InstallDirectoryOptions = InstallDirStep.Options; |
| 30 | 30 | ||
| 31 | pub const Step = @import("Build/Step.zig"); | 31 | pub const Step = @import("Build/Step.zig"); |
| 32 | pub const CheckFileStep = @import("Build/CheckFileStep.zig"); | 32 | pub const CheckFileStep = @import("Build/Step/CheckFile.zig"); |
| 33 | pub const CheckObjectStep = @import("Build/CheckObjectStep.zig"); | 33 | pub const CheckObjectStep = @import("Build/Step/CheckObject.zig"); |
| 34 | pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig"); | 34 | pub const ConfigHeaderStep = @import("Build/Step/ConfigHeader.zig"); |
| 35 | pub const FmtStep = @import("Build/FmtStep.zig"); | 35 | pub const FmtStep = @import("Build/Step/Fmt.zig"); |
| 36 | pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig"); | 36 | pub const InstallArtifactStep = @import("Build/Step/InstallArtifact.zig"); |
| 37 | pub const InstallDirStep = @import("Build/InstallDirStep.zig"); | 37 | pub const InstallDirStep = @import("Build/Step/InstallDir.zig"); |
| 38 | pub const InstallFileStep = @import("Build/InstallFileStep.zig"); | 38 | pub const InstallFileStep = @import("Build/Step/InstallFile.zig"); |
| 39 | pub const ObjCopyStep = @import("Build/ObjCopyStep.zig"); | 39 | pub const ObjCopyStep = @import("Build/Step/ObjCopy.zig"); |
| 40 | pub const CompileStep = @import("Build/CompileStep.zig"); | 40 | pub const CompileStep = @import("Build/Step/Compile.zig"); |
| 41 | pub const OptionsStep = @import("Build/OptionsStep.zig"); | 41 | pub const OptionsStep = @import("Build/Step/Options.zig"); |
| 42 | pub const RemoveDirStep = @import("Build/RemoveDirStep.zig"); | 42 | pub const RemoveDirStep = @import("Build/Step/RemoveDir.zig"); |
| 43 | pub const RunStep = @import("Build/RunStep.zig"); | 43 | pub const RunStep = @import("Build/Step/Run.zig"); |
| 44 | pub const TranslateCStep = @import("Build/TranslateCStep.zig"); | 44 | pub const TranslateCStep = @import("Build/Step/TranslateC.zig"); |
| 45 | pub const WriteFileStep = @import("Build/WriteFileStep.zig"); | 45 | pub const WriteFileStep = @import("Build/Step/WriteFile.zig"); |
| 46 | 46 | ||
| 47 | install_tls: TopLevelStep, | 47 | install_tls: TopLevelStep, |
| 48 | uninstall_tls: TopLevelStep, | 48 | uninstall_tls: TopLevelStep, |
lib/std/Build/CheckFileStep.zig deleted-88| ... | @@ -1,88 +0,0 @@ | ||
| 1 | //! Fail the build step if a file does not match certain checks. | ||
| 2 | //! TODO: make this more flexible, supporting more kinds of checks. | ||
| 3 | //! TODO: generalize the code in std.testing.expectEqualStrings and make this | ||
| 4 | //! CheckFileStep produce those helpful diagnostics when there is not a match. | ||
| 5 | |||
| 6 | step: Step, | ||
| 7 | expected_matches: []const []const u8, | ||
| 8 | expected_exact: ?[]const u8, | ||
| 9 | source: std.Build.FileSource, | ||
| 10 | max_bytes: usize = 20 * 1024 * 1024, | ||
| 11 | |||
| 12 | pub const base_id = .check_file; | ||
| 13 | |||
| 14 | pub const Options = struct { | ||
| 15 | expected_matches: []const []const u8 = &.{}, | ||
| 16 | expected_exact: ?[]const u8 = null, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub fn create( | ||
| 20 | owner: *std.Build, | ||
| 21 | source: std.Build.FileSource, | ||
| 22 | options: Options, | ||
| 23 | ) *CheckFileStep { | ||
| 24 | const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); | ||
| 25 | self.* = .{ | ||
| 26 | .step = Step.init(.{ | ||
| 27 | .id = .check_file, | ||
| 28 | .name = "CheckFile", | ||
| 29 | .owner = owner, | ||
| 30 | .makeFn = make, | ||
| 31 | }), | ||
| 32 | .source = source.dupe(owner), | ||
| 33 | .expected_matches = owner.dupeStrings(options.expected_matches), | ||
| 34 | .expected_exact = options.expected_exact, | ||
| 35 | }; | ||
| 36 | self.source.addStepDependencies(&self.step); | ||
| 37 | return self; | ||
| 38 | } | ||
| 39 | |||
| 40 | pub fn setName(self: *CheckFileStep, name: []const u8) void { | ||
| 41 | self.step.name = name; | ||
| 42 | } | ||
| 43 | |||
| 44 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 45 | _ = prog_node; | ||
| 46 | const b = step.owner; | ||
| 47 | const self = @fieldParentPtr(CheckFileStep, "step", step); | ||
| 48 | |||
| 49 | const src_path = self.source.getPath(b); | ||
| 50 | const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| { | ||
| 51 | return step.fail("unable to read '{s}': {s}", .{ | ||
| 52 | src_path, @errorName(err), | ||
| 53 | }); | ||
| 54 | }; | ||
| 55 | |||
| 56 | for (self.expected_matches) |expected_match| { | ||
| 57 | if (mem.indexOf(u8, contents, expected_match) == null) { | ||
| 58 | return step.fail( | ||
| 59 | \\ | ||
| 60 | \\========= expected to find: =================== | ||
| 61 | \\{s} | ||
| 62 | \\========= but file does not contain it: ======= | ||
| 63 | \\{s} | ||
| 64 | \\=============================================== | ||
| 65 | , .{ expected_match, contents }); | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | if (self.expected_exact) |expected_exact| { | ||
| 70 | if (!mem.eql(u8, expected_exact, contents)) { | ||
| 71 | return step.fail( | ||
| 72 | \\ | ||
| 73 | \\========= expected: ===================== | ||
| 74 | \\{s} | ||
| 75 | \\========= but found: ==================== | ||
| 76 | \\{s} | ||
| 77 | \\========= from the following file: ====== | ||
| 78 | \\{s} | ||
| 79 | , .{ expected_exact, contents, src_path }); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | const CheckFileStep = @This(); | ||
| 85 | const std = @import("../std.zig"); | ||
| 86 | const Step = std.Build.Step; | ||
| 87 | const fs = std.fs; | ||
| 88 | const mem = std.mem; | ||
lib/std/Build/CheckObjectStep.zig deleted-1055| ... | @@ -1,1055 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const fs = std.fs; | ||
| 4 | const macho = std.macho; | ||
| 5 | const math = std.math; | ||
| 6 | const mem = std.mem; | ||
| 7 | const testing = std.testing; | ||
| 8 | |||
| 9 | const CheckObjectStep = @This(); | ||
| 10 | |||
| 11 | const Allocator = mem.Allocator; | ||
| 12 | const Step = std.Build.Step; | ||
| 13 | |||
| 14 | pub const base_id = .check_object; | ||
| 15 | |||
| 16 | step: Step, | ||
| 17 | source: std.Build.FileSource, | ||
| 18 | max_bytes: usize = 20 * 1024 * 1024, | ||
| 19 | checks: std.ArrayList(Check), | ||
| 20 | dump_symtab: bool = false, | ||
| 21 | obj_format: std.Target.ObjectFormat, | ||
| 22 | |||
| 23 | pub fn create( | ||
| 24 | owner: *std.Build, | ||
| 25 | source: std.Build.FileSource, | ||
| 26 | obj_format: std.Target.ObjectFormat, | ||
| 27 | ) *CheckObjectStep { | ||
| 28 | const gpa = owner.allocator; | ||
| 29 | const self = gpa.create(CheckObjectStep) catch @panic("OOM"); | ||
| 30 | self.* = .{ | ||
| 31 | .step = Step.init(.{ | ||
| 32 | .id = .check_file, | ||
| 33 | .name = "CheckObject", | ||
| 34 | .owner = owner, | ||
| 35 | .makeFn = make, | ||
| 36 | }), | ||
| 37 | .source = source.dupe(owner), | ||
| 38 | .checks = std.ArrayList(Check).init(gpa), | ||
| 39 | .obj_format = obj_format, | ||
| 40 | }; | ||
| 41 | self.source.addStepDependencies(&self.step); | ||
| 42 | return self; | ||
| 43 | } | ||
| 44 | |||
| 45 | /// Runs and (optionally) compares the output of a binary. | ||
| 46 | /// Asserts `self` was generated from an executable step. | ||
| 47 | /// TODO this doesn't actually compare, and there's no apparent reason for it | ||
| 48 | /// to depend on the check object step. I don't see why this function should exist, | ||
| 49 | /// the caller could just add the run step directly. | ||
| 50 | pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep { | ||
| 51 | const dependencies_len = self.step.dependencies.items.len; | ||
| 52 | assert(dependencies_len > 0); | ||
| 53 | const exe_step = self.step.dependencies.items[dependencies_len - 1]; | ||
| 54 | const exe = exe_step.cast(std.Build.CompileStep).?; | ||
| 55 | const run = self.step.owner.addRunArtifact(exe); | ||
| 56 | run.skip_foreign_checks = true; | ||
| 57 | run.step.dependOn(&self.step); | ||
| 58 | return run; | ||
| 59 | } | ||
| 60 | |||
| 61 | const SearchPhrase = struct { | ||
| 62 | string: []const u8, | ||
| 63 | file_source: ?std.Build.FileSource = null, | ||
| 64 | |||
| 65 | fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 { | ||
| 66 | const file_source = phrase.file_source orelse return phrase.string; | ||
| 67 | return b.fmt("{s} {s}", .{ phrase.string, file_source.getPath2(b, step) }); | ||
| 68 | } | ||
| 69 | }; | ||
| 70 | |||
| 71 | /// There two types of actions currently supported: | ||
| 72 | /// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` | ||
| 73 | /// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature | ||
| 74 | /// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use | ||
| 75 | /// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. | ||
| 76 | /// it should be plenty useful in its current form. | ||
| 77 | /// * `.compute_cmp` - can be used to perform an operation on the extracted global variables | ||
| 78 | /// using the MatchAction. It currently only supports an addition. The operation is required | ||
| 79 | /// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, | ||
| 80 | /// to avoid any parsing really). | ||
| 81 | /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively | ||
| 82 | /// they could then be added with this simple program `vmaddr entryoff +`. | ||
| 83 | const Action = struct { | ||
| 84 | tag: enum { match, not_present, compute_cmp }, | ||
| 85 | phrase: SearchPhrase, | ||
| 86 | expected: ?ComputeCompareExpected = null, | ||
| 87 | |||
| 88 | /// Will return true if the `phrase` was found in the `haystack`. | ||
| 89 | /// Some examples include: | ||
| 90 | /// | ||
| 91 | /// LC 0 => will match in its entirety | ||
| 92 | /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 | ||
| 93 | /// and save under `vmaddr` global name (see `global_vars` param) | ||
| 94 | /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` | ||
| 95 | /// in that order with other letters in between | ||
| 96 | fn match( | ||
| 97 | act: Action, | ||
| 98 | b: *std.Build, | ||
| 99 | step: *Step, | ||
| 100 | haystack: []const u8, | ||
| 101 | global_vars: anytype, | ||
| 102 | ) !bool { | ||
| 103 | assert(act.tag == .match or act.tag == .not_present); | ||
| 104 | const phrase = act.phrase.resolve(b, step); | ||
| 105 | var candidate_var: ?struct { name: []const u8, value: u64 } = null; | ||
| 106 | var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); | ||
| 107 | var needle_it = mem.tokenize(u8, mem.trim(u8, phrase, " "), " "); | ||
| 108 | |||
| 109 | while (needle_it.next()) |needle_tok| { | ||
| 110 | const hay_tok = hay_it.next() orelse return false; | ||
| 111 | |||
| 112 | if (mem.indexOf(u8, needle_tok, "{*}")) |index| { | ||
| 113 | // We have fuzzy matchers within the search pattern, so we match substrings. | ||
| 114 | var start = index; | ||
| 115 | var n_tok = needle_tok; | ||
| 116 | var h_tok = hay_tok; | ||
| 117 | while (true) { | ||
| 118 | n_tok = n_tok[start + 3 ..]; | ||
| 119 | const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| | ||
| 120 | n_tok[0..sub_end] | ||
| 121 | else | ||
| 122 | n_tok; | ||
| 123 | if (mem.indexOf(u8, h_tok, inner) == null) return false; | ||
| 124 | start = mem.indexOf(u8, n_tok, "{*}") orelse break; | ||
| 125 | } | ||
| 126 | } else if (mem.startsWith(u8, needle_tok, "{")) { | ||
| 127 | const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; | ||
| 128 | if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; | ||
| 129 | |||
| 130 | const name = needle_tok[1..closing_brace]; | ||
| 131 | if (name.len == 0) return error.MissingBraceValue; | ||
| 132 | const value = try std.fmt.parseInt(u64, hay_tok, 16); | ||
| 133 | candidate_var = .{ | ||
| 134 | .name = name, | ||
| 135 | .value = value, | ||
| 136 | }; | ||
| 137 | } else { | ||
| 138 | if (!mem.eql(u8, hay_tok, needle_tok)) return false; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | if (candidate_var) |v| { | ||
| 143 | try global_vars.putNoClobber(v.name, v.value); | ||
| 144 | } | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | ||
| 148 | |||
| 149 | /// Will return true if the `phrase` is correctly parsed into an RPN program and | ||
| 150 | /// its reduced, computed value compares using `op` with the expected value, either | ||
| 151 | /// a literal or another extracted variable. | ||
| 152 | fn computeCmp(act: Action, b: *std.Build, step: *Step, global_vars: anytype) !bool { | ||
| 153 | const gpa = step.owner.allocator; | ||
| 154 | const phrase = act.phrase.resolve(b, step); | ||
| 155 | var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); | ||
| 156 | var values = std.ArrayList(u64).init(gpa); | ||
| 157 | |||
| 158 | var it = mem.tokenize(u8, phrase, " "); | ||
| 159 | while (it.next()) |next| { | ||
| 160 | if (mem.eql(u8, next, "+")) { | ||
| 161 | try op_stack.append(.add); | ||
| 162 | } else if (mem.eql(u8, next, "-")) { | ||
| 163 | try op_stack.append(.sub); | ||
| 164 | } else if (mem.eql(u8, next, "%")) { | ||
| 165 | try op_stack.append(.mod); | ||
| 166 | } else if (mem.eql(u8, next, "*")) { | ||
| 167 | try op_stack.append(.mul); | ||
| 168 | } else { | ||
| 169 | const val = std.fmt.parseInt(u64, next, 0) catch blk: { | ||
| 170 | break :blk global_vars.get(next) orelse { | ||
| 171 | try step.addError( | ||
| 172 | \\ | ||
| 173 | \\========= variable was not extracted: =========== | ||
| 174 | \\{s} | ||
| 175 | \\================================================= | ||
| 176 | , .{next}); | ||
| 177 | return error.UnknownVariable; | ||
| 178 | }; | ||
| 179 | }; | ||
| 180 | try values.append(val); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | var op_i: usize = 1; | ||
| 185 | var reduced: u64 = values.items[0]; | ||
| 186 | for (op_stack.items) |op| { | ||
| 187 | const other = values.items[op_i]; | ||
| 188 | switch (op) { | ||
| 189 | .add => { | ||
| 190 | reduced += other; | ||
| 191 | }, | ||
| 192 | .sub => { | ||
| 193 | reduced -= other; | ||
| 194 | }, | ||
| 195 | .mod => { | ||
| 196 | reduced %= other; | ||
| 197 | }, | ||
| 198 | .mul => { | ||
| 199 | reduced *= other; | ||
| 200 | }, | ||
| 201 | } | ||
| 202 | op_i += 1; | ||
| 203 | } | ||
| 204 | |||
| 205 | const exp_value = switch (act.expected.?.value) { | ||
| 206 | .variable => |name| global_vars.get(name) orelse { | ||
| 207 | try step.addError( | ||
| 208 | \\ | ||
| 209 | \\========= variable was not extracted: =========== | ||
| 210 | \\{s} | ||
| 211 | \\================================================= | ||
| 212 | , .{name}); | ||
| 213 | return error.UnknownVariable; | ||
| 214 | }, | ||
| 215 | .literal => |x| x, | ||
| 216 | }; | ||
| 217 | return math.compare(reduced, act.expected.?.op, exp_value); | ||
| 218 | } | ||
| 219 | }; | ||
| 220 | |||
| 221 | const ComputeCompareExpected = struct { | ||
| 222 | op: math.CompareOperator, | ||
| 223 | value: union(enum) { | ||
| 224 | variable: []const u8, | ||
| 225 | literal: u64, | ||
| 226 | }, | ||
| 227 | |||
| 228 | pub fn format( | ||
| 229 | value: @This(), | ||
| 230 | comptime fmt: []const u8, | ||
| 231 | options: std.fmt.FormatOptions, | ||
| 232 | writer: anytype, | ||
| 233 | ) !void { | ||
| 234 | if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); | ||
| 235 | _ = options; | ||
| 236 | try writer.print("{s} ", .{@tagName(value.op)}); | ||
| 237 | switch (value.value) { | ||
| 238 | .variable => |name| try writer.writeAll(name), | ||
| 239 | .literal => |x| try writer.print("{x}", .{x}), | ||
| 240 | } | ||
| 241 | } | ||
| 242 | }; | ||
| 243 | |||
| 244 | const Check = struct { | ||
| 245 | actions: std.ArrayList(Action), | ||
| 246 | |||
| 247 | fn create(allocator: Allocator) Check { | ||
| 248 | return .{ | ||
| 249 | .actions = std.ArrayList(Action).init(allocator), | ||
| 250 | }; | ||
| 251 | } | ||
| 252 | |||
| 253 | fn match(self: *Check, phrase: SearchPhrase) void { | ||
| 254 | self.actions.append(.{ | ||
| 255 | .tag = .match, | ||
| 256 | .phrase = phrase, | ||
| 257 | }) catch @panic("OOM"); | ||
| 258 | } | ||
| 259 | |||
| 260 | fn notPresent(self: *Check, phrase: SearchPhrase) void { | ||
| 261 | self.actions.append(.{ | ||
| 262 | .tag = .not_present, | ||
| 263 | .phrase = phrase, | ||
| 264 | }) catch @panic("OOM"); | ||
| 265 | } | ||
| 266 | |||
| 267 | fn computeCmp(self: *Check, phrase: SearchPhrase, expected: ComputeCompareExpected) void { | ||
| 268 | self.actions.append(.{ | ||
| 269 | .tag = .compute_cmp, | ||
| 270 | .phrase = phrase, | ||
| 271 | .expected = expected, | ||
| 272 | }) catch @panic("OOM"); | ||
| 273 | } | ||
| 274 | }; | ||
| 275 | |||
| 276 | /// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. | ||
| 277 | pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 278 | var new_check = Check.create(self.step.owner.allocator); | ||
| 279 | new_check.match(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 280 | self.checks.append(new_check) catch @panic("OOM"); | ||
| 281 | } | ||
| 282 | |||
| 283 | /// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. | ||
| 284 | /// Asserts at least one check already exists. | ||
| 285 | pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 286 | assert(self.checks.items.len > 0); | ||
| 287 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 288 | last.match(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 289 | } | ||
| 290 | |||
| 291 | /// Like `checkNext()` but takes an additional argument `FileSource` which will be | ||
| 292 | /// resolved to a full search query in `make()`. | ||
| 293 | pub fn checkNextFileSource( | ||
| 294 | self: *CheckObjectStep, | ||
| 295 | phrase: []const u8, | ||
| 296 | file_source: std.Build.FileSource, | ||
| 297 | ) void { | ||
| 298 | assert(self.checks.items.len > 0); | ||
| 299 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 300 | last.match(.{ .string = self.step.owner.dupe(phrase), .file_source = file_source }); | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` | ||
| 304 | /// however ensures there is no matching phrase in the output. | ||
| 305 | /// Asserts at least one check already exists. | ||
| 306 | pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 307 | assert(self.checks.items.len > 0); | ||
| 308 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 309 | last.notPresent(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 310 | } | ||
| 311 | |||
| 312 | /// Creates a new check checking specifically symbol table parsed and dumped from the object | ||
| 313 | /// file. | ||
| 314 | /// Issuing this check will force parsing and dumping of the symbol table. | ||
| 315 | pub fn checkInSymtab(self: *CheckObjectStep) void { | ||
| 316 | self.dump_symtab = true; | ||
| 317 | const symtab_label = switch (self.obj_format) { | ||
| 318 | .macho => MachODumper.symtab_label, | ||
| 319 | else => @panic("TODO other parsers"), | ||
| 320 | }; | ||
| 321 | self.checkStart(symtab_label); | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Creates a new standalone, singular check which allows running simple binary operations | ||
| 325 | /// on the extracted variables. It will then compare the reduced program with the value of | ||
| 326 | /// the expected variable. | ||
| 327 | pub fn checkComputeCompare( | ||
| 328 | self: *CheckObjectStep, | ||
| 329 | program: []const u8, | ||
| 330 | expected: ComputeCompareExpected, | ||
| 331 | ) void { | ||
| 332 | var new_check = Check.create(self.step.owner.allocator); | ||
| 333 | new_check.computeCmp(.{ .string = self.step.owner.dupe(program) }, expected); | ||
| 334 | self.checks.append(new_check) catch @panic("OOM"); | ||
| 335 | } | ||
| 336 | |||
| 337 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 338 | _ = prog_node; | ||
| 339 | const b = step.owner; | ||
| 340 | const gpa = b.allocator; | ||
| 341 | const self = @fieldParentPtr(CheckObjectStep, "step", step); | ||
| 342 | |||
| 343 | const src_path = self.source.getPath(b); | ||
| 344 | const contents = fs.cwd().readFileAllocOptions( | ||
| 345 | gpa, | ||
| 346 | src_path, | ||
| 347 | self.max_bytes, | ||
| 348 | null, | ||
| 349 | @alignOf(u64), | ||
| 350 | null, | ||
| 351 | ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) }); | ||
| 352 | |||
| 353 | const output = switch (self.obj_format) { | ||
| 354 | .macho => try MachODumper.parseAndDump(step, contents, .{ | ||
| 355 | .dump_symtab = self.dump_symtab, | ||
| 356 | }), | ||
| 357 | .elf => @panic("TODO elf parser"), | ||
| 358 | .coff => @panic("TODO coff parser"), | ||
| 359 | .wasm => try WasmDumper.parseAndDump(step, contents, .{ | ||
| 360 | .dump_symtab = self.dump_symtab, | ||
| 361 | }), | ||
| 362 | else => unreachable, | ||
| 363 | }; | ||
| 364 | |||
| 365 | var vars = std.StringHashMap(u64).init(gpa); | ||
| 366 | |||
| 367 | for (self.checks.items) |chk| { | ||
| 368 | var it = mem.tokenize(u8, output, "\r\n"); | ||
| 369 | for (chk.actions.items) |act| { | ||
| 370 | switch (act.tag) { | ||
| 371 | .match => { | ||
| 372 | while (it.next()) |line| { | ||
| 373 | if (try act.match(b, step, line, &vars)) break; | ||
| 374 | } else { | ||
| 375 | return step.fail( | ||
| 376 | \\ | ||
| 377 | \\========= expected to find: ========================== | ||
| 378 | \\{s} | ||
| 379 | \\========= but parsed file does not contain it: ======= | ||
| 380 | \\{s} | ||
| 381 | \\====================================================== | ||
| 382 | , .{ act.phrase.resolve(b, step), output }); | ||
| 383 | } | ||
| 384 | }, | ||
| 385 | .not_present => { | ||
| 386 | while (it.next()) |line| { | ||
| 387 | if (try act.match(b, step, line, &vars)) { | ||
| 388 | return step.fail( | ||
| 389 | \\ | ||
| 390 | \\========= expected not to find: =================== | ||
| 391 | \\{s} | ||
| 392 | \\========= but parsed file does contain it: ======== | ||
| 393 | \\{s} | ||
| 394 | \\=================================================== | ||
| 395 | , .{ act.phrase.resolve(b, step), output }); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | }, | ||
| 399 | .compute_cmp => { | ||
| 400 | const res = act.computeCmp(b, step, vars) catch |err| switch (err) { | ||
| 401 | error.UnknownVariable => { | ||
| 402 | return step.fail( | ||
| 403 | \\========= from parsed file: ===================== | ||
| 404 | \\{s} | ||
| 405 | \\================================================= | ||
| 406 | , .{output}); | ||
| 407 | }, | ||
| 408 | else => |e| return e, | ||
| 409 | }; | ||
| 410 | if (!res) { | ||
| 411 | return step.fail( | ||
| 412 | \\ | ||
| 413 | \\========= comparison failed for action: =========== | ||
| 414 | \\{s} {} | ||
| 415 | \\========= from parsed file: ======================= | ||
| 416 | \\{s} | ||
| 417 | \\=================================================== | ||
| 418 | , .{ act.phrase.resolve(b, step), act.expected.?, output }); | ||
| 419 | } | ||
| 420 | }, | ||
| 421 | } | ||
| 422 | } | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | const Opts = struct { | ||
| 427 | dump_symtab: bool = false, | ||
| 428 | }; | ||
| 429 | |||
| 430 | const MachODumper = struct { | ||
| 431 | const LoadCommandIterator = macho.LoadCommandIterator; | ||
| 432 | const symtab_label = "symtab"; | ||
| 433 | |||
| 434 | fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { | ||
| 435 | const gpa = step.owner.allocator; | ||
| 436 | var stream = std.io.fixedBufferStream(bytes); | ||
| 437 | const reader = stream.reader(); | ||
| 438 | |||
| 439 | const hdr = try reader.readStruct(macho.mach_header_64); | ||
| 440 | if (hdr.magic != macho.MH_MAGIC_64) { | ||
| 441 | return error.InvalidMagicNumber; | ||
| 442 | } | ||
| 443 | |||
| 444 | var output = std.ArrayList(u8).init(gpa); | ||
| 445 | const writer = output.writer(); | ||
| 446 | |||
| 447 | var symtab: []const macho.nlist_64 = undefined; | ||
| 448 | var strtab: []const u8 = undefined; | ||
| 449 | var sections = std.ArrayList(macho.section_64).init(gpa); | ||
| 450 | var imports = std.ArrayList([]const u8).init(gpa); | ||
| 451 | |||
| 452 | var it = LoadCommandIterator{ | ||
| 453 | .ncmds = hdr.ncmds, | ||
| 454 | .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 455 | }; | ||
| 456 | var i: usize = 0; | ||
| 457 | while (it.next()) |cmd| { | ||
| 458 | switch (cmd.cmd()) { | ||
| 459 | .SEGMENT_64 => { | ||
| 460 | const seg = cmd.cast(macho.segment_command_64).?; | ||
| 461 | try sections.ensureUnusedCapacity(seg.nsects); | ||
| 462 | for (cmd.getSections()) |sect| { | ||
| 463 | sections.appendAssumeCapacity(sect); | ||
| 464 | } | ||
| 465 | }, | ||
| 466 | .SYMTAB => if (opts.dump_symtab) { | ||
| 467 | const lc = cmd.cast(macho.symtab_command).?; | ||
| 468 | symtab = @ptrCast( | ||
| 469 | [*]const macho.nlist_64, | ||
| 470 | @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), | ||
| 471 | )[0..lc.nsyms]; | ||
| 472 | strtab = bytes[lc.stroff..][0..lc.strsize]; | ||
| 473 | }, | ||
| 474 | .LOAD_DYLIB, | ||
| 475 | .LOAD_WEAK_DYLIB, | ||
| 476 | .REEXPORT_DYLIB, | ||
| 477 | => { | ||
| 478 | try imports.append(cmd.getDylibPathName()); | ||
| 479 | }, | ||
| 480 | else => {}, | ||
| 481 | } | ||
| 482 | |||
| 483 | try dumpLoadCommand(cmd, i, writer); | ||
| 484 | try writer.writeByte('\n'); | ||
| 485 | |||
| 486 | i += 1; | ||
| 487 | } | ||
| 488 | |||
| 489 | if (opts.dump_symtab) { | ||
| 490 | try writer.print("{s}\n", .{symtab_label}); | ||
| 491 | for (symtab) |sym| { | ||
| 492 | if (sym.stab()) continue; | ||
| 493 | const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); | ||
| 494 | if (sym.sect()) { | ||
| 495 | const sect = sections.items[sym.n_sect - 1]; | ||
| 496 | try writer.print("{x} ({s},{s})", .{ | ||
| 497 | sym.n_value, | ||
| 498 | sect.segName(), | ||
| 499 | sect.sectName(), | ||
| 500 | }); | ||
| 501 | if (sym.ext()) { | ||
| 502 | try writer.writeAll(" external"); | ||
| 503 | } | ||
| 504 | try writer.print(" {s}\n", .{sym_name}); | ||
| 505 | } else if (sym.undf()) { | ||
| 506 | const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); | ||
| 507 | const import_name = blk: { | ||
| 508 | if (ordinal <= 0) { | ||
| 509 | if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) | ||
| 510 | break :blk "self import"; | ||
| 511 | if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) | ||
| 512 | break :blk "main executable"; | ||
| 513 | if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) | ||
| 514 | break :blk "flat lookup"; | ||
| 515 | unreachable; | ||
| 516 | } | ||
| 517 | const full_path = imports.items[@bitCast(u16, ordinal) - 1]; | ||
| 518 | const basename = fs.path.basename(full_path); | ||
| 519 | assert(basename.len > 0); | ||
| 520 | const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; | ||
| 521 | break :blk basename[0..ext]; | ||
| 522 | }; | ||
| 523 | try writer.writeAll("(undefined)"); | ||
| 524 | if (sym.weakRef()) { | ||
| 525 | try writer.writeAll(" weak"); | ||
| 526 | } | ||
| 527 | if (sym.ext()) { | ||
| 528 | try writer.writeAll(" external"); | ||
| 529 | } | ||
| 530 | try writer.print(" {s} (from {s})\n", .{ | ||
| 531 | sym_name, | ||
| 532 | import_name, | ||
| 533 | }); | ||
| 534 | } else unreachable; | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | return output.toOwnedSlice(); | ||
| 539 | } | ||
| 540 | |||
| 541 | fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { | ||
| 542 | // print header first | ||
| 543 | try writer.print( | ||
| 544 | \\LC {d} | ||
| 545 | \\cmd {s} | ||
| 546 | \\cmdsize {d} | ||
| 547 | , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); | ||
| 548 | |||
| 549 | switch (lc.cmd()) { | ||
| 550 | .SEGMENT_64 => { | ||
| 551 | const seg = lc.cast(macho.segment_command_64).?; | ||
| 552 | try writer.writeByte('\n'); | ||
| 553 | try writer.print( | ||
| 554 | \\segname {s} | ||
| 555 | \\vmaddr {x} | ||
| 556 | \\vmsize {x} | ||
| 557 | \\fileoff {x} | ||
| 558 | \\filesz {x} | ||
| 559 | , .{ | ||
| 560 | seg.segName(), | ||
| 561 | seg.vmaddr, | ||
| 562 | seg.vmsize, | ||
| 563 | seg.fileoff, | ||
| 564 | seg.filesize, | ||
| 565 | }); | ||
| 566 | |||
| 567 | for (lc.getSections()) |sect| { | ||
| 568 | try writer.writeByte('\n'); | ||
| 569 | try writer.print( | ||
| 570 | \\sectname {s} | ||
| 571 | \\addr {x} | ||
| 572 | \\size {x} | ||
| 573 | \\offset {x} | ||
| 574 | \\align {x} | ||
| 575 | , .{ | ||
| 576 | sect.sectName(), | ||
| 577 | sect.addr, | ||
| 578 | sect.size, | ||
| 579 | sect.offset, | ||
| 580 | sect.@"align", | ||
| 581 | }); | ||
| 582 | } | ||
| 583 | }, | ||
| 584 | |||
| 585 | .ID_DYLIB, | ||
| 586 | .LOAD_DYLIB, | ||
| 587 | .LOAD_WEAK_DYLIB, | ||
| 588 | .REEXPORT_DYLIB, | ||
| 589 | => { | ||
| 590 | const dylib = lc.cast(macho.dylib_command).?; | ||
| 591 | try writer.writeByte('\n'); | ||
| 592 | try writer.print( | ||
| 593 | \\name {s} | ||
| 594 | \\timestamp {d} | ||
| 595 | \\current version {x} | ||
| 596 | \\compatibility version {x} | ||
| 597 | , .{ | ||
| 598 | lc.getDylibPathName(), | ||
| 599 | dylib.dylib.timestamp, | ||
| 600 | dylib.dylib.current_version, | ||
| 601 | dylib.dylib.compatibility_version, | ||
| 602 | }); | ||
| 603 | }, | ||
| 604 | |||
| 605 | .MAIN => { | ||
| 606 | const main = lc.cast(macho.entry_point_command).?; | ||
| 607 | try writer.writeByte('\n'); | ||
| 608 | try writer.print( | ||
| 609 | \\entryoff {x} | ||
| 610 | \\stacksize {x} | ||
| 611 | , .{ main.entryoff, main.stacksize }); | ||
| 612 | }, | ||
| 613 | |||
| 614 | .RPATH => { | ||
| 615 | try writer.writeByte('\n'); | ||
| 616 | try writer.print( | ||
| 617 | \\path {s} | ||
| 618 | , .{ | ||
| 619 | lc.getRpathPathName(), | ||
| 620 | }); | ||
| 621 | }, | ||
| 622 | |||
| 623 | .UUID => { | ||
| 624 | const uuid = lc.cast(macho.uuid_command).?; | ||
| 625 | try writer.writeByte('\n'); | ||
| 626 | try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); | ||
| 627 | }, | ||
| 628 | |||
| 629 | .DATA_IN_CODE, | ||
| 630 | .FUNCTION_STARTS, | ||
| 631 | .CODE_SIGNATURE, | ||
| 632 | => { | ||
| 633 | const llc = lc.cast(macho.linkedit_data_command).?; | ||
| 634 | try writer.writeByte('\n'); | ||
| 635 | try writer.print( | ||
| 636 | \\dataoff {x} | ||
| 637 | \\datasize {x} | ||
| 638 | , .{ llc.dataoff, llc.datasize }); | ||
| 639 | }, | ||
| 640 | |||
| 641 | .DYLD_INFO_ONLY => { | ||
| 642 | const dlc = lc.cast(macho.dyld_info_command).?; | ||
| 643 | try writer.writeByte('\n'); | ||
| 644 | try writer.print( | ||
| 645 | \\rebaseoff {x} | ||
| 646 | \\rebasesize {x} | ||
| 647 | \\bindoff {x} | ||
| 648 | \\bindsize {x} | ||
| 649 | \\weakbindoff {x} | ||
| 650 | \\weakbindsize {x} | ||
| 651 | \\lazybindoff {x} | ||
| 652 | \\lazybindsize {x} | ||
| 653 | \\exportoff {x} | ||
| 654 | \\exportsize {x} | ||
| 655 | , .{ | ||
| 656 | dlc.rebase_off, | ||
| 657 | dlc.rebase_size, | ||
| 658 | dlc.bind_off, | ||
| 659 | dlc.bind_size, | ||
| 660 | dlc.weak_bind_off, | ||
| 661 | dlc.weak_bind_size, | ||
| 662 | dlc.lazy_bind_off, | ||
| 663 | dlc.lazy_bind_size, | ||
| 664 | dlc.export_off, | ||
| 665 | dlc.export_size, | ||
| 666 | }); | ||
| 667 | }, | ||
| 668 | |||
| 669 | .SYMTAB => { | ||
| 670 | const slc = lc.cast(macho.symtab_command).?; | ||
| 671 | try writer.writeByte('\n'); | ||
| 672 | try writer.print( | ||
| 673 | \\symoff {x} | ||
| 674 | \\nsyms {x} | ||
| 675 | \\stroff {x} | ||
| 676 | \\strsize {x} | ||
| 677 | , .{ | ||
| 678 | slc.symoff, | ||
| 679 | slc.nsyms, | ||
| 680 | slc.stroff, | ||
| 681 | slc.strsize, | ||
| 682 | }); | ||
| 683 | }, | ||
| 684 | |||
| 685 | .DYSYMTAB => { | ||
| 686 | const dlc = lc.cast(macho.dysymtab_command).?; | ||
| 687 | try writer.writeByte('\n'); | ||
| 688 | try writer.print( | ||
| 689 | \\ilocalsym {x} | ||
| 690 | \\nlocalsym {x} | ||
| 691 | \\iextdefsym {x} | ||
| 692 | \\nextdefsym {x} | ||
| 693 | \\iundefsym {x} | ||
| 694 | \\nundefsym {x} | ||
| 695 | \\indirectsymoff {x} | ||
| 696 | \\nindirectsyms {x} | ||
| 697 | , .{ | ||
| 698 | dlc.ilocalsym, | ||
| 699 | dlc.nlocalsym, | ||
| 700 | dlc.iextdefsym, | ||
| 701 | dlc.nextdefsym, | ||
| 702 | dlc.iundefsym, | ||
| 703 | dlc.nundefsym, | ||
| 704 | dlc.indirectsymoff, | ||
| 705 | dlc.nindirectsyms, | ||
| 706 | }); | ||
| 707 | }, | ||
| 708 | |||
| 709 | else => {}, | ||
| 710 | } | ||
| 711 | } | ||
| 712 | }; | ||
| 713 | |||
| 714 | const WasmDumper = struct { | ||
| 715 | const symtab_label = "symbols"; | ||
| 716 | |||
| 717 | fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { | ||
| 718 | const gpa = step.owner.allocator; | ||
| 719 | if (opts.dump_symtab) { | ||
| 720 | @panic("TODO: Implement symbol table parsing and dumping"); | ||
| 721 | } | ||
| 722 | |||
| 723 | var fbs = std.io.fixedBufferStream(bytes); | ||
| 724 | const reader = fbs.reader(); | ||
| 725 | |||
| 726 | const buf = try reader.readBytesNoEof(8); | ||
| 727 | if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { | ||
| 728 | return error.InvalidMagicByte; | ||
| 729 | } | ||
| 730 | if (!mem.eql(u8, buf[4..], &std.wasm.version)) { | ||
| 731 | return error.UnsupportedWasmVersion; | ||
| 732 | } | ||
| 733 | |||
| 734 | var output = std.ArrayList(u8).init(gpa); | ||
| 735 | errdefer output.deinit(); | ||
| 736 | const writer = output.writer(); | ||
| 737 | |||
| 738 | while (reader.readByte()) |current_byte| { | ||
| 739 | const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { | ||
| 740 | return step.fail("Found invalid section id '{d}'", .{current_byte}); | ||
| 741 | }; | ||
| 742 | |||
| 743 | const section_length = try std.leb.readULEB128(u32, reader); | ||
| 744 | try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); | ||
| 745 | fbs.pos += section_length; | ||
| 746 | } else |_| {} // reached end of stream | ||
| 747 | |||
| 748 | return output.toOwnedSlice(); | ||
| 749 | } | ||
| 750 | |||
| 751 | fn parseAndDumpSection( | ||
| 752 | step: *Step, | ||
| 753 | section: std.wasm.Section, | ||
| 754 | data: []const u8, | ||
| 755 | writer: anytype, | ||
| 756 | ) !void { | ||
| 757 | var fbs = std.io.fixedBufferStream(data); | ||
| 758 | const reader = fbs.reader(); | ||
| 759 | |||
| 760 | try writer.print( | ||
| 761 | \\Section {s} | ||
| 762 | \\size {d} | ||
| 763 | , .{ @tagName(section), data.len }); | ||
| 764 | |||
| 765 | switch (section) { | ||
| 766 | .type, | ||
| 767 | .import, | ||
| 768 | .function, | ||
| 769 | .table, | ||
| 770 | .memory, | ||
| 771 | .global, | ||
| 772 | .@"export", | ||
| 773 | .element, | ||
| 774 | .code, | ||
| 775 | .data, | ||
| 776 | => { | ||
| 777 | const entries = try std.leb.readULEB128(u32, reader); | ||
| 778 | try writer.print("\nentries {d}\n", .{entries}); | ||
| 779 | try dumpSection(step, section, data[fbs.pos..], entries, writer); | ||
| 780 | }, | ||
| 781 | .custom => { | ||
| 782 | const name_length = try std.leb.readULEB128(u32, reader); | ||
| 783 | const name = data[fbs.pos..][0..name_length]; | ||
| 784 | fbs.pos += name_length; | ||
| 785 | try writer.print("\nname {s}\n", .{name}); | ||
| 786 | |||
| 787 | if (mem.eql(u8, name, "name")) { | ||
| 788 | try parseDumpNames(step, reader, writer, data); | ||
| 789 | } else if (mem.eql(u8, name, "producers")) { | ||
| 790 | try parseDumpProducers(reader, writer, data); | ||
| 791 | } else if (mem.eql(u8, name, "target_features")) { | ||
| 792 | try parseDumpFeatures(reader, writer, data); | ||
| 793 | } | ||
| 794 | // TODO: Implement parsing and dumping other custom sections (such as relocations) | ||
| 795 | }, | ||
| 796 | .start => { | ||
| 797 | const start = try std.leb.readULEB128(u32, reader); | ||
| 798 | try writer.print("\nstart {d}\n", .{start}); | ||
| 799 | }, | ||
| 800 | else => {}, // skip unknown sections | ||
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 | fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { | ||
| 805 | var fbs = std.io.fixedBufferStream(data); | ||
| 806 | const reader = fbs.reader(); | ||
| 807 | |||
| 808 | switch (section) { | ||
| 809 | .type => { | ||
| 810 | var i: u32 = 0; | ||
| 811 | while (i < entries) : (i += 1) { | ||
| 812 | const func_type = try reader.readByte(); | ||
| 813 | if (func_type != std.wasm.function_type) { | ||
| 814 | return step.fail("expected function type, found byte '{d}'", .{func_type}); | ||
| 815 | } | ||
| 816 | const params = try std.leb.readULEB128(u32, reader); | ||
| 817 | try writer.print("params {d}\n", .{params}); | ||
| 818 | var index: u32 = 0; | ||
| 819 | while (index < params) : (index += 1) { | ||
| 820 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 821 | } else index = 0; | ||
| 822 | const returns = try std.leb.readULEB128(u32, reader); | ||
| 823 | try writer.print("returns {d}\n", .{returns}); | ||
| 824 | while (index < returns) : (index += 1) { | ||
| 825 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 826 | } | ||
| 827 | } | ||
| 828 | }, | ||
| 829 | .import => { | ||
| 830 | var i: u32 = 0; | ||
| 831 | while (i < entries) : (i += 1) { | ||
| 832 | const module_name_len = try std.leb.readULEB128(u32, reader); | ||
| 833 | const module_name = data[fbs.pos..][0..module_name_len]; | ||
| 834 | fbs.pos += module_name_len; | ||
| 835 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 836 | const name = data[fbs.pos..][0..name_len]; | ||
| 837 | fbs.pos += name_len; | ||
| 838 | |||
| 839 | const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { | ||
| 840 | return step.fail("invalid import kind", .{}); | ||
| 841 | }; | ||
| 842 | |||
| 843 | try writer.print( | ||
| 844 | \\module {s} | ||
| 845 | \\name {s} | ||
| 846 | \\kind {s} | ||
| 847 | , .{ module_name, name, @tagName(kind) }); | ||
| 848 | try writer.writeByte('\n'); | ||
| 849 | switch (kind) { | ||
| 850 | .function => { | ||
| 851 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 852 | }, | ||
| 853 | .memory => { | ||
| 854 | try parseDumpLimits(reader, writer); | ||
| 855 | }, | ||
| 856 | .global => { | ||
| 857 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 858 | try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); | ||
| 859 | }, | ||
| 860 | .table => { | ||
| 861 | try parseDumpType(step, std.wasm.RefType, reader, writer); | ||
| 862 | try parseDumpLimits(reader, writer); | ||
| 863 | }, | ||
| 864 | } | ||
| 865 | } | ||
| 866 | }, | ||
| 867 | .function => { | ||
| 868 | var i: u32 = 0; | ||
| 869 | while (i < entries) : (i += 1) { | ||
| 870 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 871 | } | ||
| 872 | }, | ||
| 873 | .table => { | ||
| 874 | var i: u32 = 0; | ||
| 875 | while (i < entries) : (i += 1) { | ||
| 876 | try parseDumpType(step, std.wasm.RefType, reader, writer); | ||
| 877 | try parseDumpLimits(reader, writer); | ||
| 878 | } | ||
| 879 | }, | ||
| 880 | .memory => { | ||
| 881 | var i: u32 = 0; | ||
| 882 | while (i < entries) : (i += 1) { | ||
| 883 | try parseDumpLimits(reader, writer); | ||
| 884 | } | ||
| 885 | }, | ||
| 886 | .global => { | ||
| 887 | var i: u32 = 0; | ||
| 888 | while (i < entries) : (i += 1) { | ||
| 889 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 890 | try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); | ||
| 891 | try parseDumpInit(step, reader, writer); | ||
| 892 | } | ||
| 893 | }, | ||
| 894 | .@"export" => { | ||
| 895 | var i: u32 = 0; | ||
| 896 | while (i < entries) : (i += 1) { | ||
| 897 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 898 | const name = data[fbs.pos..][0..name_len]; | ||
| 899 | fbs.pos += name_len; | ||
| 900 | const kind_byte = try std.leb.readULEB128(u8, reader); | ||
| 901 | const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { | ||
| 902 | return step.fail("invalid export kind value '{d}'", .{kind_byte}); | ||
| 903 | }; | ||
| 904 | const index = try std.leb.readULEB128(u32, reader); | ||
| 905 | try writer.print( | ||
| 906 | \\name {s} | ||
| 907 | \\kind {s} | ||
| 908 | \\index {d} | ||
| 909 | , .{ name, @tagName(kind), index }); | ||
| 910 | try writer.writeByte('\n'); | ||
| 911 | } | ||
| 912 | }, | ||
| 913 | .element => { | ||
| 914 | var i: u32 = 0; | ||
| 915 | while (i < entries) : (i += 1) { | ||
| 916 | try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 917 | try parseDumpInit(step, reader, writer); | ||
| 918 | |||
| 919 | const function_indexes = try std.leb.readULEB128(u32, reader); | ||
| 920 | var function_index: u32 = 0; | ||
| 921 | try writer.print("indexes {d}\n", .{function_indexes}); | ||
| 922 | while (function_index < function_indexes) : (function_index += 1) { | ||
| 923 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 924 | } | ||
| 925 | } | ||
| 926 | }, | ||
| 927 | .code => {}, // code section is considered opaque to linker | ||
| 928 | .data => { | ||
| 929 | var i: u32 = 0; | ||
| 930 | while (i < entries) : (i += 1) { | ||
| 931 | const index = try std.leb.readULEB128(u32, reader); | ||
| 932 | try writer.print("memory index 0x{x}\n", .{index}); | ||
| 933 | try parseDumpInit(step, reader, writer); | ||
| 934 | const size = try std.leb.readULEB128(u32, reader); | ||
| 935 | try writer.print("size {d}\n", .{size}); | ||
| 936 | try reader.skipBytes(size, .{}); // we do not care about the content of the segments | ||
| 937 | } | ||
| 938 | }, | ||
| 939 | else => unreachable, | ||
| 940 | } | ||
| 941 | } | ||
| 942 | |||
| 943 | fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { | ||
| 944 | const type_byte = try reader.readByte(); | ||
| 945 | const valtype = std.meta.intToEnum(WasmType, type_byte) catch { | ||
| 946 | return step.fail("Invalid wasm type value '{d}'", .{type_byte}); | ||
| 947 | }; | ||
| 948 | try writer.print("type {s}\n", .{@tagName(valtype)}); | ||
| 949 | } | ||
| 950 | |||
| 951 | fn parseDumpLimits(reader: anytype, writer: anytype) !void { | ||
| 952 | const flags = try std.leb.readULEB128(u8, reader); | ||
| 953 | const min = try std.leb.readULEB128(u32, reader); | ||
| 954 | |||
| 955 | try writer.print("min {x}\n", .{min}); | ||
| 956 | if (flags != 0) { | ||
| 957 | try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 958 | } | ||
| 959 | } | ||
| 960 | |||
| 961 | fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { | ||
| 962 | const byte = try std.leb.readULEB128(u8, reader); | ||
| 963 | const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { | ||
| 964 | return step.fail("invalid wasm opcode '{d}'", .{byte}); | ||
| 965 | }; | ||
| 966 | switch (opcode) { | ||
| 967 | .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), | ||
| 968 | .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), | ||
| 969 | .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), | ||
| 970 | .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), | ||
| 971 | .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), | ||
| 972 | else => unreachable, | ||
| 973 | } | ||
| 974 | const end_opcode = try std.leb.readULEB128(u8, reader); | ||
| 975 | if (end_opcode != std.wasm.opcode(.end)) { | ||
| 976 | return step.fail("expected 'end' opcode in init expression", .{}); | ||
| 977 | } | ||
| 978 | } | ||
| 979 | |||
| 980 | fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 981 | while (reader.context.pos < data.len) { | ||
| 982 | try parseDumpType(step, std.wasm.NameSubsection, reader, writer); | ||
| 983 | const size = try std.leb.readULEB128(u32, reader); | ||
| 984 | const entries = try std.leb.readULEB128(u32, reader); | ||
| 985 | try writer.print( | ||
| 986 | \\size {d} | ||
| 987 | \\names {d} | ||
| 988 | , .{ size, entries }); | ||
| 989 | try writer.writeByte('\n'); | ||
| 990 | var i: u32 = 0; | ||
| 991 | while (i < entries) : (i += 1) { | ||
| 992 | const index = try std.leb.readULEB128(u32, reader); | ||
| 993 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 994 | const pos = reader.context.pos; | ||
| 995 | const name = data[pos..][0..name_len]; | ||
| 996 | reader.context.pos += name_len; | ||
| 997 | |||
| 998 | try writer.print( | ||
| 999 | \\index {d} | ||
| 1000 | \\name {s} | ||
| 1001 | , .{ index, name }); | ||
| 1002 | try writer.writeByte('\n'); | ||
| 1003 | } | ||
| 1004 | } | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 1008 | const field_count = try std.leb.readULEB128(u32, reader); | ||
| 1009 | try writer.print("fields {d}\n", .{field_count}); | ||
| 1010 | var current_field: u32 = 0; | ||
| 1011 | while (current_field < field_count) : (current_field += 1) { | ||
| 1012 | const field_name_length = try std.leb.readULEB128(u32, reader); | ||
| 1013 | const field_name = data[reader.context.pos..][0..field_name_length]; | ||
| 1014 | reader.context.pos += field_name_length; | ||
| 1015 | |||
| 1016 | const value_count = try std.leb.readULEB128(u32, reader); | ||
| 1017 | try writer.print( | ||
| 1018 | \\field_name {s} | ||
| 1019 | \\values {d} | ||
| 1020 | , .{ field_name, value_count }); | ||
| 1021 | try writer.writeByte('\n'); | ||
| 1022 | var current_value: u32 = 0; | ||
| 1023 | while (current_value < value_count) : (current_value += 1) { | ||
| 1024 | const value_length = try std.leb.readULEB128(u32, reader); | ||
| 1025 | const value = data[reader.context.pos..][0..value_length]; | ||
| 1026 | reader.context.pos += value_length; | ||
| 1027 | |||
| 1028 | const version_length = try std.leb.readULEB128(u32, reader); | ||
| 1029 | const version = data[reader.context.pos..][0..version_length]; | ||
| 1030 | reader.context.pos += version_length; | ||
| 1031 | |||
| 1032 | try writer.print( | ||
| 1033 | \\value_name {s} | ||
| 1034 | \\version {s} | ||
| 1035 | , .{ value, version }); | ||
| 1036 | try writer.writeByte('\n'); | ||
| 1037 | } | ||
| 1038 | } | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 1042 | const feature_count = try std.leb.readULEB128(u32, reader); | ||
| 1043 | try writer.print("features {d}\n", .{feature_count}); | ||
| 1044 | |||
| 1045 | var index: u32 = 0; | ||
| 1046 | while (index < feature_count) : (index += 1) { | ||
| 1047 | const prefix_byte = try std.leb.readULEB128(u8, reader); | ||
| 1048 | const name_length = try std.leb.readULEB128(u32, reader); | ||
| 1049 | const feature_name = data[reader.context.pos..][0..name_length]; | ||
| 1050 | reader.context.pos += name_length; | ||
| 1051 | |||
| 1052 | try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); | ||
| 1053 | } | ||
| 1054 | } | ||
| 1055 | }; | ||
lib/std/Build/CompileStep.zig deleted-2183| ... | @@ -1,2183 +0,0 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("../std.zig"); | ||
| 3 | const mem = std.mem; | ||
| 4 | const fs = std.fs; | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | const panic = std.debug.panic; | ||
| 7 | const ArrayList = std.ArrayList; | ||
| 8 | const StringHashMap = std.StringHashMap; | ||
| 9 | const Sha256 = std.crypto.hash.sha2.Sha256; | ||
| 10 | const Allocator = mem.Allocator; | ||
| 11 | const Step = std.Build.Step; | ||
| 12 | const CrossTarget = std.zig.CrossTarget; | ||
| 13 | const NativeTargetInfo = std.zig.system.NativeTargetInfo; | ||
| 14 | const FileSource = std.Build.FileSource; | ||
| 15 | const PkgConfigPkg = std.Build.PkgConfigPkg; | ||
| 16 | const PkgConfigError = std.Build.PkgConfigError; | ||
| 17 | const ExecError = std.Build.ExecError; | ||
| 18 | const Module = std.Build.Module; | ||
| 19 | const VcpkgRoot = std.Build.VcpkgRoot; | ||
| 20 | const InstallDir = std.Build.InstallDir; | ||
| 21 | const InstallArtifactStep = std.Build.InstallArtifactStep; | ||
| 22 | const GeneratedFile = std.Build.GeneratedFile; | ||
| 23 | const ObjCopyStep = std.Build.ObjCopyStep; | ||
| 24 | const CheckObjectStep = std.Build.CheckObjectStep; | ||
| 25 | const RunStep = std.Build.RunStep; | ||
| 26 | const OptionsStep = std.Build.OptionsStep; | ||
| 27 | const ConfigHeaderStep = std.Build.ConfigHeaderStep; | ||
| 28 | const CompileStep = @This(); | ||
| 29 | |||
| 30 | pub const base_id: Step.Id = .compile; | ||
| 31 | |||
| 32 | step: Step, | ||
| 33 | name: []const u8, | ||
| 34 | target: CrossTarget, | ||
| 35 | target_info: NativeTargetInfo, | ||
| 36 | optimize: std.builtin.Mode, | ||
| 37 | linker_script: ?FileSource = null, | ||
| 38 | version_script: ?[]const u8 = null, | ||
| 39 | out_filename: []const u8, | ||
| 40 | linkage: ?Linkage = null, | ||
| 41 | version: ?std.builtin.Version, | ||
| 42 | kind: Kind, | ||
| 43 | major_only_filename: ?[]const u8, | ||
| 44 | name_only_filename: ?[]const u8, | ||
| 45 | strip: ?bool, | ||
| 46 | unwind_tables: ?bool, | ||
| 47 | // keep in sync with src/link.zig:CompressDebugSections | ||
| 48 | compress_debug_sections: enum { none, zlib } = .none, | ||
| 49 | lib_paths: ArrayList(FileSource), | ||
| 50 | rpaths: ArrayList(FileSource), | ||
| 51 | framework_dirs: ArrayList(FileSource), | ||
| 52 | frameworks: StringHashMap(FrameworkLinkInfo), | ||
| 53 | verbose_link: bool, | ||
| 54 | verbose_cc: bool, | ||
| 55 | emit_analysis: EmitOption = .default, | ||
| 56 | emit_asm: EmitOption = .default, | ||
| 57 | emit_bin: EmitOption = .default, | ||
| 58 | emit_docs: EmitOption = .default, | ||
| 59 | emit_implib: EmitOption = .default, | ||
| 60 | emit_llvm_bc: EmitOption = .default, | ||
| 61 | emit_llvm_ir: EmitOption = .default, | ||
| 62 | // Lots of things depend on emit_h having a consistent path, | ||
| 63 | // so it is not an EmitOption for now. | ||
| 64 | emit_h: bool = false, | ||
| 65 | bundle_compiler_rt: ?bool = null, | ||
| 66 | single_threaded: ?bool, | ||
| 67 | stack_protector: ?bool = null, | ||
| 68 | disable_stack_probing: bool, | ||
| 69 | disable_sanitize_c: bool, | ||
| 70 | sanitize_thread: bool, | ||
| 71 | rdynamic: bool, | ||
| 72 | dwarf_format: ?std.dwarf.Format = null, | ||
| 73 | import_memory: bool = false, | ||
| 74 | /// For WebAssembly targets, this will allow for undefined symbols to | ||
| 75 | /// be imported from the host environment. | ||
| 76 | import_symbols: bool = false, | ||
| 77 | import_table: bool = false, | ||
| 78 | export_table: bool = false, | ||
| 79 | initial_memory: ?u64 = null, | ||
| 80 | max_memory: ?u64 = null, | ||
| 81 | shared_memory: bool = false, | ||
| 82 | global_base: ?u64 = null, | ||
| 83 | c_std: std.Build.CStd, | ||
| 84 | zig_lib_dir: ?[]const u8, | ||
| 85 | main_pkg_path: ?[]const u8, | ||
| 86 | exec_cmd_args: ?[]const ?[]const u8, | ||
| 87 | filter: ?[]const u8, | ||
| 88 | test_evented_io: bool = false, | ||
| 89 | test_runner: ?[]const u8, | ||
| 90 | code_model: std.builtin.CodeModel = .default, | ||
| 91 | wasi_exec_model: ?std.builtin.WasiExecModel = null, | ||
| 92 | /// Symbols to be exported when compiling to wasm | ||
| 93 | export_symbol_names: []const []const u8 = &.{}, | ||
| 94 | |||
| 95 | root_src: ?FileSource, | ||
| 96 | out_h_filename: []const u8, | ||
| 97 | out_lib_filename: []const u8, | ||
| 98 | out_pdb_filename: []const u8, | ||
| 99 | modules: std.StringArrayHashMap(*Module), | ||
| 100 | |||
| 101 | link_objects: ArrayList(LinkObject), | ||
| 102 | include_dirs: ArrayList(IncludeDir), | ||
| 103 | c_macros: ArrayList([]const u8), | ||
| 104 | installed_headers: ArrayList(*Step), | ||
| 105 | is_linking_libc: bool, | ||
| 106 | is_linking_libcpp: bool, | ||
| 107 | vcpkg_bin_path: ?[]const u8 = null, | ||
| 108 | |||
| 109 | /// This may be set in order to override the default install directory | ||
| 110 | override_dest_dir: ?InstallDir, | ||
| 111 | installed_path: ?[]const u8, | ||
| 112 | |||
| 113 | /// Base address for an executable image. | ||
| 114 | image_base: ?u64 = null, | ||
| 115 | |||
| 116 | libc_file: ?FileSource = null, | ||
| 117 | |||
| 118 | valgrind_support: ?bool = null, | ||
| 119 | each_lib_rpath: ?bool = null, | ||
| 120 | /// On ELF targets, this will emit a link section called ".note.gnu.build-id" | ||
| 121 | /// which can be used to coordinate a stripped binary with its debug symbols. | ||
| 122 | /// As an example, the bloaty project refuses to work unless its inputs have | ||
| 123 | /// build ids, in order to prevent accidental mismatches. | ||
| 124 | /// The default is to not include this section because it slows down linking. | ||
| 125 | build_id: ?bool = null, | ||
| 126 | |||
| 127 | /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF | ||
| 128 | /// file. | ||
| 129 | link_eh_frame_hdr: bool = false, | ||
| 130 | link_emit_relocs: bool = false, | ||
| 131 | |||
| 132 | /// Place every function in its own section so that unused ones may be | ||
| 133 | /// safely garbage-collected during the linking phase. | ||
| 134 | link_function_sections: bool = false, | ||
| 135 | |||
| 136 | /// Remove functions and data that are unreachable by the entry point or | ||
| 137 | /// exported symbols. | ||
| 138 | link_gc_sections: ?bool = null, | ||
| 139 | |||
| 140 | /// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument. | ||
| 141 | linker_dynamicbase: bool = true, | ||
| 142 | |||
| 143 | linker_allow_shlib_undefined: ?bool = null, | ||
| 144 | |||
| 145 | /// Permit read-only relocations in read-only segments. Disallowed by default. | ||
| 146 | link_z_notext: bool = false, | ||
| 147 | |||
| 148 | /// Force all relocations to be read-only after processing. | ||
| 149 | link_z_relro: bool = true, | ||
| 150 | |||
| 151 | /// Allow relocations to be lazily processed after load. | ||
| 152 | link_z_lazy: bool = false, | ||
| 153 | |||
| 154 | /// Common page size | ||
| 155 | link_z_common_page_size: ?u64 = null, | ||
| 156 | |||
| 157 | /// Maximum page size | ||
| 158 | link_z_max_page_size: ?u64 = null, | ||
| 159 | |||
| 160 | /// (Darwin) Install name for the dylib | ||
| 161 | install_name: ?[]const u8 = null, | ||
| 162 | |||
| 163 | /// (Darwin) Path to entitlements file | ||
| 164 | entitlements: ?[]const u8 = null, | ||
| 165 | |||
| 166 | /// (Darwin) Size of the pagezero segment. | ||
| 167 | pagezero_size: ?u64 = null, | ||
| 168 | |||
| 169 | /// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. | ||
| 170 | /// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` | ||
| 171 | /// option. | ||
| 172 | /// By default, if no option is specified, the linker assumes `paths_first` as the default | ||
| 173 | /// search strategy. | ||
| 174 | search_strategy: ?enum { paths_first, dylibs_first } = null, | ||
| 175 | |||
| 176 | /// (Darwin) Set size of the padding between the end of load commands | ||
| 177 | /// and start of `__TEXT,__text` section. | ||
| 178 | headerpad_size: ?u32 = null, | ||
| 179 | |||
| 180 | /// (Darwin) Automatically Set size of the padding between the end of load commands | ||
| 181 | /// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. | ||
| 182 | headerpad_max_install_names: bool = false, | ||
| 183 | |||
| 184 | /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. | ||
| 185 | dead_strip_dylibs: bool = false, | ||
| 186 | |||
| 187 | /// Position Independent Code | ||
| 188 | force_pic: ?bool = null, | ||
| 189 | |||
| 190 | /// Position Independent Executable | ||
| 191 | pie: ?bool = null, | ||
| 192 | |||
| 193 | red_zone: ?bool = null, | ||
| 194 | |||
| 195 | omit_frame_pointer: ?bool = null, | ||
| 196 | dll_export_fns: ?bool = null, | ||
| 197 | |||
| 198 | subsystem: ?std.Target.SubSystem = null, | ||
| 199 | |||
| 200 | entry_symbol_name: ?[]const u8 = null, | ||
| 201 | |||
| 202 | /// List of symbols forced as undefined in the symbol table | ||
| 203 | /// thus forcing their resolution by the linker. | ||
| 204 | /// Corresponds to `-u <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE. | ||
| 205 | force_undefined_symbols: std.StringHashMap(void), | ||
| 206 | |||
| 207 | /// Overrides the default stack size | ||
| 208 | stack_size: ?u64 = null, | ||
| 209 | |||
| 210 | want_lto: ?bool = null, | ||
| 211 | use_llvm: ?bool, | ||
| 212 | use_lld: ?bool, | ||
| 213 | |||
| 214 | /// This is an advanced setting that can change the intent of this CompileStep. | ||
| 215 | /// If this slice has nonzero length, it means that this CompileStep exists to | ||
| 216 | /// check for compile errors and return *success* if they match, and failure | ||
| 217 | /// otherwise. | ||
| 218 | expect_errors: []const []const u8 = &.{}, | ||
| 219 | |||
| 220 | output_path_source: GeneratedFile, | ||
| 221 | output_lib_path_source: GeneratedFile, | ||
| 222 | output_h_path_source: GeneratedFile, | ||
| 223 | output_pdb_path_source: GeneratedFile, | ||
| 224 | output_dirname_source: GeneratedFile, | ||
| 225 | |||
| 226 | pub const CSourceFiles = struct { | ||
| 227 | files: []const []const u8, | ||
| 228 | flags: []const []const u8, | ||
| 229 | }; | ||
| 230 | |||
| 231 | pub const CSourceFile = struct { | ||
| 232 | source: FileSource, | ||
| 233 | args: []const []const u8, | ||
| 234 | |||
| 235 | pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { | ||
| 236 | return .{ | ||
| 237 | .source = self.source.dupe(b), | ||
| 238 | .args = b.dupeStrings(self.args), | ||
| 239 | }; | ||
| 240 | } | ||
| 241 | }; | ||
| 242 | |||
| 243 | pub const LinkObject = union(enum) { | ||
| 244 | static_path: FileSource, | ||
| 245 | other_step: *CompileStep, | ||
| 246 | system_lib: SystemLib, | ||
| 247 | assembly_file: FileSource, | ||
| 248 | c_source_file: *CSourceFile, | ||
| 249 | c_source_files: *CSourceFiles, | ||
| 250 | }; | ||
| 251 | |||
| 252 | pub const SystemLib = struct { | ||
| 253 | name: []const u8, | ||
| 254 | needed: bool, | ||
| 255 | weak: bool, | ||
| 256 | use_pkg_config: enum { | ||
| 257 | /// Don't use pkg-config, just pass -lfoo where foo is name. | ||
| 258 | no, | ||
| 259 | /// Try to get information on how to link the library from pkg-config. | ||
| 260 | /// If that fails, fall back to passing -lfoo where foo is name. | ||
| 261 | yes, | ||
| 262 | /// Try to get information on how to link the library from pkg-config. | ||
| 263 | /// If that fails, error out. | ||
| 264 | force, | ||
| 265 | }, | ||
| 266 | }; | ||
| 267 | |||
| 268 | const FrameworkLinkInfo = struct { | ||
| 269 | needed: bool = false, | ||
| 270 | weak: bool = false, | ||
| 271 | }; | ||
| 272 | |||
| 273 | pub const IncludeDir = union(enum) { | ||
| 274 | raw_path: []const u8, | ||
| 275 | raw_path_system: []const u8, | ||
| 276 | other_step: *CompileStep, | ||
| 277 | config_header_step: *ConfigHeaderStep, | ||
| 278 | }; | ||
| 279 | |||
| 280 | pub const Options = struct { | ||
| 281 | name: []const u8, | ||
| 282 | root_source_file: ?FileSource = null, | ||
| 283 | target: CrossTarget, | ||
| 284 | optimize: std.builtin.Mode, | ||
| 285 | kind: Kind, | ||
| 286 | linkage: ?Linkage = null, | ||
| 287 | version: ?std.builtin.Version = null, | ||
| 288 | max_rss: usize = 0, | ||
| 289 | filter: ?[]const u8 = null, | ||
| 290 | test_runner: ?[]const u8 = null, | ||
| 291 | link_libc: ?bool = null, | ||
| 292 | single_threaded: ?bool = null, | ||
| 293 | use_llvm: ?bool = null, | ||
| 294 | use_lld: ?bool = null, | ||
| 295 | }; | ||
| 296 | |||
| 297 | pub const Kind = enum { | ||
| 298 | exe, | ||
| 299 | lib, | ||
| 300 | obj, | ||
| 301 | @"test", | ||
| 302 | }; | ||
| 303 | |||
| 304 | pub const Linkage = enum { dynamic, static }; | ||
| 305 | |||
| 306 | pub const EmitOption = union(enum) { | ||
| 307 | default: void, | ||
| 308 | no_emit: void, | ||
| 309 | emit: void, | ||
| 310 | emit_to: []const u8, | ||
| 311 | |||
| 312 | fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 { | ||
| 313 | return switch (self) { | ||
| 314 | .no_emit => b.fmt("-fno-{s}", .{arg_name}), | ||
| 315 | .default => null, | ||
| 316 | .emit => b.fmt("-f{s}", .{arg_name}), | ||
| 317 | .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }), | ||
| 318 | }; | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | |||
| 322 | pub fn create(owner: *std.Build, options: Options) *CompileStep { | ||
| 323 | const name = owner.dupe(options.name); | ||
| 324 | const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; | ||
| 325 | if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { | ||
| 326 | panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); | ||
| 327 | } | ||
| 328 | |||
| 329 | // Avoid the common case of the step name looking like "zig test test". | ||
| 330 | const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test")) | ||
| 331 | "" | ||
| 332 | else | ||
| 333 | owner.fmt("{s} ", .{name}); | ||
| 334 | |||
| 335 | const step_name = owner.fmt("{s} {s}{s} {s}", .{ | ||
| 336 | switch (options.kind) { | ||
| 337 | .exe => "zig build-exe", | ||
| 338 | .lib => "zig build-lib", | ||
| 339 | .obj => "zig build-obj", | ||
| 340 | .@"test" => "zig test", | ||
| 341 | }, | ||
| 342 | name_adjusted, | ||
| 343 | @tagName(options.optimize), | ||
| 344 | options.target.zigTriple(owner.allocator) catch @panic("OOM"), | ||
| 345 | }); | ||
| 346 | |||
| 347 | const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error"); | ||
| 348 | |||
| 349 | const out_filename = std.zig.binNameAlloc(owner.allocator, .{ | ||
| 350 | .root_name = name, | ||
| 351 | .target = target_info.target, | ||
| 352 | .output_mode = switch (options.kind) { | ||
| 353 | .lib => .Lib, | ||
| 354 | .obj => .Obj, | ||
| 355 | .exe, .@"test" => .Exe, | ||
| 356 | }, | ||
| 357 | .link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) { | ||
| 358 | .dynamic => .Dynamic, | ||
| 359 | .static => .Static, | ||
| 360 | }) else null, | ||
| 361 | .version = options.version, | ||
| 362 | }) catch @panic("OOM"); | ||
| 363 | |||
| 364 | const self = owner.allocator.create(CompileStep) catch @panic("OOM"); | ||
| 365 | self.* = CompileStep{ | ||
| 366 | .strip = null, | ||
| 367 | .unwind_tables = null, | ||
| 368 | .verbose_link = false, | ||
| 369 | .verbose_cc = false, | ||
| 370 | .optimize = options.optimize, | ||
| 371 | .target = options.target, | ||
| 372 | .linkage = options.linkage, | ||
| 373 | .kind = options.kind, | ||
| 374 | .root_src = root_src, | ||
| 375 | .name = name, | ||
| 376 | .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), | ||
| 377 | .step = Step.init(.{ | ||
| 378 | .id = base_id, | ||
| 379 | .name = step_name, | ||
| 380 | .owner = owner, | ||
| 381 | .makeFn = make, | ||
| 382 | .max_rss = options.max_rss, | ||
| 383 | }), | ||
| 384 | .version = options.version, | ||
| 385 | .out_filename = out_filename, | ||
| 386 | .out_h_filename = owner.fmt("{s}.h", .{name}), | ||
| 387 | .out_lib_filename = undefined, | ||
| 388 | .out_pdb_filename = owner.fmt("{s}.pdb", .{name}), | ||
| 389 | .major_only_filename = null, | ||
| 390 | .name_only_filename = null, | ||
| 391 | .modules = std.StringArrayHashMap(*Module).init(owner.allocator), | ||
| 392 | .include_dirs = ArrayList(IncludeDir).init(owner.allocator), | ||
| 393 | .link_objects = ArrayList(LinkObject).init(owner.allocator), | ||
| 394 | .c_macros = ArrayList([]const u8).init(owner.allocator), | ||
| 395 | .lib_paths = ArrayList(FileSource).init(owner.allocator), | ||
| 396 | .rpaths = ArrayList(FileSource).init(owner.allocator), | ||
| 397 | .framework_dirs = ArrayList(FileSource).init(owner.allocator), | ||
| 398 | .installed_headers = ArrayList(*Step).init(owner.allocator), | ||
| 399 | .c_std = std.Build.CStd.C99, | ||
| 400 | .zig_lib_dir = null, | ||
| 401 | .main_pkg_path = null, | ||
| 402 | .exec_cmd_args = null, | ||
| 403 | .filter = options.filter, | ||
| 404 | .test_runner = options.test_runner, | ||
| 405 | .disable_stack_probing = false, | ||
| 406 | .disable_sanitize_c = false, | ||
| 407 | .sanitize_thread = false, | ||
| 408 | .rdynamic = false, | ||
| 409 | .override_dest_dir = null, | ||
| 410 | .installed_path = null, | ||
| 411 | .force_undefined_symbols = StringHashMap(void).init(owner.allocator), | ||
| 412 | |||
| 413 | .output_path_source = GeneratedFile{ .step = &self.step }, | ||
| 414 | .output_lib_path_source = GeneratedFile{ .step = &self.step }, | ||
| 415 | .output_h_path_source = GeneratedFile{ .step = &self.step }, | ||
| 416 | .output_pdb_path_source = GeneratedFile{ .step = &self.step }, | ||
| 417 | .output_dirname_source = GeneratedFile{ .step = &self.step }, | ||
| 418 | |||
| 419 | .target_info = target_info, | ||
| 420 | |||
| 421 | .is_linking_libc = options.link_libc orelse false, | ||
| 422 | .is_linking_libcpp = false, | ||
| 423 | .single_threaded = options.single_threaded, | ||
| 424 | .use_llvm = options.use_llvm, | ||
| 425 | .use_lld = options.use_lld, | ||
| 426 | }; | ||
| 427 | |||
| 428 | if (self.kind == .lib) { | ||
| 429 | if (self.linkage != null and self.linkage.? == .static) { | ||
| 430 | self.out_lib_filename = self.out_filename; | ||
| 431 | } else if (self.version) |version| { | ||
| 432 | if (target_info.target.isDarwin()) { | ||
| 433 | self.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{ | ||
| 434 | self.name, | ||
| 435 | version.major, | ||
| 436 | }); | ||
| 437 | self.name_only_filename = owner.fmt("lib{s}.dylib", .{self.name}); | ||
| 438 | self.out_lib_filename = self.out_filename; | ||
| 439 | } else if (target_info.target.os.tag == .windows) { | ||
| 440 | self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); | ||
| 441 | } else { | ||
| 442 | self.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ self.name, version.major }); | ||
| 443 | self.name_only_filename = owner.fmt("lib{s}.so", .{self.name}); | ||
| 444 | self.out_lib_filename = self.out_filename; | ||
| 445 | } | ||
| 446 | } else { | ||
| 447 | if (target_info.target.isDarwin()) { | ||
| 448 | self.out_lib_filename = self.out_filename; | ||
| 449 | } else if (target_info.target.os.tag == .windows) { | ||
| 450 | self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); | ||
| 451 | } else { | ||
| 452 | self.out_lib_filename = self.out_filename; | ||
| 453 | } | ||
| 454 | } | ||
| 455 | } | ||
| 456 | |||
| 457 | if (root_src) |rs| rs.addStepDependencies(&self.step); | ||
| 458 | |||
| 459 | return self; | ||
| 460 | } | ||
| 461 | |||
| 462 | pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { | ||
| 463 | const b = cs.step.owner; | ||
| 464 | const install_file = b.addInstallHeaderFile(src_path, dest_rel_path); | ||
| 465 | b.getInstallStep().dependOn(&install_file.step); | ||
| 466 | cs.installed_headers.append(&install_file.step) catch @panic("OOM"); | ||
| 467 | } | ||
| 468 | |||
| 469 | pub const InstallConfigHeaderOptions = struct { | ||
| 470 | install_dir: InstallDir = .header, | ||
| 471 | dest_rel_path: ?[]const u8 = null, | ||
| 472 | }; | ||
| 473 | |||
| 474 | pub fn installConfigHeader( | ||
| 475 | cs: *CompileStep, | ||
| 476 | config_header: *ConfigHeaderStep, | ||
| 477 | options: InstallConfigHeaderOptions, | ||
| 478 | ) void { | ||
| 479 | const dest_rel_path = options.dest_rel_path orelse config_header.include_path; | ||
| 480 | const b = cs.step.owner; | ||
| 481 | const install_file = b.addInstallFileWithDir( | ||
| 482 | .{ .generated = &config_header.output_file }, | ||
| 483 | options.install_dir, | ||
| 484 | dest_rel_path, | ||
| 485 | ); | ||
| 486 | install_file.step.dependOn(&config_header.step); | ||
| 487 | b.getInstallStep().dependOn(&install_file.step); | ||
| 488 | cs.installed_headers.append(&install_file.step) catch @panic("OOM"); | ||
| 489 | } | ||
| 490 | |||
| 491 | pub fn installHeadersDirectory( | ||
| 492 | a: *CompileStep, | ||
| 493 | src_dir_path: []const u8, | ||
| 494 | dest_rel_path: []const u8, | ||
| 495 | ) void { | ||
| 496 | return installHeadersDirectoryOptions(a, .{ | ||
| 497 | .source_dir = src_dir_path, | ||
| 498 | .install_dir = .header, | ||
| 499 | .install_subdir = dest_rel_path, | ||
| 500 | }); | ||
| 501 | } | ||
| 502 | |||
| 503 | pub fn installHeadersDirectoryOptions( | ||
| 504 | cs: *CompileStep, | ||
| 505 | options: std.Build.InstallDirStep.Options, | ||
| 506 | ) void { | ||
| 507 | const b = cs.step.owner; | ||
| 508 | const install_dir = b.addInstallDirectory(options); | ||
| 509 | b.getInstallStep().dependOn(&install_dir.step); | ||
| 510 | cs.installed_headers.append(&install_dir.step) catch @panic("OOM"); | ||
| 511 | } | ||
| 512 | |||
| 513 | pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void { | ||
| 514 | assert(l.kind == .lib); | ||
| 515 | const b = cs.step.owner; | ||
| 516 | const install_step = b.getInstallStep(); | ||
| 517 | // Copy each element from installed_headers, modifying the builder | ||
| 518 | // to be the new parent's builder. | ||
| 519 | for (l.installed_headers.items) |step| { | ||
| 520 | const step_copy = switch (step.id) { | ||
| 521 | inline .install_file, .install_dir => |id| blk: { | ||
| 522 | const T = id.Type(); | ||
| 523 | const ptr = b.allocator.create(T) catch @panic("OOM"); | ||
| 524 | ptr.* = step.cast(T).?.*; | ||
| 525 | ptr.dest_builder = b; | ||
| 526 | break :blk &ptr.step; | ||
| 527 | }, | ||
| 528 | else => unreachable, | ||
| 529 | }; | ||
| 530 | cs.installed_headers.append(step_copy) catch @panic("OOM"); | ||
| 531 | install_step.dependOn(step_copy); | ||
| 532 | } | ||
| 533 | cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); | ||
| 534 | } | ||
| 535 | |||
| 536 | pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep { | ||
| 537 | const b = cs.step.owner; | ||
| 538 | var copy = options; | ||
| 539 | if (copy.basename == null) { | ||
| 540 | if (options.format) |f| { | ||
| 541 | copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); | ||
| 542 | } else { | ||
| 543 | copy.basename = cs.name; | ||
| 544 | } | ||
| 545 | } | ||
| 546 | return b.addObjCopy(cs.getOutputSource(), copy); | ||
| 547 | } | ||
| 548 | |||
| 549 | /// This function would run in the context of the package that created the executable, | ||
| 550 | /// which is undesirable when running an executable provided by a dependency package. | ||
| 551 | pub const run = @compileError("deprecated; use std.Build.addRunArtifact"); | ||
| 552 | |||
| 553 | /// This function would install in the context of the package that created the artifact, | ||
| 554 | /// which is undesirable when installing an artifact provided by a dependency package. | ||
| 555 | pub const install = @compileError("deprecated; use std.Build.installArtifact"); | ||
| 556 | |||
| 557 | pub fn checkObject(self: *CompileStep) *CheckObjectStep { | ||
| 558 | return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); | ||
| 559 | } | ||
| 560 | |||
| 561 | pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { | ||
| 562 | const b = self.step.owner; | ||
| 563 | self.linker_script = source.dupe(b); | ||
| 564 | source.addStepDependencies(&self.step); | ||
| 565 | } | ||
| 566 | |||
| 567 | pub fn forceUndefinedSymbol(self: *CompileStep, symbol_name: []const u8) void { | ||
| 568 | const b = self.step.owner; | ||
| 569 | self.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); | ||
| 570 | } | ||
| 571 | |||
| 572 | pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { | ||
| 573 | const b = self.step.owner; | ||
| 574 | self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); | ||
| 575 | } | ||
| 576 | |||
| 577 | pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void { | ||
| 578 | const b = self.step.owner; | ||
| 579 | self.frameworks.put(b.dupe(framework_name), .{ | ||
| 580 | .needed = true, | ||
| 581 | }) catch @panic("OOM"); | ||
| 582 | } | ||
| 583 | |||
| 584 | pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void { | ||
| 585 | const b = self.step.owner; | ||
| 586 | self.frameworks.put(b.dupe(framework_name), .{ | ||
| 587 | .weak = true, | ||
| 588 | }) catch @panic("OOM"); | ||
| 589 | } | ||
| 590 | |||
| 591 | /// Returns whether the library, executable, or object depends on a particular system library. | ||
| 592 | pub fn dependsOnSystemLibrary(self: CompileStep, name: []const u8) bool { | ||
| 593 | if (isLibCLibrary(name)) { | ||
| 594 | return self.is_linking_libc; | ||
| 595 | } | ||
| 596 | if (isLibCppLibrary(name)) { | ||
| 597 | return self.is_linking_libcpp; | ||
| 598 | } | ||
| 599 | for (self.link_objects.items) |link_object| { | ||
| 600 | switch (link_object) { | ||
| 601 | .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, | ||
| 602 | else => continue, | ||
| 603 | } | ||
| 604 | } | ||
| 605 | return false; | ||
| 606 | } | ||
| 607 | |||
| 608 | pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void { | ||
| 609 | assert(lib.kind == .lib); | ||
| 610 | self.linkLibraryOrObject(lib); | ||
| 611 | } | ||
| 612 | |||
| 613 | pub fn isDynamicLibrary(self: *CompileStep) bool { | ||
| 614 | return self.kind == .lib and self.linkage == Linkage.dynamic; | ||
| 615 | } | ||
| 616 | |||
| 617 | pub fn isStaticLibrary(self: *CompileStep) bool { | ||
| 618 | return self.kind == .lib and self.linkage != Linkage.dynamic; | ||
| 619 | } | ||
| 620 | |||
| 621 | pub fn producesPdbFile(self: *CompileStep) bool { | ||
| 622 | if (!self.target.isWindows() and !self.target.isUefi()) return false; | ||
| 623 | if (self.target.getObjectFormat() == .c) return false; | ||
| 624 | if (self.strip == true) return false; | ||
| 625 | return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; | ||
| 626 | } | ||
| 627 | |||
| 628 | pub fn linkLibC(self: *CompileStep) void { | ||
| 629 | self.is_linking_libc = true; | ||
| 630 | } | ||
| 631 | |||
| 632 | pub fn linkLibCpp(self: *CompileStep) void { | ||
| 633 | self.is_linking_libcpp = true; | ||
| 634 | } | ||
| 635 | |||
| 636 | /// If the value is omitted, it is set to 1. | ||
| 637 | /// `name` and `value` need not live longer than the function call. | ||
| 638 | pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void { | ||
| 639 | const b = self.step.owner; | ||
| 640 | const macro = std.Build.constructCMacro(b.allocator, name, value); | ||
| 641 | self.c_macros.append(macro) catch @panic("OOM"); | ||
| 642 | } | ||
| 643 | |||
| 644 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. | ||
| 645 | pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void { | ||
| 646 | const b = self.step.owner; | ||
| 647 | self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); | ||
| 648 | } | ||
| 649 | |||
| 650 | /// This one has no integration with anything, it just puts -lname on the command line. | ||
| 651 | /// Prefer to use `linkSystemLibrary` instead. | ||
| 652 | pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { | ||
| 653 | const b = self.step.owner; | ||
| 654 | self.link_objects.append(.{ | ||
| 655 | .system_lib = .{ | ||
| 656 | .name = b.dupe(name), | ||
| 657 | .needed = false, | ||
| 658 | .weak = false, | ||
| 659 | .use_pkg_config = .no, | ||
| 660 | }, | ||
| 661 | }) catch @panic("OOM"); | ||
| 662 | } | ||
| 663 | |||
| 664 | /// This one has no integration with anything, it just puts -needed-lname on the command line. | ||
| 665 | /// Prefer to use `linkSystemLibraryNeeded` instead. | ||
| 666 | pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { | ||
| 667 | const b = self.step.owner; | ||
| 668 | self.link_objects.append(.{ | ||
| 669 | .system_lib = .{ | ||
| 670 | .name = b.dupe(name), | ||
| 671 | .needed = true, | ||
| 672 | .weak = false, | ||
| 673 | .use_pkg_config = .no, | ||
| 674 | }, | ||
| 675 | }) catch @panic("OOM"); | ||
| 676 | } | ||
| 677 | |||
| 678 | /// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the | ||
| 679 | /// command line. Prefer to use `linkSystemLibraryWeak` instead. | ||
| 680 | pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { | ||
| 681 | const b = self.step.owner; | ||
| 682 | self.link_objects.append(.{ | ||
| 683 | .system_lib = .{ | ||
| 684 | .name = b.dupe(name), | ||
| 685 | .needed = false, | ||
| 686 | .weak = true, | ||
| 687 | .use_pkg_config = .no, | ||
| 688 | }, | ||
| 689 | }) catch @panic("OOM"); | ||
| 690 | } | ||
| 691 | |||
| 692 | /// This links against a system library, exclusively using pkg-config to find the library. | ||
| 693 | /// Prefer to use `linkSystemLibrary` instead. | ||
| 694 | pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { | ||
| 695 | const b = self.step.owner; | ||
| 696 | self.link_objects.append(.{ | ||
| 697 | .system_lib = .{ | ||
| 698 | .name = b.dupe(lib_name), | ||
| 699 | .needed = false, | ||
| 700 | .weak = false, | ||
| 701 | .use_pkg_config = .force, | ||
| 702 | }, | ||
| 703 | }) catch @panic("OOM"); | ||
| 704 | } | ||
| 705 | |||
| 706 | /// This links against a system library, exclusively using pkg-config to find the library. | ||
| 707 | /// Prefer to use `linkSystemLibraryNeeded` instead. | ||
| 708 | pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { | ||
| 709 | const b = self.step.owner; | ||
| 710 | self.link_objects.append(.{ | ||
| 711 | .system_lib = .{ | ||
| 712 | .name = b.dupe(lib_name), | ||
| 713 | .needed = true, | ||
| 714 | .weak = false, | ||
| 715 | .use_pkg_config = .force, | ||
| 716 | }, | ||
| 717 | }) catch @panic("OOM"); | ||
| 718 | } | ||
| 719 | |||
| 720 | /// Run pkg-config for the given library name and parse the output, returning the arguments | ||
| 721 | /// that should be passed to zig to link the given library. | ||
| 722 | fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { | ||
| 723 | const b = self.step.owner; | ||
| 724 | const pkg_name = match: { | ||
| 725 | // First we have to map the library name to pkg config name. Unfortunately, | ||
| 726 | // there are several examples where this is not straightforward: | ||
| 727 | // -lSDL2 -> pkg-config sdl2 | ||
| 728 | // -lgdk-3 -> pkg-config gdk-3.0 | ||
| 729 | // -latk-1.0 -> pkg-config atk | ||
| 730 | const pkgs = try getPkgConfigList(b); | ||
| 731 | |||
| 732 | // Exact match means instant winner. | ||
| 733 | for (pkgs) |pkg| { | ||
| 734 | if (mem.eql(u8, pkg.name, lib_name)) { | ||
| 735 | break :match pkg.name; | ||
| 736 | } | ||
| 737 | } | ||
| 738 | |||
| 739 | // Next we'll try ignoring case. | ||
| 740 | for (pkgs) |pkg| { | ||
| 741 | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { | ||
| 742 | break :match pkg.name; | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 746 | // Now try appending ".0". | ||
| 747 | for (pkgs) |pkg| { | ||
| 748 | if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { | ||
| 749 | if (pos != 0) continue; | ||
| 750 | if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { | ||
| 751 | break :match pkg.name; | ||
| 752 | } | ||
| 753 | } | ||
| 754 | } | ||
| 755 | |||
| 756 | // Trimming "-1.0". | ||
| 757 | if (mem.endsWith(u8, lib_name, "-1.0")) { | ||
| 758 | const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; | ||
| 759 | for (pkgs) |pkg| { | ||
| 760 | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { | ||
| 761 | break :match pkg.name; | ||
| 762 | } | ||
| 763 | } | ||
| 764 | } | ||
| 765 | |||
| 766 | return error.PackageNotFound; | ||
| 767 | }; | ||
| 768 | |||
| 769 | var code: u8 = undefined; | ||
| 770 | const stdout = if (b.execAllowFail(&[_][]const u8{ | ||
| 771 | "pkg-config", | ||
| 772 | pkg_name, | ||
| 773 | "--cflags", | ||
| 774 | "--libs", | ||
| 775 | }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { | ||
| 776 | error.ProcessTerminated => return error.PkgConfigCrashed, | ||
| 777 | error.ExecNotSupported => return error.PkgConfigFailed, | ||
| 778 | error.ExitCodeFailure => return error.PkgConfigFailed, | ||
| 779 | error.FileNotFound => return error.PkgConfigNotInstalled, | ||
| 780 | else => return err, | ||
| 781 | }; | ||
| 782 | |||
| 783 | var zig_args = ArrayList([]const u8).init(b.allocator); | ||
| 784 | defer zig_args.deinit(); | ||
| 785 | |||
| 786 | var it = mem.tokenize(u8, stdout, " \r\n\t"); | ||
| 787 | while (it.next()) |tok| { | ||
| 788 | if (mem.eql(u8, tok, "-I")) { | ||
| 789 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 790 | try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); | ||
| 791 | } else if (mem.startsWith(u8, tok, "-I")) { | ||
| 792 | try zig_args.append(tok); | ||
| 793 | } else if (mem.eql(u8, tok, "-L")) { | ||
| 794 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 795 | try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); | ||
| 796 | } else if (mem.startsWith(u8, tok, "-L")) { | ||
| 797 | try zig_args.append(tok); | ||
| 798 | } else if (mem.eql(u8, tok, "-l")) { | ||
| 799 | const lib = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 800 | try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); | ||
| 801 | } else if (mem.startsWith(u8, tok, "-l")) { | ||
| 802 | try zig_args.append(tok); | ||
| 803 | } else if (mem.eql(u8, tok, "-D")) { | ||
| 804 | const macro = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 805 | try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); | ||
| 806 | } else if (mem.startsWith(u8, tok, "-D")) { | ||
| 807 | try zig_args.append(tok); | ||
| 808 | } else if (b.debug_pkg_config) { | ||
| 809 | return self.step.fail("unknown pkg-config flag '{s}'", .{tok}); | ||
| 810 | } | ||
| 811 | } | ||
| 812 | |||
| 813 | return zig_args.toOwnedSlice(); | ||
| 814 | } | ||
| 815 | |||
| 816 | pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void { | ||
| 817 | self.linkSystemLibraryInner(name, .{}); | ||
| 818 | } | ||
| 819 | |||
| 820 | pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void { | ||
| 821 | self.linkSystemLibraryInner(name, .{ .needed = true }); | ||
| 822 | } | ||
| 823 | |||
| 824 | pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void { | ||
| 825 | self.linkSystemLibraryInner(name, .{ .weak = true }); | ||
| 826 | } | ||
| 827 | |||
| 828 | fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { | ||
| 829 | needed: bool = false, | ||
| 830 | weak: bool = false, | ||
| 831 | }) void { | ||
| 832 | const b = self.step.owner; | ||
| 833 | if (isLibCLibrary(name)) { | ||
| 834 | self.linkLibC(); | ||
| 835 | return; | ||
| 836 | } | ||
| 837 | if (isLibCppLibrary(name)) { | ||
| 838 | self.linkLibCpp(); | ||
| 839 | return; | ||
| 840 | } | ||
| 841 | |||
| 842 | self.link_objects.append(.{ | ||
| 843 | .system_lib = .{ | ||
| 844 | .name = b.dupe(name), | ||
| 845 | .needed = opts.needed, | ||
| 846 | .weak = opts.weak, | ||
| 847 | .use_pkg_config = .yes, | ||
| 848 | }, | ||
| 849 | }) catch @panic("OOM"); | ||
| 850 | } | ||
| 851 | |||
| 852 | /// Handy when you have many C/C++ source files and want them all to have the same flags. | ||
| 853 | pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void { | ||
| 854 | const b = self.step.owner; | ||
| 855 | const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); | ||
| 856 | |||
| 857 | const files_copy = b.dupeStrings(files); | ||
| 858 | const flags_copy = b.dupeStrings(flags); | ||
| 859 | |||
| 860 | c_source_files.* = .{ | ||
| 861 | .files = files_copy, | ||
| 862 | .flags = flags_copy, | ||
| 863 | }; | ||
| 864 | self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); | ||
| 865 | } | ||
| 866 | |||
| 867 | pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []const u8) void { | ||
| 868 | self.addCSourceFileSource(.{ | ||
| 869 | .args = flags, | ||
| 870 | .source = .{ .path = file }, | ||
| 871 | }); | ||
| 872 | } | ||
| 873 | |||
| 874 | pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void { | ||
| 875 | const b = self.step.owner; | ||
| 876 | const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); | ||
| 877 | c_source_file.* = source.dupe(b); | ||
| 878 | self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); | ||
| 879 | source.source.addStepDependencies(&self.step); | ||
| 880 | } | ||
| 881 | |||
| 882 | pub fn setVerboseLink(self: *CompileStep, value: bool) void { | ||
| 883 | self.verbose_link = value; | ||
| 884 | } | ||
| 885 | |||
| 886 | pub fn setVerboseCC(self: *CompileStep, value: bool) void { | ||
| 887 | self.verbose_cc = value; | ||
| 888 | } | ||
| 889 | |||
| 890 | pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void { | ||
| 891 | const b = self.step.owner; | ||
| 892 | self.zig_lib_dir = b.dupePath(dir_path); | ||
| 893 | } | ||
| 894 | |||
| 895 | pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void { | ||
| 896 | const b = self.step.owner; | ||
| 897 | self.main_pkg_path = b.dupePath(dir_path); | ||
| 898 | } | ||
| 899 | |||
| 900 | pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { | ||
| 901 | const b = self.step.owner; | ||
| 902 | self.libc_file = if (libc_file) |f| f.dupe(b) else null; | ||
| 903 | } | ||
| 904 | |||
| 905 | /// Returns the generated executable, library or object file. | ||
| 906 | /// To run an executable built with zig build, use `run`, or create an install step and invoke it. | ||
| 907 | pub fn getOutputSource(self: *CompileStep) FileSource { | ||
| 908 | return .{ .generated = &self.output_path_source }; | ||
| 909 | } | ||
| 910 | |||
| 911 | pub fn getOutputDirectorySource(self: *CompileStep) FileSource { | ||
| 912 | return .{ .generated = &self.output_dirname_source }; | ||
| 913 | } | ||
| 914 | |||
| 915 | /// Returns the generated import library. This function can only be called for libraries. | ||
| 916 | pub fn getOutputLibSource(self: *CompileStep) FileSource { | ||
| 917 | assert(self.kind == .lib); | ||
| 918 | return .{ .generated = &self.output_lib_path_source }; | ||
| 919 | } | ||
| 920 | |||
| 921 | /// Returns the generated header file. | ||
| 922 | /// This function can only be called for libraries or object files which have `emit_h` set. | ||
| 923 | pub fn getOutputHSource(self: *CompileStep) FileSource { | ||
| 924 | assert(self.kind != .exe and self.kind != .@"test"); | ||
| 925 | assert(self.emit_h); | ||
| 926 | return .{ .generated = &self.output_h_path_source }; | ||
| 927 | } | ||
| 928 | |||
| 929 | /// Returns the generated PDB file. This function can only be called for Windows and UEFI. | ||
| 930 | pub fn getOutputPdbSource(self: *CompileStep) FileSource { | ||
| 931 | // TODO: Is this right? Isn't PDB for *any* PE/COFF file? | ||
| 932 | assert(self.target.isWindows() or self.target.isUefi()); | ||
| 933 | return .{ .generated = &self.output_pdb_path_source }; | ||
| 934 | } | ||
| 935 | |||
| 936 | pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { | ||
| 937 | const b = self.step.owner; | ||
| 938 | self.link_objects.append(.{ | ||
| 939 | .assembly_file = .{ .path = b.dupe(path) }, | ||
| 940 | }) catch @panic("OOM"); | ||
| 941 | } | ||
| 942 | |||
| 943 | pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void { | ||
| 944 | const b = self.step.owner; | ||
| 945 | const source_duped = source.dupe(b); | ||
| 946 | self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); | ||
| 947 | source_duped.addStepDependencies(&self.step); | ||
| 948 | } | ||
| 949 | |||
| 950 | pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void { | ||
| 951 | self.addObjectFileSource(.{ .path = source_file }); | ||
| 952 | } | ||
| 953 | |||
| 954 | pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void { | ||
| 955 | const b = self.step.owner; | ||
| 956 | self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); | ||
| 957 | source.addStepDependencies(&self.step); | ||
| 958 | } | ||
| 959 | |||
| 960 | pub fn addObject(self: *CompileStep, obj: *CompileStep) void { | ||
| 961 | assert(obj.kind == .obj); | ||
| 962 | self.linkLibraryOrObject(obj); | ||
| 963 | } | ||
| 964 | |||
| 965 | pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); | ||
| 966 | pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); | ||
| 967 | pub const addLibPath = @compileError("deprecated, use addLibraryPath"); | ||
| 968 | pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); | ||
| 969 | |||
| 970 | pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void { | ||
| 971 | const b = self.step.owner; | ||
| 972 | self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM"); | ||
| 973 | } | ||
| 974 | |||
| 975 | pub fn addIncludePath(self: *CompileStep, path: []const u8) void { | ||
| 976 | const b = self.step.owner; | ||
| 977 | self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM"); | ||
| 978 | } | ||
| 979 | |||
| 980 | pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void { | ||
| 981 | self.step.dependOn(&config_header.step); | ||
| 982 | self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); | ||
| 983 | } | ||
| 984 | |||
| 985 | pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { | ||
| 986 | const b = self.step.owner; | ||
| 987 | self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); | ||
| 988 | } | ||
| 989 | |||
| 990 | pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 991 | self.lib_paths.append(directory_source) catch @panic("OOM"); | ||
| 992 | directory_source.addStepDependencies(&self.step); | ||
| 993 | } | ||
| 994 | |||
| 995 | pub fn addRPath(self: *CompileStep, path: []const u8) void { | ||
| 996 | const b = self.step.owner; | ||
| 997 | self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); | ||
| 998 | } | ||
| 999 | |||
| 1000 | pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 1001 | self.rpaths.append(directory_source) catch @panic("OOM"); | ||
| 1002 | directory_source.addStepDependencies(&self.step); | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { | ||
| 1006 | const b = self.step.owner; | ||
| 1007 | self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM"); | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 1011 | self.framework_dirs.append(directory_source) catch @panic("OOM"); | ||
| 1012 | directory_source.addStepDependencies(&self.step); | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | /// Adds a module to be used with `@import` and exposing it in the current | ||
| 1016 | /// package's module table using `name`. | ||
| 1017 | pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { | ||
| 1018 | const b = cs.step.owner; | ||
| 1019 | cs.modules.put(b.dupe(name), module) catch @panic("OOM"); | ||
| 1020 | |||
| 1021 | var done = std.AutoHashMap(*Module, void).init(b.allocator); | ||
| 1022 | defer done.deinit(); | ||
| 1023 | cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | /// Adds a module to be used with `@import` without exposing it in the current | ||
| 1027 | /// package's module table. | ||
| 1028 | pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void { | ||
| 1029 | const b = cs.step.owner; | ||
| 1030 | const module = b.createModule(options); | ||
| 1031 | return addModule(cs, name, module); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void { | ||
| 1035 | addModule(cs, module_name, options.createModule()); | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashMap(*Module, void)) !void { | ||
| 1039 | if (done.contains(module)) return; | ||
| 1040 | try done.put(module, {}); | ||
| 1041 | module.source_file.addStepDependencies(&cs.step); | ||
| 1042 | for (module.dependencies.values()) |dep| { | ||
| 1043 | try cs.addRecursiveBuildDeps(dep, done); | ||
| 1044 | } | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | /// If Vcpkg was found on the system, it will be added to include and lib | ||
| 1048 | /// paths for the specified target. | ||
| 1049 | pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { | ||
| 1050 | const b = self.step.owner; | ||
| 1051 | // Ideally in the Unattempted case we would call the function recursively | ||
| 1052 | // after findVcpkgRoot and have only one switch statement, but the compiler | ||
| 1053 | // cannot resolve the error set. | ||
| 1054 | switch (b.vcpkg_root) { | ||
| 1055 | .unattempted => { | ||
| 1056 | b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root| | ||
| 1057 | VcpkgRoot{ .found = root } | ||
| 1058 | else | ||
| 1059 | .not_found; | ||
| 1060 | }, | ||
| 1061 | .not_found => return error.VcpkgNotFound, | ||
| 1062 | .found => {}, | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | switch (b.vcpkg_root) { | ||
| 1066 | .unattempted => unreachable, | ||
| 1067 | .not_found => return error.VcpkgNotFound, | ||
| 1068 | .found => |root| { | ||
| 1069 | const allocator = b.allocator; | ||
| 1070 | const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); | ||
| 1071 | defer b.allocator.free(triplet); | ||
| 1072 | |||
| 1073 | const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); | ||
| 1074 | errdefer allocator.free(include_path); | ||
| 1075 | try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); | ||
| 1076 | |||
| 1077 | const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); | ||
| 1078 | try self.lib_paths.append(.{ .path = lib_path }); | ||
| 1079 | |||
| 1080 | self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); | ||
| 1081 | }, | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void { | ||
| 1086 | const b = self.step.owner; | ||
| 1087 | assert(self.kind == .@"test"); | ||
| 1088 | const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); | ||
| 1089 | for (args, 0..) |arg, i| { | ||
| 1090 | duped_args[i] = if (arg) |a| b.dupe(a) else null; | ||
| 1091 | } | ||
| 1092 | self.exec_cmd_args = duped_args; | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void { | ||
| 1096 | self.step.dependOn(&other.step); | ||
| 1097 | self.link_objects.append(.{ .other_step = other }) catch @panic("OOM"); | ||
| 1098 | self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM"); | ||
| 1099 | |||
| 1100 | for (other.installed_headers.items) |install_step| { | ||
| 1101 | self.step.dependOn(install_step); | ||
| 1102 | } | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | fn appendModuleArgs( | ||
| 1106 | cs: *CompileStep, | ||
| 1107 | zig_args: *ArrayList([]const u8), | ||
| 1108 | ) error{OutOfMemory}!void { | ||
| 1109 | const b = cs.step.owner; | ||
| 1110 | // First, traverse the whole dependency graph and give every module a unique name, ideally one | ||
| 1111 | // named after what it's called somewhere in the graph. It will help here to have both a mapping | ||
| 1112 | // from module to name and a set of all the currently-used names. | ||
| 1113 | var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); | ||
| 1114 | var names = std.StringHashMap(void).init(b.allocator); | ||
| 1115 | |||
| 1116 | var to_name = std.ArrayList(struct { | ||
| 1117 | name: []const u8, | ||
| 1118 | mod: *Module, | ||
| 1119 | }).init(b.allocator); | ||
| 1120 | { | ||
| 1121 | var it = cs.modules.iterator(); | ||
| 1122 | while (it.next()) |kv| { | ||
| 1123 | // While we're traversing the root dependencies, let's make sure that no module names | ||
| 1124 | // have colons in them, since the CLI forbids it. We handle this for transitive | ||
| 1125 | // dependencies further down. | ||
| 1126 | if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { | ||
| 1127 | @panic("Module names cannot contain colons"); | ||
| 1128 | } | ||
| 1129 | try to_name.append(.{ | ||
| 1130 | .name = kv.key_ptr.*, | ||
| 1131 | .mod = kv.value_ptr.*, | ||
| 1132 | }); | ||
| 1133 | } | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | while (to_name.popOrNull()) |dep| { | ||
| 1137 | if (mod_names.contains(dep.mod)) continue; | ||
| 1138 | |||
| 1139 | // We'll use this buffer to store the name we decide on | ||
| 1140 | var buf = try b.allocator.alloc(u8, dep.name.len + 32); | ||
| 1141 | // First, try just the exposed dependency name | ||
| 1142 | @memcpy(buf[0..dep.name.len], dep.name); | ||
| 1143 | var name = buf[0..dep.name.len]; | ||
| 1144 | var n: usize = 0; | ||
| 1145 | while (names.contains(name)) { | ||
| 1146 | // If that failed, append an incrementing number to the end | ||
| 1147 | name = std.fmt.bufPrint(buf, "{s}{}", .{ dep.name, n }) catch unreachable; | ||
| 1148 | n += 1; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | try mod_names.put(dep.mod, name); | ||
| 1152 | try names.put(name, {}); | ||
| 1153 | |||
| 1154 | var it = dep.mod.dependencies.iterator(); | ||
| 1155 | while (it.next()) |kv| { | ||
| 1156 | // Same colon-in-name check as above, but for transitive dependencies. | ||
| 1157 | if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { | ||
| 1158 | @panic("Module names cannot contain colons"); | ||
| 1159 | } | ||
| 1160 | try to_name.append(.{ | ||
| 1161 | .name = kv.key_ptr.*, | ||
| 1162 | .mod = kv.value_ptr.*, | ||
| 1163 | }); | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | // Since the module names given to the CLI are based off of the exposed names, we already know | ||
| 1168 | // that none of the CLI names have colons in them, so there's no need to check that explicitly. | ||
| 1169 | |||
| 1170 | // Every module in the graph is now named; output their definitions | ||
| 1171 | { | ||
| 1172 | var it = mod_names.iterator(); | ||
| 1173 | while (it.next()) |kv| { | ||
| 1174 | const mod = kv.key_ptr.*; | ||
| 1175 | const name = kv.value_ptr.*; | ||
| 1176 | |||
| 1177 | const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); | ||
| 1178 | const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder)); | ||
| 1179 | try zig_args.append("--mod"); | ||
| 1180 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); | ||
| 1181 | } | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | // Lastly, output the root dependencies | ||
| 1185 | const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); | ||
| 1186 | if (deps_str.len > 0) { | ||
| 1187 | try zig_args.append("--deps"); | ||
| 1188 | try zig_args.append(deps_str); | ||
| 1189 | } | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | fn constructDepString( | ||
| 1193 | allocator: std.mem.Allocator, | ||
| 1194 | mod_names: std.AutoHashMap(*Module, []const u8), | ||
| 1195 | deps: std.StringArrayHashMap(*Module), | ||
| 1196 | ) ![]const u8 { | ||
| 1197 | var deps_str = std.ArrayList(u8).init(allocator); | ||
| 1198 | var it = deps.iterator(); | ||
| 1199 | while (it.next()) |kv| { | ||
| 1200 | const expose = kv.key_ptr.*; | ||
| 1201 | const name = mod_names.get(kv.value_ptr.*).?; | ||
| 1202 | if (std.mem.eql(u8, expose, name)) { | ||
| 1203 | try deps_str.writer().print("{s},", .{name}); | ||
| 1204 | } else { | ||
| 1205 | try deps_str.writer().print("{s}={s},", .{ expose, name }); | ||
| 1206 | } | ||
| 1207 | } | ||
| 1208 | if (deps_str.items.len > 0) { | ||
| 1209 | return deps_str.items[0 .. deps_str.items.len - 1]; // omit trailing comma | ||
| 1210 | } else { | ||
| 1211 | return ""; | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 1216 | const b = step.owner; | ||
| 1217 | const self = @fieldParentPtr(CompileStep, "step", step); | ||
| 1218 | |||
| 1219 | if (self.root_src == null and self.link_objects.items.len == 0) { | ||
| 1220 | return step.fail("the linker needs one or more objects to link", .{}); | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | var zig_args = ArrayList([]const u8).init(b.allocator); | ||
| 1224 | defer zig_args.deinit(); | ||
| 1225 | |||
| 1226 | try zig_args.append(b.zig_exe); | ||
| 1227 | |||
| 1228 | const cmd = switch (self.kind) { | ||
| 1229 | .lib => "build-lib", | ||
| 1230 | .exe => "build-exe", | ||
| 1231 | .obj => "build-obj", | ||
| 1232 | .@"test" => "test", | ||
| 1233 | }; | ||
| 1234 | try zig_args.append(cmd); | ||
| 1235 | |||
| 1236 | if (b.reference_trace) |some| { | ||
| 1237 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some})); | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | try addFlag(&zig_args, "LLVM", self.use_llvm); | ||
| 1241 | try addFlag(&zig_args, "LLD", self.use_lld); | ||
| 1242 | |||
| 1243 | if (self.target.ofmt) |ofmt| { | ||
| 1244 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | if (self.entry_symbol_name) |entry| { | ||
| 1248 | try zig_args.append("--entry"); | ||
| 1249 | try zig_args.append(entry); | ||
| 1250 | } | ||
| 1251 | |||
| 1252 | { | ||
| 1253 | var it = self.force_undefined_symbols.keyIterator(); | ||
| 1254 | while (it.next()) |symbol_name| { | ||
| 1255 | try zig_args.append("--force_undefined"); | ||
| 1256 | try zig_args.append(symbol_name.*); | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | |||
| 1260 | if (self.stack_size) |stack_size| { | ||
| 1261 | try zig_args.append("--stack"); | ||
| 1262 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); | ||
| 1266 | |||
| 1267 | // We will add link objects from transitive dependencies, but we want to keep | ||
| 1268 | // all link objects in the same order provided. | ||
| 1269 | // This array is used to keep self.link_objects immutable. | ||
| 1270 | var transitive_deps: TransitiveDeps = .{ | ||
| 1271 | .link_objects = ArrayList(LinkObject).init(b.allocator), | ||
| 1272 | .seen_system_libs = StringHashMap(void).init(b.allocator), | ||
| 1273 | .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), | ||
| 1274 | .is_linking_libcpp = self.is_linking_libcpp, | ||
| 1275 | .is_linking_libc = self.is_linking_libc, | ||
| 1276 | .frameworks = &self.frameworks, | ||
| 1277 | }; | ||
| 1278 | |||
| 1279 | try transitive_deps.seen_steps.put(&self.step, {}); | ||
| 1280 | try transitive_deps.add(self.link_objects.items); | ||
| 1281 | |||
| 1282 | var prev_has_extra_flags = false; | ||
| 1283 | |||
| 1284 | for (transitive_deps.link_objects.items) |link_object| { | ||
| 1285 | switch (link_object) { | ||
| 1286 | .static_path => |static_path| try zig_args.append(static_path.getPath(b)), | ||
| 1287 | |||
| 1288 | .other_step => |other| switch (other.kind) { | ||
| 1289 | .exe => @panic("Cannot link with an executable build artifact"), | ||
| 1290 | .@"test" => @panic("Cannot link with a test"), | ||
| 1291 | .obj => { | ||
| 1292 | try zig_args.append(other.getOutputSource().getPath(b)); | ||
| 1293 | }, | ||
| 1294 | .lib => l: { | ||
| 1295 | if (self.isStaticLibrary() and other.isStaticLibrary()) { | ||
| 1296 | // Avoid putting a static library inside a static library. | ||
| 1297 | break :l; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | const full_path_lib = other.getOutputLibSource().getPath(b); | ||
| 1301 | try zig_args.append(full_path_lib); | ||
| 1302 | |||
| 1303 | if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { | ||
| 1304 | if (fs.path.dirname(full_path_lib)) |dirname| { | ||
| 1305 | try zig_args.append("-rpath"); | ||
| 1306 | try zig_args.append(dirname); | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | }, | ||
| 1310 | }, | ||
| 1311 | |||
| 1312 | .system_lib => |system_lib| { | ||
| 1313 | const prefix: []const u8 = prefix: { | ||
| 1314 | if (system_lib.needed) break :prefix "-needed-l"; | ||
| 1315 | if (system_lib.weak) break :prefix "-weak-l"; | ||
| 1316 | break :prefix "-l"; | ||
| 1317 | }; | ||
| 1318 | switch (system_lib.use_pkg_config) { | ||
| 1319 | .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), | ||
| 1320 | .yes, .force => { | ||
| 1321 | if (self.runPkgConfig(system_lib.name)) |args| { | ||
| 1322 | try zig_args.appendSlice(args); | ||
| 1323 | } else |err| switch (err) { | ||
| 1324 | error.PkgConfigInvalidOutput, | ||
| 1325 | error.PkgConfigCrashed, | ||
| 1326 | error.PkgConfigFailed, | ||
| 1327 | error.PkgConfigNotInstalled, | ||
| 1328 | error.PackageNotFound, | ||
| 1329 | => switch (system_lib.use_pkg_config) { | ||
| 1330 | .yes => { | ||
| 1331 | // pkg-config failed, so fall back to linking the library | ||
| 1332 | // by name directly. | ||
| 1333 | try zig_args.append(b.fmt("{s}{s}", .{ | ||
| 1334 | prefix, | ||
| 1335 | system_lib.name, | ||
| 1336 | })); | ||
| 1337 | }, | ||
| 1338 | .force => { | ||
| 1339 | panic("pkg-config failed for library {s}", .{system_lib.name}); | ||
| 1340 | }, | ||
| 1341 | .no => unreachable, | ||
| 1342 | }, | ||
| 1343 | |||
| 1344 | else => |e| return e, | ||
| 1345 | } | ||
| 1346 | }, | ||
| 1347 | } | ||
| 1348 | }, | ||
| 1349 | |||
| 1350 | .assembly_file => |asm_file| { | ||
| 1351 | if (prev_has_extra_flags) { | ||
| 1352 | try zig_args.append("-extra-cflags"); | ||
| 1353 | try zig_args.append("--"); | ||
| 1354 | prev_has_extra_flags = false; | ||
| 1355 | } | ||
| 1356 | try zig_args.append(asm_file.getPath(b)); | ||
| 1357 | }, | ||
| 1358 | |||
| 1359 | .c_source_file => |c_source_file| { | ||
| 1360 | if (c_source_file.args.len == 0) { | ||
| 1361 | if (prev_has_extra_flags) { | ||
| 1362 | try zig_args.append("-cflags"); | ||
| 1363 | try zig_args.append("--"); | ||
| 1364 | prev_has_extra_flags = false; | ||
| 1365 | } | ||
| 1366 | } else { | ||
| 1367 | try zig_args.append("-cflags"); | ||
| 1368 | for (c_source_file.args) |arg| { | ||
| 1369 | try zig_args.append(arg); | ||
| 1370 | } | ||
| 1371 | try zig_args.append("--"); | ||
| 1372 | } | ||
| 1373 | try zig_args.append(c_source_file.source.getPath(b)); | ||
| 1374 | }, | ||
| 1375 | |||
| 1376 | .c_source_files => |c_source_files| { | ||
| 1377 | if (c_source_files.flags.len == 0) { | ||
| 1378 | if (prev_has_extra_flags) { | ||
| 1379 | try zig_args.append("-cflags"); | ||
| 1380 | try zig_args.append("--"); | ||
| 1381 | prev_has_extra_flags = false; | ||
| 1382 | } | ||
| 1383 | } else { | ||
| 1384 | try zig_args.append("-cflags"); | ||
| 1385 | for (c_source_files.flags) |flag| { | ||
| 1386 | try zig_args.append(flag); | ||
| 1387 | } | ||
| 1388 | try zig_args.append("--"); | ||
| 1389 | } | ||
| 1390 | for (c_source_files.files) |file| { | ||
| 1391 | try zig_args.append(b.pathFromRoot(file)); | ||
| 1392 | } | ||
| 1393 | }, | ||
| 1394 | } | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | if (transitive_deps.is_linking_libcpp) { | ||
| 1398 | try zig_args.append("-lc++"); | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | if (transitive_deps.is_linking_libc) { | ||
| 1402 | try zig_args.append("-lc"); | ||
| 1403 | } | ||
| 1404 | |||
| 1405 | if (self.image_base) |image_base| { | ||
| 1406 | try zig_args.append("--image-base"); | ||
| 1407 | try zig_args.append(b.fmt("0x{x}", .{image_base})); | ||
| 1408 | } | ||
| 1409 | |||
| 1410 | if (self.filter) |filter| { | ||
| 1411 | try zig_args.append("--test-filter"); | ||
| 1412 | try zig_args.append(filter); | ||
| 1413 | } | ||
| 1414 | |||
| 1415 | if (self.test_evented_io) { | ||
| 1416 | try zig_args.append("--test-evented-io"); | ||
| 1417 | } | ||
| 1418 | |||
| 1419 | if (self.test_runner) |test_runner| { | ||
| 1420 | try zig_args.append("--test-runner"); | ||
| 1421 | try zig_args.append(b.pathFromRoot(test_runner)); | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | for (b.debug_log_scopes) |log_scope| { | ||
| 1425 | try zig_args.append("--debug-log"); | ||
| 1426 | try zig_args.append(log_scope); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | if (b.debug_compile_errors) { | ||
| 1430 | try zig_args.append("--debug-compile-errors"); | ||
| 1431 | } | ||
| 1432 | |||
| 1433 | if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); | ||
| 1434 | if (b.verbose_air) try zig_args.append("--verbose-air"); | ||
| 1435 | if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); | ||
| 1436 | if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path})); | ||
| 1437 | if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); | ||
| 1438 | if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); | ||
| 1439 | if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); | ||
| 1440 | |||
| 1441 | if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg); | ||
| 1442 | if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg); | ||
| 1443 | if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg); | ||
| 1444 | if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg); | ||
| 1445 | if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg); | ||
| 1446 | if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg); | ||
| 1447 | if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg); | ||
| 1448 | |||
| 1449 | if (self.emit_h) try zig_args.append("-femit-h"); | ||
| 1450 | |||
| 1451 | try addFlag(&zig_args, "strip", self.strip); | ||
| 1452 | try addFlag(&zig_args, "unwind-tables", self.unwind_tables); | ||
| 1453 | |||
| 1454 | if (self.dwarf_format) |dwarf_format| { | ||
| 1455 | try zig_args.append(switch (dwarf_format) { | ||
| 1456 | .@"32" => "-gdwarf32", | ||
| 1457 | .@"64" => "-gdwarf64", | ||
| 1458 | }); | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | switch (self.compress_debug_sections) { | ||
| 1462 | .none => {}, | ||
| 1463 | .zlib => try zig_args.append("--compress-debug-sections=zlib"), | ||
| 1464 | } | ||
| 1465 | |||
| 1466 | if (self.link_eh_frame_hdr) { | ||
| 1467 | try zig_args.append("--eh-frame-hdr"); | ||
| 1468 | } | ||
| 1469 | if (self.link_emit_relocs) { | ||
| 1470 | try zig_args.append("--emit-relocs"); | ||
| 1471 | } | ||
| 1472 | if (self.link_function_sections) { | ||
| 1473 | try zig_args.append("-ffunction-sections"); | ||
| 1474 | } | ||
| 1475 | if (self.link_gc_sections) |x| { | ||
| 1476 | try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections"); | ||
| 1477 | } | ||
| 1478 | if (!self.linker_dynamicbase) { | ||
| 1479 | try zig_args.append("--no-dynamicbase"); | ||
| 1480 | } | ||
| 1481 | if (self.linker_allow_shlib_undefined) |x| { | ||
| 1482 | try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); | ||
| 1483 | } | ||
| 1484 | if (self.link_z_notext) { | ||
| 1485 | try zig_args.append("-z"); | ||
| 1486 | try zig_args.append("notext"); | ||
| 1487 | } | ||
| 1488 | if (!self.link_z_relro) { | ||
| 1489 | try zig_args.append("-z"); | ||
| 1490 | try zig_args.append("norelro"); | ||
| 1491 | } | ||
| 1492 | if (self.link_z_lazy) { | ||
| 1493 | try zig_args.append("-z"); | ||
| 1494 | try zig_args.append("lazy"); | ||
| 1495 | } | ||
| 1496 | if (self.link_z_common_page_size) |size| { | ||
| 1497 | try zig_args.append("-z"); | ||
| 1498 | try zig_args.append(b.fmt("common-page-size={d}", .{size})); | ||
| 1499 | } | ||
| 1500 | if (self.link_z_max_page_size) |size| { | ||
| 1501 | try zig_args.append("-z"); | ||
| 1502 | try zig_args.append(b.fmt("max-page-size={d}", .{size})); | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | if (self.libc_file) |libc_file| { | ||
| 1506 | try zig_args.append("--libc"); | ||
| 1507 | try zig_args.append(libc_file.getPath(b)); | ||
| 1508 | } else if (b.libc_file) |libc_file| { | ||
| 1509 | try zig_args.append("--libc"); | ||
| 1510 | try zig_args.append(libc_file); | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | switch (self.optimize) { | ||
| 1514 | .Debug => {}, // Skip since it's the default. | ||
| 1515 | else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), | ||
| 1516 | } | ||
| 1517 | |||
| 1518 | try zig_args.append("--cache-dir"); | ||
| 1519 | try zig_args.append(b.cache_root.path orelse "."); | ||
| 1520 | |||
| 1521 | try zig_args.append("--global-cache-dir"); | ||
| 1522 | try zig_args.append(b.global_cache_root.path orelse "."); | ||
| 1523 | |||
| 1524 | try zig_args.append("--name"); | ||
| 1525 | try zig_args.append(self.name); | ||
| 1526 | |||
| 1527 | if (self.linkage) |some| switch (some) { | ||
| 1528 | .dynamic => try zig_args.append("-dynamic"), | ||
| 1529 | .static => try zig_args.append("-static"), | ||
| 1530 | }; | ||
| 1531 | if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { | ||
| 1532 | if (self.version) |version| { | ||
| 1533 | try zig_args.append("--version"); | ||
| 1534 | try zig_args.append(b.fmt("{}", .{version})); | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | if (self.target.isDarwin()) { | ||
| 1538 | const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ | ||
| 1539 | self.target.libPrefix(), | ||
| 1540 | self.name, | ||
| 1541 | self.target.dynamicLibSuffix(), | ||
| 1542 | }); | ||
| 1543 | try zig_args.append("-install_name"); | ||
| 1544 | try zig_args.append(install_name); | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | if (self.entitlements) |entitlements| { | ||
| 1549 | try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); | ||
| 1550 | } | ||
| 1551 | if (self.pagezero_size) |pagezero_size| { | ||
| 1552 | const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); | ||
| 1553 | try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); | ||
| 1554 | } | ||
| 1555 | if (self.search_strategy) |strat| switch (strat) { | ||
| 1556 | .paths_first => try zig_args.append("-search_paths_first"), | ||
| 1557 | .dylibs_first => try zig_args.append("-search_dylibs_first"), | ||
| 1558 | }; | ||
| 1559 | if (self.headerpad_size) |headerpad_size| { | ||
| 1560 | const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); | ||
| 1561 | try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); | ||
| 1562 | } | ||
| 1563 | if (self.headerpad_max_install_names) { | ||
| 1564 | try zig_args.append("-headerpad_max_install_names"); | ||
| 1565 | } | ||
| 1566 | if (self.dead_strip_dylibs) { | ||
| 1567 | try zig_args.append("-dead_strip_dylibs"); | ||
| 1568 | } | ||
| 1569 | |||
| 1570 | try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); | ||
| 1571 | try addFlag(&zig_args, "single-threaded", self.single_threaded); | ||
| 1572 | if (self.disable_stack_probing) { | ||
| 1573 | try zig_args.append("-fno-stack-check"); | ||
| 1574 | } | ||
| 1575 | try addFlag(&zig_args, "stack-protector", self.stack_protector); | ||
| 1576 | if (self.red_zone) |red_zone| { | ||
| 1577 | if (red_zone) { | ||
| 1578 | try zig_args.append("-mred-zone"); | ||
| 1579 | } else { | ||
| 1580 | try zig_args.append("-mno-red-zone"); | ||
| 1581 | } | ||
| 1582 | } | ||
| 1583 | try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); | ||
| 1584 | try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); | ||
| 1585 | |||
| 1586 | if (self.disable_sanitize_c) { | ||
| 1587 | try zig_args.append("-fno-sanitize-c"); | ||
| 1588 | } | ||
| 1589 | if (self.sanitize_thread) { | ||
| 1590 | try zig_args.append("-fsanitize-thread"); | ||
| 1591 | } | ||
| 1592 | if (self.rdynamic) { | ||
| 1593 | try zig_args.append("-rdynamic"); | ||
| 1594 | } | ||
| 1595 | if (self.import_memory) { | ||
| 1596 | try zig_args.append("--import-memory"); | ||
| 1597 | } | ||
| 1598 | if (self.import_symbols) { | ||
| 1599 | try zig_args.append("--import-symbols"); | ||
| 1600 | } | ||
| 1601 | if (self.import_table) { | ||
| 1602 | try zig_args.append("--import-table"); | ||
| 1603 | } | ||
| 1604 | if (self.export_table) { | ||
| 1605 | try zig_args.append("--export-table"); | ||
| 1606 | } | ||
| 1607 | if (self.initial_memory) |initial_memory| { | ||
| 1608 | try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); | ||
| 1609 | } | ||
| 1610 | if (self.max_memory) |max_memory| { | ||
| 1611 | try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); | ||
| 1612 | } | ||
| 1613 | if (self.shared_memory) { | ||
| 1614 | try zig_args.append("--shared-memory"); | ||
| 1615 | } | ||
| 1616 | if (self.global_base) |global_base| { | ||
| 1617 | try zig_args.append(b.fmt("--global-base={d}", .{global_base})); | ||
| 1618 | } | ||
| 1619 | |||
| 1620 | if (self.code_model != .default) { | ||
| 1621 | try zig_args.append("-mcmodel"); | ||
| 1622 | try zig_args.append(@tagName(self.code_model)); | ||
| 1623 | } | ||
| 1624 | if (self.wasi_exec_model) |model| { | ||
| 1625 | try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); | ||
| 1626 | } | ||
| 1627 | for (self.export_symbol_names) |symbol_name| { | ||
| 1628 | try zig_args.append(b.fmt("--export={s}", .{symbol_name})); | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | if (!self.target.isNative()) { | ||
| 1632 | try zig_args.appendSlice(&.{ | ||
| 1633 | "-target", try self.target.zigTriple(b.allocator), | ||
| 1634 | "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), | ||
| 1635 | }); | ||
| 1636 | |||
| 1637 | if (self.target.dynamic_linker.get()) |dynamic_linker| { | ||
| 1638 | try zig_args.append("--dynamic-linker"); | ||
| 1639 | try zig_args.append(dynamic_linker); | ||
| 1640 | } | ||
| 1641 | } | ||
| 1642 | |||
| 1643 | if (self.linker_script) |linker_script| { | ||
| 1644 | try zig_args.append("--script"); | ||
| 1645 | try zig_args.append(linker_script.getPath(b)); | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | if (self.version_script) |version_script| { | ||
| 1649 | try zig_args.append("--version-script"); | ||
| 1650 | try zig_args.append(b.pathFromRoot(version_script)); | ||
| 1651 | } | ||
| 1652 | |||
| 1653 | if (self.kind == .@"test") { | ||
| 1654 | if (self.exec_cmd_args) |exec_cmd_args| { | ||
| 1655 | for (exec_cmd_args) |cmd_arg| { | ||
| 1656 | if (cmd_arg) |arg| { | ||
| 1657 | try zig_args.append("--test-cmd"); | ||
| 1658 | try zig_args.append(arg); | ||
| 1659 | } else { | ||
| 1660 | try zig_args.append("--test-cmd-bin"); | ||
| 1661 | } | ||
| 1662 | } | ||
| 1663 | } | ||
| 1664 | } | ||
| 1665 | |||
| 1666 | try self.appendModuleArgs(&zig_args); | ||
| 1667 | |||
| 1668 | for (self.include_dirs.items) |include_dir| { | ||
| 1669 | switch (include_dir) { | ||
| 1670 | .raw_path => |include_path| { | ||
| 1671 | try zig_args.append("-I"); | ||
| 1672 | try zig_args.append(b.pathFromRoot(include_path)); | ||
| 1673 | }, | ||
| 1674 | .raw_path_system => |include_path| { | ||
| 1675 | if (b.sysroot != null) { | ||
| 1676 | try zig_args.append("-iwithsysroot"); | ||
| 1677 | } else { | ||
| 1678 | try zig_args.append("-isystem"); | ||
| 1679 | } | ||
| 1680 | |||
| 1681 | const resolved_include_path = b.pathFromRoot(include_path); | ||
| 1682 | |||
| 1683 | const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { | ||
| 1684 | // We need to check for disk designator and strip it out from dir path so | ||
| 1685 | // that zig/clang can concat resolved_include_path with sysroot. | ||
| 1686 | const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); | ||
| 1687 | |||
| 1688 | if (mem.indexOf(u8, resolved_include_path, disk_designator)) |where| { | ||
| 1689 | break :blk resolved_include_path[where + disk_designator.len ..]; | ||
| 1690 | } | ||
| 1691 | |||
| 1692 | break :blk resolved_include_path; | ||
| 1693 | } else resolved_include_path; | ||
| 1694 | |||
| 1695 | try zig_args.append(common_include_path); | ||
| 1696 | }, | ||
| 1697 | .other_step => |other| { | ||
| 1698 | if (other.emit_h) { | ||
| 1699 | const h_path = other.getOutputHSource().getPath(b); | ||
| 1700 | try zig_args.append("-isystem"); | ||
| 1701 | try zig_args.append(fs.path.dirname(h_path).?); | ||
| 1702 | } | ||
| 1703 | if (other.installed_headers.items.len > 0) { | ||
| 1704 | try zig_args.append("-I"); | ||
| 1705 | try zig_args.append(b.pathJoin(&.{ | ||
| 1706 | other.step.owner.install_prefix, "include", | ||
| 1707 | })); | ||
| 1708 | } | ||
| 1709 | }, | ||
| 1710 | .config_header_step => |config_header| { | ||
| 1711 | const full_file_path = config_header.output_file.path.?; | ||
| 1712 | const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; | ||
| 1713 | try zig_args.appendSlice(&.{ "-I", header_dir_path }); | ||
| 1714 | }, | ||
| 1715 | } | ||
| 1716 | } | ||
| 1717 | |||
| 1718 | for (self.c_macros.items) |c_macro| { | ||
| 1719 | try zig_args.append("-D"); | ||
| 1720 | try zig_args.append(c_macro); | ||
| 1721 | } | ||
| 1722 | |||
| 1723 | try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); | ||
| 1724 | for (self.lib_paths.items) |lib_path| { | ||
| 1725 | zig_args.appendAssumeCapacity("-L"); | ||
| 1726 | zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); | ||
| 1727 | } | ||
| 1728 | |||
| 1729 | try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); | ||
| 1730 | for (self.rpaths.items) |rpath| { | ||
| 1731 | zig_args.appendAssumeCapacity("-rpath"); | ||
| 1732 | |||
| 1733 | if (self.target_info.target.isDarwin()) switch (rpath) { | ||
| 1734 | .path => |path| { | ||
| 1735 | // On Darwin, we should not try to expand special runtime paths such as | ||
| 1736 | // * @executable_path | ||
| 1737 | // * @loader_path | ||
| 1738 | if (mem.startsWith(u8, path, "@executable_path") or | ||
| 1739 | mem.startsWith(u8, path, "@loader_path")) | ||
| 1740 | { | ||
| 1741 | zig_args.appendAssumeCapacity(path); | ||
| 1742 | continue; | ||
| 1743 | } | ||
| 1744 | }, | ||
| 1745 | .generated => {}, | ||
| 1746 | }; | ||
| 1747 | |||
| 1748 | zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); | ||
| 1749 | } | ||
| 1750 | |||
| 1751 | for (self.framework_dirs.items) |directory_source| { | ||
| 1752 | if (b.sysroot != null) { | ||
| 1753 | try zig_args.append("-iframeworkwithsysroot"); | ||
| 1754 | } else { | ||
| 1755 | try zig_args.append("-iframework"); | ||
| 1756 | } | ||
| 1757 | try zig_args.append(directory_source.getPath2(b, step)); | ||
| 1758 | try zig_args.append("-F"); | ||
| 1759 | try zig_args.append(directory_source.getPath2(b, step)); | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | { | ||
| 1763 | var it = self.frameworks.iterator(); | ||
| 1764 | while (it.next()) |entry| { | ||
| 1765 | const name = entry.key_ptr.*; | ||
| 1766 | const info = entry.value_ptr.*; | ||
| 1767 | if (info.needed) { | ||
| 1768 | try zig_args.append("-needed_framework"); | ||
| 1769 | } else if (info.weak) { | ||
| 1770 | try zig_args.append("-weak_framework"); | ||
| 1771 | } else { | ||
| 1772 | try zig_args.append("-framework"); | ||
| 1773 | } | ||
| 1774 | try zig_args.append(name); | ||
| 1775 | } | ||
| 1776 | } | ||
| 1777 | |||
| 1778 | if (b.sysroot) |sysroot| { | ||
| 1779 | try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | for (b.search_prefixes.items) |search_prefix| { | ||
| 1783 | var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { | ||
| 1784 | return step.fail("unable to open prefix directory '{s}': {s}", .{ | ||
| 1785 | search_prefix, @errorName(err), | ||
| 1786 | }); | ||
| 1787 | }; | ||
| 1788 | defer prefix_dir.close(); | ||
| 1789 | |||
| 1790 | // Avoid passing -L and -I flags for nonexistent directories. | ||
| 1791 | // This prevents a warning, that should probably be upgraded to an error in Zig's | ||
| 1792 | // CLI parsing code, when the linker sees an -L directory that does not exist. | ||
| 1793 | |||
| 1794 | if (prefix_dir.accessZ("lib", .{})) |_| { | ||
| 1795 | try zig_args.appendSlice(&.{ | ||
| 1796 | "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), | ||
| 1797 | }); | ||
| 1798 | } else |err| switch (err) { | ||
| 1799 | error.FileNotFound => {}, | ||
| 1800 | else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ | ||
| 1801 | search_prefix, @errorName(e), | ||
| 1802 | }), | ||
| 1803 | } | ||
| 1804 | |||
| 1805 | if (prefix_dir.accessZ("include", .{})) |_| { | ||
| 1806 | try zig_args.appendSlice(&.{ | ||
| 1807 | "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), | ||
| 1808 | }); | ||
| 1809 | } else |err| switch (err) { | ||
| 1810 | error.FileNotFound => {}, | ||
| 1811 | else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ | ||
| 1812 | search_prefix, @errorName(e), | ||
| 1813 | }), | ||
| 1814 | } | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | try addFlag(&zig_args, "valgrind", self.valgrind_support); | ||
| 1818 | try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); | ||
| 1819 | try addFlag(&zig_args, "build-id", self.build_id); | ||
| 1820 | |||
| 1821 | if (self.zig_lib_dir) |dir| { | ||
| 1822 | try zig_args.append("--zig-lib-dir"); | ||
| 1823 | try zig_args.append(b.pathFromRoot(dir)); | ||
| 1824 | } else if (b.zig_lib_dir) |dir| { | ||
| 1825 | try zig_args.append("--zig-lib-dir"); | ||
| 1826 | try zig_args.append(dir); | ||
| 1827 | } | ||
| 1828 | |||
| 1829 | if (self.main_pkg_path) |dir| { | ||
| 1830 | try zig_args.append("--main-pkg-path"); | ||
| 1831 | try zig_args.append(b.pathFromRoot(dir)); | ||
| 1832 | } | ||
| 1833 | |||
| 1834 | try addFlag(&zig_args, "PIC", self.force_pic); | ||
| 1835 | try addFlag(&zig_args, "PIE", self.pie); | ||
| 1836 | try addFlag(&zig_args, "lto", self.want_lto); | ||
| 1837 | |||
| 1838 | if (self.subsystem) |subsystem| { | ||
| 1839 | try zig_args.append("--subsystem"); | ||
| 1840 | try zig_args.append(switch (subsystem) { | ||
| 1841 | .Console => "console", | ||
| 1842 | .Windows => "windows", | ||
| 1843 | .Posix => "posix", | ||
| 1844 | .Native => "native", | ||
| 1845 | .EfiApplication => "efi_application", | ||
| 1846 | .EfiBootServiceDriver => "efi_boot_service_driver", | ||
| 1847 | .EfiRom => "efi_rom", | ||
| 1848 | .EfiRuntimeDriver => "efi_runtime_driver", | ||
| 1849 | }); | ||
| 1850 | } | ||
| 1851 | |||
| 1852 | try zig_args.append("--listen=-"); | ||
| 1853 | |||
| 1854 | // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux | ||
| 1855 | // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and | ||
| 1856 | // pass that to zig, e.g. via 'zig build-lib @args.rsp' | ||
| 1857 | // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html | ||
| 1858 | var args_length: usize = 0; | ||
| 1859 | for (zig_args.items) |arg| { | ||
| 1860 | args_length += arg.len + 1; // +1 to account for null terminator | ||
| 1861 | } | ||
| 1862 | if (args_length >= 30 * 1024) { | ||
| 1863 | try b.cache_root.handle.makePath("args"); | ||
| 1864 | |||
| 1865 | const args_to_escape = zig_args.items[2..]; | ||
| 1866 | var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len); | ||
| 1867 | arg_blk: for (args_to_escape) |arg| { | ||
| 1868 | for (arg, 0..) |c, arg_idx| { | ||
| 1869 | if (c == '\\' or c == '"') { | ||
| 1870 | // Slow path for arguments that need to be escaped. We'll need to allocate and copy | ||
| 1871 | var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1); | ||
| 1872 | const writer = escaped.writer(); | ||
| 1873 | try writer.writeAll(arg[0..arg_idx]); | ||
| 1874 | for (arg[arg_idx..]) |to_escape| { | ||
| 1875 | if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); | ||
| 1876 | try writer.writeByte(to_escape); | ||
| 1877 | } | ||
| 1878 | escaped_args.appendAssumeCapacity(escaped.items); | ||
| 1879 | continue :arg_blk; | ||
| 1880 | } | ||
| 1881 | } | ||
| 1882 | escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument | ||
| 1883 | } | ||
| 1884 | |||
| 1885 | // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with | ||
| 1886 | // other zig build commands running in parallel. | ||
| 1887 | const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items); | ||
| 1888 | const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); | ||
| 1889 | |||
| 1890 | var args_hash: [Sha256.digest_length]u8 = undefined; | ||
| 1891 | Sha256.hash(args, &args_hash, .{}); | ||
| 1892 | var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; | ||
| 1893 | _ = try std.fmt.bufPrint( | ||
| 1894 | &args_hex_hash, | ||
| 1895 | "{s}", | ||
| 1896 | .{std.fmt.fmtSliceHexLower(&args_hash)}, | ||
| 1897 | ); | ||
| 1898 | |||
| 1899 | const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; | ||
| 1900 | try b.cache_root.handle.writeFile(args_file, args); | ||
| 1901 | |||
| 1902 | const resolved_args_file = try mem.concat(b.allocator, u8, &.{ | ||
| 1903 | "@", | ||
| 1904 | try b.cache_root.join(b.allocator, &.{args_file}), | ||
| 1905 | }); | ||
| 1906 | |||
| 1907 | zig_args.shrinkRetainingCapacity(2); | ||
| 1908 | try zig_args.append(resolved_args_file); | ||
| 1909 | } | ||
| 1910 | |||
| 1911 | const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { | ||
| 1912 | error.NeedCompileErrorCheck => { | ||
| 1913 | assert(self.expect_errors.len != 0); | ||
| 1914 | try checkCompileErrors(self); | ||
| 1915 | return; | ||
| 1916 | }, | ||
| 1917 | else => |e| return e, | ||
| 1918 | }; | ||
| 1919 | const output_dir = fs.path.dirname(output_bin_path).?; | ||
| 1920 | |||
| 1921 | // Update generated files | ||
| 1922 | { | ||
| 1923 | self.output_dirname_source.path = output_dir; | ||
| 1924 | |||
| 1925 | self.output_path_source.path = b.pathJoin( | ||
| 1926 | &.{ output_dir, self.out_filename }, | ||
| 1927 | ); | ||
| 1928 | |||
| 1929 | if (self.kind == .lib) { | ||
| 1930 | self.output_lib_path_source.path = b.pathJoin( | ||
| 1931 | &.{ output_dir, self.out_lib_filename }, | ||
| 1932 | ); | ||
| 1933 | } | ||
| 1934 | |||
| 1935 | if (self.emit_h) { | ||
| 1936 | self.output_h_path_source.path = b.pathJoin( | ||
| 1937 | &.{ output_dir, self.out_h_filename }, | ||
| 1938 | ); | ||
| 1939 | } | ||
| 1940 | |||
| 1941 | if (self.target.isWindows() or self.target.isUefi()) { | ||
| 1942 | self.output_pdb_path_source.path = b.pathJoin( | ||
| 1943 | &.{ output_dir, self.out_pdb_filename }, | ||
| 1944 | ); | ||
| 1945 | } | ||
| 1946 | } | ||
| 1947 | |||
| 1948 | if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and | ||
| 1949 | self.version != null and self.target.wantSharedLibSymLinks()) | ||
| 1950 | { | ||
| 1951 | try doAtomicSymLinks( | ||
| 1952 | step, | ||
| 1953 | self.getOutputSource().getPath(b), | ||
| 1954 | self.major_only_filename.?, | ||
| 1955 | self.name_only_filename.?, | ||
| 1956 | ); | ||
| 1957 | } | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | fn isLibCLibrary(name: []const u8) bool { | ||
| 1961 | const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; | ||
| 1962 | for (libc_libraries) |libc_lib_name| { | ||
| 1963 | if (mem.eql(u8, name, libc_lib_name)) | ||
| 1964 | return true; | ||
| 1965 | } | ||
| 1966 | return false; | ||
| 1967 | } | ||
| 1968 | |||
| 1969 | fn isLibCppLibrary(name: []const u8) bool { | ||
| 1970 | const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; | ||
| 1971 | for (libcpp_libraries) |libcpp_lib_name| { | ||
| 1972 | if (mem.eql(u8, name, libcpp_lib_name)) | ||
| 1973 | return true; | ||
| 1974 | } | ||
| 1975 | return false; | ||
| 1976 | } | ||
| 1977 | |||
| 1978 | /// Returned slice must be freed by the caller. | ||
| 1979 | fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { | ||
| 1980 | const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); | ||
| 1981 | defer allocator.free(appdata_path); | ||
| 1982 | |||
| 1983 | const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); | ||
| 1984 | defer allocator.free(path_file); | ||
| 1985 | |||
| 1986 | const file = fs.cwd().openFile(path_file, .{}) catch return null; | ||
| 1987 | defer file.close(); | ||
| 1988 | |||
| 1989 | const size = @intCast(usize, try file.getEndPos()); | ||
| 1990 | const vcpkg_path = try allocator.alloc(u8, size); | ||
| 1991 | const size_read = try file.read(vcpkg_path); | ||
| 1992 | std.debug.assert(size == size_read); | ||
| 1993 | |||
| 1994 | return vcpkg_path; | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | pub fn doAtomicSymLinks( | ||
| 1998 | step: *Step, | ||
| 1999 | output_path: []const u8, | ||
| 2000 | filename_major_only: []const u8, | ||
| 2001 | filename_name_only: []const u8, | ||
| 2002 | ) !void { | ||
| 2003 | const arena = step.owner.allocator; | ||
| 2004 | const out_dir = fs.path.dirname(output_path) orelse "."; | ||
| 2005 | const out_basename = fs.path.basename(output_path); | ||
| 2006 | // sym link for libfoo.so.1 to libfoo.so.1.2.3 | ||
| 2007 | const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only }); | ||
| 2008 | fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| { | ||
| 2009 | return step.fail("unable to symlink {s} -> {s}: {s}", .{ | ||
| 2010 | major_only_path, out_basename, @errorName(err), | ||
| 2011 | }); | ||
| 2012 | }; | ||
| 2013 | // sym link for libfoo.so to libfoo.so.1 | ||
| 2014 | const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only }); | ||
| 2015 | fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| { | ||
| 2016 | return step.fail("Unable to symlink {s} -> {s}: {s}", .{ | ||
| 2017 | name_only_path, filename_major_only, @errorName(err), | ||
| 2018 | }); | ||
| 2019 | }; | ||
| 2020 | } | ||
| 2021 | |||
| 2022 | fn execPkgConfigList(self: *std.Build, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg { | ||
| 2023 | const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); | ||
| 2024 | var list = ArrayList(PkgConfigPkg).init(self.allocator); | ||
| 2025 | errdefer list.deinit(); | ||
| 2026 | var line_it = mem.tokenize(u8, stdout, "\r\n"); | ||
| 2027 | while (line_it.next()) |line| { | ||
| 2028 | if (mem.trim(u8, line, " \t").len == 0) continue; | ||
| 2029 | var tok_it = mem.tokenize(u8, line, " \t"); | ||
| 2030 | try list.append(PkgConfigPkg{ | ||
| 2031 | .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, | ||
| 2032 | .desc = tok_it.rest(), | ||
| 2033 | }); | ||
| 2034 | } | ||
| 2035 | return list.toOwnedSlice(); | ||
| 2036 | } | ||
| 2037 | |||
| 2038 | fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg { | ||
| 2039 | if (self.pkg_config_pkg_list) |res| { | ||
| 2040 | return res; | ||
| 2041 | } | ||
| 2042 | var code: u8 = undefined; | ||
| 2043 | if (execPkgConfigList(self, &code)) |list| { | ||
| 2044 | self.pkg_config_pkg_list = list; | ||
| 2045 | return list; | ||
| 2046 | } else |err| { | ||
| 2047 | const result = switch (err) { | ||
| 2048 | error.ProcessTerminated => error.PkgConfigCrashed, | ||
| 2049 | error.ExecNotSupported => error.PkgConfigFailed, | ||
| 2050 | error.ExitCodeFailure => error.PkgConfigFailed, | ||
| 2051 | error.FileNotFound => error.PkgConfigNotInstalled, | ||
| 2052 | error.InvalidName => error.PkgConfigNotInstalled, | ||
| 2053 | error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, | ||
| 2054 | else => return err, | ||
| 2055 | }; | ||
| 2056 | self.pkg_config_pkg_list = result; | ||
| 2057 | return result; | ||
| 2058 | } | ||
| 2059 | } | ||
| 2060 | |||
| 2061 | fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { | ||
| 2062 | const cond = opt orelse return; | ||
| 2063 | try args.ensureUnusedCapacity(1); | ||
| 2064 | if (cond) { | ||
| 2065 | args.appendAssumeCapacity("-f" ++ name); | ||
| 2066 | } else { | ||
| 2067 | args.appendAssumeCapacity("-fno-" ++ name); | ||
| 2068 | } | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | const TransitiveDeps = struct { | ||
| 2072 | link_objects: ArrayList(LinkObject), | ||
| 2073 | seen_system_libs: StringHashMap(void), | ||
| 2074 | seen_steps: std.AutoHashMap(*const Step, void), | ||
| 2075 | is_linking_libcpp: bool, | ||
| 2076 | is_linking_libc: bool, | ||
| 2077 | frameworks: *StringHashMap(FrameworkLinkInfo), | ||
| 2078 | |||
| 2079 | fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { | ||
| 2080 | try td.link_objects.ensureUnusedCapacity(link_objects.len); | ||
| 2081 | |||
| 2082 | for (link_objects) |link_object| { | ||
| 2083 | try td.link_objects.append(link_object); | ||
| 2084 | switch (link_object) { | ||
| 2085 | .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), | ||
| 2086 | else => {}, | ||
| 2087 | } | ||
| 2088 | } | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | fn addInner(td: *TransitiveDeps, other: *CompileStep, dyn: bool) !void { | ||
| 2092 | // Inherit dependency on libc and libc++ | ||
| 2093 | td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; | ||
| 2094 | td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; | ||
| 2095 | |||
| 2096 | // Inherit dependencies on darwin frameworks | ||
| 2097 | if (!dyn) { | ||
| 2098 | var it = other.frameworks.iterator(); | ||
| 2099 | while (it.next()) |framework| { | ||
| 2100 | try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); | ||
| 2101 | } | ||
| 2102 | } | ||
| 2103 | |||
| 2104 | // Inherit dependencies on system libraries and static libraries. | ||
| 2105 | for (other.link_objects.items) |other_link_object| { | ||
| 2106 | switch (other_link_object) { | ||
| 2107 | .system_lib => |system_lib| { | ||
| 2108 | if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) | ||
| 2109 | continue; | ||
| 2110 | |||
| 2111 | if (dyn) | ||
| 2112 | continue; | ||
| 2113 | |||
| 2114 | try td.link_objects.append(other_link_object); | ||
| 2115 | }, | ||
| 2116 | .other_step => |inner_other| { | ||
| 2117 | if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) | ||
| 2118 | continue; | ||
| 2119 | |||
| 2120 | if (!dyn) | ||
| 2121 | try td.link_objects.append(other_link_object); | ||
| 2122 | |||
| 2123 | try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); | ||
| 2124 | }, | ||
| 2125 | else => continue, | ||
| 2126 | } | ||
| 2127 | } | ||
| 2128 | } | ||
| 2129 | }; | ||
| 2130 | |||
| 2131 | fn checkCompileErrors(self: *CompileStep) !void { | ||
| 2132 | // Clear this field so that it does not get printed by the build runner. | ||
| 2133 | const actual_eb = self.step.result_error_bundle; | ||
| 2134 | self.step.result_error_bundle = std.zig.ErrorBundle.empty; | ||
| 2135 | |||
| 2136 | const arena = self.step.owner.allocator; | ||
| 2137 | |||
| 2138 | var actual_stderr_list = std.ArrayList(u8).init(arena); | ||
| 2139 | try actual_eb.renderToWriter(.{ | ||
| 2140 | .ttyconf = .no_color, | ||
| 2141 | .include_reference_trace = false, | ||
| 2142 | .include_source_line = false, | ||
| 2143 | }, actual_stderr_list.writer()); | ||
| 2144 | const actual_stderr = try actual_stderr_list.toOwnedSlice(); | ||
| 2145 | |||
| 2146 | // Render the expected lines into a string that we can compare verbatim. | ||
| 2147 | var expected_generated = std.ArrayList(u8).init(arena); | ||
| 2148 | |||
| 2149 | var actual_line_it = mem.split(u8, actual_stderr, "\n"); | ||
| 2150 | for (self.expect_errors) |expect_line| { | ||
| 2151 | const actual_line = actual_line_it.next() orelse { | ||
| 2152 | try expected_generated.appendSlice(expect_line); | ||
| 2153 | try expected_generated.append('\n'); | ||
| 2154 | continue; | ||
| 2155 | }; | ||
| 2156 | if (mem.endsWith(u8, actual_line, expect_line)) { | ||
| 2157 | try expected_generated.appendSlice(actual_line); | ||
| 2158 | try expected_generated.append('\n'); | ||
| 2159 | continue; | ||
| 2160 | } | ||
| 2161 | if (mem.startsWith(u8, expect_line, ":?:?: ")) { | ||
| 2162 | if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { | ||
| 2163 | try expected_generated.appendSlice(actual_line); | ||
| 2164 | try expected_generated.append('\n'); | ||
| 2165 | continue; | ||
| 2166 | } | ||
| 2167 | } | ||
| 2168 | try expected_generated.appendSlice(expect_line); | ||
| 2169 | try expected_generated.append('\n'); | ||
| 2170 | } | ||
| 2171 | |||
| 2172 | if (mem.eql(u8, expected_generated.items, actual_stderr)) return; | ||
| 2173 | |||
| 2174 | // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile | ||
| 2175 | return self.step.fail( | ||
| 2176 | \\ | ||
| 2177 | \\========= expected: ===================== | ||
| 2178 | \\{s} | ||
| 2179 | \\========= but found: ==================== | ||
| 2180 | \\{s} | ||
| 2181 | \\========================================= | ||
| 2182 | , .{ expected_generated.items, actual_stderr }); | ||
| 2183 | } | ||
lib/std/Build/ConfigHeaderStep.zig deleted-437| ... | @@ -1,437 +0,0 @@ | ||
| 1 | pub const Style = union(enum) { | ||
| 2 | /// The configure format supported by autotools. It uses `#undef foo` to | ||
| 3 | /// mark lines that can be substituted with different values. | ||
| 4 | autoconf: std.Build.FileSource, | ||
| 5 | /// The configure format supported by CMake. It uses `@@FOO@@` and | ||
| 6 | /// `#cmakedefine` for template substitution. | ||
| 7 | cmake: std.Build.FileSource, | ||
| 8 | /// Instead of starting with an input file, start with nothing. | ||
| 9 | blank, | ||
| 10 | /// Start with nothing, like blank, and output a nasm .asm file. | ||
| 11 | nasm, | ||
| 12 | |||
| 13 | pub fn getFileSource(style: Style) ?std.Build.FileSource { | ||
| 14 | switch (style) { | ||
| 15 | .autoconf, .cmake => |s| return s, | ||
| 16 | .blank, .nasm => return null, | ||
| 17 | } | ||
| 18 | } | ||
| 19 | }; | ||
| 20 | |||
| 21 | pub const Value = union(enum) { | ||
| 22 | undef, | ||
| 23 | defined, | ||
| 24 | boolean: bool, | ||
| 25 | int: i64, | ||
| 26 | ident: []const u8, | ||
| 27 | string: []const u8, | ||
| 28 | }; | ||
| 29 | |||
| 30 | step: Step, | ||
| 31 | values: std.StringArrayHashMap(Value), | ||
| 32 | output_file: std.Build.GeneratedFile, | ||
| 33 | |||
| 34 | style: Style, | ||
| 35 | max_bytes: usize, | ||
| 36 | include_path: []const u8, | ||
| 37 | |||
| 38 | pub const base_id: Step.Id = .config_header; | ||
| 39 | |||
| 40 | pub const Options = struct { | ||
| 41 | style: Style = .blank, | ||
| 42 | max_bytes: usize = 2 * 1024 * 1024, | ||
| 43 | include_path: ?[]const u8 = null, | ||
| 44 | first_ret_addr: ?usize = null, | ||
| 45 | }; | ||
| 46 | |||
| 47 | pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep { | ||
| 48 | const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM"); | ||
| 49 | |||
| 50 | var include_path: []const u8 = "config.h"; | ||
| 51 | |||
| 52 | if (options.style.getFileSource()) |s| switch (s) { | ||
| 53 | .path => |p| { | ||
| 54 | const basename = std.fs.path.basename(p); | ||
| 55 | if (std.mem.endsWith(u8, basename, ".h.in")) { | ||
| 56 | include_path = basename[0 .. basename.len - 3]; | ||
| 57 | } | ||
| 58 | }, | ||
| 59 | else => {}, | ||
| 60 | }; | ||
| 61 | |||
| 62 | if (options.include_path) |p| { | ||
| 63 | include_path = p; | ||
| 64 | } | ||
| 65 | |||
| 66 | const name = if (options.style.getFileSource()) |s| | ||
| 67 | owner.fmt("configure {s} header {s} to {s}", .{ | ||
| 68 | @tagName(options.style), s.getDisplayName(), include_path, | ||
| 69 | }) | ||
| 70 | else | ||
| 71 | owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); | ||
| 72 | |||
| 73 | self.* = .{ | ||
| 74 | .step = Step.init(.{ | ||
| 75 | .id = base_id, | ||
| 76 | .name = name, | ||
| 77 | .owner = owner, | ||
| 78 | .makeFn = make, | ||
| 79 | .first_ret_addr = options.first_ret_addr orelse @returnAddress(), | ||
| 80 | }), | ||
| 81 | .style = options.style, | ||
| 82 | .values = std.StringArrayHashMap(Value).init(owner.allocator), | ||
| 83 | |||
| 84 | .max_bytes = options.max_bytes, | ||
| 85 | .include_path = include_path, | ||
| 86 | .output_file = .{ .step = &self.step }, | ||
| 87 | }; | ||
| 88 | |||
| 89 | return self; | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { | ||
| 93 | return addValuesInner(self, values) catch @panic("OOM"); | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource { | ||
| 97 | return .{ .generated = &self.output_file }; | ||
| 98 | } | ||
| 99 | |||
| 100 | fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { | ||
| 101 | inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { | ||
| 102 | try putValue(self, field.name, field.type, @field(values, field.name)); | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void { | ||
| 107 | switch (@typeInfo(T)) { | ||
| 108 | .Null => { | ||
| 109 | try self.values.put(field_name, .undef); | ||
| 110 | }, | ||
| 111 | .Void => { | ||
| 112 | try self.values.put(field_name, .defined); | ||
| 113 | }, | ||
| 114 | .Bool => { | ||
| 115 | try self.values.put(field_name, .{ .boolean = v }); | ||
| 116 | }, | ||
| 117 | .Int => { | ||
| 118 | try self.values.put(field_name, .{ .int = v }); | ||
| 119 | }, | ||
| 120 | .ComptimeInt => { | ||
| 121 | try self.values.put(field_name, .{ .int = v }); | ||
| 122 | }, | ||
| 123 | .EnumLiteral => { | ||
| 124 | try self.values.put(field_name, .{ .ident = @tagName(v) }); | ||
| 125 | }, | ||
| 126 | .Optional => { | ||
| 127 | if (v) |x| { | ||
| 128 | return putValue(self, field_name, @TypeOf(x), x); | ||
| 129 | } else { | ||
| 130 | try self.values.put(field_name, .undef); | ||
| 131 | } | ||
| 132 | }, | ||
| 133 | .Pointer => |ptr| { | ||
| 134 | switch (@typeInfo(ptr.child)) { | ||
| 135 | .Array => |array| { | ||
| 136 | if (ptr.size == .One and array.child == u8) { | ||
| 137 | try self.values.put(field_name, .{ .string = v }); | ||
| 138 | return; | ||
| 139 | } | ||
| 140 | }, | ||
| 141 | .Int => { | ||
| 142 | if (ptr.size == .Slice and ptr.child == u8) { | ||
| 143 | try self.values.put(field_name, .{ .string = v }); | ||
| 144 | return; | ||
| 145 | } | ||
| 146 | }, | ||
| 147 | else => {}, | ||
| 148 | } | ||
| 149 | |||
| 150 | @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)); | ||
| 151 | }, | ||
| 152 | else => @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)), | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 157 | _ = prog_node; | ||
| 158 | const b = step.owner; | ||
| 159 | const self = @fieldParentPtr(ConfigHeaderStep, "step", step); | ||
| 160 | const gpa = b.allocator; | ||
| 161 | const arena = b.allocator; | ||
| 162 | |||
| 163 | var man = b.cache.obtain(); | ||
| 164 | defer man.deinit(); | ||
| 165 | |||
| 166 | // Random bytes to make ConfigHeaderStep unique. Refresh this with new | ||
| 167 | // random bytes when ConfigHeaderStep implementation is modified in a | ||
| 168 | // non-backwards-compatible way. | ||
| 169 | man.hash.add(@as(u32, 0xdef08d23)); | ||
| 170 | |||
| 171 | var output = std.ArrayList(u8).init(gpa); | ||
| 172 | defer output.deinit(); | ||
| 173 | |||
| 174 | const header_text = "This file was generated by ConfigHeaderStep using the Zig Build System."; | ||
| 175 | const c_generated_line = "/* " ++ header_text ++ " */\n"; | ||
| 176 | const asm_generated_line = "; " ++ header_text ++ "\n"; | ||
| 177 | |||
| 178 | switch (self.style) { | ||
| 179 | .autoconf => |file_source| { | ||
| 180 | try output.appendSlice(c_generated_line); | ||
| 181 | const src_path = file_source.getPath(b); | ||
| 182 | const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); | ||
| 183 | try render_autoconf(step, contents, &output, self.values, src_path); | ||
| 184 | }, | ||
| 185 | .cmake => |file_source| { | ||
| 186 | try output.appendSlice(c_generated_line); | ||
| 187 | const src_path = file_source.getPath(b); | ||
| 188 | const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); | ||
| 189 | try render_cmake(step, contents, &output, self.values, src_path); | ||
| 190 | }, | ||
| 191 | .blank => { | ||
| 192 | try output.appendSlice(c_generated_line); | ||
| 193 | try render_blank(&output, self.values, self.include_path); | ||
| 194 | }, | ||
| 195 | .nasm => { | ||
| 196 | try output.appendSlice(asm_generated_line); | ||
| 197 | try render_nasm(&output, self.values); | ||
| 198 | }, | ||
| 199 | } | ||
| 200 | |||
| 201 | man.hash.addBytes(output.items); | ||
| 202 | |||
| 203 | if (try step.cacheHit(&man)) { | ||
| 204 | const digest = man.final(); | ||
| 205 | self.output_file.path = try b.cache_root.join(arena, &.{ | ||
| 206 | "o", &digest, self.include_path, | ||
| 207 | }); | ||
| 208 | return; | ||
| 209 | } | ||
| 210 | |||
| 211 | const digest = man.final(); | ||
| 212 | |||
| 213 | // If output_path has directory parts, deal with them. Example: | ||
| 214 | // output_dir is zig-cache/o/HASH | ||
| 215 | // output_path is libavutil/avconfig.h | ||
| 216 | // We want to open directory zig-cache/o/HASH/libavutil/ | ||
| 217 | // but keep output_dir as zig-cache/o/HASH for -I include | ||
| 218 | const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path }); | ||
| 219 | const sub_path_dirname = std.fs.path.dirname(sub_path).?; | ||
| 220 | |||
| 221 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | ||
| 222 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 223 | b.cache_root, sub_path_dirname, @errorName(err), | ||
| 224 | }); | ||
| 225 | }; | ||
| 226 | |||
| 227 | b.cache_root.handle.writeFile(sub_path, output.items) catch |err| { | ||
| 228 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 229 | b.cache_root, sub_path, @errorName(err), | ||
| 230 | }); | ||
| 231 | }; | ||
| 232 | |||
| 233 | self.output_file.path = try b.cache_root.join(arena, &.{sub_path}); | ||
| 234 | try man.writeManifest(); | ||
| 235 | } | ||
| 236 | |||
| 237 | fn render_autoconf( | ||
| 238 | step: *Step, | ||
| 239 | contents: []const u8, | ||
| 240 | output: *std.ArrayList(u8), | ||
| 241 | values: std.StringArrayHashMap(Value), | ||
| 242 | src_path: []const u8, | ||
| 243 | ) !void { | ||
| 244 | var values_copy = try values.clone(); | ||
| 245 | defer values_copy.deinit(); | ||
| 246 | |||
| 247 | var any_errors = false; | ||
| 248 | var line_index: u32 = 0; | ||
| 249 | var line_it = std.mem.split(u8, contents, "\n"); | ||
| 250 | while (line_it.next()) |line| : (line_index += 1) { | ||
| 251 | if (!std.mem.startsWith(u8, line, "#")) { | ||
| 252 | try output.appendSlice(line); | ||
| 253 | try output.appendSlice("\n"); | ||
| 254 | continue; | ||
| 255 | } | ||
| 256 | var it = std.mem.tokenize(u8, line[1..], " \t\r"); | ||
| 257 | const undef = it.next().?; | ||
| 258 | if (!std.mem.eql(u8, undef, "undef")) { | ||
| 259 | try output.appendSlice(line); | ||
| 260 | try output.appendSlice("\n"); | ||
| 261 | continue; | ||
| 262 | } | ||
| 263 | const name = it.rest(); | ||
| 264 | const kv = values_copy.fetchSwapRemove(name) orelse { | ||
| 265 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | ||
| 266 | src_path, line_index + 1, name, | ||
| 267 | }); | ||
| 268 | any_errors = true; | ||
| 269 | continue; | ||
| 270 | }; | ||
| 271 | try renderValueC(output, name, kv.value); | ||
| 272 | } | ||
| 273 | |||
| 274 | for (values_copy.keys()) |name| { | ||
| 275 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); | ||
| 276 | any_errors = true; | ||
| 277 | } | ||
| 278 | |||
| 279 | if (any_errors) { | ||
| 280 | return error.MakeFailed; | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | fn render_cmake( | ||
| 285 | step: *Step, | ||
| 286 | contents: []const u8, | ||
| 287 | output: *std.ArrayList(u8), | ||
| 288 | values: std.StringArrayHashMap(Value), | ||
| 289 | src_path: []const u8, | ||
| 290 | ) !void { | ||
| 291 | var values_copy = try values.clone(); | ||
| 292 | defer values_copy.deinit(); | ||
| 293 | |||
| 294 | var any_errors = false; | ||
| 295 | var line_index: u32 = 0; | ||
| 296 | var line_it = std.mem.split(u8, contents, "\n"); | ||
| 297 | while (line_it.next()) |line| : (line_index += 1) { | ||
| 298 | if (!std.mem.startsWith(u8, line, "#")) { | ||
| 299 | try output.appendSlice(line); | ||
| 300 | try output.appendSlice("\n"); | ||
| 301 | continue; | ||
| 302 | } | ||
| 303 | var it = std.mem.tokenize(u8, line[1..], " \t\r"); | ||
| 304 | const cmakedefine = it.next().?; | ||
| 305 | if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { | ||
| 306 | try output.appendSlice(line); | ||
| 307 | try output.appendSlice("\n"); | ||
| 308 | continue; | ||
| 309 | } | ||
| 310 | const name = it.next() orelse { | ||
| 311 | try step.addError("{s}:{d}: error: missing define name", .{ | ||
| 312 | src_path, line_index + 1, | ||
| 313 | }); | ||
| 314 | any_errors = true; | ||
| 315 | continue; | ||
| 316 | }; | ||
| 317 | const kv = values_copy.fetchSwapRemove(name) orelse { | ||
| 318 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | ||
| 319 | src_path, line_index + 1, name, | ||
| 320 | }); | ||
| 321 | any_errors = true; | ||
| 322 | continue; | ||
| 323 | }; | ||
| 324 | try renderValueC(output, name, kv.value); | ||
| 325 | } | ||
| 326 | |||
| 327 | for (values_copy.keys()) |name| { | ||
| 328 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); | ||
| 329 | any_errors = true; | ||
| 330 | } | ||
| 331 | |||
| 332 | if (any_errors) { | ||
| 333 | return error.HeaderConfigFailed; | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 337 | fn render_blank( | ||
| 338 | output: *std.ArrayList(u8), | ||
| 339 | defines: std.StringArrayHashMap(Value), | ||
| 340 | include_path: []const u8, | ||
| 341 | ) !void { | ||
| 342 | const include_guard_name = try output.allocator.dupe(u8, include_path); | ||
| 343 | for (include_guard_name) |*byte| { | ||
| 344 | switch (byte.*) { | ||
| 345 | 'a'...'z' => byte.* = byte.* - 'a' + 'A', | ||
| 346 | 'A'...'Z', '0'...'9' => continue, | ||
| 347 | else => byte.* = '_', | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | try output.appendSlice("#ifndef "); | ||
| 352 | try output.appendSlice(include_guard_name); | ||
| 353 | try output.appendSlice("\n#define "); | ||
| 354 | try output.appendSlice(include_guard_name); | ||
| 355 | try output.appendSlice("\n"); | ||
| 356 | |||
| 357 | const values = defines.values(); | ||
| 358 | for (defines.keys(), 0..) |name, i| { | ||
| 359 | try renderValueC(output, name, values[i]); | ||
| 360 | } | ||
| 361 | |||
| 362 | try output.appendSlice("#endif /* "); | ||
| 363 | try output.appendSlice(include_guard_name); | ||
| 364 | try output.appendSlice(" */\n"); | ||
| 365 | } | ||
| 366 | |||
| 367 | fn render_nasm(output: *std.ArrayList(u8), defines: std.StringArrayHashMap(Value)) !void { | ||
| 368 | const values = defines.values(); | ||
| 369 | for (defines.keys(), 0..) |name, i| { | ||
| 370 | try renderValueNasm(output, name, values[i]); | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { | ||
| 375 | switch (value) { | ||
| 376 | .undef => { | ||
| 377 | try output.appendSlice("/* #undef "); | ||
| 378 | try output.appendSlice(name); | ||
| 379 | try output.appendSlice(" */\n"); | ||
| 380 | }, | ||
| 381 | .defined => { | ||
| 382 | try output.appendSlice("#define "); | ||
| 383 | try output.appendSlice(name); | ||
| 384 | try output.appendSlice("\n"); | ||
| 385 | }, | ||
| 386 | .boolean => |b| { | ||
| 387 | try output.appendSlice("#define "); | ||
| 388 | try output.appendSlice(name); | ||
| 389 | try output.appendSlice(" "); | ||
| 390 | try output.appendSlice(if (b) "true\n" else "false\n"); | ||
| 391 | }, | ||
| 392 | .int => |i| { | ||
| 393 | try output.writer().print("#define {s} {d}\n", .{ name, i }); | ||
| 394 | }, | ||
| 395 | .ident => |ident| { | ||
| 396 | try output.writer().print("#define {s} {s}\n", .{ name, ident }); | ||
| 397 | }, | ||
| 398 | .string => |string| { | ||
| 399 | // TODO: use C-specific escaping instead of zig string literals | ||
| 400 | try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); | ||
| 401 | }, | ||
| 402 | } | ||
| 403 | } | ||
| 404 | |||
| 405 | fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { | ||
| 406 | switch (value) { | ||
| 407 | .undef => { | ||
| 408 | try output.appendSlice("; %undef "); | ||
| 409 | try output.appendSlice(name); | ||
| 410 | try output.appendSlice("\n"); | ||
| 411 | }, | ||
| 412 | .defined => { | ||
| 413 | try output.appendSlice("%define "); | ||
| 414 | try output.appendSlice(name); | ||
| 415 | try output.appendSlice("\n"); | ||
| 416 | }, | ||
| 417 | .boolean => |b| { | ||
| 418 | try output.appendSlice("%define "); | ||
| 419 | try output.appendSlice(name); | ||
| 420 | try output.appendSlice(if (b) " 1\n" else " 0\n"); | ||
| 421 | }, | ||
| 422 | .int => |i| { | ||
| 423 | try output.writer().print("%define {s} {d}\n", .{ name, i }); | ||
| 424 | }, | ||
| 425 | .ident => |ident| { | ||
| 426 | try output.writer().print("%define {s} {s}\n", .{ name, ident }); | ||
| 427 | }, | ||
| 428 | .string => |string| { | ||
| 429 | // TODO: use nasm-specific escaping instead of zig string literals | ||
| 430 | try output.writer().print("%define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); | ||
| 431 | }, | ||
| 432 | } | ||
| 433 | } | ||
| 434 | |||
| 435 | const std = @import("../std.zig"); | ||
| 436 | const ConfigHeaderStep = @This(); | ||
| 437 | const Step = std.Build.Step; | ||
lib/std/Build/FmtStep.zig deleted-73| ... | @@ -1,73 +0,0 @@ | ||
| 1 | //! This step has two modes: | ||
| 2 | //! * Modify mode: directly modify source files, formatting them in place. | ||
| 3 | //! * Check mode: fail the step if a non-conforming file is found. | ||
| 4 | |||
| 5 | step: Step, | ||
| 6 | paths: []const []const u8, | ||
| 7 | exclude_paths: []const []const u8, | ||
| 8 | check: bool, | ||
| 9 | |||
| 10 | pub const base_id = .fmt; | ||
| 11 | |||
| 12 | pub const Options = struct { | ||
| 13 | paths: []const []const u8 = &.{}, | ||
| 14 | exclude_paths: []const []const u8 = &.{}, | ||
| 15 | /// If true, fails the build step when any non-conforming files are encountered. | ||
| 16 | check: bool = false, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub fn create(owner: *std.Build, options: Options) *FmtStep { | ||
| 20 | const self = owner.allocator.create(FmtStep) catch @panic("OOM"); | ||
| 21 | const name = if (options.check) "zig fmt --check" else "zig fmt"; | ||
| 22 | self.* = .{ | ||
| 23 | .step = Step.init(.{ | ||
| 24 | .id = base_id, | ||
| 25 | .name = name, | ||
| 26 | .owner = owner, | ||
| 27 | .makeFn = make, | ||
| 28 | }), | ||
| 29 | .paths = options.paths, | ||
| 30 | .exclude_paths = options.exclude_paths, | ||
| 31 | .check = options.check, | ||
| 32 | }; | ||
| 33 | return self; | ||
| 34 | } | ||
| 35 | |||
| 36 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 37 | // zig fmt is fast enough that no progress is needed. | ||
| 38 | _ = prog_node; | ||
| 39 | |||
| 40 | // TODO: if check=false, this means we are modifying source files in place, which | ||
| 41 | // is an operation that could race against other operations also modifying source files | ||
| 42 | // in place. In this case, this step should obtain a write lock while making those | ||
| 43 | // modifications. | ||
| 44 | |||
| 45 | const b = step.owner; | ||
| 46 | const arena = b.allocator; | ||
| 47 | const self = @fieldParentPtr(FmtStep, "step", step); | ||
| 48 | |||
| 49 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; | ||
| 50 | try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len); | ||
| 51 | |||
| 52 | argv.appendAssumeCapacity(b.zig_exe); | ||
| 53 | argv.appendAssumeCapacity("fmt"); | ||
| 54 | |||
| 55 | if (self.check) { | ||
| 56 | argv.appendAssumeCapacity("--check"); | ||
| 57 | } | ||
| 58 | |||
| 59 | for (self.paths) |p| { | ||
| 60 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | ||
| 61 | } | ||
| 62 | |||
| 63 | for (self.exclude_paths) |p| { | ||
| 64 | argv.appendAssumeCapacity("--exclude"); | ||
| 65 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | ||
| 66 | } | ||
| 67 | |||
| 68 | return step.evalChildProcess(argv.items); | ||
| 69 | } | ||
| 70 | |||
| 71 | const std = @import("../std.zig"); | ||
| 72 | const Step = std.Build.Step; | ||
| 73 | const FmtStep = @This(); | ||
lib/std/Build/InstallArtifactStep.zig deleted-130| ... | @@ -1,130 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const CompileStep = std.Build.CompileStep; | ||
| 4 | const InstallDir = std.Build.InstallDir; | ||
| 5 | const InstallArtifactStep = @This(); | ||
| 6 | const fs = std.fs; | ||
| 7 | |||
| 8 | pub const base_id = .install_artifact; | ||
| 9 | |||
| 10 | step: Step, | ||
| 11 | artifact: *CompileStep, | ||
| 12 | dest_dir: InstallDir, | ||
| 13 | pdb_dir: ?InstallDir, | ||
| 14 | h_dir: ?InstallDir, | ||
| 15 | /// If non-null, adds additional path components relative to dest_dir, and | ||
| 16 | /// overrides the basename of the CompileStep. | ||
| 17 | dest_sub_path: ?[]const u8, | ||
| 18 | |||
| 19 | pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { | ||
| 20 | const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM"); | ||
| 21 | self.* = InstallArtifactStep{ | ||
| 22 | .step = Step.init(.{ | ||
| 23 | .id = base_id, | ||
| 24 | .name = owner.fmt("install {s}", .{artifact.name}), | ||
| 25 | .owner = owner, | ||
| 26 | .makeFn = make, | ||
| 27 | }), | ||
| 28 | .artifact = artifact, | ||
| 29 | .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { | ||
| 30 | .obj => @panic("Cannot install a .obj build artifact."), | ||
| 31 | .exe, .@"test" => InstallDir{ .bin = {} }, | ||
| 32 | .lib => InstallDir{ .lib = {} }, | ||
| 33 | }, | ||
| 34 | .pdb_dir = if (artifact.producesPdbFile()) blk: { | ||
| 35 | if (artifact.kind == .exe or artifact.kind == .@"test") { | ||
| 36 | break :blk InstallDir{ .bin = {} }; | ||
| 37 | } else { | ||
| 38 | break :blk InstallDir{ .lib = {} }; | ||
| 39 | } | ||
| 40 | } else null, | ||
| 41 | .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, | ||
| 42 | .dest_sub_path = null, | ||
| 43 | }; | ||
| 44 | self.step.dependOn(&artifact.step); | ||
| 45 | |||
| 46 | owner.pushInstalledFile(self.dest_dir, artifact.out_filename); | ||
| 47 | if (self.artifact.isDynamicLibrary()) { | ||
| 48 | if (artifact.major_only_filename) |name| { | ||
| 49 | owner.pushInstalledFile(.lib, name); | ||
| 50 | } | ||
| 51 | if (artifact.name_only_filename) |name| { | ||
| 52 | owner.pushInstalledFile(.lib, name); | ||
| 53 | } | ||
| 54 | if (self.artifact.target.isWindows()) { | ||
| 55 | owner.pushInstalledFile(.lib, artifact.out_lib_filename); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | if (self.pdb_dir) |pdb_dir| { | ||
| 59 | owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); | ||
| 60 | } | ||
| 61 | if (self.h_dir) |h_dir| { | ||
| 62 | owner.pushInstalledFile(h_dir, artifact.out_h_filename); | ||
| 63 | } | ||
| 64 | return self; | ||
| 65 | } | ||
| 66 | |||
| 67 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 68 | _ = prog_node; | ||
| 69 | const self = @fieldParentPtr(InstallArtifactStep, "step", step); | ||
| 70 | const src_builder = self.artifact.step.owner; | ||
| 71 | const dest_builder = step.owner; | ||
| 72 | |||
| 73 | const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; | ||
| 74 | const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); | ||
| 75 | const cwd = fs.cwd(); | ||
| 76 | |||
| 77 | var all_cached = true; | ||
| 78 | |||
| 79 | { | ||
| 80 | const full_src_path = self.artifact.getOutputSource().getPath(src_builder); | ||
| 81 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { | ||
| 82 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 83 | full_src_path, full_dest_path, @errorName(err), | ||
| 84 | }); | ||
| 85 | }; | ||
| 86 | all_cached = all_cached and p == .fresh; | ||
| 87 | } | ||
| 88 | |||
| 89 | if (self.artifact.isDynamicLibrary() and | ||
| 90 | self.artifact.version != null and | ||
| 91 | self.artifact.target.wantSharedLibSymLinks()) | ||
| 92 | { | ||
| 93 | try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); | ||
| 94 | } | ||
| 95 | if (self.artifact.isDynamicLibrary() and | ||
| 96 | self.artifact.target.isWindows() and | ||
| 97 | self.artifact.emit_implib != .no_emit) | ||
| 98 | { | ||
| 99 | const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder); | ||
| 100 | const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); | ||
| 101 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { | ||
| 102 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 103 | full_src_path, full_implib_path, @errorName(err), | ||
| 104 | }); | ||
| 105 | }; | ||
| 106 | all_cached = all_cached and p == .fresh; | ||
| 107 | } | ||
| 108 | if (self.pdb_dir) |pdb_dir| { | ||
| 109 | const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder); | ||
| 110 | const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); | ||
| 111 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { | ||
| 112 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 113 | full_src_path, full_pdb_path, @errorName(err), | ||
| 114 | }); | ||
| 115 | }; | ||
| 116 | all_cached = all_cached and p == .fresh; | ||
| 117 | } | ||
| 118 | if (self.h_dir) |h_dir| { | ||
| 119 | const full_src_path = self.artifact.getOutputHSource().getPath(src_builder); | ||
| 120 | const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); | ||
| 121 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { | ||
| 122 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 123 | full_src_path, full_h_path, @errorName(err), | ||
| 124 | }); | ||
| 125 | }; | ||
| 126 | all_cached = all_cached and p == .fresh; | ||
| 127 | } | ||
| 128 | self.artifact.installed_path = full_dest_path; | ||
| 129 | step.result_cached = all_cached; | ||
| 130 | } | ||
lib/std/Build/InstallDirStep.zig deleted-110| ... | @@ -1,110 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const fs = std.fs; | ||
| 4 | const Step = std.Build.Step; | ||
| 5 | const InstallDir = std.Build.InstallDir; | ||
| 6 | const InstallDirStep = @This(); | ||
| 7 | |||
| 8 | step: Step, | ||
| 9 | options: Options, | ||
| 10 | /// This is used by the build system when a file being installed comes from one | ||
| 11 | /// package but is being installed by another. | ||
| 12 | dest_builder: *std.Build, | ||
| 13 | |||
| 14 | pub const base_id = .install_dir; | ||
| 15 | |||
| 16 | pub const Options = struct { | ||
| 17 | source_dir: []const u8, | ||
| 18 | install_dir: InstallDir, | ||
| 19 | install_subdir: []const u8, | ||
| 20 | /// File paths which end in any of these suffixes will be excluded | ||
| 21 | /// from being installed. | ||
| 22 | exclude_extensions: []const []const u8 = &.{}, | ||
| 23 | /// File paths which end in any of these suffixes will result in | ||
| 24 | /// empty files being installed. This is mainly intended for large | ||
| 25 | /// test.zig files in order to prevent needless installation bloat. | ||
| 26 | /// However if the files were not present at all, then | ||
| 27 | /// `@import("test.zig")` would be a compile error. | ||
| 28 | blank_extensions: []const []const u8 = &.{}, | ||
| 29 | |||
| 30 | fn dupe(self: Options, b: *std.Build) Options { | ||
| 31 | return .{ | ||
| 32 | .source_dir = b.dupe(self.source_dir), | ||
| 33 | .install_dir = self.install_dir.dupe(b), | ||
| 34 | .install_subdir = b.dupe(self.install_subdir), | ||
| 35 | .exclude_extensions = b.dupeStrings(self.exclude_extensions), | ||
| 36 | .blank_extensions = b.dupeStrings(self.blank_extensions), | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | |||
| 41 | pub fn init(owner: *std.Build, options: Options) InstallDirStep { | ||
| 42 | owner.pushInstalledFile(options.install_dir, options.install_subdir); | ||
| 43 | return .{ | ||
| 44 | .step = Step.init(.{ | ||
| 45 | .id = .install_dir, | ||
| 46 | .name = owner.fmt("install {s}/", .{options.source_dir}), | ||
| 47 | .owner = owner, | ||
| 48 | .makeFn = make, | ||
| 49 | }), | ||
| 50 | .options = options.dupe(owner), | ||
| 51 | .dest_builder = owner, | ||
| 52 | }; | ||
| 53 | } | ||
| 54 | |||
| 55 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 56 | _ = prog_node; | ||
| 57 | const self = @fieldParentPtr(InstallDirStep, "step", step); | ||
| 58 | const dest_builder = self.dest_builder; | ||
| 59 | const arena = dest_builder.allocator; | ||
| 60 | const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); | ||
| 61 | const src_builder = self.step.owner; | ||
| 62 | var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| { | ||
| 63 | return step.fail("unable to open source directory '{}{s}': {s}", .{ | ||
| 64 | src_builder.build_root, self.options.source_dir, @errorName(err), | ||
| 65 | }); | ||
| 66 | }; | ||
| 67 | defer src_dir.close(); | ||
| 68 | var it = try src_dir.walk(arena); | ||
| 69 | var all_cached = true; | ||
| 70 | next_entry: while (try it.next()) |entry| { | ||
| 71 | for (self.options.exclude_extensions) |ext| { | ||
| 72 | if (mem.endsWith(u8, entry.path, ext)) { | ||
| 73 | continue :next_entry; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | // relative to src build root | ||
| 78 | const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path }); | ||
| 79 | const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path }); | ||
| 80 | const cwd = fs.cwd(); | ||
| 81 | |||
| 82 | switch (entry.kind) { | ||
| 83 | .Directory => try cwd.makePath(dest_path), | ||
| 84 | .File => { | ||
| 85 | for (self.options.blank_extensions) |ext| { | ||
| 86 | if (mem.endsWith(u8, entry.path, ext)) { | ||
| 87 | try dest_builder.truncateFile(dest_path); | ||
| 88 | continue :next_entry; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | const prev_status = fs.Dir.updateFile( | ||
| 93 | src_builder.build_root.handle, | ||
| 94 | src_sub_path, | ||
| 95 | cwd, | ||
| 96 | dest_path, | ||
| 97 | .{}, | ||
| 98 | ) catch |err| { | ||
| 99 | return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{ | ||
| 100 | src_builder.build_root, src_sub_path, dest_path, @errorName(err), | ||
| 101 | }); | ||
| 102 | }; | ||
| 103 | all_cached = all_cached and prev_status == .fresh; | ||
| 104 | }, | ||
| 105 | else => continue, | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | step.result_cached = all_cached; | ||
| 110 | } | ||
lib/std/Build/InstallFileStep.zig deleted-57| ... | @@ -1,57 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const FileSource = std.Build.FileSource; | ||
| 4 | const InstallDir = std.Build.InstallDir; | ||
| 5 | const InstallFileStep = @This(); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | |||
| 8 | pub const base_id = .install_file; | ||
| 9 | |||
| 10 | step: Step, | ||
| 11 | source: FileSource, | ||
| 12 | dir: InstallDir, | ||
| 13 | dest_rel_path: []const u8, | ||
| 14 | /// This is used by the build system when a file being installed comes from one | ||
| 15 | /// package but is being installed by another. | ||
| 16 | dest_builder: *std.Build, | ||
| 17 | |||
| 18 | pub fn create( | ||
| 19 | owner: *std.Build, | ||
| 20 | source: FileSource, | ||
| 21 | dir: InstallDir, | ||
| 22 | dest_rel_path: []const u8, | ||
| 23 | ) *InstallFileStep { | ||
| 24 | assert(dest_rel_path.len != 0); | ||
| 25 | owner.pushInstalledFile(dir, dest_rel_path); | ||
| 26 | const self = owner.allocator.create(InstallFileStep) catch @panic("OOM"); | ||
| 27 | self.* = .{ | ||
| 28 | .step = Step.init(.{ | ||
| 29 | .id = base_id, | ||
| 30 | .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), | ||
| 31 | .owner = owner, | ||
| 32 | .makeFn = make, | ||
| 33 | }), | ||
| 34 | .source = source.dupe(owner), | ||
| 35 | .dir = dir.dupe(owner), | ||
| 36 | .dest_rel_path = owner.dupePath(dest_rel_path), | ||
| 37 | .dest_builder = owner, | ||
| 38 | }; | ||
| 39 | source.addStepDependencies(&self.step); | ||
| 40 | return self; | ||
| 41 | } | ||
| 42 | |||
| 43 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 44 | _ = prog_node; | ||
| 45 | const src_builder = step.owner; | ||
| 46 | const self = @fieldParentPtr(InstallFileStep, "step", step); | ||
| 47 | const dest_builder = self.dest_builder; | ||
| 48 | const full_src_path = self.source.getPath2(src_builder, step); | ||
| 49 | const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); | ||
| 50 | const cwd = std.fs.cwd(); | ||
| 51 | const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { | ||
| 52 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 53 | full_src_path, full_dest_path, @errorName(err), | ||
| 54 | }); | ||
| 55 | }; | ||
| 56 | step.result_cached = prev == .fresh; | ||
| 57 | } | ||
lib/std/Build/ObjCopyStep.zig deleted-122| ... | @@ -1,122 +0,0 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const ObjCopyStep = @This(); | ||
| 3 | |||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const ArenaAllocator = std.heap.ArenaAllocator; | ||
| 6 | const ArrayListUnmanaged = std.ArrayListUnmanaged; | ||
| 7 | const File = std.fs.File; | ||
| 8 | const InstallDir = std.Build.InstallDir; | ||
| 9 | const CompileStep = std.Build.CompileStep; | ||
| 10 | const Step = std.Build.Step; | ||
| 11 | const elf = std.elf; | ||
| 12 | const fs = std.fs; | ||
| 13 | const io = std.io; | ||
| 14 | const sort = std.sort; | ||
| 15 | |||
| 16 | pub const base_id: Step.Id = .objcopy; | ||
| 17 | |||
| 18 | pub const RawFormat = enum { | ||
| 19 | bin, | ||
| 20 | hex, | ||
| 21 | }; | ||
| 22 | |||
| 23 | step: Step, | ||
| 24 | file_source: std.Build.FileSource, | ||
| 25 | basename: []const u8, | ||
| 26 | output_file: std.Build.GeneratedFile, | ||
| 27 | |||
| 28 | format: ?RawFormat, | ||
| 29 | only_section: ?[]const u8, | ||
| 30 | pad_to: ?u64, | ||
| 31 | |||
| 32 | pub const Options = struct { | ||
| 33 | basename: ?[]const u8 = null, | ||
| 34 | format: ?RawFormat = null, | ||
| 35 | only_section: ?[]const u8 = null, | ||
| 36 | pad_to: ?u64 = null, | ||
| 37 | }; | ||
| 38 | |||
| 39 | pub fn create( | ||
| 40 | owner: *std.Build, | ||
| 41 | file_source: std.Build.FileSource, | ||
| 42 | options: Options, | ||
| 43 | ) *ObjCopyStep { | ||
| 44 | const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM"); | ||
| 45 | self.* = ObjCopyStep{ | ||
| 46 | .step = Step.init(.{ | ||
| 47 | .id = base_id, | ||
| 48 | .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}), | ||
| 49 | .owner = owner, | ||
| 50 | .makeFn = make, | ||
| 51 | }), | ||
| 52 | .file_source = file_source, | ||
| 53 | .basename = options.basename orelse file_source.getDisplayName(), | ||
| 54 | .output_file = std.Build.GeneratedFile{ .step = &self.step }, | ||
| 55 | |||
| 56 | .format = options.format, | ||
| 57 | .only_section = options.only_section, | ||
| 58 | .pad_to = options.pad_to, | ||
| 59 | }; | ||
| 60 | file_source.addStepDependencies(&self.step); | ||
| 61 | return self; | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { | ||
| 65 | return .{ .generated = &self.output_file }; | ||
| 66 | } | ||
| 67 | |||
| 68 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 69 | const b = step.owner; | ||
| 70 | const self = @fieldParentPtr(ObjCopyStep, "step", step); | ||
| 71 | |||
| 72 | var man = b.cache.obtain(); | ||
| 73 | defer man.deinit(); | ||
| 74 | |||
| 75 | // Random bytes to make ObjCopyStep unique. Refresh this with new random | ||
| 76 | // bytes when ObjCopyStep implementation is modified incompatibly. | ||
| 77 | man.hash.add(@as(u32, 0xe18b7baf)); | ||
| 78 | |||
| 79 | const full_src_path = self.file_source.getPath(b); | ||
| 80 | _ = try man.addFile(full_src_path, null); | ||
| 81 | man.hash.addOptionalBytes(self.only_section); | ||
| 82 | man.hash.addOptional(self.pad_to); | ||
| 83 | man.hash.addOptional(self.format); | ||
| 84 | |||
| 85 | if (try step.cacheHit(&man)) { | ||
| 86 | // Cache hit, skip subprocess execution. | ||
| 87 | const digest = man.final(); | ||
| 88 | self.output_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 89 | "o", &digest, self.basename, | ||
| 90 | }); | ||
| 91 | return; | ||
| 92 | } | ||
| 93 | |||
| 94 | const digest = man.final(); | ||
| 95 | const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); | ||
| 96 | const cache_path = "o" ++ fs.path.sep_str ++ digest; | ||
| 97 | b.cache_root.handle.makePath(cache_path) catch |err| { | ||
| 98 | return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); | ||
| 99 | }; | ||
| 100 | |||
| 101 | var argv = std.ArrayList([]const u8).init(b.allocator); | ||
| 102 | try argv.appendSlice(&.{ b.zig_exe, "objcopy" }); | ||
| 103 | |||
| 104 | if (self.only_section) |only_section| { | ||
| 105 | try argv.appendSlice(&.{ "-j", only_section }); | ||
| 106 | } | ||
| 107 | if (self.pad_to) |pad_to| { | ||
| 108 | try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); | ||
| 109 | } | ||
| 110 | if (self.format) |format| switch (format) { | ||
| 111 | .bin => try argv.appendSlice(&.{ "-O", "binary" }), | ||
| 112 | .hex => try argv.appendSlice(&.{ "-O", "hex" }), | ||
| 113 | }; | ||
| 114 | |||
| 115 | try argv.appendSlice(&.{ full_src_path, full_dest_path }); | ||
| 116 | |||
| 117 | try argv.append("--listen=-"); | ||
| 118 | _ = try step.evalZigProcess(argv.items, prog_node); | ||
| 119 | |||
| 120 | self.output_file.path = full_dest_path; | ||
| 121 | try man.writeManifest(); | ||
| 122 | } | ||
lib/std/Build/OptionsStep.zig deleted-421| ... | @@ -1,421 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const fs = std.fs; | ||
| 4 | const Step = std.Build.Step; | ||
| 5 | const GeneratedFile = std.Build.GeneratedFile; | ||
| 6 | const CompileStep = std.Build.CompileStep; | ||
| 7 | const FileSource = std.Build.FileSource; | ||
| 8 | |||
| 9 | const OptionsStep = @This(); | ||
| 10 | |||
| 11 | pub const base_id = .options; | ||
| 12 | |||
| 13 | step: Step, | ||
| 14 | generated_file: GeneratedFile, | ||
| 15 | |||
| 16 | contents: std.ArrayList(u8), | ||
| 17 | artifact_args: std.ArrayList(OptionArtifactArg), | ||
| 18 | file_source_args: std.ArrayList(OptionFileSourceArg), | ||
| 19 | |||
| 20 | pub fn create(owner: *std.Build) *OptionsStep { | ||
| 21 | const self = owner.allocator.create(OptionsStep) catch @panic("OOM"); | ||
| 22 | self.* = .{ | ||
| 23 | .step = Step.init(.{ | ||
| 24 | .id = base_id, | ||
| 25 | .name = "options", | ||
| 26 | .owner = owner, | ||
| 27 | .makeFn = make, | ||
| 28 | }), | ||
| 29 | .generated_file = undefined, | ||
| 30 | .contents = std.ArrayList(u8).init(owner.allocator), | ||
| 31 | .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator), | ||
| 32 | .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator), | ||
| 33 | }; | ||
| 34 | self.generated_file = .{ .step = &self.step }; | ||
| 35 | |||
| 36 | return self; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void { | ||
| 40 | return addOptionFallible(self, T, name, value) catch @panic("unhandled error"); | ||
| 41 | } | ||
| 42 | |||
| 43 | fn addOptionFallible(self: *OptionsStep, comptime T: type, name: []const u8, value: T) !void { | ||
| 44 | const out = self.contents.writer(); | ||
| 45 | switch (T) { | ||
| 46 | []const []const u8 => { | ||
| 47 | try out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}); | ||
| 48 | for (value) |slice| { | ||
| 49 | try out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}); | ||
| 50 | } | ||
| 51 | try out.writeAll("};\n"); | ||
| 52 | return; | ||
| 53 | }, | ||
| 54 | [:0]const u8 => { | ||
| 55 | try out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); | ||
| 56 | return; | ||
| 57 | }, | ||
| 58 | []const u8 => { | ||
| 59 | try out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); | ||
| 60 | return; | ||
| 61 | }, | ||
| 62 | ?[:0]const u8 => { | ||
| 63 | try out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}); | ||
| 64 | if (value) |payload| { | ||
| 65 | try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); | ||
| 66 | } else { | ||
| 67 | try out.writeAll("null;\n"); | ||
| 68 | } | ||
| 69 | return; | ||
| 70 | }, | ||
| 71 | ?[]const u8 => { | ||
| 72 | try out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}); | ||
| 73 | if (value) |payload| { | ||
| 74 | try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); | ||
| 75 | } else { | ||
| 76 | try out.writeAll("null;\n"); | ||
| 77 | } | ||
| 78 | return; | ||
| 79 | }, | ||
| 80 | std.builtin.Version => { | ||
| 81 | try out.print( | ||
| 82 | \\pub const {}: @import("std").builtin.Version = .{{ | ||
| 83 | \\ .major = {d}, | ||
| 84 | \\ .minor = {d}, | ||
| 85 | \\ .patch = {d}, | ||
| 86 | \\}}; | ||
| 87 | \\ | ||
| 88 | , .{ | ||
| 89 | std.zig.fmtId(name), | ||
| 90 | |||
| 91 | value.major, | ||
| 92 | value.minor, | ||
| 93 | value.patch, | ||
| 94 | }); | ||
| 95 | return; | ||
| 96 | }, | ||
| 97 | std.SemanticVersion => { | ||
| 98 | try out.print( | ||
| 99 | \\pub const {}: @import("std").SemanticVersion = .{{ | ||
| 100 | \\ .major = {d}, | ||
| 101 | \\ .minor = {d}, | ||
| 102 | \\ .patch = {d}, | ||
| 103 | \\ | ||
| 104 | , .{ | ||
| 105 | std.zig.fmtId(name), | ||
| 106 | |||
| 107 | value.major, | ||
| 108 | value.minor, | ||
| 109 | value.patch, | ||
| 110 | }); | ||
| 111 | if (value.pre) |some| { | ||
| 112 | try out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}); | ||
| 113 | } | ||
| 114 | if (value.build) |some| { | ||
| 115 | try out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}); | ||
| 116 | } | ||
| 117 | try out.writeAll("};\n"); | ||
| 118 | return; | ||
| 119 | }, | ||
| 120 | else => {}, | ||
| 121 | } | ||
| 122 | switch (@typeInfo(T)) { | ||
| 123 | .Enum => |enum_info| { | ||
| 124 | try out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}); | ||
| 125 | inline for (enum_info.fields) |field| { | ||
| 126 | try out.print(" {},\n", .{std.zig.fmtId(field.name)}); | ||
| 127 | } | ||
| 128 | try out.writeAll("};\n"); | ||
| 129 | try out.print("pub const {}: {s} = {s}.{s};\n", .{ | ||
| 130 | std.zig.fmtId(name), | ||
| 131 | std.zig.fmtId(@typeName(T)), | ||
| 132 | std.zig.fmtId(@typeName(T)), | ||
| 133 | std.zig.fmtId(@tagName(value)), | ||
| 134 | }); | ||
| 135 | return; | ||
| 136 | }, | ||
| 137 | else => {}, | ||
| 138 | } | ||
| 139 | try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) }); | ||
| 140 | try printLiteral(out, value, 0); | ||
| 141 | try out.writeAll(";\n"); | ||
| 142 | } | ||
| 143 | |||
| 144 | // TODO: non-recursive? | ||
| 145 | fn printLiteral(out: anytype, val: anytype, indent: u8) !void { | ||
| 146 | const T = @TypeOf(val); | ||
| 147 | switch (@typeInfo(T)) { | ||
| 148 | .Array => { | ||
| 149 | try out.print("{s} {{\n", .{@typeName(T)}); | ||
| 150 | for (val) |item| { | ||
| 151 | try out.writeByteNTimes(' ', indent + 4); | ||
| 152 | try printLiteral(out, item, indent + 4); | ||
| 153 | try out.writeAll(",\n"); | ||
| 154 | } | ||
| 155 | try out.writeByteNTimes(' ', indent); | ||
| 156 | try out.writeAll("}"); | ||
| 157 | }, | ||
| 158 | .Pointer => |p| { | ||
| 159 | if (p.size != .Slice) { | ||
| 160 | @compileError("Non-slice pointers are not yet supported in build options"); | ||
| 161 | } | ||
| 162 | try out.print("&[_]{s} {{\n", .{@typeName(p.child)}); | ||
| 163 | for (val) |item| { | ||
| 164 | try out.writeByteNTimes(' ', indent + 4); | ||
| 165 | try printLiteral(out, item, indent + 4); | ||
| 166 | try out.writeAll(",\n"); | ||
| 167 | } | ||
| 168 | try out.writeByteNTimes(' ', indent); | ||
| 169 | try out.writeAll("}"); | ||
| 170 | }, | ||
| 171 | .Optional => { | ||
| 172 | if (val) |inner| { | ||
| 173 | return printLiteral(out, inner, indent); | ||
| 174 | } else { | ||
| 175 | return out.writeAll("null"); | ||
| 176 | } | ||
| 177 | }, | ||
| 178 | .Void, | ||
| 179 | .Bool, | ||
| 180 | .Int, | ||
| 181 | .ComptimeInt, | ||
| 182 | .Float, | ||
| 183 | .Null, | ||
| 184 | => try out.print("{any}", .{val}), | ||
| 185 | else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})), | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | /// The value is the path in the cache dir. | ||
| 190 | /// Adds a dependency automatically. | ||
| 191 | pub fn addOptionFileSource( | ||
| 192 | self: *OptionsStep, | ||
| 193 | name: []const u8, | ||
| 194 | source: FileSource, | ||
| 195 | ) void { | ||
| 196 | self.file_source_args.append(.{ | ||
| 197 | .name = name, | ||
| 198 | .source = source.dupe(self.step.owner), | ||
| 199 | }) catch @panic("OOM"); | ||
| 200 | source.addStepDependencies(&self.step); | ||
| 201 | } | ||
| 202 | |||
| 203 | /// The value is the path in the cache dir. | ||
| 204 | /// Adds a dependency automatically. | ||
| 205 | pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void { | ||
| 206 | self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM"); | ||
| 207 | self.step.dependOn(&artifact.step); | ||
| 208 | } | ||
| 209 | |||
| 210 | pub fn createModule(self: *OptionsStep) *std.Build.Module { | ||
| 211 | return self.step.owner.createModule(.{ | ||
| 212 | .source_file = self.getSource(), | ||
| 213 | .dependencies = &.{}, | ||
| 214 | }); | ||
| 215 | } | ||
| 216 | |||
| 217 | pub fn getSource(self: *OptionsStep) FileSource { | ||
| 218 | return .{ .generated = &self.generated_file }; | ||
| 219 | } | ||
| 220 | |||
| 221 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 222 | // This step completes so quickly that no progress is necessary. | ||
| 223 | _ = prog_node; | ||
| 224 | |||
| 225 | const b = step.owner; | ||
| 226 | const self = @fieldParentPtr(OptionsStep, "step", step); | ||
| 227 | |||
| 228 | for (self.artifact_args.items) |item| { | ||
| 229 | self.addOption( | ||
| 230 | []const u8, | ||
| 231 | item.name, | ||
| 232 | b.pathFromRoot(item.artifact.getOutputSource().getPath(b)), | ||
| 233 | ); | ||
| 234 | } | ||
| 235 | |||
| 236 | for (self.file_source_args.items) |item| { | ||
| 237 | self.addOption( | ||
| 238 | []const u8, | ||
| 239 | item.name, | ||
| 240 | item.source.getPath(b), | ||
| 241 | ); | ||
| 242 | } | ||
| 243 | |||
| 244 | const basename = "options.zig"; | ||
| 245 | |||
| 246 | // Hash contents to file name. | ||
| 247 | var hash = b.cache.hash; | ||
| 248 | // Random bytes to make unique. Refresh this with new random bytes when | ||
| 249 | // implementation is modified in a non-backwards-compatible way. | ||
| 250 | hash.add(@as(u32, 0x38845ef8)); | ||
| 251 | hash.addBytes(self.contents.items); | ||
| 252 | const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename; | ||
| 253 | |||
| 254 | self.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path}); | ||
| 255 | |||
| 256 | // Optimize for the hot path. Stat the file, and if it already exists, | ||
| 257 | // cache hit. | ||
| 258 | if (b.cache_root.handle.access(sub_path, .{})) |_| { | ||
| 259 | // This is the hot path, success. | ||
| 260 | step.result_cached = true; | ||
| 261 | return; | ||
| 262 | } else |outer_err| switch (outer_err) { | ||
| 263 | error.FileNotFound => { | ||
| 264 | const sub_dirname = fs.path.dirname(sub_path).?; | ||
| 265 | b.cache_root.handle.makePath(sub_dirname) catch |e| { | ||
| 266 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 267 | b.cache_root, sub_dirname, @errorName(e), | ||
| 268 | }); | ||
| 269 | }; | ||
| 270 | |||
| 271 | const rand_int = std.crypto.random.int(u64); | ||
| 272 | const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ | ||
| 273 | std.Build.hex64(rand_int) ++ fs.path.sep_str ++ | ||
| 274 | basename; | ||
| 275 | const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; | ||
| 276 | |||
| 277 | b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { | ||
| 278 | return step.fail("unable to make temporary directory '{}{s}': {s}", .{ | ||
| 279 | b.cache_root, tmp_sub_path_dirname, @errorName(err), | ||
| 280 | }); | ||
| 281 | }; | ||
| 282 | |||
| 283 | b.cache_root.handle.writeFile(tmp_sub_path, self.contents.items) catch |err| { | ||
| 284 | return step.fail("unable to write options to '{}{s}': {s}", .{ | ||
| 285 | b.cache_root, tmp_sub_path, @errorName(err), | ||
| 286 | }); | ||
| 287 | }; | ||
| 288 | |||
| 289 | b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) { | ||
| 290 | error.PathAlreadyExists => { | ||
| 291 | // Other process beat us to it. Clean up the temp file. | ||
| 292 | b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { | ||
| 293 | try step.addError("warning: unable to delete temp file '{}{s}': {s}", .{ | ||
| 294 | b.cache_root, tmp_sub_path, @errorName(e), | ||
| 295 | }); | ||
| 296 | }; | ||
| 297 | step.result_cached = true; | ||
| 298 | return; | ||
| 299 | }, | ||
| 300 | else => { | ||
| 301 | return step.fail("unable to rename options from '{}{s}' to '{}{s}': {s}", .{ | ||
| 302 | b.cache_root, tmp_sub_path, | ||
| 303 | b.cache_root, sub_path, | ||
| 304 | @errorName(err), | ||
| 305 | }); | ||
| 306 | }, | ||
| 307 | }; | ||
| 308 | }, | ||
| 309 | else => |e| return step.fail("unable to access options file '{}{s}': {s}", .{ | ||
| 310 | b.cache_root, sub_path, @errorName(e), | ||
| 311 | }), | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | const OptionArtifactArg = struct { | ||
| 316 | name: []const u8, | ||
| 317 | artifact: *CompileStep, | ||
| 318 | }; | ||
| 319 | |||
| 320 | const OptionFileSourceArg = struct { | ||
| 321 | name: []const u8, | ||
| 322 | source: FileSource, | ||
| 323 | }; | ||
| 324 | |||
| 325 | test "OptionsStep" { | ||
| 326 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 327 | |||
| 328 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | ||
| 329 | defer arena.deinit(); | ||
| 330 | |||
| 331 | const host = try std.zig.system.NativeTargetInfo.detect(.{}); | ||
| 332 | |||
| 333 | var cache: std.Build.Cache = .{ | ||
| 334 | .gpa = arena.allocator(), | ||
| 335 | .manifest_dir = std.fs.cwd(), | ||
| 336 | }; | ||
| 337 | |||
| 338 | var builder = try std.Build.create( | ||
| 339 | arena.allocator(), | ||
| 340 | "test", | ||
| 341 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 342 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 343 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 344 | host, | ||
| 345 | &cache, | ||
| 346 | ); | ||
| 347 | defer builder.destroy(); | ||
| 348 | |||
| 349 | const options = builder.addOptions(); | ||
| 350 | |||
| 351 | // TODO this regressed at some point | ||
| 352 | //const KeywordEnum = enum { | ||
| 353 | // @"0.8.1", | ||
| 354 | //}; | ||
| 355 | |||
| 356 | const nested_array = [2][2]u16{ | ||
| 357 | [2]u16{ 300, 200 }, | ||
| 358 | [2]u16{ 300, 200 }, | ||
| 359 | }; | ||
| 360 | const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; | ||
| 361 | |||
| 362 | options.addOption(usize, "option1", 1); | ||
| 363 | options.addOption(?usize, "option2", null); | ||
| 364 | options.addOption(?usize, "option3", 3); | ||
| 365 | options.addOption(comptime_int, "option4", 4); | ||
| 366 | options.addOption([]const u8, "string", "zigisthebest"); | ||
| 367 | options.addOption(?[]const u8, "optional_string", null); | ||
| 368 | options.addOption([2][2]u16, "nested_array", nested_array); | ||
| 369 | options.addOption([]const []const u16, "nested_slice", nested_slice); | ||
| 370 | //options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); | ||
| 371 | options.addOption(std.builtin.Version, "version", try std.builtin.Version.parse("0.1.2")); | ||
| 372 | options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); | ||
| 373 | |||
| 374 | try std.testing.expectEqualStrings( | ||
| 375 | \\pub const option1: usize = 1; | ||
| 376 | \\pub const option2: ?usize = null; | ||
| 377 | \\pub const option3: ?usize = 3; | ||
| 378 | \\pub const option4: comptime_int = 4; | ||
| 379 | \\pub const string: []const u8 = "zigisthebest"; | ||
| 380 | \\pub const optional_string: ?[]const u8 = null; | ||
| 381 | \\pub const nested_array: [2][2]u16 = [2][2]u16 { | ||
| 382 | \\ [2]u16 { | ||
| 383 | \\ 300, | ||
| 384 | \\ 200, | ||
| 385 | \\ }, | ||
| 386 | \\ [2]u16 { | ||
| 387 | \\ 300, | ||
| 388 | \\ 200, | ||
| 389 | \\ }, | ||
| 390 | \\}; | ||
| 391 | \\pub const nested_slice: []const []const u16 = &[_][]const u16 { | ||
| 392 | \\ &[_]u16 { | ||
| 393 | \\ 300, | ||
| 394 | \\ 200, | ||
| 395 | \\ }, | ||
| 396 | \\ &[_]u16 { | ||
| 397 | \\ 300, | ||
| 398 | \\ 200, | ||
| 399 | \\ }, | ||
| 400 | \\}; | ||
| 401 | //\\pub const KeywordEnum = enum { | ||
| 402 | //\\ @"0.8.1", | ||
| 403 | //\\}; | ||
| 404 | //\\pub const keyword_enum: KeywordEnum = KeywordEnum.@"0.8.1"; | ||
| 405 | \\pub const version: @import("std").builtin.Version = .{ | ||
| 406 | \\ .major = 0, | ||
| 407 | \\ .minor = 1, | ||
| 408 | \\ .patch = 2, | ||
| 409 | \\}; | ||
| 410 | \\pub const semantic_version: @import("std").SemanticVersion = .{ | ||
| 411 | \\ .major = 0, | ||
| 412 | \\ .minor = 1, | ||
| 413 | \\ .patch = 2, | ||
| 414 | \\ .pre = "foo", | ||
| 415 | \\ .build = "bar", | ||
| 416 | \\}; | ||
| 417 | \\ | ||
| 418 | , options.contents.items); | ||
| 419 | |||
| 420 | _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0), .zig); | ||
| 421 | } | ||
lib/std/Build/RemoveDirStep.zig deleted-42| ... | @@ -1,42 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const fs = std.fs; | ||
| 3 | const Step = std.Build.Step; | ||
| 4 | const RemoveDirStep = @This(); | ||
| 5 | |||
| 6 | pub const base_id = .remove_dir; | ||
| 7 | |||
| 8 | step: Step, | ||
| 9 | dir_path: []const u8, | ||
| 10 | |||
| 11 | pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep { | ||
| 12 | return RemoveDirStep{ | ||
| 13 | .step = Step.init(.{ | ||
| 14 | .id = .remove_dir, | ||
| 15 | .name = owner.fmt("RemoveDir {s}", .{dir_path}), | ||
| 16 | .owner = owner, | ||
| 17 | .makeFn = make, | ||
| 18 | }), | ||
| 19 | .dir_path = owner.dupePath(dir_path), | ||
| 20 | }; | ||
| 21 | } | ||
| 22 | |||
| 23 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 24 | // TODO update progress node while walking file system. | ||
| 25 | // Should the standard library support this use case?? | ||
| 26 | _ = prog_node; | ||
| 27 | |||
| 28 | const b = step.owner; | ||
| 29 | const self = @fieldParentPtr(RemoveDirStep, "step", step); | ||
| 30 | |||
| 31 | b.build_root.handle.deleteTree(self.dir_path) catch |err| { | ||
| 32 | if (b.build_root.path) |base| { | ||
| 33 | return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ | ||
| 34 | base, self.dir_path, @errorName(err), | ||
| 35 | }); | ||
| 36 | } else { | ||
| 37 | return step.fail("unable to recursively delete path '{s}': {s}", .{ | ||
| 38 | self.dir_path, @errorName(err), | ||
| 39 | }); | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | } | ||
lib/std/Build/RunStep.zig deleted-1254| ... | @@ -1,1254 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const Step = std.Build.Step; | ||
| 4 | const CompileStep = std.Build.CompileStep; | ||
| 5 | const WriteFileStep = std.Build.WriteFileStep; | ||
| 6 | const fs = std.fs; | ||
| 7 | const mem = std.mem; | ||
| 8 | const process = std.process; | ||
| 9 | const ArrayList = std.ArrayList; | ||
| 10 | const EnvMap = process.EnvMap; | ||
| 11 | const Allocator = mem.Allocator; | ||
| 12 | const ExecError = std.Build.ExecError; | ||
| 13 | const assert = std.debug.assert; | ||
| 14 | |||
| 15 | const RunStep = @This(); | ||
| 16 | |||
| 17 | pub const base_id: Step.Id = .run; | ||
| 18 | |||
| 19 | step: Step, | ||
| 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 | /// TODO change this to a Build.Cache.Directory to better integrate with | ||
| 26 | /// future child process cwd API. | ||
| 27 | cwd: ?[]const u8, | ||
| 28 | |||
| 29 | /// Override this field to modify the environment, or use setEnvironmentVariable | ||
| 30 | env_map: ?*EnvMap, | ||
| 31 | |||
| 32 | /// Configures whether the RunStep is considered to have side-effects, and also | ||
| 33 | /// whether the RunStep will inherit stdio streams, forwarding them to the | ||
| 34 | /// parent process, in which case will require a global lock to prevent other | ||
| 35 | /// steps from interfering with stdio while the subprocess associated with this | ||
| 36 | /// RunStep is running. | ||
| 37 | /// If the RunStep is determined to not have side-effects, then execution will | ||
| 38 | /// be skipped if all output files are up-to-date and input files are | ||
| 39 | /// unchanged. | ||
| 40 | stdio: StdIo = .infer_from_args, | ||
| 41 | /// This field must be `null` if stdio is `inherit`. | ||
| 42 | stdin: ?[]const u8 = null, | ||
| 43 | |||
| 44 | /// Additional file paths relative to build.zig that, when modified, indicate | ||
| 45 | /// that the RunStep should be re-executed. | ||
| 46 | /// If the RunStep is determined to have side-effects, this field is ignored | ||
| 47 | /// and the RunStep is always executed when it appears in the build graph. | ||
| 48 | extra_file_dependencies: []const []const u8 = &.{}, | ||
| 49 | |||
| 50 | /// After adding an output argument, this step will by default rename itself | ||
| 51 | /// for a better display name in the build summary. | ||
| 52 | /// This can be disabled by setting this to false. | ||
| 53 | rename_step_with_output_arg: bool = true, | ||
| 54 | |||
| 55 | /// If this is true, a RunStep which is configured to check the output of the | ||
| 56 | /// executed binary will not fail the build if the binary cannot be executed | ||
| 57 | /// due to being for a foreign binary to the host system which is running the | ||
| 58 | /// build graph. | ||
| 59 | /// Command-line arguments such as -fqemu and -fwasmtime may affect whether a | ||
| 60 | /// binary is detected as foreign, as well as system configuration such as | ||
| 61 | /// Rosetta (macOS) and binfmt_misc (Linux). | ||
| 62 | /// If this RunStep is considered to have side-effects, then this flag does | ||
| 63 | /// nothing. | ||
| 64 | skip_foreign_checks: bool = false, | ||
| 65 | |||
| 66 | /// If stderr or stdout exceeds this amount, the child process is killed and | ||
| 67 | /// the step fails. | ||
| 68 | max_stdio_size: usize = 10 * 1024 * 1024, | ||
| 69 | |||
| 70 | captured_stdout: ?*Output = null, | ||
| 71 | captured_stderr: ?*Output = null, | ||
| 72 | |||
| 73 | has_side_effects: bool = false, | ||
| 74 | |||
| 75 | pub const StdIo = union(enum) { | ||
| 76 | /// Whether the RunStep has side-effects will be determined by whether or not one | ||
| 77 | /// of the args is an output file (added with `addOutputFileArg`). | ||
| 78 | /// If the RunStep is determined to have side-effects, this is the same as `inherit`. | ||
| 79 | /// The step will fail if the subprocess crashes or returns a non-zero exit code. | ||
| 80 | infer_from_args, | ||
| 81 | /// Causes the RunStep to be considered to have side-effects, and therefore | ||
| 82 | /// always execute when it appears in the build graph. | ||
| 83 | /// It also means that this step will obtain a global lock to prevent other | ||
| 84 | /// steps from running in the meantime. | ||
| 85 | /// The step will fail if the subprocess crashes or returns a non-zero exit code. | ||
| 86 | inherit, | ||
| 87 | /// Causes the RunStep to be considered to *not* have side-effects. The | ||
| 88 | /// process will be re-executed if any of the input dependencies are | ||
| 89 | /// modified. The exit code and standard I/O streams will be checked for | ||
| 90 | /// certain conditions, and the step will succeed or fail based on these | ||
| 91 | /// conditions. | ||
| 92 | /// Note that an explicit check for exit code 0 needs to be added to this | ||
| 93 | /// list if such a check is desirable. | ||
| 94 | check: std.ArrayList(Check), | ||
| 95 | /// This RunStep is running a zig unit test binary and will communicate | ||
| 96 | /// extra metadata over the IPC protocol. | ||
| 97 | zig_test, | ||
| 98 | |||
| 99 | pub const Check = union(enum) { | ||
| 100 | expect_stderr_exact: []const u8, | ||
| 101 | expect_stderr_match: []const u8, | ||
| 102 | expect_stdout_exact: []const u8, | ||
| 103 | expect_stdout_match: []const u8, | ||
| 104 | expect_term: std.process.Child.Term, | ||
| 105 | }; | ||
| 106 | }; | ||
| 107 | |||
| 108 | pub const Arg = union(enum) { | ||
| 109 | artifact: *CompileStep, | ||
| 110 | file_source: std.Build.FileSource, | ||
| 111 | directory_source: std.Build.FileSource, | ||
| 112 | bytes: []u8, | ||
| 113 | output: *Output, | ||
| 114 | }; | ||
| 115 | |||
| 116 | pub const Output = struct { | ||
| 117 | generated_file: std.Build.GeneratedFile, | ||
| 118 | prefix: []const u8, | ||
| 119 | basename: []const u8, | ||
| 120 | }; | ||
| 121 | |||
| 122 | pub fn create(owner: *std.Build, name: []const u8) *RunStep { | ||
| 123 | const self = owner.allocator.create(RunStep) catch @panic("OOM"); | ||
| 124 | self.* = .{ | ||
| 125 | .step = Step.init(.{ | ||
| 126 | .id = base_id, | ||
| 127 | .name = name, | ||
| 128 | .owner = owner, | ||
| 129 | .makeFn = make, | ||
| 130 | }), | ||
| 131 | .argv = ArrayList(Arg).init(owner.allocator), | ||
| 132 | .cwd = null, | ||
| 133 | .env_map = null, | ||
| 134 | }; | ||
| 135 | return self; | ||
| 136 | } | ||
| 137 | |||
| 138 | pub fn setName(self: *RunStep, name: []const u8) void { | ||
| 139 | self.step.name = name; | ||
| 140 | self.rename_step_with_output_arg = false; | ||
| 141 | } | ||
| 142 | |||
| 143 | pub fn enableTestRunnerMode(rs: *RunStep) void { | ||
| 144 | rs.stdio = .zig_test; | ||
| 145 | rs.addArgs(&.{"--listen=-"}); | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { | ||
| 149 | self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); | ||
| 150 | self.step.dependOn(&artifact.step); | ||
| 151 | } | ||
| 152 | |||
| 153 | /// This provides file path as a command line argument to the command being | ||
| 154 | /// run, and returns a FileSource which can be used as inputs to other APIs | ||
| 155 | /// throughout the build system. | ||
| 156 | pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { | ||
| 157 | return addPrefixedOutputFileArg(rs, "", basename); | ||
| 158 | } | ||
| 159 | |||
| 160 | pub fn addPrefixedOutputFileArg( | ||
| 161 | rs: *RunStep, | ||
| 162 | prefix: []const u8, | ||
| 163 | basename: []const u8, | ||
| 164 | ) std.Build.FileSource { | ||
| 165 | const b = rs.step.owner; | ||
| 166 | |||
| 167 | const output = b.allocator.create(Output) catch @panic("OOM"); | ||
| 168 | output.* = .{ | ||
| 169 | .prefix = prefix, | ||
| 170 | .basename = basename, | ||
| 171 | .generated_file = .{ .step = &rs.step }, | ||
| 172 | }; | ||
| 173 | rs.argv.append(.{ .output = output }) catch @panic("OOM"); | ||
| 174 | |||
| 175 | if (rs.rename_step_with_output_arg) { | ||
| 176 | rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); | ||
| 177 | } | ||
| 178 | |||
| 179 | return .{ .generated = &output.generated_file }; | ||
| 180 | } | ||
| 181 | |||
| 182 | pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { | ||
| 183 | self.argv.append(.{ | ||
| 184 | .file_source = file_source.dupe(self.step.owner), | ||
| 185 | }) catch @panic("OOM"); | ||
| 186 | file_source.addStepDependencies(&self.step); | ||
| 187 | } | ||
| 188 | |||
| 189 | pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void { | ||
| 190 | self.argv.append(.{ | ||
| 191 | .directory_source = directory_source.dupe(self.step.owner), | ||
| 192 | }) catch @panic("OOM"); | ||
| 193 | directory_source.addStepDependencies(&self.step); | ||
| 194 | } | ||
| 195 | |||
| 196 | pub fn addArg(self: *RunStep, arg: []const u8) void { | ||
| 197 | self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); | ||
| 198 | } | ||
| 199 | |||
| 200 | pub fn addArgs(self: *RunStep, args: []const []const u8) void { | ||
| 201 | for (args) |arg| { | ||
| 202 | self.addArg(arg); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | pub fn clearEnvironment(self: *RunStep) void { | ||
| 207 | const b = self.step.owner; | ||
| 208 | const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); | ||
| 209 | new_env_map.* = EnvMap.init(b.allocator); | ||
| 210 | self.env_map = new_env_map; | ||
| 211 | } | ||
| 212 | |||
| 213 | pub fn addPathDir(self: *RunStep, search_path: []const u8) void { | ||
| 214 | const b = self.step.owner; | ||
| 215 | const env_map = getEnvMapInternal(self); | ||
| 216 | |||
| 217 | const key = "PATH"; | ||
| 218 | var prev_path = env_map.get(key); | ||
| 219 | |||
| 220 | if (prev_path) |pp| { | ||
| 221 | const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); | ||
| 222 | env_map.put(key, new_path) catch @panic("OOM"); | ||
| 223 | } else { | ||
| 224 | env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn getEnvMap(self: *RunStep) *EnvMap { | ||
| 229 | return getEnvMapInternal(self); | ||
| 230 | } | ||
| 231 | |||
| 232 | fn getEnvMapInternal(self: *RunStep) *EnvMap { | ||
| 233 | const arena = self.step.owner.allocator; | ||
| 234 | return self.env_map orelse { | ||
| 235 | const env_map = arena.create(EnvMap) catch @panic("OOM"); | ||
| 236 | env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); | ||
| 237 | self.env_map = env_map; | ||
| 238 | return env_map; | ||
| 239 | }; | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { | ||
| 243 | const b = self.step.owner; | ||
| 244 | const env_map = self.getEnvMap(); | ||
| 245 | env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); | ||
| 246 | } | ||
| 247 | |||
| 248 | pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void { | ||
| 249 | self.getEnvMap().remove(key); | ||
| 250 | } | ||
| 251 | |||
| 252 | /// Adds a check for exact stderr match. Does not add any other checks. | ||
| 253 | pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { | ||
| 254 | const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; | ||
| 255 | self.addCheck(new_check); | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Adds a check for exact stdout match as well as a check for exit code 0, if | ||
| 259 | /// there is not already an expected termination check. | ||
| 260 | pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { | ||
| 261 | const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; | ||
| 262 | self.addCheck(new_check); | ||
| 263 | if (!self.hasTermCheck()) { | ||
| 264 | self.expectExitCode(0); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | pub fn expectExitCode(self: *RunStep, code: u8) void { | ||
| 269 | const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; | ||
| 270 | self.addCheck(new_check); | ||
| 271 | } | ||
| 272 | |||
| 273 | pub fn hasTermCheck(self: RunStep) bool { | ||
| 274 | for (self.stdio.check.items) |check| switch (check) { | ||
| 275 | .expect_term => return true, | ||
| 276 | else => continue, | ||
| 277 | }; | ||
| 278 | return false; | ||
| 279 | } | ||
| 280 | |||
| 281 | pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { | ||
| 282 | switch (self.stdio) { | ||
| 283 | .infer_from_args => { | ||
| 284 | self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; | ||
| 285 | self.stdio.check.append(new_check) catch @panic("OOM"); | ||
| 286 | }, | ||
| 287 | .check => |*checks| checks.append(new_check) catch @panic("OOM"), | ||
| 288 | else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | pub fn captureStdErr(self: *RunStep) std.Build.FileSource { | ||
| 293 | assert(self.stdio != .inherit); | ||
| 294 | |||
| 295 | if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; | ||
| 296 | |||
| 297 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); | ||
| 298 | output.* = .{ | ||
| 299 | .prefix = "", | ||
| 300 | .basename = "stderr", | ||
| 301 | .generated_file = .{ .step = &self.step }, | ||
| 302 | }; | ||
| 303 | self.captured_stderr = output; | ||
| 304 | return .{ .generated = &output.generated_file }; | ||
| 305 | } | ||
| 306 | |||
| 307 | pub fn captureStdOut(self: *RunStep) std.Build.FileSource { | ||
| 308 | assert(self.stdio != .inherit); | ||
| 309 | |||
| 310 | if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; | ||
| 311 | |||
| 312 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); | ||
| 313 | output.* = .{ | ||
| 314 | .prefix = "", | ||
| 315 | .basename = "stdout", | ||
| 316 | .generated_file = .{ .step = &self.step }, | ||
| 317 | }; | ||
| 318 | self.captured_stdout = output; | ||
| 319 | return .{ .generated = &output.generated_file }; | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Returns whether the RunStep has side effects *other than* updating the output arguments. | ||
| 323 | fn hasSideEffects(self: RunStep) bool { | ||
| 324 | if (self.has_side_effects) return true; | ||
| 325 | return switch (self.stdio) { | ||
| 326 | .infer_from_args => !self.hasAnyOutputArgs(), | ||
| 327 | .inherit => true, | ||
| 328 | .check => false, | ||
| 329 | .zig_test => false, | ||
| 330 | }; | ||
| 331 | } | ||
| 332 | |||
| 333 | fn hasAnyOutputArgs(self: RunStep) bool { | ||
| 334 | if (self.captured_stdout != null) return true; | ||
| 335 | if (self.captured_stderr != null) return true; | ||
| 336 | for (self.argv.items) |arg| switch (arg) { | ||
| 337 | .output => return true, | ||
| 338 | else => continue, | ||
| 339 | }; | ||
| 340 | return false; | ||
| 341 | } | ||
| 342 | |||
| 343 | fn checksContainStdout(checks: []const StdIo.Check) bool { | ||
| 344 | for (checks) |check| switch (check) { | ||
| 345 | .expect_stderr_exact, | ||
| 346 | .expect_stderr_match, | ||
| 347 | .expect_term, | ||
| 348 | => continue, | ||
| 349 | |||
| 350 | .expect_stdout_exact, | ||
| 351 | .expect_stdout_match, | ||
| 352 | => return true, | ||
| 353 | }; | ||
| 354 | return false; | ||
| 355 | } | ||
| 356 | |||
| 357 | fn checksContainStderr(checks: []const StdIo.Check) bool { | ||
| 358 | for (checks) |check| switch (check) { | ||
| 359 | .expect_stdout_exact, | ||
| 360 | .expect_stdout_match, | ||
| 361 | .expect_term, | ||
| 362 | => continue, | ||
| 363 | |||
| 364 | .expect_stderr_exact, | ||
| 365 | .expect_stderr_match, | ||
| 366 | => return true, | ||
| 367 | }; | ||
| 368 | return false; | ||
| 369 | } | ||
| 370 | |||
| 371 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 372 | const b = step.owner; | ||
| 373 | const arena = b.allocator; | ||
| 374 | const self = @fieldParentPtr(RunStep, "step", step); | ||
| 375 | const has_side_effects = self.hasSideEffects(); | ||
| 376 | |||
| 377 | var argv_list = ArrayList([]const u8).init(arena); | ||
| 378 | var output_placeholders = ArrayList(struct { | ||
| 379 | index: usize, | ||
| 380 | output: *Output, | ||
| 381 | }).init(arena); | ||
| 382 | |||
| 383 | var man = b.cache.obtain(); | ||
| 384 | defer man.deinit(); | ||
| 385 | |||
| 386 | for (self.argv.items) |arg| { | ||
| 387 | switch (arg) { | ||
| 388 | .bytes => |bytes| { | ||
| 389 | try argv_list.append(bytes); | ||
| 390 | man.hash.addBytes(bytes); | ||
| 391 | }, | ||
| 392 | .file_source => |file| { | ||
| 393 | const file_path = file.getPath(b); | ||
| 394 | try argv_list.append(file_path); | ||
| 395 | _ = try man.addFile(file_path, null); | ||
| 396 | }, | ||
| 397 | .directory_source => |file| { | ||
| 398 | const file_path = file.getPath(b); | ||
| 399 | try argv_list.append(file_path); | ||
| 400 | man.hash.addBytes(file_path); | ||
| 401 | }, | ||
| 402 | .artifact => |artifact| { | ||
| 403 | if (artifact.target.isWindows()) { | ||
| 404 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | ||
| 405 | self.addPathForDynLibs(artifact); | ||
| 406 | } | ||
| 407 | const file_path = artifact.installed_path orelse | ||
| 408 | artifact.getOutputSource().getPath(b); | ||
| 409 | |||
| 410 | try argv_list.append(file_path); | ||
| 411 | |||
| 412 | _ = try man.addFile(file_path, null); | ||
| 413 | }, | ||
| 414 | .output => |output| { | ||
| 415 | man.hash.addBytes(output.prefix); | ||
| 416 | man.hash.addBytes(output.basename); | ||
| 417 | // Add a placeholder into the argument list because we need the | ||
| 418 | // manifest hash to be updated with all arguments before the | ||
| 419 | // object directory is computed. | ||
| 420 | try argv_list.append(""); | ||
| 421 | try output_placeholders.append(.{ | ||
| 422 | .index = argv_list.items.len - 1, | ||
| 423 | .output = output, | ||
| 424 | }); | ||
| 425 | }, | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | if (self.captured_stdout) |output| { | ||
| 430 | man.hash.addBytes(output.basename); | ||
| 431 | } | ||
| 432 | |||
| 433 | if (self.captured_stderr) |output| { | ||
| 434 | man.hash.addBytes(output.basename); | ||
| 435 | } | ||
| 436 | |||
| 437 | hashStdIo(&man.hash, self.stdio); | ||
| 438 | |||
| 439 | if (has_side_effects) { | ||
| 440 | try runCommand(self, argv_list.items, has_side_effects, null, prog_node); | ||
| 441 | return; | ||
| 442 | } | ||
| 443 | |||
| 444 | for (self.extra_file_dependencies) |file_path| { | ||
| 445 | _ = try man.addFile(b.pathFromRoot(file_path), null); | ||
| 446 | } | ||
| 447 | |||
| 448 | if (try step.cacheHit(&man)) { | ||
| 449 | // cache hit, skip running command | ||
| 450 | const digest = man.final(); | ||
| 451 | for (output_placeholders.items) |placeholder| { | ||
| 452 | placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 453 | "o", &digest, placeholder.output.basename, | ||
| 454 | }); | ||
| 455 | } | ||
| 456 | |||
| 457 | if (self.captured_stdout) |output| { | ||
| 458 | output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 459 | "o", &digest, output.basename, | ||
| 460 | }); | ||
| 461 | } | ||
| 462 | |||
| 463 | if (self.captured_stderr) |output| { | ||
| 464 | output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 465 | "o", &digest, output.basename, | ||
| 466 | }); | ||
| 467 | } | ||
| 468 | |||
| 469 | step.result_cached = true; | ||
| 470 | return; | ||
| 471 | } | ||
| 472 | |||
| 473 | const digest = man.final(); | ||
| 474 | |||
| 475 | for (output_placeholders.items) |placeholder| { | ||
| 476 | const output_components = .{ "o", &digest, placeholder.output.basename }; | ||
| 477 | const output_sub_path = try fs.path.join(arena, &output_components); | ||
| 478 | const output_sub_dir_path = fs.path.dirname(output_sub_path).?; | ||
| 479 | b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { | ||
| 480 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 481 | b.cache_root, output_sub_dir_path, @errorName(err), | ||
| 482 | }); | ||
| 483 | }; | ||
| 484 | const output_path = try b.cache_root.join(arena, &output_components); | ||
| 485 | placeholder.output.generated_file.path = output_path; | ||
| 486 | const cli_arg = if (placeholder.output.prefix.len == 0) | ||
| 487 | output_path | ||
| 488 | else | ||
| 489 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); | ||
| 490 | argv_list.items[placeholder.index] = cli_arg; | ||
| 491 | } | ||
| 492 | |||
| 493 | try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); | ||
| 494 | |||
| 495 | try step.writeManifest(&man); | ||
| 496 | } | ||
| 497 | |||
| 498 | fn formatTerm( | ||
| 499 | term: ?std.process.Child.Term, | ||
| 500 | comptime fmt: []const u8, | ||
| 501 | options: std.fmt.FormatOptions, | ||
| 502 | writer: anytype, | ||
| 503 | ) !void { | ||
| 504 | _ = fmt; | ||
| 505 | _ = options; | ||
| 506 | if (term) |t| switch (t) { | ||
| 507 | .Exited => |code| try writer.print("exited with code {}", .{code}), | ||
| 508 | .Signal => |sig| try writer.print("terminated with signal {}", .{sig}), | ||
| 509 | .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}), | ||
| 510 | .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}), | ||
| 511 | } else { | ||
| 512 | try writer.writeAll("exited with any code"); | ||
| 513 | } | ||
| 514 | } | ||
| 515 | fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) { | ||
| 516 | return .{ .data = term }; | ||
| 517 | } | ||
| 518 | |||
| 519 | fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { | ||
| 520 | return if (expected) |e| switch (e) { | ||
| 521 | .Exited => |expected_code| switch (actual) { | ||
| 522 | .Exited => |actual_code| expected_code == actual_code, | ||
| 523 | else => false, | ||
| 524 | }, | ||
| 525 | .Signal => |expected_sig| switch (actual) { | ||
| 526 | .Signal => |actual_sig| expected_sig == actual_sig, | ||
| 527 | else => false, | ||
| 528 | }, | ||
| 529 | .Stopped => |expected_sig| switch (actual) { | ||
| 530 | .Stopped => |actual_sig| expected_sig == actual_sig, | ||
| 531 | else => false, | ||
| 532 | }, | ||
| 533 | .Unknown => |expected_code| switch (actual) { | ||
| 534 | .Unknown => |actual_code| expected_code == actual_code, | ||
| 535 | else => false, | ||
| 536 | }, | ||
| 537 | } else switch (actual) { | ||
| 538 | .Exited => true, | ||
| 539 | else => false, | ||
| 540 | }; | ||
| 541 | } | ||
| 542 | |||
| 543 | fn runCommand( | ||
| 544 | self: *RunStep, | ||
| 545 | argv: []const []const u8, | ||
| 546 | has_side_effects: bool, | ||
| 547 | digest: ?*const [std.Build.Cache.hex_digest_len]u8, | ||
| 548 | prog_node: *std.Progress.Node, | ||
| 549 | ) !void { | ||
| 550 | const step = &self.step; | ||
| 551 | const b = step.owner; | ||
| 552 | const arena = b.allocator; | ||
| 553 | |||
| 554 | try step.handleChildProcUnsupported(self.cwd, argv); | ||
| 555 | try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv); | ||
| 556 | |||
| 557 | const allow_skip = switch (self.stdio) { | ||
| 558 | .check, .zig_test => self.skip_foreign_checks, | ||
| 559 | else => false, | ||
| 560 | }; | ||
| 561 | |||
| 562 | var interp_argv = std.ArrayList([]const u8).init(b.allocator); | ||
| 563 | defer interp_argv.deinit(); | ||
| 564 | |||
| 565 | const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { | ||
| 566 | // InvalidExe: cpu arch mismatch | ||
| 567 | // FileNotFound: can happen with a wrong dynamic linker path | ||
| 568 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { | ||
| 569 | // TODO: learn the target from the binary directly rather than from | ||
| 570 | // relying on it being a CompileStep. This will make this logic | ||
| 571 | // work even for the edge case that the binary was produced by a | ||
| 572 | // third party. | ||
| 573 | const exe = switch (self.argv.items[0]) { | ||
| 574 | .artifact => |exe| exe, | ||
| 575 | else => break :interpret, | ||
| 576 | }; | ||
| 577 | switch (exe.kind) { | ||
| 578 | .exe, .@"test" => {}, | ||
| 579 | else => break :interpret, | ||
| 580 | } | ||
| 581 | |||
| 582 | const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; | ||
| 583 | switch (b.host.getExternalExecutor(exe.target_info, .{ | ||
| 584 | .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, | ||
| 585 | .link_libc = exe.is_linking_libc, | ||
| 586 | })) { | ||
| 587 | .native, .rosetta => { | ||
| 588 | if (allow_skip) return error.MakeSkipped; | ||
| 589 | break :interpret; | ||
| 590 | }, | ||
| 591 | .wine => |bin_name| { | ||
| 592 | if (b.enable_wine) { | ||
| 593 | try interp_argv.append(bin_name); | ||
| 594 | try interp_argv.appendSlice(argv); | ||
| 595 | } else { | ||
| 596 | return failForeign(self, "-fwine", argv[0], exe); | ||
| 597 | } | ||
| 598 | }, | ||
| 599 | .qemu => |bin_name| { | ||
| 600 | if (b.enable_qemu) { | ||
| 601 | const glibc_dir_arg = if (need_cross_glibc) | ||
| 602 | b.glibc_runtimes_dir orelse | ||
| 603 | return failForeign(self, "--glibc-runtimes", argv[0], exe) | ||
| 604 | else | ||
| 605 | null; | ||
| 606 | |||
| 607 | try interp_argv.append(bin_name); | ||
| 608 | |||
| 609 | if (glibc_dir_arg) |dir| { | ||
| 610 | // TODO look into making this a call to `linuxTriple`. This | ||
| 611 | // needs the directory to be called "i686" rather than | ||
| 612 | // "x86" which is why we do it manually here. | ||
| 613 | const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; | ||
| 614 | const cpu_arch = exe.target.getCpuArch(); | ||
| 615 | const os_tag = exe.target.getOsTag(); | ||
| 616 | const abi = exe.target.getAbi(); | ||
| 617 | const cpu_arch_name: []const u8 = if (cpu_arch == .x86) | ||
| 618 | "i686" | ||
| 619 | else | ||
| 620 | @tagName(cpu_arch); | ||
| 621 | const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ | ||
| 622 | dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), | ||
| 623 | }); | ||
| 624 | |||
| 625 | try interp_argv.append("-L"); | ||
| 626 | try interp_argv.append(full_dir); | ||
| 627 | } | ||
| 628 | |||
| 629 | try interp_argv.appendSlice(argv); | ||
| 630 | } else { | ||
| 631 | return failForeign(self, "-fqemu", argv[0], exe); | ||
| 632 | } | ||
| 633 | }, | ||
| 634 | .darling => |bin_name| { | ||
| 635 | if (b.enable_darling) { | ||
| 636 | try interp_argv.append(bin_name); | ||
| 637 | try interp_argv.appendSlice(argv); | ||
| 638 | } else { | ||
| 639 | return failForeign(self, "-fdarling", argv[0], exe); | ||
| 640 | } | ||
| 641 | }, | ||
| 642 | .wasmtime => |bin_name| { | ||
| 643 | if (b.enable_wasmtime) { | ||
| 644 | try interp_argv.append(bin_name); | ||
| 645 | try interp_argv.append("--dir=."); | ||
| 646 | try interp_argv.append(argv[0]); | ||
| 647 | try interp_argv.append("--"); | ||
| 648 | try interp_argv.appendSlice(argv[1..]); | ||
| 649 | } else { | ||
| 650 | return failForeign(self, "-fwasmtime", argv[0], exe); | ||
| 651 | } | ||
| 652 | }, | ||
| 653 | .bad_dl => |foreign_dl| { | ||
| 654 | if (allow_skip) return error.MakeSkipped; | ||
| 655 | |||
| 656 | const host_dl = b.host.dynamic_linker.get() orelse "(none)"; | ||
| 657 | |||
| 658 | return step.fail( | ||
| 659 | \\the host system is unable to execute binaries from the target | ||
| 660 | \\ because the host dynamic linker is '{s}', | ||
| 661 | \\ while the target dynamic linker is '{s}'. | ||
| 662 | \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step | ||
| 663 | , .{ host_dl, foreign_dl }); | ||
| 664 | }, | ||
| 665 | .bad_os_or_cpu => { | ||
| 666 | if (allow_skip) return error.MakeSkipped; | ||
| 667 | |||
| 668 | const host_name = try b.host.target.zigTriple(b.allocator); | ||
| 669 | const foreign_name = try exe.target.zigTriple(b.allocator); | ||
| 670 | |||
| 671 | return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ | ||
| 672 | host_name, foreign_name, | ||
| 673 | }); | ||
| 674 | }, | ||
| 675 | } | ||
| 676 | |||
| 677 | if (exe.target.isWindows()) { | ||
| 678 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | ||
| 679 | self.addPathForDynLibs(exe); | ||
| 680 | } | ||
| 681 | |||
| 682 | try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); | ||
| 683 | |||
| 684 | break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { | ||
| 685 | return step.fail("unable to spawn interpreter {s}: {s}", .{ | ||
| 686 | interp_argv.items[0], @errorName(e), | ||
| 687 | }); | ||
| 688 | }; | ||
| 689 | } | ||
| 690 | |||
| 691 | return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); | ||
| 692 | }; | ||
| 693 | |||
| 694 | step.result_duration_ns = result.elapsed_ns; | ||
| 695 | step.result_peak_rss = result.peak_rss; | ||
| 696 | step.test_results = result.stdio.test_results; | ||
| 697 | |||
| 698 | // Capture stdout and stderr to GeneratedFile objects. | ||
| 699 | const Stream = struct { | ||
| 700 | captured: ?*Output, | ||
| 701 | is_null: bool, | ||
| 702 | bytes: []const u8, | ||
| 703 | }; | ||
| 704 | for ([_]Stream{ | ||
| 705 | .{ | ||
| 706 | .captured = self.captured_stdout, | ||
| 707 | .is_null = result.stdio.stdout_null, | ||
| 708 | .bytes = result.stdio.stdout, | ||
| 709 | }, | ||
| 710 | .{ | ||
| 711 | .captured = self.captured_stderr, | ||
| 712 | .is_null = result.stdio.stderr_null, | ||
| 713 | .bytes = result.stdio.stderr, | ||
| 714 | }, | ||
| 715 | }) |stream| { | ||
| 716 | if (stream.captured) |output| { | ||
| 717 | assert(!stream.is_null); | ||
| 718 | |||
| 719 | const output_components = .{ "o", digest.?, output.basename }; | ||
| 720 | const output_path = try b.cache_root.join(arena, &output_components); | ||
| 721 | output.generated_file.path = output_path; | ||
| 722 | |||
| 723 | const sub_path = try fs.path.join(arena, &output_components); | ||
| 724 | const sub_path_dirname = fs.path.dirname(sub_path).?; | ||
| 725 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | ||
| 726 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 727 | b.cache_root, sub_path_dirname, @errorName(err), | ||
| 728 | }); | ||
| 729 | }; | ||
| 730 | b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { | ||
| 731 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 732 | b.cache_root, sub_path, @errorName(err), | ||
| 733 | }); | ||
| 734 | }; | ||
| 735 | } | ||
| 736 | } | ||
| 737 | |||
| 738 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; | ||
| 739 | |||
| 740 | switch (self.stdio) { | ||
| 741 | .check => |checks| for (checks.items) |check| switch (check) { | ||
| 742 | .expect_stderr_exact => |expected_bytes| { | ||
| 743 | assert(!result.stdio.stderr_null); | ||
| 744 | if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { | ||
| 745 | return step.fail( | ||
| 746 | \\ | ||
| 747 | \\========= expected this stderr: ========= | ||
| 748 | \\{s} | ||
| 749 | \\========= but found: ==================== | ||
| 750 | \\{s} | ||
| 751 | \\========= from the following command: === | ||
| 752 | \\{s} | ||
| 753 | , .{ | ||
| 754 | expected_bytes, | ||
| 755 | result.stdio.stderr, | ||
| 756 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 757 | }); | ||
| 758 | } | ||
| 759 | }, | ||
| 760 | .expect_stderr_match => |match| { | ||
| 761 | assert(!result.stdio.stderr_null); | ||
| 762 | if (mem.indexOf(u8, result.stdio.stderr, match) == null) { | ||
| 763 | return step.fail( | ||
| 764 | \\ | ||
| 765 | \\========= expected to find in stderr: ========= | ||
| 766 | \\{s} | ||
| 767 | \\========= but stderr does not contain it: ===== | ||
| 768 | \\{s} | ||
| 769 | \\========= from the following command: ========= | ||
| 770 | \\{s} | ||
| 771 | , .{ | ||
| 772 | match, | ||
| 773 | result.stdio.stderr, | ||
| 774 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 775 | }); | ||
| 776 | } | ||
| 777 | }, | ||
| 778 | .expect_stdout_exact => |expected_bytes| { | ||
| 779 | assert(!result.stdio.stdout_null); | ||
| 780 | if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { | ||
| 781 | return step.fail( | ||
| 782 | \\ | ||
| 783 | \\========= expected this stdout: ========= | ||
| 784 | \\{s} | ||
| 785 | \\========= but found: ==================== | ||
| 786 | \\{s} | ||
| 787 | \\========= from the following command: === | ||
| 788 | \\{s} | ||
| 789 | , .{ | ||
| 790 | expected_bytes, | ||
| 791 | result.stdio.stdout, | ||
| 792 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 793 | }); | ||
| 794 | } | ||
| 795 | }, | ||
| 796 | .expect_stdout_match => |match| { | ||
| 797 | assert(!result.stdio.stdout_null); | ||
| 798 | if (mem.indexOf(u8, result.stdio.stdout, match) == null) { | ||
| 799 | return step.fail( | ||
| 800 | \\ | ||
| 801 | \\========= expected to find in stdout: ========= | ||
| 802 | \\{s} | ||
| 803 | \\========= but stdout does not contain it: ===== | ||
| 804 | \\{s} | ||
| 805 | \\========= from the following command: ========= | ||
| 806 | \\{s} | ||
| 807 | , .{ | ||
| 808 | match, | ||
| 809 | result.stdio.stdout, | ||
| 810 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 811 | }); | ||
| 812 | } | ||
| 813 | }, | ||
| 814 | .expect_term => |expected_term| { | ||
| 815 | if (!termMatches(expected_term, result.term)) { | ||
| 816 | return step.fail("the following command {} (expected {}):\n{s}", .{ | ||
| 817 | fmtTerm(result.term), | ||
| 818 | fmtTerm(expected_term), | ||
| 819 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 820 | }); | ||
| 821 | } | ||
| 822 | }, | ||
| 823 | }, | ||
| 824 | .zig_test => { | ||
| 825 | const prefix: []const u8 = p: { | ||
| 826 | if (result.stdio.test_metadata) |tm| { | ||
| 827 | if (tm.next_index <= tm.names.len) { | ||
| 828 | const name = tm.testName(tm.next_index - 1); | ||
| 829 | break :p b.fmt("while executing test '{s}', ", .{name}); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | break :p ""; | ||
| 833 | }; | ||
| 834 | const expected_term: std.process.Child.Term = .{ .Exited = 0 }; | ||
| 835 | if (!termMatches(expected_term, result.term)) { | ||
| 836 | return step.fail("{s}the following command {} (expected {}):\n{s}", .{ | ||
| 837 | prefix, | ||
| 838 | fmtTerm(result.term), | ||
| 839 | fmtTerm(expected_term), | ||
| 840 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 841 | }); | ||
| 842 | } | ||
| 843 | if (!result.stdio.test_results.isSuccess()) { | ||
| 844 | return step.fail( | ||
| 845 | "{s}the following test command failed:\n{s}", | ||
| 846 | .{ prefix, try Step.allocPrintCmd(arena, self.cwd, final_argv) }, | ||
| 847 | ); | ||
| 848 | } | ||
| 849 | }, | ||
| 850 | else => { | ||
| 851 | try step.handleChildProcessTerm(result.term, self.cwd, final_argv); | ||
| 852 | }, | ||
| 853 | } | ||
| 854 | } | ||
| 855 | |||
| 856 | const ChildProcResult = struct { | ||
| 857 | term: std.process.Child.Term, | ||
| 858 | elapsed_ns: u64, | ||
| 859 | peak_rss: usize, | ||
| 860 | |||
| 861 | stdio: StdIoResult, | ||
| 862 | }; | ||
| 863 | |||
| 864 | fn spawnChildAndCollect( | ||
| 865 | self: *RunStep, | ||
| 866 | argv: []const []const u8, | ||
| 867 | has_side_effects: bool, | ||
| 868 | prog_node: *std.Progress.Node, | ||
| 869 | ) !ChildProcResult { | ||
| 870 | const b = self.step.owner; | ||
| 871 | const arena = b.allocator; | ||
| 872 | |||
| 873 | var child = std.process.Child.init(argv, arena); | ||
| 874 | if (self.cwd) |cwd| { | ||
| 875 | child.cwd = b.pathFromRoot(cwd); | ||
| 876 | } else { | ||
| 877 | child.cwd = b.build_root.path; | ||
| 878 | child.cwd_dir = b.build_root.handle; | ||
| 879 | } | ||
| 880 | child.env_map = self.env_map orelse b.env_map; | ||
| 881 | child.request_resource_usage_statistics = true; | ||
| 882 | |||
| 883 | child.stdin_behavior = switch (self.stdio) { | ||
| 884 | .infer_from_args => if (has_side_effects) .Inherit else .Ignore, | ||
| 885 | .inherit => .Inherit, | ||
| 886 | .check => .Ignore, | ||
| 887 | .zig_test => .Pipe, | ||
| 888 | }; | ||
| 889 | child.stdout_behavior = switch (self.stdio) { | ||
| 890 | .infer_from_args => if (has_side_effects) .Inherit else .Ignore, | ||
| 891 | .inherit => .Inherit, | ||
| 892 | .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, | ||
| 893 | .zig_test => .Pipe, | ||
| 894 | }; | ||
| 895 | child.stderr_behavior = switch (self.stdio) { | ||
| 896 | .infer_from_args => if (has_side_effects) .Inherit else .Pipe, | ||
| 897 | .inherit => .Inherit, | ||
| 898 | .check => .Pipe, | ||
| 899 | .zig_test => .Pipe, | ||
| 900 | }; | ||
| 901 | if (self.captured_stdout != null) child.stdout_behavior = .Pipe; | ||
| 902 | if (self.captured_stderr != null) child.stderr_behavior = .Pipe; | ||
| 903 | if (self.stdin != null) { | ||
| 904 | assert(child.stdin_behavior != .Inherit); | ||
| 905 | child.stdin_behavior = .Pipe; | ||
| 906 | } | ||
| 907 | |||
| 908 | try child.spawn(); | ||
| 909 | var timer = try std.time.Timer.start(); | ||
| 910 | |||
| 911 | const result = if (self.stdio == .zig_test) | ||
| 912 | evalZigTest(self, &child, prog_node) | ||
| 913 | else | ||
| 914 | evalGeneric(self, &child); | ||
| 915 | |||
| 916 | const term = try child.wait(); | ||
| 917 | const elapsed_ns = timer.read(); | ||
| 918 | |||
| 919 | return .{ | ||
| 920 | .stdio = try result, | ||
| 921 | .term = term, | ||
| 922 | .elapsed_ns = elapsed_ns, | ||
| 923 | .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, | ||
| 924 | }; | ||
| 925 | } | ||
| 926 | |||
| 927 | const StdIoResult = struct { | ||
| 928 | // These use boolean flags instead of optionals as a workaround for | ||
| 929 | // https://github.com/ziglang/zig/issues/14783 | ||
| 930 | stdout: []const u8, | ||
| 931 | stderr: []const u8, | ||
| 932 | stdout_null: bool, | ||
| 933 | stderr_null: bool, | ||
| 934 | test_results: Step.TestResults, | ||
| 935 | test_metadata: ?TestMetadata, | ||
| 936 | }; | ||
| 937 | |||
| 938 | fn evalZigTest( | ||
| 939 | self: *RunStep, | ||
| 940 | child: *std.process.Child, | ||
| 941 | prog_node: *std.Progress.Node, | ||
| 942 | ) !StdIoResult { | ||
| 943 | const gpa = self.step.owner.allocator; | ||
| 944 | const arena = self.step.owner.allocator; | ||
| 945 | |||
| 946 | var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ | ||
| 947 | .stdout = child.stdout.?, | ||
| 948 | .stderr = child.stderr.?, | ||
| 949 | }); | ||
| 950 | defer poller.deinit(); | ||
| 951 | |||
| 952 | try sendMessage(child.stdin.?, .query_test_metadata); | ||
| 953 | |||
| 954 | const Header = std.zig.Server.Message.Header; | ||
| 955 | |||
| 956 | const stdout = poller.fifo(.stdout); | ||
| 957 | const stderr = poller.fifo(.stderr); | ||
| 958 | |||
| 959 | var fail_count: u32 = 0; | ||
| 960 | var skip_count: u32 = 0; | ||
| 961 | var leak_count: u32 = 0; | ||
| 962 | var test_count: u32 = 0; | ||
| 963 | |||
| 964 | var metadata: ?TestMetadata = null; | ||
| 965 | |||
| 966 | var sub_prog_node: ?std.Progress.Node = null; | ||
| 967 | defer if (sub_prog_node) |*n| n.end(); | ||
| 968 | |||
| 969 | poll: while (true) { | ||
| 970 | while (stdout.readableLength() < @sizeOf(Header)) { | ||
| 971 | if (!(try poller.poll())) break :poll; | ||
| 972 | } | ||
| 973 | const header = stdout.reader().readStruct(Header) catch unreachable; | ||
| 974 | while (stdout.readableLength() < header.bytes_len) { | ||
| 975 | if (!(try poller.poll())) break :poll; | ||
| 976 | } | ||
| 977 | const body = stdout.readableSliceOfLen(header.bytes_len); | ||
| 978 | |||
| 979 | switch (header.tag) { | ||
| 980 | .zig_version => { | ||
| 981 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | ||
| 982 | return self.step.fail( | ||
| 983 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | ||
| 984 | .{ builtin.zig_version_string, body }, | ||
| 985 | ); | ||
| 986 | } | ||
| 987 | }, | ||
| 988 | .test_metadata => { | ||
| 989 | const TmHdr = std.zig.Server.Message.TestMetadata; | ||
| 990 | const tm_hdr = @ptrCast(*align(1) const TmHdr, body); | ||
| 991 | test_count = tm_hdr.tests_len; | ||
| 992 | |||
| 993 | const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; | ||
| 994 | const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; | ||
| 995 | const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; | ||
| 996 | const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; | ||
| 997 | |||
| 998 | const names = std.mem.bytesAsSlice(u32, names_bytes); | ||
| 999 | const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); | ||
| 1000 | const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); | ||
| 1001 | const names_aligned = try arena.alloc(u32, names.len); | ||
| 1002 | for (names_aligned, names) |*dest, src| dest.* = src; | ||
| 1003 | |||
| 1004 | const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); | ||
| 1005 | for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; | ||
| 1006 | |||
| 1007 | const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); | ||
| 1008 | for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; | ||
| 1009 | |||
| 1010 | prog_node.setEstimatedTotalItems(names.len); | ||
| 1011 | metadata = .{ | ||
| 1012 | .string_bytes = try arena.dupe(u8, string_bytes), | ||
| 1013 | .names = names_aligned, | ||
| 1014 | .async_frame_lens = async_frame_lens_aligned, | ||
| 1015 | .expected_panic_msgs = expected_panic_msgs_aligned, | ||
| 1016 | .next_index = 0, | ||
| 1017 | .prog_node = prog_node, | ||
| 1018 | }; | ||
| 1019 | |||
| 1020 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); | ||
| 1021 | }, | ||
| 1022 | .test_results => { | ||
| 1023 | const md = metadata.?; | ||
| 1024 | |||
| 1025 | const TrHdr = std.zig.Server.Message.TestResults; | ||
| 1026 | const tr_hdr = @ptrCast(*align(1) const TrHdr, body); | ||
| 1027 | fail_count += @boolToInt(tr_hdr.flags.fail); | ||
| 1028 | skip_count += @boolToInt(tr_hdr.flags.skip); | ||
| 1029 | leak_count += @boolToInt(tr_hdr.flags.leak); | ||
| 1030 | |||
| 1031 | if (tr_hdr.flags.fail or tr_hdr.flags.leak) { | ||
| 1032 | const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); | ||
| 1033 | const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); | ||
| 1034 | const label = if (tr_hdr.flags.fail) "failed" else "leaked"; | ||
| 1035 | if (msg.len > 0) { | ||
| 1036 | try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); | ||
| 1037 | } else { | ||
| 1038 | try self.step.addError("'{s}' {s}", .{ name, label }); | ||
| 1039 | } | ||
| 1040 | stderr.discard(msg.len); | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); | ||
| 1044 | }, | ||
| 1045 | else => {}, // ignore other messages | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | stdout.discard(body.len); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | if (stderr.readableLength() > 0) { | ||
| 1052 | const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); | ||
| 1053 | if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | // Send EOF to stdin. | ||
| 1057 | child.stdin.?.close(); | ||
| 1058 | child.stdin = null; | ||
| 1059 | |||
| 1060 | return .{ | ||
| 1061 | .stdout = &.{}, | ||
| 1062 | .stderr = &.{}, | ||
| 1063 | .stdout_null = true, | ||
| 1064 | .stderr_null = true, | ||
| 1065 | .test_results = .{ | ||
| 1066 | .test_count = test_count, | ||
| 1067 | .fail_count = fail_count, | ||
| 1068 | .skip_count = skip_count, | ||
| 1069 | .leak_count = leak_count, | ||
| 1070 | }, | ||
| 1071 | .test_metadata = metadata, | ||
| 1072 | }; | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | const TestMetadata = struct { | ||
| 1076 | names: []const u32, | ||
| 1077 | async_frame_lens: []const u32, | ||
| 1078 | expected_panic_msgs: []const u32, | ||
| 1079 | string_bytes: []const u8, | ||
| 1080 | next_index: u32, | ||
| 1081 | prog_node: *std.Progress.Node, | ||
| 1082 | |||
| 1083 | fn testName(tm: TestMetadata, index: u32) []const u8 { | ||
| 1084 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); | ||
| 1085 | } | ||
| 1086 | }; | ||
| 1087 | |||
| 1088 | fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | ||
| 1089 | while (metadata.next_index < metadata.names.len) { | ||
| 1090 | const i = metadata.next_index; | ||
| 1091 | metadata.next_index += 1; | ||
| 1092 | |||
| 1093 | if (metadata.async_frame_lens[i] != 0) continue; | ||
| 1094 | if (metadata.expected_panic_msgs[i] != 0) continue; | ||
| 1095 | |||
| 1096 | const name = metadata.testName(i); | ||
| 1097 | if (sub_prog_node.*) |*n| n.end(); | ||
| 1098 | sub_prog_node.* = metadata.prog_node.start(name, 0); | ||
| 1099 | |||
| 1100 | try sendRunTestMessage(in, i); | ||
| 1101 | return; | ||
| 1102 | } else { | ||
| 1103 | try sendMessage(in, .exit); | ||
| 1104 | } | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | ||
| 1108 | const header: std.zig.Client.Message.Header = .{ | ||
| 1109 | .tag = tag, | ||
| 1110 | .bytes_len = 0, | ||
| 1111 | }; | ||
| 1112 | try file.writeAll(std.mem.asBytes(&header)); | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | fn sendRunTestMessage(file: std.fs.File, index: u32) !void { | ||
| 1116 | const header: std.zig.Client.Message.Header = .{ | ||
| 1117 | .tag = .run_test, | ||
| 1118 | .bytes_len = 4, | ||
| 1119 | }; | ||
| 1120 | const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); | ||
| 1121 | try file.writeAll(full_msg); | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { | ||
| 1125 | const arena = self.step.owner.allocator; | ||
| 1126 | |||
| 1127 | if (self.stdin) |stdin| { | ||
| 1128 | child.stdin.?.writeAll(stdin) catch |err| { | ||
| 1129 | return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); | ||
| 1130 | }; | ||
| 1131 | child.stdin.?.close(); | ||
| 1132 | child.stdin = null; | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | // These are not optionals, as a workaround for | ||
| 1136 | // https://github.com/ziglang/zig/issues/14783 | ||
| 1137 | var stdout_bytes: []const u8 = undefined; | ||
| 1138 | var stderr_bytes: []const u8 = undefined; | ||
| 1139 | var stdout_null = true; | ||
| 1140 | var stderr_null = true; | ||
| 1141 | |||
| 1142 | if (child.stdout) |stdout| { | ||
| 1143 | if (child.stderr) |stderr| { | ||
| 1144 | var poller = std.io.poll(arena, enum { stdout, stderr }, .{ | ||
| 1145 | .stdout = stdout, | ||
| 1146 | .stderr = stderr, | ||
| 1147 | }); | ||
| 1148 | defer poller.deinit(); | ||
| 1149 | |||
| 1150 | while (try poller.poll()) { | ||
| 1151 | if (poller.fifo(.stdout).count > self.max_stdio_size) | ||
| 1152 | return error.StdoutStreamTooLong; | ||
| 1153 | if (poller.fifo(.stderr).count > self.max_stdio_size) | ||
| 1154 | return error.StderrStreamTooLong; | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); | ||
| 1158 | stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); | ||
| 1159 | stdout_null = false; | ||
| 1160 | stderr_null = false; | ||
| 1161 | } else { | ||
| 1162 | stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); | ||
| 1163 | stdout_null = false; | ||
| 1164 | } | ||
| 1165 | } else if (child.stderr) |stderr| { | ||
| 1166 | stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); | ||
| 1167 | stderr_null = false; | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | if (!stderr_null and stderr_bytes.len > 0) { | ||
| 1171 | // Treat stderr as an error message. | ||
| 1172 | const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { | ||
| 1173 | .check => |checks| !checksContainStderr(checks.items), | ||
| 1174 | else => true, | ||
| 1175 | }; | ||
| 1176 | if (stderr_is_diagnostic) { | ||
| 1177 | try self.step.result_error_msgs.append(arena, stderr_bytes); | ||
| 1178 | } | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | return .{ | ||
| 1182 | .stdout = stdout_bytes, | ||
| 1183 | .stderr = stderr_bytes, | ||
| 1184 | .stdout_null = stdout_null, | ||
| 1185 | .stderr_null = stderr_null, | ||
| 1186 | .test_results = .{}, | ||
| 1187 | .test_metadata = null, | ||
| 1188 | }; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { | ||
| 1192 | const b = self.step.owner; | ||
| 1193 | for (artifact.link_objects.items) |link_object| { | ||
| 1194 | switch (link_object) { | ||
| 1195 | .other_step => |other| { | ||
| 1196 | if (other.target.isWindows() and other.isDynamicLibrary()) { | ||
| 1197 | addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?); | ||
| 1198 | addPathForDynLibs(self, other); | ||
| 1199 | } | ||
| 1200 | }, | ||
| 1201 | else => {}, | ||
| 1202 | } | ||
| 1203 | } | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | fn failForeign( | ||
| 1207 | self: *RunStep, | ||
| 1208 | suggested_flag: []const u8, | ||
| 1209 | argv0: []const u8, | ||
| 1210 | exe: *CompileStep, | ||
| 1211 | ) error{ MakeFailed, MakeSkipped, OutOfMemory } { | ||
| 1212 | switch (self.stdio) { | ||
| 1213 | .check, .zig_test => { | ||
| 1214 | if (self.skip_foreign_checks) | ||
| 1215 | return error.MakeSkipped; | ||
| 1216 | |||
| 1217 | const b = self.step.owner; | ||
| 1218 | const host_name = try b.host.target.zigTriple(b.allocator); | ||
| 1219 | const foreign_name = try exe.target.zigTriple(b.allocator); | ||
| 1220 | |||
| 1221 | return self.step.fail( | ||
| 1222 | \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) | ||
| 1223 | \\ consider using {s} or enabling skip_foreign_checks in the Run step | ||
| 1224 | , .{ argv0, foreign_name, host_name, suggested_flag }); | ||
| 1225 | }, | ||
| 1226 | else => { | ||
| 1227 | return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); | ||
| 1228 | }, | ||
| 1229 | } | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { | ||
| 1233 | switch (stdio) { | ||
| 1234 | .infer_from_args, .inherit, .zig_test => {}, | ||
| 1235 | .check => |checks| for (checks.items) |check| { | ||
| 1236 | hh.add(@as(std.meta.Tag(StdIo.Check), check)); | ||
| 1237 | switch (check) { | ||
| 1238 | .expect_stderr_exact, | ||
| 1239 | .expect_stderr_match, | ||
| 1240 | .expect_stdout_exact, | ||
| 1241 | .expect_stdout_match, | ||
| 1242 | => |s| hh.addBytes(s), | ||
| 1243 | |||
| 1244 | .expect_term => |term| { | ||
| 1245 | hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); | ||
| 1246 | switch (term) { | ||
| 1247 | .Exited => |x| hh.add(x), | ||
| 1248 | .Signal, .Stopped, .Unknown => |x| hh.add(x), | ||
| 1249 | } | ||
| 1250 | }, | ||
| 1251 | } | ||
| 1252 | }, | ||
| 1253 | } | ||
| 1254 | } | ||
lib/std/Build/Step/CheckFile.zig created+87| ... | @@ -0,0 +1,87 @@ | ||
| 1 | //! Fail the build step if a file does not match certain checks. | ||
| 2 | //! TODO: make this more flexible, supporting more kinds of checks. | ||
| 3 | //! TODO: generalize the code in std.testing.expectEqualStrings and make this | ||
| 4 | //! CheckFileStep produce those helpful diagnostics when there is not a match. | ||
| 5 | const CheckFileStep = @This(); | ||
| 6 | const std = @import("std"); | ||
| 7 | const Step = std.Build.Step; | ||
| 8 | const fs = std.fs; | ||
| 9 | const mem = std.mem; | ||
| 10 | |||
| 11 | step: Step, | ||
| 12 | expected_matches: []const []const u8, | ||
| 13 | expected_exact: ?[]const u8, | ||
| 14 | source: std.Build.FileSource, | ||
| 15 | max_bytes: usize = 20 * 1024 * 1024, | ||
| 16 | |||
| 17 | pub const base_id = .check_file; | ||
| 18 | |||
| 19 | pub const Options = struct { | ||
| 20 | expected_matches: []const []const u8 = &.{}, | ||
| 21 | expected_exact: ?[]const u8 = null, | ||
| 22 | }; | ||
| 23 | |||
| 24 | pub fn create( | ||
| 25 | owner: *std.Build, | ||
| 26 | source: std.Build.FileSource, | ||
| 27 | options: Options, | ||
| 28 | ) *CheckFileStep { | ||
| 29 | const self = owner.allocator.create(CheckFileStep) catch @panic("OOM"); | ||
| 30 | self.* = .{ | ||
| 31 | .step = Step.init(.{ | ||
| 32 | .id = .check_file, | ||
| 33 | .name = "CheckFile", | ||
| 34 | .owner = owner, | ||
| 35 | .makeFn = make, | ||
| 36 | }), | ||
| 37 | .source = source.dupe(owner), | ||
| 38 | .expected_matches = owner.dupeStrings(options.expected_matches), | ||
| 39 | .expected_exact = options.expected_exact, | ||
| 40 | }; | ||
| 41 | self.source.addStepDependencies(&self.step); | ||
| 42 | return self; | ||
| 43 | } | ||
| 44 | |||
| 45 | pub fn setName(self: *CheckFileStep, name: []const u8) void { | ||
| 46 | self.step.name = name; | ||
| 47 | } | ||
| 48 | |||
| 49 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 50 | _ = prog_node; | ||
| 51 | const b = step.owner; | ||
| 52 | const self = @fieldParentPtr(CheckFileStep, "step", step); | ||
| 53 | |||
| 54 | const src_path = self.source.getPath(b); | ||
| 55 | const contents = fs.cwd().readFileAlloc(b.allocator, src_path, self.max_bytes) catch |err| { | ||
| 56 | return step.fail("unable to read '{s}': {s}", .{ | ||
| 57 | src_path, @errorName(err), | ||
| 58 | }); | ||
| 59 | }; | ||
| 60 | |||
| 61 | for (self.expected_matches) |expected_match| { | ||
| 62 | if (mem.indexOf(u8, contents, expected_match) == null) { | ||
| 63 | return step.fail( | ||
| 64 | \\ | ||
| 65 | \\========= expected to find: =================== | ||
| 66 | \\{s} | ||
| 67 | \\========= but file does not contain it: ======= | ||
| 68 | \\{s} | ||
| 69 | \\=============================================== | ||
| 70 | , .{ expected_match, contents }); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | if (self.expected_exact) |expected_exact| { | ||
| 75 | if (!mem.eql(u8, expected_exact, contents)) { | ||
| 76 | return step.fail( | ||
| 77 | \\ | ||
| 78 | \\========= expected: ===================== | ||
| 79 | \\{s} | ||
| 80 | \\========= but found: ==================== | ||
| 81 | \\{s} | ||
| 82 | \\========= from the following file: ====== | ||
| 83 | \\{s} | ||
| 84 | , .{ expected_exact, contents, src_path }); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
lib/std/Build/Step/CheckObject.zig created+1055| ... | @@ -0,0 +1,1055 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const assert = std.debug.assert; | ||
| 3 | const fs = std.fs; | ||
| 4 | const macho = std.macho; | ||
| 5 | const math = std.math; | ||
| 6 | const mem = std.mem; | ||
| 7 | const testing = std.testing; | ||
| 8 | |||
| 9 | const CheckObjectStep = @This(); | ||
| 10 | |||
| 11 | const Allocator = mem.Allocator; | ||
| 12 | const Step = std.Build.Step; | ||
| 13 | |||
| 14 | pub const base_id = .check_object; | ||
| 15 | |||
| 16 | step: Step, | ||
| 17 | source: std.Build.FileSource, | ||
| 18 | max_bytes: usize = 20 * 1024 * 1024, | ||
| 19 | checks: std.ArrayList(Check), | ||
| 20 | dump_symtab: bool = false, | ||
| 21 | obj_format: std.Target.ObjectFormat, | ||
| 22 | |||
| 23 | pub fn create( | ||
| 24 | owner: *std.Build, | ||
| 25 | source: std.Build.FileSource, | ||
| 26 | obj_format: std.Target.ObjectFormat, | ||
| 27 | ) *CheckObjectStep { | ||
| 28 | const gpa = owner.allocator; | ||
| 29 | const self = gpa.create(CheckObjectStep) catch @panic("OOM"); | ||
| 30 | self.* = .{ | ||
| 31 | .step = Step.init(.{ | ||
| 32 | .id = .check_file, | ||
| 33 | .name = "CheckObject", | ||
| 34 | .owner = owner, | ||
| 35 | .makeFn = make, | ||
| 36 | }), | ||
| 37 | .source = source.dupe(owner), | ||
| 38 | .checks = std.ArrayList(Check).init(gpa), | ||
| 39 | .obj_format = obj_format, | ||
| 40 | }; | ||
| 41 | self.source.addStepDependencies(&self.step); | ||
| 42 | return self; | ||
| 43 | } | ||
| 44 | |||
| 45 | /// Runs and (optionally) compares the output of a binary. | ||
| 46 | /// Asserts `self` was generated from an executable step. | ||
| 47 | /// TODO this doesn't actually compare, and there's no apparent reason for it | ||
| 48 | /// to depend on the check object step. I don't see why this function should exist, | ||
| 49 | /// the caller could just add the run step directly. | ||
| 50 | pub fn runAndCompare(self: *CheckObjectStep) *std.Build.RunStep { | ||
| 51 | const dependencies_len = self.step.dependencies.items.len; | ||
| 52 | assert(dependencies_len > 0); | ||
| 53 | const exe_step = self.step.dependencies.items[dependencies_len - 1]; | ||
| 54 | const exe = exe_step.cast(std.Build.CompileStep).?; | ||
| 55 | const run = self.step.owner.addRunArtifact(exe); | ||
| 56 | run.skip_foreign_checks = true; | ||
| 57 | run.step.dependOn(&self.step); | ||
| 58 | return run; | ||
| 59 | } | ||
| 60 | |||
| 61 | const SearchPhrase = struct { | ||
| 62 | string: []const u8, | ||
| 63 | file_source: ?std.Build.FileSource = null, | ||
| 64 | |||
| 65 | fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 { | ||
| 66 | const file_source = phrase.file_source orelse return phrase.string; | ||
| 67 | return b.fmt("{s} {s}", .{ phrase.string, file_source.getPath2(b, step) }); | ||
| 68 | } | ||
| 69 | }; | ||
| 70 | |||
| 71 | /// There two types of actions currently supported: | ||
| 72 | /// * `.match` - is the main building block of standard matchers with optional eat-all token `{*}` | ||
| 73 | /// and extractors by name such as `{n_value}`. Please note this action is very simplistic in nature | ||
| 74 | /// i.e., it won't really handle edge cases/nontrivial examples. But given that we do want to use | ||
| 75 | /// it mainly to test the output of our object format parser-dumpers when testing the linkers, etc. | ||
| 76 | /// it should be plenty useful in its current form. | ||
| 77 | /// * `.compute_cmp` - can be used to perform an operation on the extracted global variables | ||
| 78 | /// using the MatchAction. It currently only supports an addition. The operation is required | ||
| 79 | /// to be specified in Reverse Polish Notation to ease in operator-precedence parsing (well, | ||
| 80 | /// to avoid any parsing really). | ||
| 81 | /// For example, if the two extracted values were saved as `vmaddr` and `entryoff` respectively | ||
| 82 | /// they could then be added with this simple program `vmaddr entryoff +`. | ||
| 83 | const Action = struct { | ||
| 84 | tag: enum { match, not_present, compute_cmp }, | ||
| 85 | phrase: SearchPhrase, | ||
| 86 | expected: ?ComputeCompareExpected = null, | ||
| 87 | |||
| 88 | /// Will return true if the `phrase` was found in the `haystack`. | ||
| 89 | /// Some examples include: | ||
| 90 | /// | ||
| 91 | /// LC 0 => will match in its entirety | ||
| 92 | /// vmaddr {vmaddr} => will match `vmaddr` and then extract the following value as u64 | ||
| 93 | /// and save under `vmaddr` global name (see `global_vars` param) | ||
| 94 | /// name {*}libobjc{*}.dylib => will match `name` followed by a token which contains `libobjc` and `.dylib` | ||
| 95 | /// in that order with other letters in between | ||
| 96 | fn match( | ||
| 97 | act: Action, | ||
| 98 | b: *std.Build, | ||
| 99 | step: *Step, | ||
| 100 | haystack: []const u8, | ||
| 101 | global_vars: anytype, | ||
| 102 | ) !bool { | ||
| 103 | assert(act.tag == .match or act.tag == .not_present); | ||
| 104 | const phrase = act.phrase.resolve(b, step); | ||
| 105 | var candidate_var: ?struct { name: []const u8, value: u64 } = null; | ||
| 106 | var hay_it = mem.tokenize(u8, mem.trim(u8, haystack, " "), " "); | ||
| 107 | var needle_it = mem.tokenize(u8, mem.trim(u8, phrase, " "), " "); | ||
| 108 | |||
| 109 | while (needle_it.next()) |needle_tok| { | ||
| 110 | const hay_tok = hay_it.next() orelse return false; | ||
| 111 | |||
| 112 | if (mem.indexOf(u8, needle_tok, "{*}")) |index| { | ||
| 113 | // We have fuzzy matchers within the search pattern, so we match substrings. | ||
| 114 | var start = index; | ||
| 115 | var n_tok = needle_tok; | ||
| 116 | var h_tok = hay_tok; | ||
| 117 | while (true) { | ||
| 118 | n_tok = n_tok[start + 3 ..]; | ||
| 119 | const inner = if (mem.indexOf(u8, n_tok, "{*}")) |sub_end| | ||
| 120 | n_tok[0..sub_end] | ||
| 121 | else | ||
| 122 | n_tok; | ||
| 123 | if (mem.indexOf(u8, h_tok, inner) == null) return false; | ||
| 124 | start = mem.indexOf(u8, n_tok, "{*}") orelse break; | ||
| 125 | } | ||
| 126 | } else if (mem.startsWith(u8, needle_tok, "{")) { | ||
| 127 | const closing_brace = mem.indexOf(u8, needle_tok, "}") orelse return error.MissingClosingBrace; | ||
| 128 | if (closing_brace != needle_tok.len - 1) return error.ClosingBraceNotLast; | ||
| 129 | |||
| 130 | const name = needle_tok[1..closing_brace]; | ||
| 131 | if (name.len == 0) return error.MissingBraceValue; | ||
| 132 | const value = try std.fmt.parseInt(u64, hay_tok, 16); | ||
| 133 | candidate_var = .{ | ||
| 134 | .name = name, | ||
| 135 | .value = value, | ||
| 136 | }; | ||
| 137 | } else { | ||
| 138 | if (!mem.eql(u8, hay_tok, needle_tok)) return false; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | |||
| 142 | if (candidate_var) |v| { | ||
| 143 | try global_vars.putNoClobber(v.name, v.value); | ||
| 144 | } | ||
| 145 | |||
| 146 | return true; | ||
| 147 | } | ||
| 148 | |||
| 149 | /// Will return true if the `phrase` is correctly parsed into an RPN program and | ||
| 150 | /// its reduced, computed value compares using `op` with the expected value, either | ||
| 151 | /// a literal or another extracted variable. | ||
| 152 | fn computeCmp(act: Action, b: *std.Build, step: *Step, global_vars: anytype) !bool { | ||
| 153 | const gpa = step.owner.allocator; | ||
| 154 | const phrase = act.phrase.resolve(b, step); | ||
| 155 | var op_stack = std.ArrayList(enum { add, sub, mod, mul }).init(gpa); | ||
| 156 | var values = std.ArrayList(u64).init(gpa); | ||
| 157 | |||
| 158 | var it = mem.tokenize(u8, phrase, " "); | ||
| 159 | while (it.next()) |next| { | ||
| 160 | if (mem.eql(u8, next, "+")) { | ||
| 161 | try op_stack.append(.add); | ||
| 162 | } else if (mem.eql(u8, next, "-")) { | ||
| 163 | try op_stack.append(.sub); | ||
| 164 | } else if (mem.eql(u8, next, "%")) { | ||
| 165 | try op_stack.append(.mod); | ||
| 166 | } else if (mem.eql(u8, next, "*")) { | ||
| 167 | try op_stack.append(.mul); | ||
| 168 | } else { | ||
| 169 | const val = std.fmt.parseInt(u64, next, 0) catch blk: { | ||
| 170 | break :blk global_vars.get(next) orelse { | ||
| 171 | try step.addError( | ||
| 172 | \\ | ||
| 173 | \\========= variable was not extracted: =========== | ||
| 174 | \\{s} | ||
| 175 | \\================================================= | ||
| 176 | , .{next}); | ||
| 177 | return error.UnknownVariable; | ||
| 178 | }; | ||
| 179 | }; | ||
| 180 | try values.append(val); | ||
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | var op_i: usize = 1; | ||
| 185 | var reduced: u64 = values.items[0]; | ||
| 186 | for (op_stack.items) |op| { | ||
| 187 | const other = values.items[op_i]; | ||
| 188 | switch (op) { | ||
| 189 | .add => { | ||
| 190 | reduced += other; | ||
| 191 | }, | ||
| 192 | .sub => { | ||
| 193 | reduced -= other; | ||
| 194 | }, | ||
| 195 | .mod => { | ||
| 196 | reduced %= other; | ||
| 197 | }, | ||
| 198 | .mul => { | ||
| 199 | reduced *= other; | ||
| 200 | }, | ||
| 201 | } | ||
| 202 | op_i += 1; | ||
| 203 | } | ||
| 204 | |||
| 205 | const exp_value = switch (act.expected.?.value) { | ||
| 206 | .variable => |name| global_vars.get(name) orelse { | ||
| 207 | try step.addError( | ||
| 208 | \\ | ||
| 209 | \\========= variable was not extracted: =========== | ||
| 210 | \\{s} | ||
| 211 | \\================================================= | ||
| 212 | , .{name}); | ||
| 213 | return error.UnknownVariable; | ||
| 214 | }, | ||
| 215 | .literal => |x| x, | ||
| 216 | }; | ||
| 217 | return math.compare(reduced, act.expected.?.op, exp_value); | ||
| 218 | } | ||
| 219 | }; | ||
| 220 | |||
| 221 | const ComputeCompareExpected = struct { | ||
| 222 | op: math.CompareOperator, | ||
| 223 | value: union(enum) { | ||
| 224 | variable: []const u8, | ||
| 225 | literal: u64, | ||
| 226 | }, | ||
| 227 | |||
| 228 | pub fn format( | ||
| 229 | value: @This(), | ||
| 230 | comptime fmt: []const u8, | ||
| 231 | options: std.fmt.FormatOptions, | ||
| 232 | writer: anytype, | ||
| 233 | ) !void { | ||
| 234 | if (fmt.len != 0) std.fmt.invalidFmtError(fmt, value); | ||
| 235 | _ = options; | ||
| 236 | try writer.print("{s} ", .{@tagName(value.op)}); | ||
| 237 | switch (value.value) { | ||
| 238 | .variable => |name| try writer.writeAll(name), | ||
| 239 | .literal => |x| try writer.print("{x}", .{x}), | ||
| 240 | } | ||
| 241 | } | ||
| 242 | }; | ||
| 243 | |||
| 244 | const Check = struct { | ||
| 245 | actions: std.ArrayList(Action), | ||
| 246 | |||
| 247 | fn create(allocator: Allocator) Check { | ||
| 248 | return .{ | ||
| 249 | .actions = std.ArrayList(Action).init(allocator), | ||
| 250 | }; | ||
| 251 | } | ||
| 252 | |||
| 253 | fn match(self: *Check, phrase: SearchPhrase) void { | ||
| 254 | self.actions.append(.{ | ||
| 255 | .tag = .match, | ||
| 256 | .phrase = phrase, | ||
| 257 | }) catch @panic("OOM"); | ||
| 258 | } | ||
| 259 | |||
| 260 | fn notPresent(self: *Check, phrase: SearchPhrase) void { | ||
| 261 | self.actions.append(.{ | ||
| 262 | .tag = .not_present, | ||
| 263 | .phrase = phrase, | ||
| 264 | }) catch @panic("OOM"); | ||
| 265 | } | ||
| 266 | |||
| 267 | fn computeCmp(self: *Check, phrase: SearchPhrase, expected: ComputeCompareExpected) void { | ||
| 268 | self.actions.append(.{ | ||
| 269 | .tag = .compute_cmp, | ||
| 270 | .phrase = phrase, | ||
| 271 | .expected = expected, | ||
| 272 | }) catch @panic("OOM"); | ||
| 273 | } | ||
| 274 | }; | ||
| 275 | |||
| 276 | /// Creates a new sequence of actions with `phrase` as the first anchor searched phrase. | ||
| 277 | pub fn checkStart(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 278 | var new_check = Check.create(self.step.owner.allocator); | ||
| 279 | new_check.match(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 280 | self.checks.append(new_check) catch @panic("OOM"); | ||
| 281 | } | ||
| 282 | |||
| 283 | /// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)`. | ||
| 284 | /// Asserts at least one check already exists. | ||
| 285 | pub fn checkNext(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 286 | assert(self.checks.items.len > 0); | ||
| 287 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 288 | last.match(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 289 | } | ||
| 290 | |||
| 291 | /// Like `checkNext()` but takes an additional argument `FileSource` which will be | ||
| 292 | /// resolved to a full search query in `make()`. | ||
| 293 | pub fn checkNextFileSource( | ||
| 294 | self: *CheckObjectStep, | ||
| 295 | phrase: []const u8, | ||
| 296 | file_source: std.Build.FileSource, | ||
| 297 | ) void { | ||
| 298 | assert(self.checks.items.len > 0); | ||
| 299 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 300 | last.match(.{ .string = self.step.owner.dupe(phrase), .file_source = file_source }); | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Adds another searched phrase to the latest created Check with `CheckObjectStep.checkStart(...)` | ||
| 304 | /// however ensures there is no matching phrase in the output. | ||
| 305 | /// Asserts at least one check already exists. | ||
| 306 | pub fn checkNotPresent(self: *CheckObjectStep, phrase: []const u8) void { | ||
| 307 | assert(self.checks.items.len > 0); | ||
| 308 | const last = &self.checks.items[self.checks.items.len - 1]; | ||
| 309 | last.notPresent(.{ .string = self.step.owner.dupe(phrase) }); | ||
| 310 | } | ||
| 311 | |||
| 312 | /// Creates a new check checking specifically symbol table parsed and dumped from the object | ||
| 313 | /// file. | ||
| 314 | /// Issuing this check will force parsing and dumping of the symbol table. | ||
| 315 | pub fn checkInSymtab(self: *CheckObjectStep) void { | ||
| 316 | self.dump_symtab = true; | ||
| 317 | const symtab_label = switch (self.obj_format) { | ||
| 318 | .macho => MachODumper.symtab_label, | ||
| 319 | else => @panic("TODO other parsers"), | ||
| 320 | }; | ||
| 321 | self.checkStart(symtab_label); | ||
| 322 | } | ||
| 323 | |||
| 324 | /// Creates a new standalone, singular check which allows running simple binary operations | ||
| 325 | /// on the extracted variables. It will then compare the reduced program with the value of | ||
| 326 | /// the expected variable. | ||
| 327 | pub fn checkComputeCompare( | ||
| 328 | self: *CheckObjectStep, | ||
| 329 | program: []const u8, | ||
| 330 | expected: ComputeCompareExpected, | ||
| 331 | ) void { | ||
| 332 | var new_check = Check.create(self.step.owner.allocator); | ||
| 333 | new_check.computeCmp(.{ .string = self.step.owner.dupe(program) }, expected); | ||
| 334 | self.checks.append(new_check) catch @panic("OOM"); | ||
| 335 | } | ||
| 336 | |||
| 337 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 338 | _ = prog_node; | ||
| 339 | const b = step.owner; | ||
| 340 | const gpa = b.allocator; | ||
| 341 | const self = @fieldParentPtr(CheckObjectStep, "step", step); | ||
| 342 | |||
| 343 | const src_path = self.source.getPath(b); | ||
| 344 | const contents = fs.cwd().readFileAllocOptions( | ||
| 345 | gpa, | ||
| 346 | src_path, | ||
| 347 | self.max_bytes, | ||
| 348 | null, | ||
| 349 | @alignOf(u64), | ||
| 350 | null, | ||
| 351 | ) catch |err| return step.fail("unable to read '{s}': {s}", .{ src_path, @errorName(err) }); | ||
| 352 | |||
| 353 | const output = switch (self.obj_format) { | ||
| 354 | .macho => try MachODumper.parseAndDump(step, contents, .{ | ||
| 355 | .dump_symtab = self.dump_symtab, | ||
| 356 | }), | ||
| 357 | .elf => @panic("TODO elf parser"), | ||
| 358 | .coff => @panic("TODO coff parser"), | ||
| 359 | .wasm => try WasmDumper.parseAndDump(step, contents, .{ | ||
| 360 | .dump_symtab = self.dump_symtab, | ||
| 361 | }), | ||
| 362 | else => unreachable, | ||
| 363 | }; | ||
| 364 | |||
| 365 | var vars = std.StringHashMap(u64).init(gpa); | ||
| 366 | |||
| 367 | for (self.checks.items) |chk| { | ||
| 368 | var it = mem.tokenize(u8, output, "\r\n"); | ||
| 369 | for (chk.actions.items) |act| { | ||
| 370 | switch (act.tag) { | ||
| 371 | .match => { | ||
| 372 | while (it.next()) |line| { | ||
| 373 | if (try act.match(b, step, line, &vars)) break; | ||
| 374 | } else { | ||
| 375 | return step.fail( | ||
| 376 | \\ | ||
| 377 | \\========= expected to find: ========================== | ||
| 378 | \\{s} | ||
| 379 | \\========= but parsed file does not contain it: ======= | ||
| 380 | \\{s} | ||
| 381 | \\====================================================== | ||
| 382 | , .{ act.phrase.resolve(b, step), output }); | ||
| 383 | } | ||
| 384 | }, | ||
| 385 | .not_present => { | ||
| 386 | while (it.next()) |line| { | ||
| 387 | if (try act.match(b, step, line, &vars)) { | ||
| 388 | return step.fail( | ||
| 389 | \\ | ||
| 390 | \\========= expected not to find: =================== | ||
| 391 | \\{s} | ||
| 392 | \\========= but parsed file does contain it: ======== | ||
| 393 | \\{s} | ||
| 394 | \\=================================================== | ||
| 395 | , .{ act.phrase.resolve(b, step), output }); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | }, | ||
| 399 | .compute_cmp => { | ||
| 400 | const res = act.computeCmp(b, step, vars) catch |err| switch (err) { | ||
| 401 | error.UnknownVariable => { | ||
| 402 | return step.fail( | ||
| 403 | \\========= from parsed file: ===================== | ||
| 404 | \\{s} | ||
| 405 | \\================================================= | ||
| 406 | , .{output}); | ||
| 407 | }, | ||
| 408 | else => |e| return e, | ||
| 409 | }; | ||
| 410 | if (!res) { | ||
| 411 | return step.fail( | ||
| 412 | \\ | ||
| 413 | \\========= comparison failed for action: =========== | ||
| 414 | \\{s} {} | ||
| 415 | \\========= from parsed file: ======================= | ||
| 416 | \\{s} | ||
| 417 | \\=================================================== | ||
| 418 | , .{ act.phrase.resolve(b, step), act.expected.?, output }); | ||
| 419 | } | ||
| 420 | }, | ||
| 421 | } | ||
| 422 | } | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | const Opts = struct { | ||
| 427 | dump_symtab: bool = false, | ||
| 428 | }; | ||
| 429 | |||
| 430 | const MachODumper = struct { | ||
| 431 | const LoadCommandIterator = macho.LoadCommandIterator; | ||
| 432 | const symtab_label = "symtab"; | ||
| 433 | |||
| 434 | fn parseAndDump(step: *Step, bytes: []align(@alignOf(u64)) const u8, opts: Opts) ![]const u8 { | ||
| 435 | const gpa = step.owner.allocator; | ||
| 436 | var stream = std.io.fixedBufferStream(bytes); | ||
| 437 | const reader = stream.reader(); | ||
| 438 | |||
| 439 | const hdr = try reader.readStruct(macho.mach_header_64); | ||
| 440 | if (hdr.magic != macho.MH_MAGIC_64) { | ||
| 441 | return error.InvalidMagicNumber; | ||
| 442 | } | ||
| 443 | |||
| 444 | var output = std.ArrayList(u8).init(gpa); | ||
| 445 | const writer = output.writer(); | ||
| 446 | |||
| 447 | var symtab: []const macho.nlist_64 = undefined; | ||
| 448 | var strtab: []const u8 = undefined; | ||
| 449 | var sections = std.ArrayList(macho.section_64).init(gpa); | ||
| 450 | var imports = std.ArrayList([]const u8).init(gpa); | ||
| 451 | |||
| 452 | var it = LoadCommandIterator{ | ||
| 453 | .ncmds = hdr.ncmds, | ||
| 454 | .buffer = bytes[@sizeOf(macho.mach_header_64)..][0..hdr.sizeofcmds], | ||
| 455 | }; | ||
| 456 | var i: usize = 0; | ||
| 457 | while (it.next()) |cmd| { | ||
| 458 | switch (cmd.cmd()) { | ||
| 459 | .SEGMENT_64 => { | ||
| 460 | const seg = cmd.cast(macho.segment_command_64).?; | ||
| 461 | try sections.ensureUnusedCapacity(seg.nsects); | ||
| 462 | for (cmd.getSections()) |sect| { | ||
| 463 | sections.appendAssumeCapacity(sect); | ||
| 464 | } | ||
| 465 | }, | ||
| 466 | .SYMTAB => if (opts.dump_symtab) { | ||
| 467 | const lc = cmd.cast(macho.symtab_command).?; | ||
| 468 | symtab = @ptrCast( | ||
| 469 | [*]const macho.nlist_64, | ||
| 470 | @alignCast(@alignOf(macho.nlist_64), &bytes[lc.symoff]), | ||
| 471 | )[0..lc.nsyms]; | ||
| 472 | strtab = bytes[lc.stroff..][0..lc.strsize]; | ||
| 473 | }, | ||
| 474 | .LOAD_DYLIB, | ||
| 475 | .LOAD_WEAK_DYLIB, | ||
| 476 | .REEXPORT_DYLIB, | ||
| 477 | => { | ||
| 478 | try imports.append(cmd.getDylibPathName()); | ||
| 479 | }, | ||
| 480 | else => {}, | ||
| 481 | } | ||
| 482 | |||
| 483 | try dumpLoadCommand(cmd, i, writer); | ||
| 484 | try writer.writeByte('\n'); | ||
| 485 | |||
| 486 | i += 1; | ||
| 487 | } | ||
| 488 | |||
| 489 | if (opts.dump_symtab) { | ||
| 490 | try writer.print("{s}\n", .{symtab_label}); | ||
| 491 | for (symtab) |sym| { | ||
| 492 | if (sym.stab()) continue; | ||
| 493 | const sym_name = mem.sliceTo(@ptrCast([*:0]const u8, strtab.ptr + sym.n_strx), 0); | ||
| 494 | if (sym.sect()) { | ||
| 495 | const sect = sections.items[sym.n_sect - 1]; | ||
| 496 | try writer.print("{x} ({s},{s})", .{ | ||
| 497 | sym.n_value, | ||
| 498 | sect.segName(), | ||
| 499 | sect.sectName(), | ||
| 500 | }); | ||
| 501 | if (sym.ext()) { | ||
| 502 | try writer.writeAll(" external"); | ||
| 503 | } | ||
| 504 | try writer.print(" {s}\n", .{sym_name}); | ||
| 505 | } else if (sym.undf()) { | ||
| 506 | const ordinal = @divTrunc(@bitCast(i16, sym.n_desc), macho.N_SYMBOL_RESOLVER); | ||
| 507 | const import_name = blk: { | ||
| 508 | if (ordinal <= 0) { | ||
| 509 | if (ordinal == macho.BIND_SPECIAL_DYLIB_SELF) | ||
| 510 | break :blk "self import"; | ||
| 511 | if (ordinal == macho.BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE) | ||
| 512 | break :blk "main executable"; | ||
| 513 | if (ordinal == macho.BIND_SPECIAL_DYLIB_FLAT_LOOKUP) | ||
| 514 | break :blk "flat lookup"; | ||
| 515 | unreachable; | ||
| 516 | } | ||
| 517 | const full_path = imports.items[@bitCast(u16, ordinal) - 1]; | ||
| 518 | const basename = fs.path.basename(full_path); | ||
| 519 | assert(basename.len > 0); | ||
| 520 | const ext = mem.lastIndexOfScalar(u8, basename, '.') orelse basename.len; | ||
| 521 | break :blk basename[0..ext]; | ||
| 522 | }; | ||
| 523 | try writer.writeAll("(undefined)"); | ||
| 524 | if (sym.weakRef()) { | ||
| 525 | try writer.writeAll(" weak"); | ||
| 526 | } | ||
| 527 | if (sym.ext()) { | ||
| 528 | try writer.writeAll(" external"); | ||
| 529 | } | ||
| 530 | try writer.print(" {s} (from {s})\n", .{ | ||
| 531 | sym_name, | ||
| 532 | import_name, | ||
| 533 | }); | ||
| 534 | } else unreachable; | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | return output.toOwnedSlice(); | ||
| 539 | } | ||
| 540 | |||
| 541 | fn dumpLoadCommand(lc: macho.LoadCommandIterator.LoadCommand, index: usize, writer: anytype) !void { | ||
| 542 | // print header first | ||
| 543 | try writer.print( | ||
| 544 | \\LC {d} | ||
| 545 | \\cmd {s} | ||
| 546 | \\cmdsize {d} | ||
| 547 | , .{ index, @tagName(lc.cmd()), lc.cmdsize() }); | ||
| 548 | |||
| 549 | switch (lc.cmd()) { | ||
| 550 | .SEGMENT_64 => { | ||
| 551 | const seg = lc.cast(macho.segment_command_64).?; | ||
| 552 | try writer.writeByte('\n'); | ||
| 553 | try writer.print( | ||
| 554 | \\segname {s} | ||
| 555 | \\vmaddr {x} | ||
| 556 | \\vmsize {x} | ||
| 557 | \\fileoff {x} | ||
| 558 | \\filesz {x} | ||
| 559 | , .{ | ||
| 560 | seg.segName(), | ||
| 561 | seg.vmaddr, | ||
| 562 | seg.vmsize, | ||
| 563 | seg.fileoff, | ||
| 564 | seg.filesize, | ||
| 565 | }); | ||
| 566 | |||
| 567 | for (lc.getSections()) |sect| { | ||
| 568 | try writer.writeByte('\n'); | ||
| 569 | try writer.print( | ||
| 570 | \\sectname {s} | ||
| 571 | \\addr {x} | ||
| 572 | \\size {x} | ||
| 573 | \\offset {x} | ||
| 574 | \\align {x} | ||
| 575 | , .{ | ||
| 576 | sect.sectName(), | ||
| 577 | sect.addr, | ||
| 578 | sect.size, | ||
| 579 | sect.offset, | ||
| 580 | sect.@"align", | ||
| 581 | }); | ||
| 582 | } | ||
| 583 | }, | ||
| 584 | |||
| 585 | .ID_DYLIB, | ||
| 586 | .LOAD_DYLIB, | ||
| 587 | .LOAD_WEAK_DYLIB, | ||
| 588 | .REEXPORT_DYLIB, | ||
| 589 | => { | ||
| 590 | const dylib = lc.cast(macho.dylib_command).?; | ||
| 591 | try writer.writeByte('\n'); | ||
| 592 | try writer.print( | ||
| 593 | \\name {s} | ||
| 594 | \\timestamp {d} | ||
| 595 | \\current version {x} | ||
| 596 | \\compatibility version {x} | ||
| 597 | , .{ | ||
| 598 | lc.getDylibPathName(), | ||
| 599 | dylib.dylib.timestamp, | ||
| 600 | dylib.dylib.current_version, | ||
| 601 | dylib.dylib.compatibility_version, | ||
| 602 | }); | ||
| 603 | }, | ||
| 604 | |||
| 605 | .MAIN => { | ||
| 606 | const main = lc.cast(macho.entry_point_command).?; | ||
| 607 | try writer.writeByte('\n'); | ||
| 608 | try writer.print( | ||
| 609 | \\entryoff {x} | ||
| 610 | \\stacksize {x} | ||
| 611 | , .{ main.entryoff, main.stacksize }); | ||
| 612 | }, | ||
| 613 | |||
| 614 | .RPATH => { | ||
| 615 | try writer.writeByte('\n'); | ||
| 616 | try writer.print( | ||
| 617 | \\path {s} | ||
| 618 | , .{ | ||
| 619 | lc.getRpathPathName(), | ||
| 620 | }); | ||
| 621 | }, | ||
| 622 | |||
| 623 | .UUID => { | ||
| 624 | const uuid = lc.cast(macho.uuid_command).?; | ||
| 625 | try writer.writeByte('\n'); | ||
| 626 | try writer.print("uuid {x}", .{std.fmt.fmtSliceHexLower(&uuid.uuid)}); | ||
| 627 | }, | ||
| 628 | |||
| 629 | .DATA_IN_CODE, | ||
| 630 | .FUNCTION_STARTS, | ||
| 631 | .CODE_SIGNATURE, | ||
| 632 | => { | ||
| 633 | const llc = lc.cast(macho.linkedit_data_command).?; | ||
| 634 | try writer.writeByte('\n'); | ||
| 635 | try writer.print( | ||
| 636 | \\dataoff {x} | ||
| 637 | \\datasize {x} | ||
| 638 | , .{ llc.dataoff, llc.datasize }); | ||
| 639 | }, | ||
| 640 | |||
| 641 | .DYLD_INFO_ONLY => { | ||
| 642 | const dlc = lc.cast(macho.dyld_info_command).?; | ||
| 643 | try writer.writeByte('\n'); | ||
| 644 | try writer.print( | ||
| 645 | \\rebaseoff {x} | ||
| 646 | \\rebasesize {x} | ||
| 647 | \\bindoff {x} | ||
| 648 | \\bindsize {x} | ||
| 649 | \\weakbindoff {x} | ||
| 650 | \\weakbindsize {x} | ||
| 651 | \\lazybindoff {x} | ||
| 652 | \\lazybindsize {x} | ||
| 653 | \\exportoff {x} | ||
| 654 | \\exportsize {x} | ||
| 655 | , .{ | ||
| 656 | dlc.rebase_off, | ||
| 657 | dlc.rebase_size, | ||
| 658 | dlc.bind_off, | ||
| 659 | dlc.bind_size, | ||
| 660 | dlc.weak_bind_off, | ||
| 661 | dlc.weak_bind_size, | ||
| 662 | dlc.lazy_bind_off, | ||
| 663 | dlc.lazy_bind_size, | ||
| 664 | dlc.export_off, | ||
| 665 | dlc.export_size, | ||
| 666 | }); | ||
| 667 | }, | ||
| 668 | |||
| 669 | .SYMTAB => { | ||
| 670 | const slc = lc.cast(macho.symtab_command).?; | ||
| 671 | try writer.writeByte('\n'); | ||
| 672 | try writer.print( | ||
| 673 | \\symoff {x} | ||
| 674 | \\nsyms {x} | ||
| 675 | \\stroff {x} | ||
| 676 | \\strsize {x} | ||
| 677 | , .{ | ||
| 678 | slc.symoff, | ||
| 679 | slc.nsyms, | ||
| 680 | slc.stroff, | ||
| 681 | slc.strsize, | ||
| 682 | }); | ||
| 683 | }, | ||
| 684 | |||
| 685 | .DYSYMTAB => { | ||
| 686 | const dlc = lc.cast(macho.dysymtab_command).?; | ||
| 687 | try writer.writeByte('\n'); | ||
| 688 | try writer.print( | ||
| 689 | \\ilocalsym {x} | ||
| 690 | \\nlocalsym {x} | ||
| 691 | \\iextdefsym {x} | ||
| 692 | \\nextdefsym {x} | ||
| 693 | \\iundefsym {x} | ||
| 694 | \\nundefsym {x} | ||
| 695 | \\indirectsymoff {x} | ||
| 696 | \\nindirectsyms {x} | ||
| 697 | , .{ | ||
| 698 | dlc.ilocalsym, | ||
| 699 | dlc.nlocalsym, | ||
| 700 | dlc.iextdefsym, | ||
| 701 | dlc.nextdefsym, | ||
| 702 | dlc.iundefsym, | ||
| 703 | dlc.nundefsym, | ||
| 704 | dlc.indirectsymoff, | ||
| 705 | dlc.nindirectsyms, | ||
| 706 | }); | ||
| 707 | }, | ||
| 708 | |||
| 709 | else => {}, | ||
| 710 | } | ||
| 711 | } | ||
| 712 | }; | ||
| 713 | |||
| 714 | const WasmDumper = struct { | ||
| 715 | const symtab_label = "symbols"; | ||
| 716 | |||
| 717 | fn parseAndDump(step: *Step, bytes: []const u8, opts: Opts) ![]const u8 { | ||
| 718 | const gpa = step.owner.allocator; | ||
| 719 | if (opts.dump_symtab) { | ||
| 720 | @panic("TODO: Implement symbol table parsing and dumping"); | ||
| 721 | } | ||
| 722 | |||
| 723 | var fbs = std.io.fixedBufferStream(bytes); | ||
| 724 | const reader = fbs.reader(); | ||
| 725 | |||
| 726 | const buf = try reader.readBytesNoEof(8); | ||
| 727 | if (!mem.eql(u8, buf[0..4], &std.wasm.magic)) { | ||
| 728 | return error.InvalidMagicByte; | ||
| 729 | } | ||
| 730 | if (!mem.eql(u8, buf[4..], &std.wasm.version)) { | ||
| 731 | return error.UnsupportedWasmVersion; | ||
| 732 | } | ||
| 733 | |||
| 734 | var output = std.ArrayList(u8).init(gpa); | ||
| 735 | errdefer output.deinit(); | ||
| 736 | const writer = output.writer(); | ||
| 737 | |||
| 738 | while (reader.readByte()) |current_byte| { | ||
| 739 | const section = std.meta.intToEnum(std.wasm.Section, current_byte) catch { | ||
| 740 | return step.fail("Found invalid section id '{d}'", .{current_byte}); | ||
| 741 | }; | ||
| 742 | |||
| 743 | const section_length = try std.leb.readULEB128(u32, reader); | ||
| 744 | try parseAndDumpSection(step, section, bytes[fbs.pos..][0..section_length], writer); | ||
| 745 | fbs.pos += section_length; | ||
| 746 | } else |_| {} // reached end of stream | ||
| 747 | |||
| 748 | return output.toOwnedSlice(); | ||
| 749 | } | ||
| 750 | |||
| 751 | fn parseAndDumpSection( | ||
| 752 | step: *Step, | ||
| 753 | section: std.wasm.Section, | ||
| 754 | data: []const u8, | ||
| 755 | writer: anytype, | ||
| 756 | ) !void { | ||
| 757 | var fbs = std.io.fixedBufferStream(data); | ||
| 758 | const reader = fbs.reader(); | ||
| 759 | |||
| 760 | try writer.print( | ||
| 761 | \\Section {s} | ||
| 762 | \\size {d} | ||
| 763 | , .{ @tagName(section), data.len }); | ||
| 764 | |||
| 765 | switch (section) { | ||
| 766 | .type, | ||
| 767 | .import, | ||
| 768 | .function, | ||
| 769 | .table, | ||
| 770 | .memory, | ||
| 771 | .global, | ||
| 772 | .@"export", | ||
| 773 | .element, | ||
| 774 | .code, | ||
| 775 | .data, | ||
| 776 | => { | ||
| 777 | const entries = try std.leb.readULEB128(u32, reader); | ||
| 778 | try writer.print("\nentries {d}\n", .{entries}); | ||
| 779 | try dumpSection(step, section, data[fbs.pos..], entries, writer); | ||
| 780 | }, | ||
| 781 | .custom => { | ||
| 782 | const name_length = try std.leb.readULEB128(u32, reader); | ||
| 783 | const name = data[fbs.pos..][0..name_length]; | ||
| 784 | fbs.pos += name_length; | ||
| 785 | try writer.print("\nname {s}\n", .{name}); | ||
| 786 | |||
| 787 | if (mem.eql(u8, name, "name")) { | ||
| 788 | try parseDumpNames(step, reader, writer, data); | ||
| 789 | } else if (mem.eql(u8, name, "producers")) { | ||
| 790 | try parseDumpProducers(reader, writer, data); | ||
| 791 | } else if (mem.eql(u8, name, "target_features")) { | ||
| 792 | try parseDumpFeatures(reader, writer, data); | ||
| 793 | } | ||
| 794 | // TODO: Implement parsing and dumping other custom sections (such as relocations) | ||
| 795 | }, | ||
| 796 | .start => { | ||
| 797 | const start = try std.leb.readULEB128(u32, reader); | ||
| 798 | try writer.print("\nstart {d}\n", .{start}); | ||
| 799 | }, | ||
| 800 | else => {}, // skip unknown sections | ||
| 801 | } | ||
| 802 | } | ||
| 803 | |||
| 804 | fn dumpSection(step: *Step, section: std.wasm.Section, data: []const u8, entries: u32, writer: anytype) !void { | ||
| 805 | var fbs = std.io.fixedBufferStream(data); | ||
| 806 | const reader = fbs.reader(); | ||
| 807 | |||
| 808 | switch (section) { | ||
| 809 | .type => { | ||
| 810 | var i: u32 = 0; | ||
| 811 | while (i < entries) : (i += 1) { | ||
| 812 | const func_type = try reader.readByte(); | ||
| 813 | if (func_type != std.wasm.function_type) { | ||
| 814 | return step.fail("expected function type, found byte '{d}'", .{func_type}); | ||
| 815 | } | ||
| 816 | const params = try std.leb.readULEB128(u32, reader); | ||
| 817 | try writer.print("params {d}\n", .{params}); | ||
| 818 | var index: u32 = 0; | ||
| 819 | while (index < params) : (index += 1) { | ||
| 820 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 821 | } else index = 0; | ||
| 822 | const returns = try std.leb.readULEB128(u32, reader); | ||
| 823 | try writer.print("returns {d}\n", .{returns}); | ||
| 824 | while (index < returns) : (index += 1) { | ||
| 825 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 826 | } | ||
| 827 | } | ||
| 828 | }, | ||
| 829 | .import => { | ||
| 830 | var i: u32 = 0; | ||
| 831 | while (i < entries) : (i += 1) { | ||
| 832 | const module_name_len = try std.leb.readULEB128(u32, reader); | ||
| 833 | const module_name = data[fbs.pos..][0..module_name_len]; | ||
| 834 | fbs.pos += module_name_len; | ||
| 835 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 836 | const name = data[fbs.pos..][0..name_len]; | ||
| 837 | fbs.pos += name_len; | ||
| 838 | |||
| 839 | const kind = std.meta.intToEnum(std.wasm.ExternalKind, try reader.readByte()) catch { | ||
| 840 | return step.fail("invalid import kind", .{}); | ||
| 841 | }; | ||
| 842 | |||
| 843 | try writer.print( | ||
| 844 | \\module {s} | ||
| 845 | \\name {s} | ||
| 846 | \\kind {s} | ||
| 847 | , .{ module_name, name, @tagName(kind) }); | ||
| 848 | try writer.writeByte('\n'); | ||
| 849 | switch (kind) { | ||
| 850 | .function => { | ||
| 851 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 852 | }, | ||
| 853 | .memory => { | ||
| 854 | try parseDumpLimits(reader, writer); | ||
| 855 | }, | ||
| 856 | .global => { | ||
| 857 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 858 | try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u32, reader)}); | ||
| 859 | }, | ||
| 860 | .table => { | ||
| 861 | try parseDumpType(step, std.wasm.RefType, reader, writer); | ||
| 862 | try parseDumpLimits(reader, writer); | ||
| 863 | }, | ||
| 864 | } | ||
| 865 | } | ||
| 866 | }, | ||
| 867 | .function => { | ||
| 868 | var i: u32 = 0; | ||
| 869 | while (i < entries) : (i += 1) { | ||
| 870 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 871 | } | ||
| 872 | }, | ||
| 873 | .table => { | ||
| 874 | var i: u32 = 0; | ||
| 875 | while (i < entries) : (i += 1) { | ||
| 876 | try parseDumpType(step, std.wasm.RefType, reader, writer); | ||
| 877 | try parseDumpLimits(reader, writer); | ||
| 878 | } | ||
| 879 | }, | ||
| 880 | .memory => { | ||
| 881 | var i: u32 = 0; | ||
| 882 | while (i < entries) : (i += 1) { | ||
| 883 | try parseDumpLimits(reader, writer); | ||
| 884 | } | ||
| 885 | }, | ||
| 886 | .global => { | ||
| 887 | var i: u32 = 0; | ||
| 888 | while (i < entries) : (i += 1) { | ||
| 889 | try parseDumpType(step, std.wasm.Valtype, reader, writer); | ||
| 890 | try writer.print("mutable {}\n", .{0x01 == try std.leb.readULEB128(u1, reader)}); | ||
| 891 | try parseDumpInit(step, reader, writer); | ||
| 892 | } | ||
| 893 | }, | ||
| 894 | .@"export" => { | ||
| 895 | var i: u32 = 0; | ||
| 896 | while (i < entries) : (i += 1) { | ||
| 897 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 898 | const name = data[fbs.pos..][0..name_len]; | ||
| 899 | fbs.pos += name_len; | ||
| 900 | const kind_byte = try std.leb.readULEB128(u8, reader); | ||
| 901 | const kind = std.meta.intToEnum(std.wasm.ExternalKind, kind_byte) catch { | ||
| 902 | return step.fail("invalid export kind value '{d}'", .{kind_byte}); | ||
| 903 | }; | ||
| 904 | const index = try std.leb.readULEB128(u32, reader); | ||
| 905 | try writer.print( | ||
| 906 | \\name {s} | ||
| 907 | \\kind {s} | ||
| 908 | \\index {d} | ||
| 909 | , .{ name, @tagName(kind), index }); | ||
| 910 | try writer.writeByte('\n'); | ||
| 911 | } | ||
| 912 | }, | ||
| 913 | .element => { | ||
| 914 | var i: u32 = 0; | ||
| 915 | while (i < entries) : (i += 1) { | ||
| 916 | try writer.print("table index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 917 | try parseDumpInit(step, reader, writer); | ||
| 918 | |||
| 919 | const function_indexes = try std.leb.readULEB128(u32, reader); | ||
| 920 | var function_index: u32 = 0; | ||
| 921 | try writer.print("indexes {d}\n", .{function_indexes}); | ||
| 922 | while (function_index < function_indexes) : (function_index += 1) { | ||
| 923 | try writer.print("index {d}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 924 | } | ||
| 925 | } | ||
| 926 | }, | ||
| 927 | .code => {}, // code section is considered opaque to linker | ||
| 928 | .data => { | ||
| 929 | var i: u32 = 0; | ||
| 930 | while (i < entries) : (i += 1) { | ||
| 931 | const index = try std.leb.readULEB128(u32, reader); | ||
| 932 | try writer.print("memory index 0x{x}\n", .{index}); | ||
| 933 | try parseDumpInit(step, reader, writer); | ||
| 934 | const size = try std.leb.readULEB128(u32, reader); | ||
| 935 | try writer.print("size {d}\n", .{size}); | ||
| 936 | try reader.skipBytes(size, .{}); // we do not care about the content of the segments | ||
| 937 | } | ||
| 938 | }, | ||
| 939 | else => unreachable, | ||
| 940 | } | ||
| 941 | } | ||
| 942 | |||
| 943 | fn parseDumpType(step: *Step, comptime WasmType: type, reader: anytype, writer: anytype) !void { | ||
| 944 | const type_byte = try reader.readByte(); | ||
| 945 | const valtype = std.meta.intToEnum(WasmType, type_byte) catch { | ||
| 946 | return step.fail("Invalid wasm type value '{d}'", .{type_byte}); | ||
| 947 | }; | ||
| 948 | try writer.print("type {s}\n", .{@tagName(valtype)}); | ||
| 949 | } | ||
| 950 | |||
| 951 | fn parseDumpLimits(reader: anytype, writer: anytype) !void { | ||
| 952 | const flags = try std.leb.readULEB128(u8, reader); | ||
| 953 | const min = try std.leb.readULEB128(u32, reader); | ||
| 954 | |||
| 955 | try writer.print("min {x}\n", .{min}); | ||
| 956 | if (flags != 0) { | ||
| 957 | try writer.print("max {x}\n", .{try std.leb.readULEB128(u32, reader)}); | ||
| 958 | } | ||
| 959 | } | ||
| 960 | |||
| 961 | fn parseDumpInit(step: *Step, reader: anytype, writer: anytype) !void { | ||
| 962 | const byte = try std.leb.readULEB128(u8, reader); | ||
| 963 | const opcode = std.meta.intToEnum(std.wasm.Opcode, byte) catch { | ||
| 964 | return step.fail("invalid wasm opcode '{d}'", .{byte}); | ||
| 965 | }; | ||
| 966 | switch (opcode) { | ||
| 967 | .i32_const => try writer.print("i32.const {x}\n", .{try std.leb.readILEB128(i32, reader)}), | ||
| 968 | .i64_const => try writer.print("i64.const {x}\n", .{try std.leb.readILEB128(i64, reader)}), | ||
| 969 | .f32_const => try writer.print("f32.const {x}\n", .{@bitCast(f32, try reader.readIntLittle(u32))}), | ||
| 970 | .f64_const => try writer.print("f64.const {x}\n", .{@bitCast(f64, try reader.readIntLittle(u64))}), | ||
| 971 | .global_get => try writer.print("global.get {x}\n", .{try std.leb.readULEB128(u32, reader)}), | ||
| 972 | else => unreachable, | ||
| 973 | } | ||
| 974 | const end_opcode = try std.leb.readULEB128(u8, reader); | ||
| 975 | if (end_opcode != std.wasm.opcode(.end)) { | ||
| 976 | return step.fail("expected 'end' opcode in init expression", .{}); | ||
| 977 | } | ||
| 978 | } | ||
| 979 | |||
| 980 | fn parseDumpNames(step: *Step, reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 981 | while (reader.context.pos < data.len) { | ||
| 982 | try parseDumpType(step, std.wasm.NameSubsection, reader, writer); | ||
| 983 | const size = try std.leb.readULEB128(u32, reader); | ||
| 984 | const entries = try std.leb.readULEB128(u32, reader); | ||
| 985 | try writer.print( | ||
| 986 | \\size {d} | ||
| 987 | \\names {d} | ||
| 988 | , .{ size, entries }); | ||
| 989 | try writer.writeByte('\n'); | ||
| 990 | var i: u32 = 0; | ||
| 991 | while (i < entries) : (i += 1) { | ||
| 992 | const index = try std.leb.readULEB128(u32, reader); | ||
| 993 | const name_len = try std.leb.readULEB128(u32, reader); | ||
| 994 | const pos = reader.context.pos; | ||
| 995 | const name = data[pos..][0..name_len]; | ||
| 996 | reader.context.pos += name_len; | ||
| 997 | |||
| 998 | try writer.print( | ||
| 999 | \\index {d} | ||
| 1000 | \\name {s} | ||
| 1001 | , .{ index, name }); | ||
| 1002 | try writer.writeByte('\n'); | ||
| 1003 | } | ||
| 1004 | } | ||
| 1005 | } | ||
| 1006 | |||
| 1007 | fn parseDumpProducers(reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 1008 | const field_count = try std.leb.readULEB128(u32, reader); | ||
| 1009 | try writer.print("fields {d}\n", .{field_count}); | ||
| 1010 | var current_field: u32 = 0; | ||
| 1011 | while (current_field < field_count) : (current_field += 1) { | ||
| 1012 | const field_name_length = try std.leb.readULEB128(u32, reader); | ||
| 1013 | const field_name = data[reader.context.pos..][0..field_name_length]; | ||
| 1014 | reader.context.pos += field_name_length; | ||
| 1015 | |||
| 1016 | const value_count = try std.leb.readULEB128(u32, reader); | ||
| 1017 | try writer.print( | ||
| 1018 | \\field_name {s} | ||
| 1019 | \\values {d} | ||
| 1020 | , .{ field_name, value_count }); | ||
| 1021 | try writer.writeByte('\n'); | ||
| 1022 | var current_value: u32 = 0; | ||
| 1023 | while (current_value < value_count) : (current_value += 1) { | ||
| 1024 | const value_length = try std.leb.readULEB128(u32, reader); | ||
| 1025 | const value = data[reader.context.pos..][0..value_length]; | ||
| 1026 | reader.context.pos += value_length; | ||
| 1027 | |||
| 1028 | const version_length = try std.leb.readULEB128(u32, reader); | ||
| 1029 | const version = data[reader.context.pos..][0..version_length]; | ||
| 1030 | reader.context.pos += version_length; | ||
| 1031 | |||
| 1032 | try writer.print( | ||
| 1033 | \\value_name {s} | ||
| 1034 | \\version {s} | ||
| 1035 | , .{ value, version }); | ||
| 1036 | try writer.writeByte('\n'); | ||
| 1037 | } | ||
| 1038 | } | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | fn parseDumpFeatures(reader: anytype, writer: anytype, data: []const u8) !void { | ||
| 1042 | const feature_count = try std.leb.readULEB128(u32, reader); | ||
| 1043 | try writer.print("features {d}\n", .{feature_count}); | ||
| 1044 | |||
| 1045 | var index: u32 = 0; | ||
| 1046 | while (index < feature_count) : (index += 1) { | ||
| 1047 | const prefix_byte = try std.leb.readULEB128(u8, reader); | ||
| 1048 | const name_length = try std.leb.readULEB128(u32, reader); | ||
| 1049 | const feature_name = data[reader.context.pos..][0..name_length]; | ||
| 1050 | reader.context.pos += name_length; | ||
| 1051 | |||
| 1052 | try writer.print("{c} {s}\n", .{ prefix_byte, feature_name }); | ||
| 1053 | } | ||
| 1054 | } | ||
| 1055 | }; | ||
lib/std/Build/Step/Compile.zig created+2183| ... | @@ -0,0 +1,2183 @@ | ||
| 1 | const builtin = @import("builtin"); | ||
| 2 | const std = @import("std"); | ||
| 3 | const mem = std.mem; | ||
| 4 | const fs = std.fs; | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | const panic = std.debug.panic; | ||
| 7 | const ArrayList = std.ArrayList; | ||
| 8 | const StringHashMap = std.StringHashMap; | ||
| 9 | const Sha256 = std.crypto.hash.sha2.Sha256; | ||
| 10 | const Allocator = mem.Allocator; | ||
| 11 | const Step = std.Build.Step; | ||
| 12 | const CrossTarget = std.zig.CrossTarget; | ||
| 13 | const NativeTargetInfo = std.zig.system.NativeTargetInfo; | ||
| 14 | const FileSource = std.Build.FileSource; | ||
| 15 | const PkgConfigPkg = std.Build.PkgConfigPkg; | ||
| 16 | const PkgConfigError = std.Build.PkgConfigError; | ||
| 17 | const ExecError = std.Build.ExecError; | ||
| 18 | const Module = std.Build.Module; | ||
| 19 | const VcpkgRoot = std.Build.VcpkgRoot; | ||
| 20 | const InstallDir = std.Build.InstallDir; | ||
| 21 | const InstallArtifactStep = std.Build.InstallArtifactStep; | ||
| 22 | const GeneratedFile = std.Build.GeneratedFile; | ||
| 23 | const ObjCopyStep = std.Build.ObjCopyStep; | ||
| 24 | const CheckObjectStep = std.Build.CheckObjectStep; | ||
| 25 | const RunStep = std.Build.RunStep; | ||
| 26 | const OptionsStep = std.Build.OptionsStep; | ||
| 27 | const ConfigHeaderStep = std.Build.ConfigHeaderStep; | ||
| 28 | const CompileStep = @This(); | ||
| 29 | |||
| 30 | pub const base_id: Step.Id = .compile; | ||
| 31 | |||
| 32 | step: Step, | ||
| 33 | name: []const u8, | ||
| 34 | target: CrossTarget, | ||
| 35 | target_info: NativeTargetInfo, | ||
| 36 | optimize: std.builtin.Mode, | ||
| 37 | linker_script: ?FileSource = null, | ||
| 38 | version_script: ?[]const u8 = null, | ||
| 39 | out_filename: []const u8, | ||
| 40 | linkage: ?Linkage = null, | ||
| 41 | version: ?std.builtin.Version, | ||
| 42 | kind: Kind, | ||
| 43 | major_only_filename: ?[]const u8, | ||
| 44 | name_only_filename: ?[]const u8, | ||
| 45 | strip: ?bool, | ||
| 46 | unwind_tables: ?bool, | ||
| 47 | // keep in sync with src/link.zig:CompressDebugSections | ||
| 48 | compress_debug_sections: enum { none, zlib } = .none, | ||
| 49 | lib_paths: ArrayList(FileSource), | ||
| 50 | rpaths: ArrayList(FileSource), | ||
| 51 | framework_dirs: ArrayList(FileSource), | ||
| 52 | frameworks: StringHashMap(FrameworkLinkInfo), | ||
| 53 | verbose_link: bool, | ||
| 54 | verbose_cc: bool, | ||
| 55 | emit_analysis: EmitOption = .default, | ||
| 56 | emit_asm: EmitOption = .default, | ||
| 57 | emit_bin: EmitOption = .default, | ||
| 58 | emit_docs: EmitOption = .default, | ||
| 59 | emit_implib: EmitOption = .default, | ||
| 60 | emit_llvm_bc: EmitOption = .default, | ||
| 61 | emit_llvm_ir: EmitOption = .default, | ||
| 62 | // Lots of things depend on emit_h having a consistent path, | ||
| 63 | // so it is not an EmitOption for now. | ||
| 64 | emit_h: bool = false, | ||
| 65 | bundle_compiler_rt: ?bool = null, | ||
| 66 | single_threaded: ?bool, | ||
| 67 | stack_protector: ?bool = null, | ||
| 68 | disable_stack_probing: bool, | ||
| 69 | disable_sanitize_c: bool, | ||
| 70 | sanitize_thread: bool, | ||
| 71 | rdynamic: bool, | ||
| 72 | dwarf_format: ?std.dwarf.Format = null, | ||
| 73 | import_memory: bool = false, | ||
| 74 | /// For WebAssembly targets, this will allow for undefined symbols to | ||
| 75 | /// be imported from the host environment. | ||
| 76 | import_symbols: bool = false, | ||
| 77 | import_table: bool = false, | ||
| 78 | export_table: bool = false, | ||
| 79 | initial_memory: ?u64 = null, | ||
| 80 | max_memory: ?u64 = null, | ||
| 81 | shared_memory: bool = false, | ||
| 82 | global_base: ?u64 = null, | ||
| 83 | c_std: std.Build.CStd, | ||
| 84 | zig_lib_dir: ?[]const u8, | ||
| 85 | main_pkg_path: ?[]const u8, | ||
| 86 | exec_cmd_args: ?[]const ?[]const u8, | ||
| 87 | filter: ?[]const u8, | ||
| 88 | test_evented_io: bool = false, | ||
| 89 | test_runner: ?[]const u8, | ||
| 90 | code_model: std.builtin.CodeModel = .default, | ||
| 91 | wasi_exec_model: ?std.builtin.WasiExecModel = null, | ||
| 92 | /// Symbols to be exported when compiling to wasm | ||
| 93 | export_symbol_names: []const []const u8 = &.{}, | ||
| 94 | |||
| 95 | root_src: ?FileSource, | ||
| 96 | out_h_filename: []const u8, | ||
| 97 | out_lib_filename: []const u8, | ||
| 98 | out_pdb_filename: []const u8, | ||
| 99 | modules: std.StringArrayHashMap(*Module), | ||
| 100 | |||
| 101 | link_objects: ArrayList(LinkObject), | ||
| 102 | include_dirs: ArrayList(IncludeDir), | ||
| 103 | c_macros: ArrayList([]const u8), | ||
| 104 | installed_headers: ArrayList(*Step), | ||
| 105 | is_linking_libc: bool, | ||
| 106 | is_linking_libcpp: bool, | ||
| 107 | vcpkg_bin_path: ?[]const u8 = null, | ||
| 108 | |||
| 109 | /// This may be set in order to override the default install directory | ||
| 110 | override_dest_dir: ?InstallDir, | ||
| 111 | installed_path: ?[]const u8, | ||
| 112 | |||
| 113 | /// Base address for an executable image. | ||
| 114 | image_base: ?u64 = null, | ||
| 115 | |||
| 116 | libc_file: ?FileSource = null, | ||
| 117 | |||
| 118 | valgrind_support: ?bool = null, | ||
| 119 | each_lib_rpath: ?bool = null, | ||
| 120 | /// On ELF targets, this will emit a link section called ".note.gnu.build-id" | ||
| 121 | /// which can be used to coordinate a stripped binary with its debug symbols. | ||
| 122 | /// As an example, the bloaty project refuses to work unless its inputs have | ||
| 123 | /// build ids, in order to prevent accidental mismatches. | ||
| 124 | /// The default is to not include this section because it slows down linking. | ||
| 125 | build_id: ?bool = null, | ||
| 126 | |||
| 127 | /// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF | ||
| 128 | /// file. | ||
| 129 | link_eh_frame_hdr: bool = false, | ||
| 130 | link_emit_relocs: bool = false, | ||
| 131 | |||
| 132 | /// Place every function in its own section so that unused ones may be | ||
| 133 | /// safely garbage-collected during the linking phase. | ||
| 134 | link_function_sections: bool = false, | ||
| 135 | |||
| 136 | /// Remove functions and data that are unreachable by the entry point or | ||
| 137 | /// exported symbols. | ||
| 138 | link_gc_sections: ?bool = null, | ||
| 139 | |||
| 140 | /// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument. | ||
| 141 | linker_dynamicbase: bool = true, | ||
| 142 | |||
| 143 | linker_allow_shlib_undefined: ?bool = null, | ||
| 144 | |||
| 145 | /// Permit read-only relocations in read-only segments. Disallowed by default. | ||
| 146 | link_z_notext: bool = false, | ||
| 147 | |||
| 148 | /// Force all relocations to be read-only after processing. | ||
| 149 | link_z_relro: bool = true, | ||
| 150 | |||
| 151 | /// Allow relocations to be lazily processed after load. | ||
| 152 | link_z_lazy: bool = false, | ||
| 153 | |||
| 154 | /// Common page size | ||
| 155 | link_z_common_page_size: ?u64 = null, | ||
| 156 | |||
| 157 | /// Maximum page size | ||
| 158 | link_z_max_page_size: ?u64 = null, | ||
| 159 | |||
| 160 | /// (Darwin) Install name for the dylib | ||
| 161 | install_name: ?[]const u8 = null, | ||
| 162 | |||
| 163 | /// (Darwin) Path to entitlements file | ||
| 164 | entitlements: ?[]const u8 = null, | ||
| 165 | |||
| 166 | /// (Darwin) Size of the pagezero segment. | ||
| 167 | pagezero_size: ?u64 = null, | ||
| 168 | |||
| 169 | /// (Darwin) Search strategy for searching system libraries. Either `paths_first` or `dylibs_first`. | ||
| 170 | /// The former lowers to `-search_paths_first` linker option, while the latter to `-search_dylibs_first` | ||
| 171 | /// option. | ||
| 172 | /// By default, if no option is specified, the linker assumes `paths_first` as the default | ||
| 173 | /// search strategy. | ||
| 174 | search_strategy: ?enum { paths_first, dylibs_first } = null, | ||
| 175 | |||
| 176 | /// (Darwin) Set size of the padding between the end of load commands | ||
| 177 | /// and start of `__TEXT,__text` section. | ||
| 178 | headerpad_size: ?u32 = null, | ||
| 179 | |||
| 180 | /// (Darwin) Automatically Set size of the padding between the end of load commands | ||
| 181 | /// and start of `__TEXT,__text` section to a value fitting all paths expanded to MAXPATHLEN. | ||
| 182 | headerpad_max_install_names: bool = false, | ||
| 183 | |||
| 184 | /// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols. | ||
| 185 | dead_strip_dylibs: bool = false, | ||
| 186 | |||
| 187 | /// Position Independent Code | ||
| 188 | force_pic: ?bool = null, | ||
| 189 | |||
| 190 | /// Position Independent Executable | ||
| 191 | pie: ?bool = null, | ||
| 192 | |||
| 193 | red_zone: ?bool = null, | ||
| 194 | |||
| 195 | omit_frame_pointer: ?bool = null, | ||
| 196 | dll_export_fns: ?bool = null, | ||
| 197 | |||
| 198 | subsystem: ?std.Target.SubSystem = null, | ||
| 199 | |||
| 200 | entry_symbol_name: ?[]const u8 = null, | ||
| 201 | |||
| 202 | /// List of symbols forced as undefined in the symbol table | ||
| 203 | /// thus forcing their resolution by the linker. | ||
| 204 | /// Corresponds to `-u <symbol>` for ELF/MachO and `/include:<symbol>` for COFF/PE. | ||
| 205 | force_undefined_symbols: std.StringHashMap(void), | ||
| 206 | |||
| 207 | /// Overrides the default stack size | ||
| 208 | stack_size: ?u64 = null, | ||
| 209 | |||
| 210 | want_lto: ?bool = null, | ||
| 211 | use_llvm: ?bool, | ||
| 212 | use_lld: ?bool, | ||
| 213 | |||
| 214 | /// This is an advanced setting that can change the intent of this CompileStep. | ||
| 215 | /// If this slice has nonzero length, it means that this CompileStep exists to | ||
| 216 | /// check for compile errors and return *success* if they match, and failure | ||
| 217 | /// otherwise. | ||
| 218 | expect_errors: []const []const u8 = &.{}, | ||
| 219 | |||
| 220 | output_path_source: GeneratedFile, | ||
| 221 | output_lib_path_source: GeneratedFile, | ||
| 222 | output_h_path_source: GeneratedFile, | ||
| 223 | output_pdb_path_source: GeneratedFile, | ||
| 224 | output_dirname_source: GeneratedFile, | ||
| 225 | |||
| 226 | pub const CSourceFiles = struct { | ||
| 227 | files: []const []const u8, | ||
| 228 | flags: []const []const u8, | ||
| 229 | }; | ||
| 230 | |||
| 231 | pub const CSourceFile = struct { | ||
| 232 | source: FileSource, | ||
| 233 | args: []const []const u8, | ||
| 234 | |||
| 235 | pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile { | ||
| 236 | return .{ | ||
| 237 | .source = self.source.dupe(b), | ||
| 238 | .args = b.dupeStrings(self.args), | ||
| 239 | }; | ||
| 240 | } | ||
| 241 | }; | ||
| 242 | |||
| 243 | pub const LinkObject = union(enum) { | ||
| 244 | static_path: FileSource, | ||
| 245 | other_step: *CompileStep, | ||
| 246 | system_lib: SystemLib, | ||
| 247 | assembly_file: FileSource, | ||
| 248 | c_source_file: *CSourceFile, | ||
| 249 | c_source_files: *CSourceFiles, | ||
| 250 | }; | ||
| 251 | |||
| 252 | pub const SystemLib = struct { | ||
| 253 | name: []const u8, | ||
| 254 | needed: bool, | ||
| 255 | weak: bool, | ||
| 256 | use_pkg_config: enum { | ||
| 257 | /// Don't use pkg-config, just pass -lfoo where foo is name. | ||
| 258 | no, | ||
| 259 | /// Try to get information on how to link the library from pkg-config. | ||
| 260 | /// If that fails, fall back to passing -lfoo where foo is name. | ||
| 261 | yes, | ||
| 262 | /// Try to get information on how to link the library from pkg-config. | ||
| 263 | /// If that fails, error out. | ||
| 264 | force, | ||
| 265 | }, | ||
| 266 | }; | ||
| 267 | |||
| 268 | const FrameworkLinkInfo = struct { | ||
| 269 | needed: bool = false, | ||
| 270 | weak: bool = false, | ||
| 271 | }; | ||
| 272 | |||
| 273 | pub const IncludeDir = union(enum) { | ||
| 274 | raw_path: []const u8, | ||
| 275 | raw_path_system: []const u8, | ||
| 276 | other_step: *CompileStep, | ||
| 277 | config_header_step: *ConfigHeaderStep, | ||
| 278 | }; | ||
| 279 | |||
| 280 | pub const Options = struct { | ||
| 281 | name: []const u8, | ||
| 282 | root_source_file: ?FileSource = null, | ||
| 283 | target: CrossTarget, | ||
| 284 | optimize: std.builtin.Mode, | ||
| 285 | kind: Kind, | ||
| 286 | linkage: ?Linkage = null, | ||
| 287 | version: ?std.builtin.Version = null, | ||
| 288 | max_rss: usize = 0, | ||
| 289 | filter: ?[]const u8 = null, | ||
| 290 | test_runner: ?[]const u8 = null, | ||
| 291 | link_libc: ?bool = null, | ||
| 292 | single_threaded: ?bool = null, | ||
| 293 | use_llvm: ?bool = null, | ||
| 294 | use_lld: ?bool = null, | ||
| 295 | }; | ||
| 296 | |||
| 297 | pub const Kind = enum { | ||
| 298 | exe, | ||
| 299 | lib, | ||
| 300 | obj, | ||
| 301 | @"test", | ||
| 302 | }; | ||
| 303 | |||
| 304 | pub const Linkage = enum { dynamic, static }; | ||
| 305 | |||
| 306 | pub const EmitOption = union(enum) { | ||
| 307 | default: void, | ||
| 308 | no_emit: void, | ||
| 309 | emit: void, | ||
| 310 | emit_to: []const u8, | ||
| 311 | |||
| 312 | fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 { | ||
| 313 | return switch (self) { | ||
| 314 | .no_emit => b.fmt("-fno-{s}", .{arg_name}), | ||
| 315 | .default => null, | ||
| 316 | .emit => b.fmt("-f{s}", .{arg_name}), | ||
| 317 | .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }), | ||
| 318 | }; | ||
| 319 | } | ||
| 320 | }; | ||
| 321 | |||
| 322 | pub fn create(owner: *std.Build, options: Options) *CompileStep { | ||
| 323 | const name = owner.dupe(options.name); | ||
| 324 | const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null; | ||
| 325 | if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) { | ||
| 326 | panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name}); | ||
| 327 | } | ||
| 328 | |||
| 329 | // Avoid the common case of the step name looking like "zig test test". | ||
| 330 | const name_adjusted = if (options.kind == .@"test" and mem.eql(u8, name, "test")) | ||
| 331 | "" | ||
| 332 | else | ||
| 333 | owner.fmt("{s} ", .{name}); | ||
| 334 | |||
| 335 | const step_name = owner.fmt("{s} {s}{s} {s}", .{ | ||
| 336 | switch (options.kind) { | ||
| 337 | .exe => "zig build-exe", | ||
| 338 | .lib => "zig build-lib", | ||
| 339 | .obj => "zig build-obj", | ||
| 340 | .@"test" => "zig test", | ||
| 341 | }, | ||
| 342 | name_adjusted, | ||
| 343 | @tagName(options.optimize), | ||
| 344 | options.target.zigTriple(owner.allocator) catch @panic("OOM"), | ||
| 345 | }); | ||
| 346 | |||
| 347 | const target_info = NativeTargetInfo.detect(options.target) catch @panic("unhandled error"); | ||
| 348 | |||
| 349 | const out_filename = std.zig.binNameAlloc(owner.allocator, .{ | ||
| 350 | .root_name = name, | ||
| 351 | .target = target_info.target, | ||
| 352 | .output_mode = switch (options.kind) { | ||
| 353 | .lib => .Lib, | ||
| 354 | .obj => .Obj, | ||
| 355 | .exe, .@"test" => .Exe, | ||
| 356 | }, | ||
| 357 | .link_mode = if (options.linkage) |some| @as(std.builtin.LinkMode, switch (some) { | ||
| 358 | .dynamic => .Dynamic, | ||
| 359 | .static => .Static, | ||
| 360 | }) else null, | ||
| 361 | .version = options.version, | ||
| 362 | }) catch @panic("OOM"); | ||
| 363 | |||
| 364 | const self = owner.allocator.create(CompileStep) catch @panic("OOM"); | ||
| 365 | self.* = CompileStep{ | ||
| 366 | .strip = null, | ||
| 367 | .unwind_tables = null, | ||
| 368 | .verbose_link = false, | ||
| 369 | .verbose_cc = false, | ||
| 370 | .optimize = options.optimize, | ||
| 371 | .target = options.target, | ||
| 372 | .linkage = options.linkage, | ||
| 373 | .kind = options.kind, | ||
| 374 | .root_src = root_src, | ||
| 375 | .name = name, | ||
| 376 | .frameworks = StringHashMap(FrameworkLinkInfo).init(owner.allocator), | ||
| 377 | .step = Step.init(.{ | ||
| 378 | .id = base_id, | ||
| 379 | .name = step_name, | ||
| 380 | .owner = owner, | ||
| 381 | .makeFn = make, | ||
| 382 | .max_rss = options.max_rss, | ||
| 383 | }), | ||
| 384 | .version = options.version, | ||
| 385 | .out_filename = out_filename, | ||
| 386 | .out_h_filename = owner.fmt("{s}.h", .{name}), | ||
| 387 | .out_lib_filename = undefined, | ||
| 388 | .out_pdb_filename = owner.fmt("{s}.pdb", .{name}), | ||
| 389 | .major_only_filename = null, | ||
| 390 | .name_only_filename = null, | ||
| 391 | .modules = std.StringArrayHashMap(*Module).init(owner.allocator), | ||
| 392 | .include_dirs = ArrayList(IncludeDir).init(owner.allocator), | ||
| 393 | .link_objects = ArrayList(LinkObject).init(owner.allocator), | ||
| 394 | .c_macros = ArrayList([]const u8).init(owner.allocator), | ||
| 395 | .lib_paths = ArrayList(FileSource).init(owner.allocator), | ||
| 396 | .rpaths = ArrayList(FileSource).init(owner.allocator), | ||
| 397 | .framework_dirs = ArrayList(FileSource).init(owner.allocator), | ||
| 398 | .installed_headers = ArrayList(*Step).init(owner.allocator), | ||
| 399 | .c_std = std.Build.CStd.C99, | ||
| 400 | .zig_lib_dir = null, | ||
| 401 | .main_pkg_path = null, | ||
| 402 | .exec_cmd_args = null, | ||
| 403 | .filter = options.filter, | ||
| 404 | .test_runner = options.test_runner, | ||
| 405 | .disable_stack_probing = false, | ||
| 406 | .disable_sanitize_c = false, | ||
| 407 | .sanitize_thread = false, | ||
| 408 | .rdynamic = false, | ||
| 409 | .override_dest_dir = null, | ||
| 410 | .installed_path = null, | ||
| 411 | .force_undefined_symbols = StringHashMap(void).init(owner.allocator), | ||
| 412 | |||
| 413 | .output_path_source = GeneratedFile{ .step = &self.step }, | ||
| 414 | .output_lib_path_source = GeneratedFile{ .step = &self.step }, | ||
| 415 | .output_h_path_source = GeneratedFile{ .step = &self.step }, | ||
| 416 | .output_pdb_path_source = GeneratedFile{ .step = &self.step }, | ||
| 417 | .output_dirname_source = GeneratedFile{ .step = &self.step }, | ||
| 418 | |||
| 419 | .target_info = target_info, | ||
| 420 | |||
| 421 | .is_linking_libc = options.link_libc orelse false, | ||
| 422 | .is_linking_libcpp = false, | ||
| 423 | .single_threaded = options.single_threaded, | ||
| 424 | .use_llvm = options.use_llvm, | ||
| 425 | .use_lld = options.use_lld, | ||
| 426 | }; | ||
| 427 | |||
| 428 | if (self.kind == .lib) { | ||
| 429 | if (self.linkage != null and self.linkage.? == .static) { | ||
| 430 | self.out_lib_filename = self.out_filename; | ||
| 431 | } else if (self.version) |version| { | ||
| 432 | if (target_info.target.isDarwin()) { | ||
| 433 | self.major_only_filename = owner.fmt("lib{s}.{d}.dylib", .{ | ||
| 434 | self.name, | ||
| 435 | version.major, | ||
| 436 | }); | ||
| 437 | self.name_only_filename = owner.fmt("lib{s}.dylib", .{self.name}); | ||
| 438 | self.out_lib_filename = self.out_filename; | ||
| 439 | } else if (target_info.target.os.tag == .windows) { | ||
| 440 | self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); | ||
| 441 | } else { | ||
| 442 | self.major_only_filename = owner.fmt("lib{s}.so.{d}", .{ self.name, version.major }); | ||
| 443 | self.name_only_filename = owner.fmt("lib{s}.so", .{self.name}); | ||
| 444 | self.out_lib_filename = self.out_filename; | ||
| 445 | } | ||
| 446 | } else { | ||
| 447 | if (target_info.target.isDarwin()) { | ||
| 448 | self.out_lib_filename = self.out_filename; | ||
| 449 | } else if (target_info.target.os.tag == .windows) { | ||
| 450 | self.out_lib_filename = owner.fmt("{s}.lib", .{self.name}); | ||
| 451 | } else { | ||
| 452 | self.out_lib_filename = self.out_filename; | ||
| 453 | } | ||
| 454 | } | ||
| 455 | } | ||
| 456 | |||
| 457 | if (root_src) |rs| rs.addStepDependencies(&self.step); | ||
| 458 | |||
| 459 | return self; | ||
| 460 | } | ||
| 461 | |||
| 462 | pub fn installHeader(cs: *CompileStep, src_path: []const u8, dest_rel_path: []const u8) void { | ||
| 463 | const b = cs.step.owner; | ||
| 464 | const install_file = b.addInstallHeaderFile(src_path, dest_rel_path); | ||
| 465 | b.getInstallStep().dependOn(&install_file.step); | ||
| 466 | cs.installed_headers.append(&install_file.step) catch @panic("OOM"); | ||
| 467 | } | ||
| 468 | |||
| 469 | pub const InstallConfigHeaderOptions = struct { | ||
| 470 | install_dir: InstallDir = .header, | ||
| 471 | dest_rel_path: ?[]const u8 = null, | ||
| 472 | }; | ||
| 473 | |||
| 474 | pub fn installConfigHeader( | ||
| 475 | cs: *CompileStep, | ||
| 476 | config_header: *ConfigHeaderStep, | ||
| 477 | options: InstallConfigHeaderOptions, | ||
| 478 | ) void { | ||
| 479 | const dest_rel_path = options.dest_rel_path orelse config_header.include_path; | ||
| 480 | const b = cs.step.owner; | ||
| 481 | const install_file = b.addInstallFileWithDir( | ||
| 482 | .{ .generated = &config_header.output_file }, | ||
| 483 | options.install_dir, | ||
| 484 | dest_rel_path, | ||
| 485 | ); | ||
| 486 | install_file.step.dependOn(&config_header.step); | ||
| 487 | b.getInstallStep().dependOn(&install_file.step); | ||
| 488 | cs.installed_headers.append(&install_file.step) catch @panic("OOM"); | ||
| 489 | } | ||
| 490 | |||
| 491 | pub fn installHeadersDirectory( | ||
| 492 | a: *CompileStep, | ||
| 493 | src_dir_path: []const u8, | ||
| 494 | dest_rel_path: []const u8, | ||
| 495 | ) void { | ||
| 496 | return installHeadersDirectoryOptions(a, .{ | ||
| 497 | .source_dir = src_dir_path, | ||
| 498 | .install_dir = .header, | ||
| 499 | .install_subdir = dest_rel_path, | ||
| 500 | }); | ||
| 501 | } | ||
| 502 | |||
| 503 | pub fn installHeadersDirectoryOptions( | ||
| 504 | cs: *CompileStep, | ||
| 505 | options: std.Build.InstallDirStep.Options, | ||
| 506 | ) void { | ||
| 507 | const b = cs.step.owner; | ||
| 508 | const install_dir = b.addInstallDirectory(options); | ||
| 509 | b.getInstallStep().dependOn(&install_dir.step); | ||
| 510 | cs.installed_headers.append(&install_dir.step) catch @panic("OOM"); | ||
| 511 | } | ||
| 512 | |||
| 513 | pub fn installLibraryHeaders(cs: *CompileStep, l: *CompileStep) void { | ||
| 514 | assert(l.kind == .lib); | ||
| 515 | const b = cs.step.owner; | ||
| 516 | const install_step = b.getInstallStep(); | ||
| 517 | // Copy each element from installed_headers, modifying the builder | ||
| 518 | // to be the new parent's builder. | ||
| 519 | for (l.installed_headers.items) |step| { | ||
| 520 | const step_copy = switch (step.id) { | ||
| 521 | inline .install_file, .install_dir => |id| blk: { | ||
| 522 | const T = id.Type(); | ||
| 523 | const ptr = b.allocator.create(T) catch @panic("OOM"); | ||
| 524 | ptr.* = step.cast(T).?.*; | ||
| 525 | ptr.dest_builder = b; | ||
| 526 | break :blk &ptr.step; | ||
| 527 | }, | ||
| 528 | else => unreachable, | ||
| 529 | }; | ||
| 530 | cs.installed_headers.append(step_copy) catch @panic("OOM"); | ||
| 531 | install_step.dependOn(step_copy); | ||
| 532 | } | ||
| 533 | cs.installed_headers.appendSlice(l.installed_headers.items) catch @panic("OOM"); | ||
| 534 | } | ||
| 535 | |||
| 536 | pub fn addObjCopy(cs: *CompileStep, options: ObjCopyStep.Options) *ObjCopyStep { | ||
| 537 | const b = cs.step.owner; | ||
| 538 | var copy = options; | ||
| 539 | if (copy.basename == null) { | ||
| 540 | if (options.format) |f| { | ||
| 541 | copy.basename = b.fmt("{s}.{s}", .{ cs.name, @tagName(f) }); | ||
| 542 | } else { | ||
| 543 | copy.basename = cs.name; | ||
| 544 | } | ||
| 545 | } | ||
| 546 | return b.addObjCopy(cs.getOutputSource(), copy); | ||
| 547 | } | ||
| 548 | |||
| 549 | /// This function would run in the context of the package that created the executable, | ||
| 550 | /// which is undesirable when running an executable provided by a dependency package. | ||
| 551 | pub const run = @compileError("deprecated; use std.Build.addRunArtifact"); | ||
| 552 | |||
| 553 | /// This function would install in the context of the package that created the artifact, | ||
| 554 | /// which is undesirable when installing an artifact provided by a dependency package. | ||
| 555 | pub const install = @compileError("deprecated; use std.Build.installArtifact"); | ||
| 556 | |||
| 557 | pub fn checkObject(self: *CompileStep) *CheckObjectStep { | ||
| 558 | return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt); | ||
| 559 | } | ||
| 560 | |||
| 561 | pub fn setLinkerScriptPath(self: *CompileStep, source: FileSource) void { | ||
| 562 | const b = self.step.owner; | ||
| 563 | self.linker_script = source.dupe(b); | ||
| 564 | source.addStepDependencies(&self.step); | ||
| 565 | } | ||
| 566 | |||
| 567 | pub fn forceUndefinedSymbol(self: *CompileStep, symbol_name: []const u8) void { | ||
| 568 | const b = self.step.owner; | ||
| 569 | self.force_undefined_symbols.put(b.dupe(symbol_name), {}) catch @panic("OOM"); | ||
| 570 | } | ||
| 571 | |||
| 572 | pub fn linkFramework(self: *CompileStep, framework_name: []const u8) void { | ||
| 573 | const b = self.step.owner; | ||
| 574 | self.frameworks.put(b.dupe(framework_name), .{}) catch @panic("OOM"); | ||
| 575 | } | ||
| 576 | |||
| 577 | pub fn linkFrameworkNeeded(self: *CompileStep, framework_name: []const u8) void { | ||
| 578 | const b = self.step.owner; | ||
| 579 | self.frameworks.put(b.dupe(framework_name), .{ | ||
| 580 | .needed = true, | ||
| 581 | }) catch @panic("OOM"); | ||
| 582 | } | ||
| 583 | |||
| 584 | pub fn linkFrameworkWeak(self: *CompileStep, framework_name: []const u8) void { | ||
| 585 | const b = self.step.owner; | ||
| 586 | self.frameworks.put(b.dupe(framework_name), .{ | ||
| 587 | .weak = true, | ||
| 588 | }) catch @panic("OOM"); | ||
| 589 | } | ||
| 590 | |||
| 591 | /// Returns whether the library, executable, or object depends on a particular system library. | ||
| 592 | pub fn dependsOnSystemLibrary(self: CompileStep, name: []const u8) bool { | ||
| 593 | if (isLibCLibrary(name)) { | ||
| 594 | return self.is_linking_libc; | ||
| 595 | } | ||
| 596 | if (isLibCppLibrary(name)) { | ||
| 597 | return self.is_linking_libcpp; | ||
| 598 | } | ||
| 599 | for (self.link_objects.items) |link_object| { | ||
| 600 | switch (link_object) { | ||
| 601 | .system_lib => |lib| if (mem.eql(u8, lib.name, name)) return true, | ||
| 602 | else => continue, | ||
| 603 | } | ||
| 604 | } | ||
| 605 | return false; | ||
| 606 | } | ||
| 607 | |||
| 608 | pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void { | ||
| 609 | assert(lib.kind == .lib); | ||
| 610 | self.linkLibraryOrObject(lib); | ||
| 611 | } | ||
| 612 | |||
| 613 | pub fn isDynamicLibrary(self: *CompileStep) bool { | ||
| 614 | return self.kind == .lib and self.linkage == Linkage.dynamic; | ||
| 615 | } | ||
| 616 | |||
| 617 | pub fn isStaticLibrary(self: *CompileStep) bool { | ||
| 618 | return self.kind == .lib and self.linkage != Linkage.dynamic; | ||
| 619 | } | ||
| 620 | |||
| 621 | pub fn producesPdbFile(self: *CompileStep) bool { | ||
| 622 | if (!self.target.isWindows() and !self.target.isUefi()) return false; | ||
| 623 | if (self.target.getObjectFormat() == .c) return false; | ||
| 624 | if (self.strip == true) return false; | ||
| 625 | return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test"; | ||
| 626 | } | ||
| 627 | |||
| 628 | pub fn linkLibC(self: *CompileStep) void { | ||
| 629 | self.is_linking_libc = true; | ||
| 630 | } | ||
| 631 | |||
| 632 | pub fn linkLibCpp(self: *CompileStep) void { | ||
| 633 | self.is_linking_libcpp = true; | ||
| 634 | } | ||
| 635 | |||
| 636 | /// If the value is omitted, it is set to 1. | ||
| 637 | /// `name` and `value` need not live longer than the function call. | ||
| 638 | pub fn defineCMacro(self: *CompileStep, name: []const u8, value: ?[]const u8) void { | ||
| 639 | const b = self.step.owner; | ||
| 640 | const macro = std.Build.constructCMacro(b.allocator, name, value); | ||
| 641 | self.c_macros.append(macro) catch @panic("OOM"); | ||
| 642 | } | ||
| 643 | |||
| 644 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. | ||
| 645 | pub fn defineCMacroRaw(self: *CompileStep, name_and_value: []const u8) void { | ||
| 646 | const b = self.step.owner; | ||
| 647 | self.c_macros.append(b.dupe(name_and_value)) catch @panic("OOM"); | ||
| 648 | } | ||
| 649 | |||
| 650 | /// This one has no integration with anything, it just puts -lname on the command line. | ||
| 651 | /// Prefer to use `linkSystemLibrary` instead. | ||
| 652 | pub fn linkSystemLibraryName(self: *CompileStep, name: []const u8) void { | ||
| 653 | const b = self.step.owner; | ||
| 654 | self.link_objects.append(.{ | ||
| 655 | .system_lib = .{ | ||
| 656 | .name = b.dupe(name), | ||
| 657 | .needed = false, | ||
| 658 | .weak = false, | ||
| 659 | .use_pkg_config = .no, | ||
| 660 | }, | ||
| 661 | }) catch @panic("OOM"); | ||
| 662 | } | ||
| 663 | |||
| 664 | /// This one has no integration with anything, it just puts -needed-lname on the command line. | ||
| 665 | /// Prefer to use `linkSystemLibraryNeeded` instead. | ||
| 666 | pub fn linkSystemLibraryNeededName(self: *CompileStep, name: []const u8) void { | ||
| 667 | const b = self.step.owner; | ||
| 668 | self.link_objects.append(.{ | ||
| 669 | .system_lib = .{ | ||
| 670 | .name = b.dupe(name), | ||
| 671 | .needed = true, | ||
| 672 | .weak = false, | ||
| 673 | .use_pkg_config = .no, | ||
| 674 | }, | ||
| 675 | }) catch @panic("OOM"); | ||
| 676 | } | ||
| 677 | |||
| 678 | /// Darwin-only. This one has no integration with anything, it just puts -weak-lname on the | ||
| 679 | /// command line. Prefer to use `linkSystemLibraryWeak` instead. | ||
| 680 | pub fn linkSystemLibraryWeakName(self: *CompileStep, name: []const u8) void { | ||
| 681 | const b = self.step.owner; | ||
| 682 | self.link_objects.append(.{ | ||
| 683 | .system_lib = .{ | ||
| 684 | .name = b.dupe(name), | ||
| 685 | .needed = false, | ||
| 686 | .weak = true, | ||
| 687 | .use_pkg_config = .no, | ||
| 688 | }, | ||
| 689 | }) catch @panic("OOM"); | ||
| 690 | } | ||
| 691 | |||
| 692 | /// This links against a system library, exclusively using pkg-config to find the library. | ||
| 693 | /// Prefer to use `linkSystemLibrary` instead. | ||
| 694 | pub fn linkSystemLibraryPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { | ||
| 695 | const b = self.step.owner; | ||
| 696 | self.link_objects.append(.{ | ||
| 697 | .system_lib = .{ | ||
| 698 | .name = b.dupe(lib_name), | ||
| 699 | .needed = false, | ||
| 700 | .weak = false, | ||
| 701 | .use_pkg_config = .force, | ||
| 702 | }, | ||
| 703 | }) catch @panic("OOM"); | ||
| 704 | } | ||
| 705 | |||
| 706 | /// This links against a system library, exclusively using pkg-config to find the library. | ||
| 707 | /// Prefer to use `linkSystemLibraryNeeded` instead. | ||
| 708 | pub fn linkSystemLibraryNeededPkgConfigOnly(self: *CompileStep, lib_name: []const u8) void { | ||
| 709 | const b = self.step.owner; | ||
| 710 | self.link_objects.append(.{ | ||
| 711 | .system_lib = .{ | ||
| 712 | .name = b.dupe(lib_name), | ||
| 713 | .needed = true, | ||
| 714 | .weak = false, | ||
| 715 | .use_pkg_config = .force, | ||
| 716 | }, | ||
| 717 | }) catch @panic("OOM"); | ||
| 718 | } | ||
| 719 | |||
| 720 | /// Run pkg-config for the given library name and parse the output, returning the arguments | ||
| 721 | /// that should be passed to zig to link the given library. | ||
| 722 | fn runPkgConfig(self: *CompileStep, lib_name: []const u8) ![]const []const u8 { | ||
| 723 | const b = self.step.owner; | ||
| 724 | const pkg_name = match: { | ||
| 725 | // First we have to map the library name to pkg config name. Unfortunately, | ||
| 726 | // there are several examples where this is not straightforward: | ||
| 727 | // -lSDL2 -> pkg-config sdl2 | ||
| 728 | // -lgdk-3 -> pkg-config gdk-3.0 | ||
| 729 | // -latk-1.0 -> pkg-config atk | ||
| 730 | const pkgs = try getPkgConfigList(b); | ||
| 731 | |||
| 732 | // Exact match means instant winner. | ||
| 733 | for (pkgs) |pkg| { | ||
| 734 | if (mem.eql(u8, pkg.name, lib_name)) { | ||
| 735 | break :match pkg.name; | ||
| 736 | } | ||
| 737 | } | ||
| 738 | |||
| 739 | // Next we'll try ignoring case. | ||
| 740 | for (pkgs) |pkg| { | ||
| 741 | if (std.ascii.eqlIgnoreCase(pkg.name, lib_name)) { | ||
| 742 | break :match pkg.name; | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 746 | // Now try appending ".0". | ||
| 747 | for (pkgs) |pkg| { | ||
| 748 | if (std.ascii.indexOfIgnoreCase(pkg.name, lib_name)) |pos| { | ||
| 749 | if (pos != 0) continue; | ||
| 750 | if (mem.eql(u8, pkg.name[lib_name.len..], ".0")) { | ||
| 751 | break :match pkg.name; | ||
| 752 | } | ||
| 753 | } | ||
| 754 | } | ||
| 755 | |||
| 756 | // Trimming "-1.0". | ||
| 757 | if (mem.endsWith(u8, lib_name, "-1.0")) { | ||
| 758 | const trimmed_lib_name = lib_name[0 .. lib_name.len - "-1.0".len]; | ||
| 759 | for (pkgs) |pkg| { | ||
| 760 | if (std.ascii.eqlIgnoreCase(pkg.name, trimmed_lib_name)) { | ||
| 761 | break :match pkg.name; | ||
| 762 | } | ||
| 763 | } | ||
| 764 | } | ||
| 765 | |||
| 766 | return error.PackageNotFound; | ||
| 767 | }; | ||
| 768 | |||
| 769 | var code: u8 = undefined; | ||
| 770 | const stdout = if (b.execAllowFail(&[_][]const u8{ | ||
| 771 | "pkg-config", | ||
| 772 | pkg_name, | ||
| 773 | "--cflags", | ||
| 774 | "--libs", | ||
| 775 | }, &code, .Ignore)) |stdout| stdout else |err| switch (err) { | ||
| 776 | error.ProcessTerminated => return error.PkgConfigCrashed, | ||
| 777 | error.ExecNotSupported => return error.PkgConfigFailed, | ||
| 778 | error.ExitCodeFailure => return error.PkgConfigFailed, | ||
| 779 | error.FileNotFound => return error.PkgConfigNotInstalled, | ||
| 780 | else => return err, | ||
| 781 | }; | ||
| 782 | |||
| 783 | var zig_args = ArrayList([]const u8).init(b.allocator); | ||
| 784 | defer zig_args.deinit(); | ||
| 785 | |||
| 786 | var it = mem.tokenize(u8, stdout, " \r\n\t"); | ||
| 787 | while (it.next()) |tok| { | ||
| 788 | if (mem.eql(u8, tok, "-I")) { | ||
| 789 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 790 | try zig_args.appendSlice(&[_][]const u8{ "-I", dir }); | ||
| 791 | } else if (mem.startsWith(u8, tok, "-I")) { | ||
| 792 | try zig_args.append(tok); | ||
| 793 | } else if (mem.eql(u8, tok, "-L")) { | ||
| 794 | const dir = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 795 | try zig_args.appendSlice(&[_][]const u8{ "-L", dir }); | ||
| 796 | } else if (mem.startsWith(u8, tok, "-L")) { | ||
| 797 | try zig_args.append(tok); | ||
| 798 | } else if (mem.eql(u8, tok, "-l")) { | ||
| 799 | const lib = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 800 | try zig_args.appendSlice(&[_][]const u8{ "-l", lib }); | ||
| 801 | } else if (mem.startsWith(u8, tok, "-l")) { | ||
| 802 | try zig_args.append(tok); | ||
| 803 | } else if (mem.eql(u8, tok, "-D")) { | ||
| 804 | const macro = it.next() orelse return error.PkgConfigInvalidOutput; | ||
| 805 | try zig_args.appendSlice(&[_][]const u8{ "-D", macro }); | ||
| 806 | } else if (mem.startsWith(u8, tok, "-D")) { | ||
| 807 | try zig_args.append(tok); | ||
| 808 | } else if (b.debug_pkg_config) { | ||
| 809 | return self.step.fail("unknown pkg-config flag '{s}'", .{tok}); | ||
| 810 | } | ||
| 811 | } | ||
| 812 | |||
| 813 | return zig_args.toOwnedSlice(); | ||
| 814 | } | ||
| 815 | |||
| 816 | pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void { | ||
| 817 | self.linkSystemLibraryInner(name, .{}); | ||
| 818 | } | ||
| 819 | |||
| 820 | pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void { | ||
| 821 | self.linkSystemLibraryInner(name, .{ .needed = true }); | ||
| 822 | } | ||
| 823 | |||
| 824 | pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void { | ||
| 825 | self.linkSystemLibraryInner(name, .{ .weak = true }); | ||
| 826 | } | ||
| 827 | |||
| 828 | fn linkSystemLibraryInner(self: *CompileStep, name: []const u8, opts: struct { | ||
| 829 | needed: bool = false, | ||
| 830 | weak: bool = false, | ||
| 831 | }) void { | ||
| 832 | const b = self.step.owner; | ||
| 833 | if (isLibCLibrary(name)) { | ||
| 834 | self.linkLibC(); | ||
| 835 | return; | ||
| 836 | } | ||
| 837 | if (isLibCppLibrary(name)) { | ||
| 838 | self.linkLibCpp(); | ||
| 839 | return; | ||
| 840 | } | ||
| 841 | |||
| 842 | self.link_objects.append(.{ | ||
| 843 | .system_lib = .{ | ||
| 844 | .name = b.dupe(name), | ||
| 845 | .needed = opts.needed, | ||
| 846 | .weak = opts.weak, | ||
| 847 | .use_pkg_config = .yes, | ||
| 848 | }, | ||
| 849 | }) catch @panic("OOM"); | ||
| 850 | } | ||
| 851 | |||
| 852 | /// Handy when you have many C/C++ source files and want them all to have the same flags. | ||
| 853 | pub fn addCSourceFiles(self: *CompileStep, files: []const []const u8, flags: []const []const u8) void { | ||
| 854 | const b = self.step.owner; | ||
| 855 | const c_source_files = b.allocator.create(CSourceFiles) catch @panic("OOM"); | ||
| 856 | |||
| 857 | const files_copy = b.dupeStrings(files); | ||
| 858 | const flags_copy = b.dupeStrings(flags); | ||
| 859 | |||
| 860 | c_source_files.* = .{ | ||
| 861 | .files = files_copy, | ||
| 862 | .flags = flags_copy, | ||
| 863 | }; | ||
| 864 | self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM"); | ||
| 865 | } | ||
| 866 | |||
| 867 | pub fn addCSourceFile(self: *CompileStep, file: []const u8, flags: []const []const u8) void { | ||
| 868 | self.addCSourceFileSource(.{ | ||
| 869 | .args = flags, | ||
| 870 | .source = .{ .path = file }, | ||
| 871 | }); | ||
| 872 | } | ||
| 873 | |||
| 874 | pub fn addCSourceFileSource(self: *CompileStep, source: CSourceFile) void { | ||
| 875 | const b = self.step.owner; | ||
| 876 | const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM"); | ||
| 877 | c_source_file.* = source.dupe(b); | ||
| 878 | self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM"); | ||
| 879 | source.source.addStepDependencies(&self.step); | ||
| 880 | } | ||
| 881 | |||
| 882 | pub fn setVerboseLink(self: *CompileStep, value: bool) void { | ||
| 883 | self.verbose_link = value; | ||
| 884 | } | ||
| 885 | |||
| 886 | pub fn setVerboseCC(self: *CompileStep, value: bool) void { | ||
| 887 | self.verbose_cc = value; | ||
| 888 | } | ||
| 889 | |||
| 890 | pub fn overrideZigLibDir(self: *CompileStep, dir_path: []const u8) void { | ||
| 891 | const b = self.step.owner; | ||
| 892 | self.zig_lib_dir = b.dupePath(dir_path); | ||
| 893 | } | ||
| 894 | |||
| 895 | pub fn setMainPkgPath(self: *CompileStep, dir_path: []const u8) void { | ||
| 896 | const b = self.step.owner; | ||
| 897 | self.main_pkg_path = b.dupePath(dir_path); | ||
| 898 | } | ||
| 899 | |||
| 900 | pub fn setLibCFile(self: *CompileStep, libc_file: ?FileSource) void { | ||
| 901 | const b = self.step.owner; | ||
| 902 | self.libc_file = if (libc_file) |f| f.dupe(b) else null; | ||
| 903 | } | ||
| 904 | |||
| 905 | /// Returns the generated executable, library or object file. | ||
| 906 | /// To run an executable built with zig build, use `run`, or create an install step and invoke it. | ||
| 907 | pub fn getOutputSource(self: *CompileStep) FileSource { | ||
| 908 | return .{ .generated = &self.output_path_source }; | ||
| 909 | } | ||
| 910 | |||
| 911 | pub fn getOutputDirectorySource(self: *CompileStep) FileSource { | ||
| 912 | return .{ .generated = &self.output_dirname_source }; | ||
| 913 | } | ||
| 914 | |||
| 915 | /// Returns the generated import library. This function can only be called for libraries. | ||
| 916 | pub fn getOutputLibSource(self: *CompileStep) FileSource { | ||
| 917 | assert(self.kind == .lib); | ||
| 918 | return .{ .generated = &self.output_lib_path_source }; | ||
| 919 | } | ||
| 920 | |||
| 921 | /// Returns the generated header file. | ||
| 922 | /// This function can only be called for libraries or object files which have `emit_h` set. | ||
| 923 | pub fn getOutputHSource(self: *CompileStep) FileSource { | ||
| 924 | assert(self.kind != .exe and self.kind != .@"test"); | ||
| 925 | assert(self.emit_h); | ||
| 926 | return .{ .generated = &self.output_h_path_source }; | ||
| 927 | } | ||
| 928 | |||
| 929 | /// Returns the generated PDB file. This function can only be called for Windows and UEFI. | ||
| 930 | pub fn getOutputPdbSource(self: *CompileStep) FileSource { | ||
| 931 | // TODO: Is this right? Isn't PDB for *any* PE/COFF file? | ||
| 932 | assert(self.target.isWindows() or self.target.isUefi()); | ||
| 933 | return .{ .generated = &self.output_pdb_path_source }; | ||
| 934 | } | ||
| 935 | |||
| 936 | pub fn addAssemblyFile(self: *CompileStep, path: []const u8) void { | ||
| 937 | const b = self.step.owner; | ||
| 938 | self.link_objects.append(.{ | ||
| 939 | .assembly_file = .{ .path = b.dupe(path) }, | ||
| 940 | }) catch @panic("OOM"); | ||
| 941 | } | ||
| 942 | |||
| 943 | pub fn addAssemblyFileSource(self: *CompileStep, source: FileSource) void { | ||
| 944 | const b = self.step.owner; | ||
| 945 | const source_duped = source.dupe(b); | ||
| 946 | self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM"); | ||
| 947 | source_duped.addStepDependencies(&self.step); | ||
| 948 | } | ||
| 949 | |||
| 950 | pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void { | ||
| 951 | self.addObjectFileSource(.{ .path = source_file }); | ||
| 952 | } | ||
| 953 | |||
| 954 | pub fn addObjectFileSource(self: *CompileStep, source: FileSource) void { | ||
| 955 | const b = self.step.owner; | ||
| 956 | self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM"); | ||
| 957 | source.addStepDependencies(&self.step); | ||
| 958 | } | ||
| 959 | |||
| 960 | pub fn addObject(self: *CompileStep, obj: *CompileStep) void { | ||
| 961 | assert(obj.kind == .obj); | ||
| 962 | self.linkLibraryOrObject(obj); | ||
| 963 | } | ||
| 964 | |||
| 965 | pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath"); | ||
| 966 | pub const addIncludeDir = @compileError("deprecated; use addIncludePath"); | ||
| 967 | pub const addLibPath = @compileError("deprecated, use addLibraryPath"); | ||
| 968 | pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath"); | ||
| 969 | |||
| 970 | pub fn addSystemIncludePath(self: *CompileStep, path: []const u8) void { | ||
| 971 | const b = self.step.owner; | ||
| 972 | self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM"); | ||
| 973 | } | ||
| 974 | |||
| 975 | pub fn addIncludePath(self: *CompileStep, path: []const u8) void { | ||
| 976 | const b = self.step.owner; | ||
| 977 | self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM"); | ||
| 978 | } | ||
| 979 | |||
| 980 | pub fn addConfigHeader(self: *CompileStep, config_header: *ConfigHeaderStep) void { | ||
| 981 | self.step.dependOn(&config_header.step); | ||
| 982 | self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM"); | ||
| 983 | } | ||
| 984 | |||
| 985 | pub fn addLibraryPath(self: *CompileStep, path: []const u8) void { | ||
| 986 | const b = self.step.owner; | ||
| 987 | self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); | ||
| 988 | } | ||
| 989 | |||
| 990 | pub fn addLibraryPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 991 | self.lib_paths.append(directory_source) catch @panic("OOM"); | ||
| 992 | directory_source.addStepDependencies(&self.step); | ||
| 993 | } | ||
| 994 | |||
| 995 | pub fn addRPath(self: *CompileStep, path: []const u8) void { | ||
| 996 | const b = self.step.owner; | ||
| 997 | self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM"); | ||
| 998 | } | ||
| 999 | |||
| 1000 | pub fn addRPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 1001 | self.rpaths.append(directory_source) catch @panic("OOM"); | ||
| 1002 | directory_source.addStepDependencies(&self.step); | ||
| 1003 | } | ||
| 1004 | |||
| 1005 | pub fn addFrameworkPath(self: *CompileStep, dir_path: []const u8) void { | ||
| 1006 | const b = self.step.owner; | ||
| 1007 | self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM"); | ||
| 1008 | } | ||
| 1009 | |||
| 1010 | pub fn addFrameworkPathDirectorySource(self: *CompileStep, directory_source: FileSource) void { | ||
| 1011 | self.framework_dirs.append(directory_source) catch @panic("OOM"); | ||
| 1012 | directory_source.addStepDependencies(&self.step); | ||
| 1013 | } | ||
| 1014 | |||
| 1015 | /// Adds a module to be used with `@import` and exposing it in the current | ||
| 1016 | /// package's module table using `name`. | ||
| 1017 | pub fn addModule(cs: *CompileStep, name: []const u8, module: *Module) void { | ||
| 1018 | const b = cs.step.owner; | ||
| 1019 | cs.modules.put(b.dupe(name), module) catch @panic("OOM"); | ||
| 1020 | |||
| 1021 | var done = std.AutoHashMap(*Module, void).init(b.allocator); | ||
| 1022 | defer done.deinit(); | ||
| 1023 | cs.addRecursiveBuildDeps(module, &done) catch @panic("OOM"); | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | /// Adds a module to be used with `@import` without exposing it in the current | ||
| 1027 | /// package's module table. | ||
| 1028 | pub fn addAnonymousModule(cs: *CompileStep, name: []const u8, options: std.Build.CreateModuleOptions) void { | ||
| 1029 | const b = cs.step.owner; | ||
| 1030 | const module = b.createModule(options); | ||
| 1031 | return addModule(cs, name, module); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void { | ||
| 1035 | addModule(cs, module_name, options.createModule()); | ||
| 1036 | } | ||
| 1037 | |||
| 1038 | fn addRecursiveBuildDeps(cs: *CompileStep, module: *Module, done: *std.AutoHashMap(*Module, void)) !void { | ||
| 1039 | if (done.contains(module)) return; | ||
| 1040 | try done.put(module, {}); | ||
| 1041 | module.source_file.addStepDependencies(&cs.step); | ||
| 1042 | for (module.dependencies.values()) |dep| { | ||
| 1043 | try cs.addRecursiveBuildDeps(dep, done); | ||
| 1044 | } | ||
| 1045 | } | ||
| 1046 | |||
| 1047 | /// If Vcpkg was found on the system, it will be added to include and lib | ||
| 1048 | /// paths for the specified target. | ||
| 1049 | pub fn addVcpkgPaths(self: *CompileStep, linkage: CompileStep.Linkage) !void { | ||
| 1050 | const b = self.step.owner; | ||
| 1051 | // Ideally in the Unattempted case we would call the function recursively | ||
| 1052 | // after findVcpkgRoot and have only one switch statement, but the compiler | ||
| 1053 | // cannot resolve the error set. | ||
| 1054 | switch (b.vcpkg_root) { | ||
| 1055 | .unattempted => { | ||
| 1056 | b.vcpkg_root = if (try findVcpkgRoot(b.allocator)) |root| | ||
| 1057 | VcpkgRoot{ .found = root } | ||
| 1058 | else | ||
| 1059 | .not_found; | ||
| 1060 | }, | ||
| 1061 | .not_found => return error.VcpkgNotFound, | ||
| 1062 | .found => {}, | ||
| 1063 | } | ||
| 1064 | |||
| 1065 | switch (b.vcpkg_root) { | ||
| 1066 | .unattempted => unreachable, | ||
| 1067 | .not_found => return error.VcpkgNotFound, | ||
| 1068 | .found => |root| { | ||
| 1069 | const allocator = b.allocator; | ||
| 1070 | const triplet = try self.target.vcpkgTriplet(allocator, if (linkage == .static) .Static else .Dynamic); | ||
| 1071 | defer b.allocator.free(triplet); | ||
| 1072 | |||
| 1073 | const include_path = b.pathJoin(&.{ root, "installed", triplet, "include" }); | ||
| 1074 | errdefer allocator.free(include_path); | ||
| 1075 | try self.include_dirs.append(IncludeDir{ .raw_path = include_path }); | ||
| 1076 | |||
| 1077 | const lib_path = b.pathJoin(&.{ root, "installed", triplet, "lib" }); | ||
| 1078 | try self.lib_paths.append(.{ .path = lib_path }); | ||
| 1079 | |||
| 1080 | self.vcpkg_bin_path = b.pathJoin(&.{ root, "installed", triplet, "bin" }); | ||
| 1081 | }, | ||
| 1082 | } | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | pub fn setExecCmd(self: *CompileStep, args: []const ?[]const u8) void { | ||
| 1086 | const b = self.step.owner; | ||
| 1087 | assert(self.kind == .@"test"); | ||
| 1088 | const duped_args = b.allocator.alloc(?[]u8, args.len) catch @panic("OOM"); | ||
| 1089 | for (args, 0..) |arg, i| { | ||
| 1090 | duped_args[i] = if (arg) |a| b.dupe(a) else null; | ||
| 1091 | } | ||
| 1092 | self.exec_cmd_args = duped_args; | ||
| 1093 | } | ||
| 1094 | |||
| 1095 | fn linkLibraryOrObject(self: *CompileStep, other: *CompileStep) void { | ||
| 1096 | self.step.dependOn(&other.step); | ||
| 1097 | self.link_objects.append(.{ .other_step = other }) catch @panic("OOM"); | ||
| 1098 | self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM"); | ||
| 1099 | |||
| 1100 | for (other.installed_headers.items) |install_step| { | ||
| 1101 | self.step.dependOn(install_step); | ||
| 1102 | } | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | fn appendModuleArgs( | ||
| 1106 | cs: *CompileStep, | ||
| 1107 | zig_args: *ArrayList([]const u8), | ||
| 1108 | ) error{OutOfMemory}!void { | ||
| 1109 | const b = cs.step.owner; | ||
| 1110 | // First, traverse the whole dependency graph and give every module a unique name, ideally one | ||
| 1111 | // named after what it's called somewhere in the graph. It will help here to have both a mapping | ||
| 1112 | // from module to name and a set of all the currently-used names. | ||
| 1113 | var mod_names = std.AutoHashMap(*Module, []const u8).init(b.allocator); | ||
| 1114 | var names = std.StringHashMap(void).init(b.allocator); | ||
| 1115 | |||
| 1116 | var to_name = std.ArrayList(struct { | ||
| 1117 | name: []const u8, | ||
| 1118 | mod: *Module, | ||
| 1119 | }).init(b.allocator); | ||
| 1120 | { | ||
| 1121 | var it = cs.modules.iterator(); | ||
| 1122 | while (it.next()) |kv| { | ||
| 1123 | // While we're traversing the root dependencies, let's make sure that no module names | ||
| 1124 | // have colons in them, since the CLI forbids it. We handle this for transitive | ||
| 1125 | // dependencies further down. | ||
| 1126 | if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { | ||
| 1127 | @panic("Module names cannot contain colons"); | ||
| 1128 | } | ||
| 1129 | try to_name.append(.{ | ||
| 1130 | .name = kv.key_ptr.*, | ||
| 1131 | .mod = kv.value_ptr.*, | ||
| 1132 | }); | ||
| 1133 | } | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | while (to_name.popOrNull()) |dep| { | ||
| 1137 | if (mod_names.contains(dep.mod)) continue; | ||
| 1138 | |||
| 1139 | // We'll use this buffer to store the name we decide on | ||
| 1140 | var buf = try b.allocator.alloc(u8, dep.name.len + 32); | ||
| 1141 | // First, try just the exposed dependency name | ||
| 1142 | @memcpy(buf[0..dep.name.len], dep.name); | ||
| 1143 | var name = buf[0..dep.name.len]; | ||
| 1144 | var n: usize = 0; | ||
| 1145 | while (names.contains(name)) { | ||
| 1146 | // If that failed, append an incrementing number to the end | ||
| 1147 | name = std.fmt.bufPrint(buf, "{s}{}", .{ dep.name, n }) catch unreachable; | ||
| 1148 | n += 1; | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | try mod_names.put(dep.mod, name); | ||
| 1152 | try names.put(name, {}); | ||
| 1153 | |||
| 1154 | var it = dep.mod.dependencies.iterator(); | ||
| 1155 | while (it.next()) |kv| { | ||
| 1156 | // Same colon-in-name check as above, but for transitive dependencies. | ||
| 1157 | if (std.mem.indexOfScalar(u8, kv.key_ptr.*, ':') != null) { | ||
| 1158 | @panic("Module names cannot contain colons"); | ||
| 1159 | } | ||
| 1160 | try to_name.append(.{ | ||
| 1161 | .name = kv.key_ptr.*, | ||
| 1162 | .mod = kv.value_ptr.*, | ||
| 1163 | }); | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | |||
| 1167 | // Since the module names given to the CLI are based off of the exposed names, we already know | ||
| 1168 | // that none of the CLI names have colons in them, so there's no need to check that explicitly. | ||
| 1169 | |||
| 1170 | // Every module in the graph is now named; output their definitions | ||
| 1171 | { | ||
| 1172 | var it = mod_names.iterator(); | ||
| 1173 | while (it.next()) |kv| { | ||
| 1174 | const mod = kv.key_ptr.*; | ||
| 1175 | const name = kv.value_ptr.*; | ||
| 1176 | |||
| 1177 | const deps_str = try constructDepString(b.allocator, mod_names, mod.dependencies); | ||
| 1178 | const src = mod.builder.pathFromRoot(mod.source_file.getPath(mod.builder)); | ||
| 1179 | try zig_args.append("--mod"); | ||
| 1180 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "{s}:{s}:{s}", .{ name, deps_str, src })); | ||
| 1181 | } | ||
| 1182 | } | ||
| 1183 | |||
| 1184 | // Lastly, output the root dependencies | ||
| 1185 | const deps_str = try constructDepString(b.allocator, mod_names, cs.modules); | ||
| 1186 | if (deps_str.len > 0) { | ||
| 1187 | try zig_args.append("--deps"); | ||
| 1188 | try zig_args.append(deps_str); | ||
| 1189 | } | ||
| 1190 | } | ||
| 1191 | |||
| 1192 | fn constructDepString( | ||
| 1193 | allocator: std.mem.Allocator, | ||
| 1194 | mod_names: std.AutoHashMap(*Module, []const u8), | ||
| 1195 | deps: std.StringArrayHashMap(*Module), | ||
| 1196 | ) ![]const u8 { | ||
| 1197 | var deps_str = std.ArrayList(u8).init(allocator); | ||
| 1198 | var it = deps.iterator(); | ||
| 1199 | while (it.next()) |kv| { | ||
| 1200 | const expose = kv.key_ptr.*; | ||
| 1201 | const name = mod_names.get(kv.value_ptr.*).?; | ||
| 1202 | if (std.mem.eql(u8, expose, name)) { | ||
| 1203 | try deps_str.writer().print("{s},", .{name}); | ||
| 1204 | } else { | ||
| 1205 | try deps_str.writer().print("{s}={s},", .{ expose, name }); | ||
| 1206 | } | ||
| 1207 | } | ||
| 1208 | if (deps_str.items.len > 0) { | ||
| 1209 | return deps_str.items[0 .. deps_str.items.len - 1]; // omit trailing comma | ||
| 1210 | } else { | ||
| 1211 | return ""; | ||
| 1212 | } | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 1216 | const b = step.owner; | ||
| 1217 | const self = @fieldParentPtr(CompileStep, "step", step); | ||
| 1218 | |||
| 1219 | if (self.root_src == null and self.link_objects.items.len == 0) { | ||
| 1220 | return step.fail("the linker needs one or more objects to link", .{}); | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | var zig_args = ArrayList([]const u8).init(b.allocator); | ||
| 1224 | defer zig_args.deinit(); | ||
| 1225 | |||
| 1226 | try zig_args.append(b.zig_exe); | ||
| 1227 | |||
| 1228 | const cmd = switch (self.kind) { | ||
| 1229 | .lib => "build-lib", | ||
| 1230 | .exe => "build-exe", | ||
| 1231 | .obj => "build-obj", | ||
| 1232 | .@"test" => "test", | ||
| 1233 | }; | ||
| 1234 | try zig_args.append(cmd); | ||
| 1235 | |||
| 1236 | if (b.reference_trace) |some| { | ||
| 1237 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "-freference-trace={d}", .{some})); | ||
| 1238 | } | ||
| 1239 | |||
| 1240 | try addFlag(&zig_args, "LLVM", self.use_llvm); | ||
| 1241 | try addFlag(&zig_args, "LLD", self.use_lld); | ||
| 1242 | |||
| 1243 | if (self.target.ofmt) |ofmt| { | ||
| 1244 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "-ofmt={s}", .{@tagName(ofmt)})); | ||
| 1245 | } | ||
| 1246 | |||
| 1247 | if (self.entry_symbol_name) |entry| { | ||
| 1248 | try zig_args.append("--entry"); | ||
| 1249 | try zig_args.append(entry); | ||
| 1250 | } | ||
| 1251 | |||
| 1252 | { | ||
| 1253 | var it = self.force_undefined_symbols.keyIterator(); | ||
| 1254 | while (it.next()) |symbol_name| { | ||
| 1255 | try zig_args.append("--force_undefined"); | ||
| 1256 | try zig_args.append(symbol_name.*); | ||
| 1257 | } | ||
| 1258 | } | ||
| 1259 | |||
| 1260 | if (self.stack_size) |stack_size| { | ||
| 1261 | try zig_args.append("--stack"); | ||
| 1262 | try zig_args.append(try std.fmt.allocPrint(b.allocator, "{}", .{stack_size})); | ||
| 1263 | } | ||
| 1264 | |||
| 1265 | if (self.root_src) |root_src| try zig_args.append(root_src.getPath(b)); | ||
| 1266 | |||
| 1267 | // We will add link objects from transitive dependencies, but we want to keep | ||
| 1268 | // all link objects in the same order provided. | ||
| 1269 | // This array is used to keep self.link_objects immutable. | ||
| 1270 | var transitive_deps: TransitiveDeps = .{ | ||
| 1271 | .link_objects = ArrayList(LinkObject).init(b.allocator), | ||
| 1272 | .seen_system_libs = StringHashMap(void).init(b.allocator), | ||
| 1273 | .seen_steps = std.AutoHashMap(*const Step, void).init(b.allocator), | ||
| 1274 | .is_linking_libcpp = self.is_linking_libcpp, | ||
| 1275 | .is_linking_libc = self.is_linking_libc, | ||
| 1276 | .frameworks = &self.frameworks, | ||
| 1277 | }; | ||
| 1278 | |||
| 1279 | try transitive_deps.seen_steps.put(&self.step, {}); | ||
| 1280 | try transitive_deps.add(self.link_objects.items); | ||
| 1281 | |||
| 1282 | var prev_has_extra_flags = false; | ||
| 1283 | |||
| 1284 | for (transitive_deps.link_objects.items) |link_object| { | ||
| 1285 | switch (link_object) { | ||
| 1286 | .static_path => |static_path| try zig_args.append(static_path.getPath(b)), | ||
| 1287 | |||
| 1288 | .other_step => |other| switch (other.kind) { | ||
| 1289 | .exe => @panic("Cannot link with an executable build artifact"), | ||
| 1290 | .@"test" => @panic("Cannot link with a test"), | ||
| 1291 | .obj => { | ||
| 1292 | try zig_args.append(other.getOutputSource().getPath(b)); | ||
| 1293 | }, | ||
| 1294 | .lib => l: { | ||
| 1295 | if (self.isStaticLibrary() and other.isStaticLibrary()) { | ||
| 1296 | // Avoid putting a static library inside a static library. | ||
| 1297 | break :l; | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | const full_path_lib = other.getOutputLibSource().getPath(b); | ||
| 1301 | try zig_args.append(full_path_lib); | ||
| 1302 | |||
| 1303 | if (other.linkage == Linkage.dynamic and !self.target.isWindows()) { | ||
| 1304 | if (fs.path.dirname(full_path_lib)) |dirname| { | ||
| 1305 | try zig_args.append("-rpath"); | ||
| 1306 | try zig_args.append(dirname); | ||
| 1307 | } | ||
| 1308 | } | ||
| 1309 | }, | ||
| 1310 | }, | ||
| 1311 | |||
| 1312 | .system_lib => |system_lib| { | ||
| 1313 | const prefix: []const u8 = prefix: { | ||
| 1314 | if (system_lib.needed) break :prefix "-needed-l"; | ||
| 1315 | if (system_lib.weak) break :prefix "-weak-l"; | ||
| 1316 | break :prefix "-l"; | ||
| 1317 | }; | ||
| 1318 | switch (system_lib.use_pkg_config) { | ||
| 1319 | .no => try zig_args.append(b.fmt("{s}{s}", .{ prefix, system_lib.name })), | ||
| 1320 | .yes, .force => { | ||
| 1321 | if (self.runPkgConfig(system_lib.name)) |args| { | ||
| 1322 | try zig_args.appendSlice(args); | ||
| 1323 | } else |err| switch (err) { | ||
| 1324 | error.PkgConfigInvalidOutput, | ||
| 1325 | error.PkgConfigCrashed, | ||
| 1326 | error.PkgConfigFailed, | ||
| 1327 | error.PkgConfigNotInstalled, | ||
| 1328 | error.PackageNotFound, | ||
| 1329 | => switch (system_lib.use_pkg_config) { | ||
| 1330 | .yes => { | ||
| 1331 | // pkg-config failed, so fall back to linking the library | ||
| 1332 | // by name directly. | ||
| 1333 | try zig_args.append(b.fmt("{s}{s}", .{ | ||
| 1334 | prefix, | ||
| 1335 | system_lib.name, | ||
| 1336 | })); | ||
| 1337 | }, | ||
| 1338 | .force => { | ||
| 1339 | panic("pkg-config failed for library {s}", .{system_lib.name}); | ||
| 1340 | }, | ||
| 1341 | .no => unreachable, | ||
| 1342 | }, | ||
| 1343 | |||
| 1344 | else => |e| return e, | ||
| 1345 | } | ||
| 1346 | }, | ||
| 1347 | } | ||
| 1348 | }, | ||
| 1349 | |||
| 1350 | .assembly_file => |asm_file| { | ||
| 1351 | if (prev_has_extra_flags) { | ||
| 1352 | try zig_args.append("-extra-cflags"); | ||
| 1353 | try zig_args.append("--"); | ||
| 1354 | prev_has_extra_flags = false; | ||
| 1355 | } | ||
| 1356 | try zig_args.append(asm_file.getPath(b)); | ||
| 1357 | }, | ||
| 1358 | |||
| 1359 | .c_source_file => |c_source_file| { | ||
| 1360 | if (c_source_file.args.len == 0) { | ||
| 1361 | if (prev_has_extra_flags) { | ||
| 1362 | try zig_args.append("-cflags"); | ||
| 1363 | try zig_args.append("--"); | ||
| 1364 | prev_has_extra_flags = false; | ||
| 1365 | } | ||
| 1366 | } else { | ||
| 1367 | try zig_args.append("-cflags"); | ||
| 1368 | for (c_source_file.args) |arg| { | ||
| 1369 | try zig_args.append(arg); | ||
| 1370 | } | ||
| 1371 | try zig_args.append("--"); | ||
| 1372 | } | ||
| 1373 | try zig_args.append(c_source_file.source.getPath(b)); | ||
| 1374 | }, | ||
| 1375 | |||
| 1376 | .c_source_files => |c_source_files| { | ||
| 1377 | if (c_source_files.flags.len == 0) { | ||
| 1378 | if (prev_has_extra_flags) { | ||
| 1379 | try zig_args.append("-cflags"); | ||
| 1380 | try zig_args.append("--"); | ||
| 1381 | prev_has_extra_flags = false; | ||
| 1382 | } | ||
| 1383 | } else { | ||
| 1384 | try zig_args.append("-cflags"); | ||
| 1385 | for (c_source_files.flags) |flag| { | ||
| 1386 | try zig_args.append(flag); | ||
| 1387 | } | ||
| 1388 | try zig_args.append("--"); | ||
| 1389 | } | ||
| 1390 | for (c_source_files.files) |file| { | ||
| 1391 | try zig_args.append(b.pathFromRoot(file)); | ||
| 1392 | } | ||
| 1393 | }, | ||
| 1394 | } | ||
| 1395 | } | ||
| 1396 | |||
| 1397 | if (transitive_deps.is_linking_libcpp) { | ||
| 1398 | try zig_args.append("-lc++"); | ||
| 1399 | } | ||
| 1400 | |||
| 1401 | if (transitive_deps.is_linking_libc) { | ||
| 1402 | try zig_args.append("-lc"); | ||
| 1403 | } | ||
| 1404 | |||
| 1405 | if (self.image_base) |image_base| { | ||
| 1406 | try zig_args.append("--image-base"); | ||
| 1407 | try zig_args.append(b.fmt("0x{x}", .{image_base})); | ||
| 1408 | } | ||
| 1409 | |||
| 1410 | if (self.filter) |filter| { | ||
| 1411 | try zig_args.append("--test-filter"); | ||
| 1412 | try zig_args.append(filter); | ||
| 1413 | } | ||
| 1414 | |||
| 1415 | if (self.test_evented_io) { | ||
| 1416 | try zig_args.append("--test-evented-io"); | ||
| 1417 | } | ||
| 1418 | |||
| 1419 | if (self.test_runner) |test_runner| { | ||
| 1420 | try zig_args.append("--test-runner"); | ||
| 1421 | try zig_args.append(b.pathFromRoot(test_runner)); | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | for (b.debug_log_scopes) |log_scope| { | ||
| 1425 | try zig_args.append("--debug-log"); | ||
| 1426 | try zig_args.append(log_scope); | ||
| 1427 | } | ||
| 1428 | |||
| 1429 | if (b.debug_compile_errors) { | ||
| 1430 | try zig_args.append("--debug-compile-errors"); | ||
| 1431 | } | ||
| 1432 | |||
| 1433 | if (b.verbose_cimport) try zig_args.append("--verbose-cimport"); | ||
| 1434 | if (b.verbose_air) try zig_args.append("--verbose-air"); | ||
| 1435 | if (b.verbose_llvm_ir) |path| try zig_args.append(b.fmt("--verbose-llvm-ir={s}", .{path})); | ||
| 1436 | if (b.verbose_llvm_bc) |path| try zig_args.append(b.fmt("--verbose-llvm-bc={s}", .{path})); | ||
| 1437 | if (b.verbose_link or self.verbose_link) try zig_args.append("--verbose-link"); | ||
| 1438 | if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc"); | ||
| 1439 | if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features"); | ||
| 1440 | |||
| 1441 | if (self.emit_analysis.getArg(b, "emit-analysis")) |arg| try zig_args.append(arg); | ||
| 1442 | if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg); | ||
| 1443 | if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg); | ||
| 1444 | if (self.emit_docs.getArg(b, "emit-docs")) |arg| try zig_args.append(arg); | ||
| 1445 | if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg); | ||
| 1446 | if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg); | ||
| 1447 | if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg); | ||
| 1448 | |||
| 1449 | if (self.emit_h) try zig_args.append("-femit-h"); | ||
| 1450 | |||
| 1451 | try addFlag(&zig_args, "strip", self.strip); | ||
| 1452 | try addFlag(&zig_args, "unwind-tables", self.unwind_tables); | ||
| 1453 | |||
| 1454 | if (self.dwarf_format) |dwarf_format| { | ||
| 1455 | try zig_args.append(switch (dwarf_format) { | ||
| 1456 | .@"32" => "-gdwarf32", | ||
| 1457 | .@"64" => "-gdwarf64", | ||
| 1458 | }); | ||
| 1459 | } | ||
| 1460 | |||
| 1461 | switch (self.compress_debug_sections) { | ||
| 1462 | .none => {}, | ||
| 1463 | .zlib => try zig_args.append("--compress-debug-sections=zlib"), | ||
| 1464 | } | ||
| 1465 | |||
| 1466 | if (self.link_eh_frame_hdr) { | ||
| 1467 | try zig_args.append("--eh-frame-hdr"); | ||
| 1468 | } | ||
| 1469 | if (self.link_emit_relocs) { | ||
| 1470 | try zig_args.append("--emit-relocs"); | ||
| 1471 | } | ||
| 1472 | if (self.link_function_sections) { | ||
| 1473 | try zig_args.append("-ffunction-sections"); | ||
| 1474 | } | ||
| 1475 | if (self.link_gc_sections) |x| { | ||
| 1476 | try zig_args.append(if (x) "--gc-sections" else "--no-gc-sections"); | ||
| 1477 | } | ||
| 1478 | if (!self.linker_dynamicbase) { | ||
| 1479 | try zig_args.append("--no-dynamicbase"); | ||
| 1480 | } | ||
| 1481 | if (self.linker_allow_shlib_undefined) |x| { | ||
| 1482 | try zig_args.append(if (x) "-fallow-shlib-undefined" else "-fno-allow-shlib-undefined"); | ||
| 1483 | } | ||
| 1484 | if (self.link_z_notext) { | ||
| 1485 | try zig_args.append("-z"); | ||
| 1486 | try zig_args.append("notext"); | ||
| 1487 | } | ||
| 1488 | if (!self.link_z_relro) { | ||
| 1489 | try zig_args.append("-z"); | ||
| 1490 | try zig_args.append("norelro"); | ||
| 1491 | } | ||
| 1492 | if (self.link_z_lazy) { | ||
| 1493 | try zig_args.append("-z"); | ||
| 1494 | try zig_args.append("lazy"); | ||
| 1495 | } | ||
| 1496 | if (self.link_z_common_page_size) |size| { | ||
| 1497 | try zig_args.append("-z"); | ||
| 1498 | try zig_args.append(b.fmt("common-page-size={d}", .{size})); | ||
| 1499 | } | ||
| 1500 | if (self.link_z_max_page_size) |size| { | ||
| 1501 | try zig_args.append("-z"); | ||
| 1502 | try zig_args.append(b.fmt("max-page-size={d}", .{size})); | ||
| 1503 | } | ||
| 1504 | |||
| 1505 | if (self.libc_file) |libc_file| { | ||
| 1506 | try zig_args.append("--libc"); | ||
| 1507 | try zig_args.append(libc_file.getPath(b)); | ||
| 1508 | } else if (b.libc_file) |libc_file| { | ||
| 1509 | try zig_args.append("--libc"); | ||
| 1510 | try zig_args.append(libc_file); | ||
| 1511 | } | ||
| 1512 | |||
| 1513 | switch (self.optimize) { | ||
| 1514 | .Debug => {}, // Skip since it's the default. | ||
| 1515 | else => try zig_args.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), | ||
| 1516 | } | ||
| 1517 | |||
| 1518 | try zig_args.append("--cache-dir"); | ||
| 1519 | try zig_args.append(b.cache_root.path orelse "."); | ||
| 1520 | |||
| 1521 | try zig_args.append("--global-cache-dir"); | ||
| 1522 | try zig_args.append(b.global_cache_root.path orelse "."); | ||
| 1523 | |||
| 1524 | try zig_args.append("--name"); | ||
| 1525 | try zig_args.append(self.name); | ||
| 1526 | |||
| 1527 | if (self.linkage) |some| switch (some) { | ||
| 1528 | .dynamic => try zig_args.append("-dynamic"), | ||
| 1529 | .static => try zig_args.append("-static"), | ||
| 1530 | }; | ||
| 1531 | if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic) { | ||
| 1532 | if (self.version) |version| { | ||
| 1533 | try zig_args.append("--version"); | ||
| 1534 | try zig_args.append(b.fmt("{}", .{version})); | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | if (self.target.isDarwin()) { | ||
| 1538 | const install_name = self.install_name orelse b.fmt("@rpath/{s}{s}{s}", .{ | ||
| 1539 | self.target.libPrefix(), | ||
| 1540 | self.name, | ||
| 1541 | self.target.dynamicLibSuffix(), | ||
| 1542 | }); | ||
| 1543 | try zig_args.append("-install_name"); | ||
| 1544 | try zig_args.append(install_name); | ||
| 1545 | } | ||
| 1546 | } | ||
| 1547 | |||
| 1548 | if (self.entitlements) |entitlements| { | ||
| 1549 | try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); | ||
| 1550 | } | ||
| 1551 | if (self.pagezero_size) |pagezero_size| { | ||
| 1552 | const size = try std.fmt.allocPrint(b.allocator, "{x}", .{pagezero_size}); | ||
| 1553 | try zig_args.appendSlice(&[_][]const u8{ "-pagezero_size", size }); | ||
| 1554 | } | ||
| 1555 | if (self.search_strategy) |strat| switch (strat) { | ||
| 1556 | .paths_first => try zig_args.append("-search_paths_first"), | ||
| 1557 | .dylibs_first => try zig_args.append("-search_dylibs_first"), | ||
| 1558 | }; | ||
| 1559 | if (self.headerpad_size) |headerpad_size| { | ||
| 1560 | const size = try std.fmt.allocPrint(b.allocator, "{x}", .{headerpad_size}); | ||
| 1561 | try zig_args.appendSlice(&[_][]const u8{ "-headerpad", size }); | ||
| 1562 | } | ||
| 1563 | if (self.headerpad_max_install_names) { | ||
| 1564 | try zig_args.append("-headerpad_max_install_names"); | ||
| 1565 | } | ||
| 1566 | if (self.dead_strip_dylibs) { | ||
| 1567 | try zig_args.append("-dead_strip_dylibs"); | ||
| 1568 | } | ||
| 1569 | |||
| 1570 | try addFlag(&zig_args, "compiler-rt", self.bundle_compiler_rt); | ||
| 1571 | try addFlag(&zig_args, "single-threaded", self.single_threaded); | ||
| 1572 | if (self.disable_stack_probing) { | ||
| 1573 | try zig_args.append("-fno-stack-check"); | ||
| 1574 | } | ||
| 1575 | try addFlag(&zig_args, "stack-protector", self.stack_protector); | ||
| 1576 | if (self.red_zone) |red_zone| { | ||
| 1577 | if (red_zone) { | ||
| 1578 | try zig_args.append("-mred-zone"); | ||
| 1579 | } else { | ||
| 1580 | try zig_args.append("-mno-red-zone"); | ||
| 1581 | } | ||
| 1582 | } | ||
| 1583 | try addFlag(&zig_args, "omit-frame-pointer", self.omit_frame_pointer); | ||
| 1584 | try addFlag(&zig_args, "dll-export-fns", self.dll_export_fns); | ||
| 1585 | |||
| 1586 | if (self.disable_sanitize_c) { | ||
| 1587 | try zig_args.append("-fno-sanitize-c"); | ||
| 1588 | } | ||
| 1589 | if (self.sanitize_thread) { | ||
| 1590 | try zig_args.append("-fsanitize-thread"); | ||
| 1591 | } | ||
| 1592 | if (self.rdynamic) { | ||
| 1593 | try zig_args.append("-rdynamic"); | ||
| 1594 | } | ||
| 1595 | if (self.import_memory) { | ||
| 1596 | try zig_args.append("--import-memory"); | ||
| 1597 | } | ||
| 1598 | if (self.import_symbols) { | ||
| 1599 | try zig_args.append("--import-symbols"); | ||
| 1600 | } | ||
| 1601 | if (self.import_table) { | ||
| 1602 | try zig_args.append("--import-table"); | ||
| 1603 | } | ||
| 1604 | if (self.export_table) { | ||
| 1605 | try zig_args.append("--export-table"); | ||
| 1606 | } | ||
| 1607 | if (self.initial_memory) |initial_memory| { | ||
| 1608 | try zig_args.append(b.fmt("--initial-memory={d}", .{initial_memory})); | ||
| 1609 | } | ||
| 1610 | if (self.max_memory) |max_memory| { | ||
| 1611 | try zig_args.append(b.fmt("--max-memory={d}", .{max_memory})); | ||
| 1612 | } | ||
| 1613 | if (self.shared_memory) { | ||
| 1614 | try zig_args.append("--shared-memory"); | ||
| 1615 | } | ||
| 1616 | if (self.global_base) |global_base| { | ||
| 1617 | try zig_args.append(b.fmt("--global-base={d}", .{global_base})); | ||
| 1618 | } | ||
| 1619 | |||
| 1620 | if (self.code_model != .default) { | ||
| 1621 | try zig_args.append("-mcmodel"); | ||
| 1622 | try zig_args.append(@tagName(self.code_model)); | ||
| 1623 | } | ||
| 1624 | if (self.wasi_exec_model) |model| { | ||
| 1625 | try zig_args.append(b.fmt("-mexec-model={s}", .{@tagName(model)})); | ||
| 1626 | } | ||
| 1627 | for (self.export_symbol_names) |symbol_name| { | ||
| 1628 | try zig_args.append(b.fmt("--export={s}", .{symbol_name})); | ||
| 1629 | } | ||
| 1630 | |||
| 1631 | if (!self.target.isNative()) { | ||
| 1632 | try zig_args.appendSlice(&.{ | ||
| 1633 | "-target", try self.target.zigTriple(b.allocator), | ||
| 1634 | "-mcpu", try std.Build.serializeCpu(b.allocator, self.target.getCpu()), | ||
| 1635 | }); | ||
| 1636 | |||
| 1637 | if (self.target.dynamic_linker.get()) |dynamic_linker| { | ||
| 1638 | try zig_args.append("--dynamic-linker"); | ||
| 1639 | try zig_args.append(dynamic_linker); | ||
| 1640 | } | ||
| 1641 | } | ||
| 1642 | |||
| 1643 | if (self.linker_script) |linker_script| { | ||
| 1644 | try zig_args.append("--script"); | ||
| 1645 | try zig_args.append(linker_script.getPath(b)); | ||
| 1646 | } | ||
| 1647 | |||
| 1648 | if (self.version_script) |version_script| { | ||
| 1649 | try zig_args.append("--version-script"); | ||
| 1650 | try zig_args.append(b.pathFromRoot(version_script)); | ||
| 1651 | } | ||
| 1652 | |||
| 1653 | if (self.kind == .@"test") { | ||
| 1654 | if (self.exec_cmd_args) |exec_cmd_args| { | ||
| 1655 | for (exec_cmd_args) |cmd_arg| { | ||
| 1656 | if (cmd_arg) |arg| { | ||
| 1657 | try zig_args.append("--test-cmd"); | ||
| 1658 | try zig_args.append(arg); | ||
| 1659 | } else { | ||
| 1660 | try zig_args.append("--test-cmd-bin"); | ||
| 1661 | } | ||
| 1662 | } | ||
| 1663 | } | ||
| 1664 | } | ||
| 1665 | |||
| 1666 | try self.appendModuleArgs(&zig_args); | ||
| 1667 | |||
| 1668 | for (self.include_dirs.items) |include_dir| { | ||
| 1669 | switch (include_dir) { | ||
| 1670 | .raw_path => |include_path| { | ||
| 1671 | try zig_args.append("-I"); | ||
| 1672 | try zig_args.append(b.pathFromRoot(include_path)); | ||
| 1673 | }, | ||
| 1674 | .raw_path_system => |include_path| { | ||
| 1675 | if (b.sysroot != null) { | ||
| 1676 | try zig_args.append("-iwithsysroot"); | ||
| 1677 | } else { | ||
| 1678 | try zig_args.append("-isystem"); | ||
| 1679 | } | ||
| 1680 | |||
| 1681 | const resolved_include_path = b.pathFromRoot(include_path); | ||
| 1682 | |||
| 1683 | const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: { | ||
| 1684 | // We need to check for disk designator and strip it out from dir path so | ||
| 1685 | // that zig/clang can concat resolved_include_path with sysroot. | ||
| 1686 | const disk_designator = fs.path.diskDesignatorWindows(resolved_include_path); | ||
| 1687 | |||
| 1688 | if (mem.indexOf(u8, resolved_include_path, disk_designator)) |where| { | ||
| 1689 | break :blk resolved_include_path[where + disk_designator.len ..]; | ||
| 1690 | } | ||
| 1691 | |||
| 1692 | break :blk resolved_include_path; | ||
| 1693 | } else resolved_include_path; | ||
| 1694 | |||
| 1695 | try zig_args.append(common_include_path); | ||
| 1696 | }, | ||
| 1697 | .other_step => |other| { | ||
| 1698 | if (other.emit_h) { | ||
| 1699 | const h_path = other.getOutputHSource().getPath(b); | ||
| 1700 | try zig_args.append("-isystem"); | ||
| 1701 | try zig_args.append(fs.path.dirname(h_path).?); | ||
| 1702 | } | ||
| 1703 | if (other.installed_headers.items.len > 0) { | ||
| 1704 | try zig_args.append("-I"); | ||
| 1705 | try zig_args.append(b.pathJoin(&.{ | ||
| 1706 | other.step.owner.install_prefix, "include", | ||
| 1707 | })); | ||
| 1708 | } | ||
| 1709 | }, | ||
| 1710 | .config_header_step => |config_header| { | ||
| 1711 | const full_file_path = config_header.output_file.path.?; | ||
| 1712 | const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len]; | ||
| 1713 | try zig_args.appendSlice(&.{ "-I", header_dir_path }); | ||
| 1714 | }, | ||
| 1715 | } | ||
| 1716 | } | ||
| 1717 | |||
| 1718 | for (self.c_macros.items) |c_macro| { | ||
| 1719 | try zig_args.append("-D"); | ||
| 1720 | try zig_args.append(c_macro); | ||
| 1721 | } | ||
| 1722 | |||
| 1723 | try zig_args.ensureUnusedCapacity(2 * self.lib_paths.items.len); | ||
| 1724 | for (self.lib_paths.items) |lib_path| { | ||
| 1725 | zig_args.appendAssumeCapacity("-L"); | ||
| 1726 | zig_args.appendAssumeCapacity(lib_path.getPath2(b, step)); | ||
| 1727 | } | ||
| 1728 | |||
| 1729 | try zig_args.ensureUnusedCapacity(2 * self.rpaths.items.len); | ||
| 1730 | for (self.rpaths.items) |rpath| { | ||
| 1731 | zig_args.appendAssumeCapacity("-rpath"); | ||
| 1732 | |||
| 1733 | if (self.target_info.target.isDarwin()) switch (rpath) { | ||
| 1734 | .path => |path| { | ||
| 1735 | // On Darwin, we should not try to expand special runtime paths such as | ||
| 1736 | // * @executable_path | ||
| 1737 | // * @loader_path | ||
| 1738 | if (mem.startsWith(u8, path, "@executable_path") or | ||
| 1739 | mem.startsWith(u8, path, "@loader_path")) | ||
| 1740 | { | ||
| 1741 | zig_args.appendAssumeCapacity(path); | ||
| 1742 | continue; | ||
| 1743 | } | ||
| 1744 | }, | ||
| 1745 | .generated => {}, | ||
| 1746 | }; | ||
| 1747 | |||
| 1748 | zig_args.appendAssumeCapacity(rpath.getPath2(b, step)); | ||
| 1749 | } | ||
| 1750 | |||
| 1751 | for (self.framework_dirs.items) |directory_source| { | ||
| 1752 | if (b.sysroot != null) { | ||
| 1753 | try zig_args.append("-iframeworkwithsysroot"); | ||
| 1754 | } else { | ||
| 1755 | try zig_args.append("-iframework"); | ||
| 1756 | } | ||
| 1757 | try zig_args.append(directory_source.getPath2(b, step)); | ||
| 1758 | try zig_args.append("-F"); | ||
| 1759 | try zig_args.append(directory_source.getPath2(b, step)); | ||
| 1760 | } | ||
| 1761 | |||
| 1762 | { | ||
| 1763 | var it = self.frameworks.iterator(); | ||
| 1764 | while (it.next()) |entry| { | ||
| 1765 | const name = entry.key_ptr.*; | ||
| 1766 | const info = entry.value_ptr.*; | ||
| 1767 | if (info.needed) { | ||
| 1768 | try zig_args.append("-needed_framework"); | ||
| 1769 | } else if (info.weak) { | ||
| 1770 | try zig_args.append("-weak_framework"); | ||
| 1771 | } else { | ||
| 1772 | try zig_args.append("-framework"); | ||
| 1773 | } | ||
| 1774 | try zig_args.append(name); | ||
| 1775 | } | ||
| 1776 | } | ||
| 1777 | |||
| 1778 | if (b.sysroot) |sysroot| { | ||
| 1779 | try zig_args.appendSlice(&[_][]const u8{ "--sysroot", sysroot }); | ||
| 1780 | } | ||
| 1781 | |||
| 1782 | for (b.search_prefixes.items) |search_prefix| { | ||
| 1783 | var prefix_dir = fs.cwd().openDir(search_prefix, .{}) catch |err| { | ||
| 1784 | return step.fail("unable to open prefix directory '{s}': {s}", .{ | ||
| 1785 | search_prefix, @errorName(err), | ||
| 1786 | }); | ||
| 1787 | }; | ||
| 1788 | defer prefix_dir.close(); | ||
| 1789 | |||
| 1790 | // Avoid passing -L and -I flags for nonexistent directories. | ||
| 1791 | // This prevents a warning, that should probably be upgraded to an error in Zig's | ||
| 1792 | // CLI parsing code, when the linker sees an -L directory that does not exist. | ||
| 1793 | |||
| 1794 | if (prefix_dir.accessZ("lib", .{})) |_| { | ||
| 1795 | try zig_args.appendSlice(&.{ | ||
| 1796 | "-L", try fs.path.join(b.allocator, &.{ search_prefix, "lib" }), | ||
| 1797 | }); | ||
| 1798 | } else |err| switch (err) { | ||
| 1799 | error.FileNotFound => {}, | ||
| 1800 | else => |e| return step.fail("unable to access '{s}/lib' directory: {s}", .{ | ||
| 1801 | search_prefix, @errorName(e), | ||
| 1802 | }), | ||
| 1803 | } | ||
| 1804 | |||
| 1805 | if (prefix_dir.accessZ("include", .{})) |_| { | ||
| 1806 | try zig_args.appendSlice(&.{ | ||
| 1807 | "-I", try fs.path.join(b.allocator, &.{ search_prefix, "include" }), | ||
| 1808 | }); | ||
| 1809 | } else |err| switch (err) { | ||
| 1810 | error.FileNotFound => {}, | ||
| 1811 | else => |e| return step.fail("unable to access '{s}/include' directory: {s}", .{ | ||
| 1812 | search_prefix, @errorName(e), | ||
| 1813 | }), | ||
| 1814 | } | ||
| 1815 | } | ||
| 1816 | |||
| 1817 | try addFlag(&zig_args, "valgrind", self.valgrind_support); | ||
| 1818 | try addFlag(&zig_args, "each-lib-rpath", self.each_lib_rpath); | ||
| 1819 | try addFlag(&zig_args, "build-id", self.build_id); | ||
| 1820 | |||
| 1821 | if (self.zig_lib_dir) |dir| { | ||
| 1822 | try zig_args.append("--zig-lib-dir"); | ||
| 1823 | try zig_args.append(b.pathFromRoot(dir)); | ||
| 1824 | } else if (b.zig_lib_dir) |dir| { | ||
| 1825 | try zig_args.append("--zig-lib-dir"); | ||
| 1826 | try zig_args.append(dir); | ||
| 1827 | } | ||
| 1828 | |||
| 1829 | if (self.main_pkg_path) |dir| { | ||
| 1830 | try zig_args.append("--main-pkg-path"); | ||
| 1831 | try zig_args.append(b.pathFromRoot(dir)); | ||
| 1832 | } | ||
| 1833 | |||
| 1834 | try addFlag(&zig_args, "PIC", self.force_pic); | ||
| 1835 | try addFlag(&zig_args, "PIE", self.pie); | ||
| 1836 | try addFlag(&zig_args, "lto", self.want_lto); | ||
| 1837 | |||
| 1838 | if (self.subsystem) |subsystem| { | ||
| 1839 | try zig_args.append("--subsystem"); | ||
| 1840 | try zig_args.append(switch (subsystem) { | ||
| 1841 | .Console => "console", | ||
| 1842 | .Windows => "windows", | ||
| 1843 | .Posix => "posix", | ||
| 1844 | .Native => "native", | ||
| 1845 | .EfiApplication => "efi_application", | ||
| 1846 | .EfiBootServiceDriver => "efi_boot_service_driver", | ||
| 1847 | .EfiRom => "efi_rom", | ||
| 1848 | .EfiRuntimeDriver => "efi_runtime_driver", | ||
| 1849 | }); | ||
| 1850 | } | ||
| 1851 | |||
| 1852 | try zig_args.append("--listen=-"); | ||
| 1853 | |||
| 1854 | // Windows has an argument length limit of 32,766 characters, macOS 262,144 and Linux | ||
| 1855 | // 2,097,152. If our args exceed 30 KiB, we instead write them to a "response file" and | ||
| 1856 | // pass that to zig, e.g. via 'zig build-lib @args.rsp' | ||
| 1857 | // See @file syntax here: https://gcc.gnu.org/onlinedocs/gcc/Overall-Options.html | ||
| 1858 | var args_length: usize = 0; | ||
| 1859 | for (zig_args.items) |arg| { | ||
| 1860 | args_length += arg.len + 1; // +1 to account for null terminator | ||
| 1861 | } | ||
| 1862 | if (args_length >= 30 * 1024) { | ||
| 1863 | try b.cache_root.handle.makePath("args"); | ||
| 1864 | |||
| 1865 | const args_to_escape = zig_args.items[2..]; | ||
| 1866 | var escaped_args = try ArrayList([]const u8).initCapacity(b.allocator, args_to_escape.len); | ||
| 1867 | arg_blk: for (args_to_escape) |arg| { | ||
| 1868 | for (arg, 0..) |c, arg_idx| { | ||
| 1869 | if (c == '\\' or c == '"') { | ||
| 1870 | // Slow path for arguments that need to be escaped. We'll need to allocate and copy | ||
| 1871 | var escaped = try ArrayList(u8).initCapacity(b.allocator, arg.len + 1); | ||
| 1872 | const writer = escaped.writer(); | ||
| 1873 | try writer.writeAll(arg[0..arg_idx]); | ||
| 1874 | for (arg[arg_idx..]) |to_escape| { | ||
| 1875 | if (to_escape == '\\' or to_escape == '"') try writer.writeByte('\\'); | ||
| 1876 | try writer.writeByte(to_escape); | ||
| 1877 | } | ||
| 1878 | escaped_args.appendAssumeCapacity(escaped.items); | ||
| 1879 | continue :arg_blk; | ||
| 1880 | } | ||
| 1881 | } | ||
| 1882 | escaped_args.appendAssumeCapacity(arg); // no escaping needed so just use original argument | ||
| 1883 | } | ||
| 1884 | |||
| 1885 | // Write the args to zig-cache/args/<SHA256 hash of args> to avoid conflicts with | ||
| 1886 | // other zig build commands running in parallel. | ||
| 1887 | const partially_quoted = try std.mem.join(b.allocator, "\" \"", escaped_args.items); | ||
| 1888 | const args = try std.mem.concat(b.allocator, u8, &[_][]const u8{ "\"", partially_quoted, "\"" }); | ||
| 1889 | |||
| 1890 | var args_hash: [Sha256.digest_length]u8 = undefined; | ||
| 1891 | Sha256.hash(args, &args_hash, .{}); | ||
| 1892 | var args_hex_hash: [Sha256.digest_length * 2]u8 = undefined; | ||
| 1893 | _ = try std.fmt.bufPrint( | ||
| 1894 | &args_hex_hash, | ||
| 1895 | "{s}", | ||
| 1896 | .{std.fmt.fmtSliceHexLower(&args_hash)}, | ||
| 1897 | ); | ||
| 1898 | |||
| 1899 | const args_file = "args" ++ fs.path.sep_str ++ args_hex_hash; | ||
| 1900 | try b.cache_root.handle.writeFile(args_file, args); | ||
| 1901 | |||
| 1902 | const resolved_args_file = try mem.concat(b.allocator, u8, &.{ | ||
| 1903 | "@", | ||
| 1904 | try b.cache_root.join(b.allocator, &.{args_file}), | ||
| 1905 | }); | ||
| 1906 | |||
| 1907 | zig_args.shrinkRetainingCapacity(2); | ||
| 1908 | try zig_args.append(resolved_args_file); | ||
| 1909 | } | ||
| 1910 | |||
| 1911 | const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { | ||
| 1912 | error.NeedCompileErrorCheck => { | ||
| 1913 | assert(self.expect_errors.len != 0); | ||
| 1914 | try checkCompileErrors(self); | ||
| 1915 | return; | ||
| 1916 | }, | ||
| 1917 | else => |e| return e, | ||
| 1918 | }; | ||
| 1919 | const output_dir = fs.path.dirname(output_bin_path).?; | ||
| 1920 | |||
| 1921 | // Update generated files | ||
| 1922 | { | ||
| 1923 | self.output_dirname_source.path = output_dir; | ||
| 1924 | |||
| 1925 | self.output_path_source.path = b.pathJoin( | ||
| 1926 | &.{ output_dir, self.out_filename }, | ||
| 1927 | ); | ||
| 1928 | |||
| 1929 | if (self.kind == .lib) { | ||
| 1930 | self.output_lib_path_source.path = b.pathJoin( | ||
| 1931 | &.{ output_dir, self.out_lib_filename }, | ||
| 1932 | ); | ||
| 1933 | } | ||
| 1934 | |||
| 1935 | if (self.emit_h) { | ||
| 1936 | self.output_h_path_source.path = b.pathJoin( | ||
| 1937 | &.{ output_dir, self.out_h_filename }, | ||
| 1938 | ); | ||
| 1939 | } | ||
| 1940 | |||
| 1941 | if (self.target.isWindows() or self.target.isUefi()) { | ||
| 1942 | self.output_pdb_path_source.path = b.pathJoin( | ||
| 1943 | &.{ output_dir, self.out_pdb_filename }, | ||
| 1944 | ); | ||
| 1945 | } | ||
| 1946 | } | ||
| 1947 | |||
| 1948 | if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and | ||
| 1949 | self.version != null and self.target.wantSharedLibSymLinks()) | ||
| 1950 | { | ||
| 1951 | try doAtomicSymLinks( | ||
| 1952 | step, | ||
| 1953 | self.getOutputSource().getPath(b), | ||
| 1954 | self.major_only_filename.?, | ||
| 1955 | self.name_only_filename.?, | ||
| 1956 | ); | ||
| 1957 | } | ||
| 1958 | } | ||
| 1959 | |||
| 1960 | fn isLibCLibrary(name: []const u8) bool { | ||
| 1961 | const libc_libraries = [_][]const u8{ "c", "m", "dl", "rt", "pthread" }; | ||
| 1962 | for (libc_libraries) |libc_lib_name| { | ||
| 1963 | if (mem.eql(u8, name, libc_lib_name)) | ||
| 1964 | return true; | ||
| 1965 | } | ||
| 1966 | return false; | ||
| 1967 | } | ||
| 1968 | |||
| 1969 | fn isLibCppLibrary(name: []const u8) bool { | ||
| 1970 | const libcpp_libraries = [_][]const u8{ "c++", "stdc++" }; | ||
| 1971 | for (libcpp_libraries) |libcpp_lib_name| { | ||
| 1972 | if (mem.eql(u8, name, libcpp_lib_name)) | ||
| 1973 | return true; | ||
| 1974 | } | ||
| 1975 | return false; | ||
| 1976 | } | ||
| 1977 | |||
| 1978 | /// Returned slice must be freed by the caller. | ||
| 1979 | fn findVcpkgRoot(allocator: Allocator) !?[]const u8 { | ||
| 1980 | const appdata_path = try fs.getAppDataDir(allocator, "vcpkg"); | ||
| 1981 | defer allocator.free(appdata_path); | ||
| 1982 | |||
| 1983 | const path_file = try fs.path.join(allocator, &[_][]const u8{ appdata_path, "vcpkg.path.txt" }); | ||
| 1984 | defer allocator.free(path_file); | ||
| 1985 | |||
| 1986 | const file = fs.cwd().openFile(path_file, .{}) catch return null; | ||
| 1987 | defer file.close(); | ||
| 1988 | |||
| 1989 | const size = @intCast(usize, try file.getEndPos()); | ||
| 1990 | const vcpkg_path = try allocator.alloc(u8, size); | ||
| 1991 | const size_read = try file.read(vcpkg_path); | ||
| 1992 | std.debug.assert(size == size_read); | ||
| 1993 | |||
| 1994 | return vcpkg_path; | ||
| 1995 | } | ||
| 1996 | |||
| 1997 | pub fn doAtomicSymLinks( | ||
| 1998 | step: *Step, | ||
| 1999 | output_path: []const u8, | ||
| 2000 | filename_major_only: []const u8, | ||
| 2001 | filename_name_only: []const u8, | ||
| 2002 | ) !void { | ||
| 2003 | const arena = step.owner.allocator; | ||
| 2004 | const out_dir = fs.path.dirname(output_path) orelse "."; | ||
| 2005 | const out_basename = fs.path.basename(output_path); | ||
| 2006 | // sym link for libfoo.so.1 to libfoo.so.1.2.3 | ||
| 2007 | const major_only_path = try fs.path.join(arena, &.{ out_dir, filename_major_only }); | ||
| 2008 | fs.atomicSymLink(arena, out_basename, major_only_path) catch |err| { | ||
| 2009 | return step.fail("unable to symlink {s} -> {s}: {s}", .{ | ||
| 2010 | major_only_path, out_basename, @errorName(err), | ||
| 2011 | }); | ||
| 2012 | }; | ||
| 2013 | // sym link for libfoo.so to libfoo.so.1 | ||
| 2014 | const name_only_path = try fs.path.join(arena, &.{ out_dir, filename_name_only }); | ||
| 2015 | fs.atomicSymLink(arena, filename_major_only, name_only_path) catch |err| { | ||
| 2016 | return step.fail("Unable to symlink {s} -> {s}: {s}", .{ | ||
| 2017 | name_only_path, filename_major_only, @errorName(err), | ||
| 2018 | }); | ||
| 2019 | }; | ||
| 2020 | } | ||
| 2021 | |||
| 2022 | fn execPkgConfigList(self: *std.Build, out_code: *u8) (PkgConfigError || ExecError)![]const PkgConfigPkg { | ||
| 2023 | const stdout = try self.execAllowFail(&[_][]const u8{ "pkg-config", "--list-all" }, out_code, .Ignore); | ||
| 2024 | var list = ArrayList(PkgConfigPkg).init(self.allocator); | ||
| 2025 | errdefer list.deinit(); | ||
| 2026 | var line_it = mem.tokenize(u8, stdout, "\r\n"); | ||
| 2027 | while (line_it.next()) |line| { | ||
| 2028 | if (mem.trim(u8, line, " \t").len == 0) continue; | ||
| 2029 | var tok_it = mem.tokenize(u8, line, " \t"); | ||
| 2030 | try list.append(PkgConfigPkg{ | ||
| 2031 | .name = tok_it.next() orelse return error.PkgConfigInvalidOutput, | ||
| 2032 | .desc = tok_it.rest(), | ||
| 2033 | }); | ||
| 2034 | } | ||
| 2035 | return list.toOwnedSlice(); | ||
| 2036 | } | ||
| 2037 | |||
| 2038 | fn getPkgConfigList(self: *std.Build) ![]const PkgConfigPkg { | ||
| 2039 | if (self.pkg_config_pkg_list) |res| { | ||
| 2040 | return res; | ||
| 2041 | } | ||
| 2042 | var code: u8 = undefined; | ||
| 2043 | if (execPkgConfigList(self, &code)) |list| { | ||
| 2044 | self.pkg_config_pkg_list = list; | ||
| 2045 | return list; | ||
| 2046 | } else |err| { | ||
| 2047 | const result = switch (err) { | ||
| 2048 | error.ProcessTerminated => error.PkgConfigCrashed, | ||
| 2049 | error.ExecNotSupported => error.PkgConfigFailed, | ||
| 2050 | error.ExitCodeFailure => error.PkgConfigFailed, | ||
| 2051 | error.FileNotFound => error.PkgConfigNotInstalled, | ||
| 2052 | error.InvalidName => error.PkgConfigNotInstalled, | ||
| 2053 | error.PkgConfigInvalidOutput => error.PkgConfigInvalidOutput, | ||
| 2054 | else => return err, | ||
| 2055 | }; | ||
| 2056 | self.pkg_config_pkg_list = result; | ||
| 2057 | return result; | ||
| 2058 | } | ||
| 2059 | } | ||
| 2060 | |||
| 2061 | fn addFlag(args: *ArrayList([]const u8), comptime name: []const u8, opt: ?bool) !void { | ||
| 2062 | const cond = opt orelse return; | ||
| 2063 | try args.ensureUnusedCapacity(1); | ||
| 2064 | if (cond) { | ||
| 2065 | args.appendAssumeCapacity("-f" ++ name); | ||
| 2066 | } else { | ||
| 2067 | args.appendAssumeCapacity("-fno-" ++ name); | ||
| 2068 | } | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | const TransitiveDeps = struct { | ||
| 2072 | link_objects: ArrayList(LinkObject), | ||
| 2073 | seen_system_libs: StringHashMap(void), | ||
| 2074 | seen_steps: std.AutoHashMap(*const Step, void), | ||
| 2075 | is_linking_libcpp: bool, | ||
| 2076 | is_linking_libc: bool, | ||
| 2077 | frameworks: *StringHashMap(FrameworkLinkInfo), | ||
| 2078 | |||
| 2079 | fn add(td: *TransitiveDeps, link_objects: []const LinkObject) !void { | ||
| 2080 | try td.link_objects.ensureUnusedCapacity(link_objects.len); | ||
| 2081 | |||
| 2082 | for (link_objects) |link_object| { | ||
| 2083 | try td.link_objects.append(link_object); | ||
| 2084 | switch (link_object) { | ||
| 2085 | .other_step => |other| try addInner(td, other, other.isDynamicLibrary()), | ||
| 2086 | else => {}, | ||
| 2087 | } | ||
| 2088 | } | ||
| 2089 | } | ||
| 2090 | |||
| 2091 | fn addInner(td: *TransitiveDeps, other: *CompileStep, dyn: bool) !void { | ||
| 2092 | // Inherit dependency on libc and libc++ | ||
| 2093 | td.is_linking_libcpp = td.is_linking_libcpp or other.is_linking_libcpp; | ||
| 2094 | td.is_linking_libc = td.is_linking_libc or other.is_linking_libc; | ||
| 2095 | |||
| 2096 | // Inherit dependencies on darwin frameworks | ||
| 2097 | if (!dyn) { | ||
| 2098 | var it = other.frameworks.iterator(); | ||
| 2099 | while (it.next()) |framework| { | ||
| 2100 | try td.frameworks.put(framework.key_ptr.*, framework.value_ptr.*); | ||
| 2101 | } | ||
| 2102 | } | ||
| 2103 | |||
| 2104 | // Inherit dependencies on system libraries and static libraries. | ||
| 2105 | for (other.link_objects.items) |other_link_object| { | ||
| 2106 | switch (other_link_object) { | ||
| 2107 | .system_lib => |system_lib| { | ||
| 2108 | if ((try td.seen_system_libs.fetchPut(system_lib.name, {})) != null) | ||
| 2109 | continue; | ||
| 2110 | |||
| 2111 | if (dyn) | ||
| 2112 | continue; | ||
| 2113 | |||
| 2114 | try td.link_objects.append(other_link_object); | ||
| 2115 | }, | ||
| 2116 | .other_step => |inner_other| { | ||
| 2117 | if ((try td.seen_steps.fetchPut(&inner_other.step, {})) != null) | ||
| 2118 | continue; | ||
| 2119 | |||
| 2120 | if (!dyn) | ||
| 2121 | try td.link_objects.append(other_link_object); | ||
| 2122 | |||
| 2123 | try addInner(td, inner_other, dyn or inner_other.isDynamicLibrary()); | ||
| 2124 | }, | ||
| 2125 | else => continue, | ||
| 2126 | } | ||
| 2127 | } | ||
| 2128 | } | ||
| 2129 | }; | ||
| 2130 | |||
| 2131 | fn checkCompileErrors(self: *CompileStep) !void { | ||
| 2132 | // Clear this field so that it does not get printed by the build runner. | ||
| 2133 | const actual_eb = self.step.result_error_bundle; | ||
| 2134 | self.step.result_error_bundle = std.zig.ErrorBundle.empty; | ||
| 2135 | |||
| 2136 | const arena = self.step.owner.allocator; | ||
| 2137 | |||
| 2138 | var actual_stderr_list = std.ArrayList(u8).init(arena); | ||
| 2139 | try actual_eb.renderToWriter(.{ | ||
| 2140 | .ttyconf = .no_color, | ||
| 2141 | .include_reference_trace = false, | ||
| 2142 | .include_source_line = false, | ||
| 2143 | }, actual_stderr_list.writer()); | ||
| 2144 | const actual_stderr = try actual_stderr_list.toOwnedSlice(); | ||
| 2145 | |||
| 2146 | // Render the expected lines into a string that we can compare verbatim. | ||
| 2147 | var expected_generated = std.ArrayList(u8).init(arena); | ||
| 2148 | |||
| 2149 | var actual_line_it = mem.split(u8, actual_stderr, "\n"); | ||
| 2150 | for (self.expect_errors) |expect_line| { | ||
| 2151 | const actual_line = actual_line_it.next() orelse { | ||
| 2152 | try expected_generated.appendSlice(expect_line); | ||
| 2153 | try expected_generated.append('\n'); | ||
| 2154 | continue; | ||
| 2155 | }; | ||
| 2156 | if (mem.endsWith(u8, actual_line, expect_line)) { | ||
| 2157 | try expected_generated.appendSlice(actual_line); | ||
| 2158 | try expected_generated.append('\n'); | ||
| 2159 | continue; | ||
| 2160 | } | ||
| 2161 | if (mem.startsWith(u8, expect_line, ":?:?: ")) { | ||
| 2162 | if (mem.endsWith(u8, actual_line, expect_line[":?:?: ".len..])) { | ||
| 2163 | try expected_generated.appendSlice(actual_line); | ||
| 2164 | try expected_generated.append('\n'); | ||
| 2165 | continue; | ||
| 2166 | } | ||
| 2167 | } | ||
| 2168 | try expected_generated.appendSlice(expect_line); | ||
| 2169 | try expected_generated.append('\n'); | ||
| 2170 | } | ||
| 2171 | |||
| 2172 | if (mem.eql(u8, expected_generated.items, actual_stderr)) return; | ||
| 2173 | |||
| 2174 | // TODO merge this with the testing.expectEqualStrings logic, and also CheckFile | ||
| 2175 | return self.step.fail( | ||
| 2176 | \\ | ||
| 2177 | \\========= expected: ===================== | ||
| 2178 | \\{s} | ||
| 2179 | \\========= but found: ==================== | ||
| 2180 | \\{s} | ||
| 2181 | \\========================================= | ||
| 2182 | , .{ expected_generated.items, actual_stderr }); | ||
| 2183 | } | ||
lib/std/Build/Step/ConfigHeader.zig created+437| ... | @@ -0,0 +1,437 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const ConfigHeaderStep = @This(); | ||
| 3 | const Step = std.Build.Step; | ||
| 4 | |||
| 5 | pub const Style = union(enum) { | ||
| 6 | /// The configure format supported by autotools. It uses `#undef foo` to | ||
| 7 | /// mark lines that can be substituted with different values. | ||
| 8 | autoconf: std.Build.FileSource, | ||
| 9 | /// The configure format supported by CMake. It uses `@@FOO@@` and | ||
| 10 | /// `#cmakedefine` for template substitution. | ||
| 11 | cmake: std.Build.FileSource, | ||
| 12 | /// Instead of starting with an input file, start with nothing. | ||
| 13 | blank, | ||
| 14 | /// Start with nothing, like blank, and output a nasm .asm file. | ||
| 15 | nasm, | ||
| 16 | |||
| 17 | pub fn getFileSource(style: Style) ?std.Build.FileSource { | ||
| 18 | switch (style) { | ||
| 19 | .autoconf, .cmake => |s| return s, | ||
| 20 | .blank, .nasm => return null, | ||
| 21 | } | ||
| 22 | } | ||
| 23 | }; | ||
| 24 | |||
| 25 | pub const Value = union(enum) { | ||
| 26 | undef, | ||
| 27 | defined, | ||
| 28 | boolean: bool, | ||
| 29 | int: i64, | ||
| 30 | ident: []const u8, | ||
| 31 | string: []const u8, | ||
| 32 | }; | ||
| 33 | |||
| 34 | step: Step, | ||
| 35 | values: std.StringArrayHashMap(Value), | ||
| 36 | output_file: std.Build.GeneratedFile, | ||
| 37 | |||
| 38 | style: Style, | ||
| 39 | max_bytes: usize, | ||
| 40 | include_path: []const u8, | ||
| 41 | |||
| 42 | pub const base_id: Step.Id = .config_header; | ||
| 43 | |||
| 44 | pub const Options = struct { | ||
| 45 | style: Style = .blank, | ||
| 46 | max_bytes: usize = 2 * 1024 * 1024, | ||
| 47 | include_path: ?[]const u8 = null, | ||
| 48 | first_ret_addr: ?usize = null, | ||
| 49 | }; | ||
| 50 | |||
| 51 | pub fn create(owner: *std.Build, options: Options) *ConfigHeaderStep { | ||
| 52 | const self = owner.allocator.create(ConfigHeaderStep) catch @panic("OOM"); | ||
| 53 | |||
| 54 | var include_path: []const u8 = "config.h"; | ||
| 55 | |||
| 56 | if (options.style.getFileSource()) |s| switch (s) { | ||
| 57 | .path => |p| { | ||
| 58 | const basename = std.fs.path.basename(p); | ||
| 59 | if (std.mem.endsWith(u8, basename, ".h.in")) { | ||
| 60 | include_path = basename[0 .. basename.len - 3]; | ||
| 61 | } | ||
| 62 | }, | ||
| 63 | else => {}, | ||
| 64 | }; | ||
| 65 | |||
| 66 | if (options.include_path) |p| { | ||
| 67 | include_path = p; | ||
| 68 | } | ||
| 69 | |||
| 70 | const name = if (options.style.getFileSource()) |s| | ||
| 71 | owner.fmt("configure {s} header {s} to {s}", .{ | ||
| 72 | @tagName(options.style), s.getDisplayName(), include_path, | ||
| 73 | }) | ||
| 74 | else | ||
| 75 | owner.fmt("configure {s} header to {s}", .{ @tagName(options.style), include_path }); | ||
| 76 | |||
| 77 | self.* = .{ | ||
| 78 | .step = Step.init(.{ | ||
| 79 | .id = base_id, | ||
| 80 | .name = name, | ||
| 81 | .owner = owner, | ||
| 82 | .makeFn = make, | ||
| 83 | .first_ret_addr = options.first_ret_addr orelse @returnAddress(), | ||
| 84 | }), | ||
| 85 | .style = options.style, | ||
| 86 | .values = std.StringArrayHashMap(Value).init(owner.allocator), | ||
| 87 | |||
| 88 | .max_bytes = options.max_bytes, | ||
| 89 | .include_path = include_path, | ||
| 90 | .output_file = .{ .step = &self.step }, | ||
| 91 | }; | ||
| 92 | |||
| 93 | return self; | ||
| 94 | } | ||
| 95 | |||
| 96 | pub fn addValues(self: *ConfigHeaderStep, values: anytype) void { | ||
| 97 | return addValuesInner(self, values) catch @panic("OOM"); | ||
| 98 | } | ||
| 99 | |||
| 100 | pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource { | ||
| 101 | return .{ .generated = &self.output_file }; | ||
| 102 | } | ||
| 103 | |||
| 104 | fn addValuesInner(self: *ConfigHeaderStep, values: anytype) !void { | ||
| 105 | inline for (@typeInfo(@TypeOf(values)).Struct.fields) |field| { | ||
| 106 | try putValue(self, field.name, field.type, @field(values, field.name)); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | fn putValue(self: *ConfigHeaderStep, field_name: []const u8, comptime T: type, v: T) !void { | ||
| 111 | switch (@typeInfo(T)) { | ||
| 112 | .Null => { | ||
| 113 | try self.values.put(field_name, .undef); | ||
| 114 | }, | ||
| 115 | .Void => { | ||
| 116 | try self.values.put(field_name, .defined); | ||
| 117 | }, | ||
| 118 | .Bool => { | ||
| 119 | try self.values.put(field_name, .{ .boolean = v }); | ||
| 120 | }, | ||
| 121 | .Int => { | ||
| 122 | try self.values.put(field_name, .{ .int = v }); | ||
| 123 | }, | ||
| 124 | .ComptimeInt => { | ||
| 125 | try self.values.put(field_name, .{ .int = v }); | ||
| 126 | }, | ||
| 127 | .EnumLiteral => { | ||
| 128 | try self.values.put(field_name, .{ .ident = @tagName(v) }); | ||
| 129 | }, | ||
| 130 | .Optional => { | ||
| 131 | if (v) |x| { | ||
| 132 | return putValue(self, field_name, @TypeOf(x), x); | ||
| 133 | } else { | ||
| 134 | try self.values.put(field_name, .undef); | ||
| 135 | } | ||
| 136 | }, | ||
| 137 | .Pointer => |ptr| { | ||
| 138 | switch (@typeInfo(ptr.child)) { | ||
| 139 | .Array => |array| { | ||
| 140 | if (ptr.size == .One and array.child == u8) { | ||
| 141 | try self.values.put(field_name, .{ .string = v }); | ||
| 142 | return; | ||
| 143 | } | ||
| 144 | }, | ||
| 145 | .Int => { | ||
| 146 | if (ptr.size == .Slice and ptr.child == u8) { | ||
| 147 | try self.values.put(field_name, .{ .string = v }); | ||
| 148 | return; | ||
| 149 | } | ||
| 150 | }, | ||
| 151 | else => {}, | ||
| 152 | } | ||
| 153 | |||
| 154 | @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)); | ||
| 155 | }, | ||
| 156 | else => @compileError("unsupported ConfigHeaderStep value type: " ++ @typeName(T)), | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 161 | _ = prog_node; | ||
| 162 | const b = step.owner; | ||
| 163 | const self = @fieldParentPtr(ConfigHeaderStep, "step", step); | ||
| 164 | const gpa = b.allocator; | ||
| 165 | const arena = b.allocator; | ||
| 166 | |||
| 167 | var man = b.cache.obtain(); | ||
| 168 | defer man.deinit(); | ||
| 169 | |||
| 170 | // Random bytes to make ConfigHeaderStep unique. Refresh this with new | ||
| 171 | // random bytes when ConfigHeaderStep implementation is modified in a | ||
| 172 | // non-backwards-compatible way. | ||
| 173 | man.hash.add(@as(u32, 0xdef08d23)); | ||
| 174 | |||
| 175 | var output = std.ArrayList(u8).init(gpa); | ||
| 176 | defer output.deinit(); | ||
| 177 | |||
| 178 | const header_text = "This file was generated by ConfigHeaderStep using the Zig Build System."; | ||
| 179 | const c_generated_line = "/* " ++ header_text ++ " */\n"; | ||
| 180 | const asm_generated_line = "; " ++ header_text ++ "\n"; | ||
| 181 | |||
| 182 | switch (self.style) { | ||
| 183 | .autoconf => |file_source| { | ||
| 184 | try output.appendSlice(c_generated_line); | ||
| 185 | const src_path = file_source.getPath(b); | ||
| 186 | const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); | ||
| 187 | try render_autoconf(step, contents, &output, self.values, src_path); | ||
| 188 | }, | ||
| 189 | .cmake => |file_source| { | ||
| 190 | try output.appendSlice(c_generated_line); | ||
| 191 | const src_path = file_source.getPath(b); | ||
| 192 | const contents = try std.fs.cwd().readFileAlloc(arena, src_path, self.max_bytes); | ||
| 193 | try render_cmake(step, contents, &output, self.values, src_path); | ||
| 194 | }, | ||
| 195 | .blank => { | ||
| 196 | try output.appendSlice(c_generated_line); | ||
| 197 | try render_blank(&output, self.values, self.include_path); | ||
| 198 | }, | ||
| 199 | .nasm => { | ||
| 200 | try output.appendSlice(asm_generated_line); | ||
| 201 | try render_nasm(&output, self.values); | ||
| 202 | }, | ||
| 203 | } | ||
| 204 | |||
| 205 | man.hash.addBytes(output.items); | ||
| 206 | |||
| 207 | if (try step.cacheHit(&man)) { | ||
| 208 | const digest = man.final(); | ||
| 209 | self.output_file.path = try b.cache_root.join(arena, &.{ | ||
| 210 | "o", &digest, self.include_path, | ||
| 211 | }); | ||
| 212 | return; | ||
| 213 | } | ||
| 214 | |||
| 215 | const digest = man.final(); | ||
| 216 | |||
| 217 | // If output_path has directory parts, deal with them. Example: | ||
| 218 | // output_dir is zig-cache/o/HASH | ||
| 219 | // output_path is libavutil/avconfig.h | ||
| 220 | // We want to open directory zig-cache/o/HASH/libavutil/ | ||
| 221 | // but keep output_dir as zig-cache/o/HASH for -I include | ||
| 222 | const sub_path = try std.fs.path.join(arena, &.{ "o", &digest, self.include_path }); | ||
| 223 | const sub_path_dirname = std.fs.path.dirname(sub_path).?; | ||
| 224 | |||
| 225 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | ||
| 226 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 227 | b.cache_root, sub_path_dirname, @errorName(err), | ||
| 228 | }); | ||
| 229 | }; | ||
| 230 | |||
| 231 | b.cache_root.handle.writeFile(sub_path, output.items) catch |err| { | ||
| 232 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 233 | b.cache_root, sub_path, @errorName(err), | ||
| 234 | }); | ||
| 235 | }; | ||
| 236 | |||
| 237 | self.output_file.path = try b.cache_root.join(arena, &.{sub_path}); | ||
| 238 | try man.writeManifest(); | ||
| 239 | } | ||
| 240 | |||
| 241 | fn render_autoconf( | ||
| 242 | step: *Step, | ||
| 243 | contents: []const u8, | ||
| 244 | output: *std.ArrayList(u8), | ||
| 245 | values: std.StringArrayHashMap(Value), | ||
| 246 | src_path: []const u8, | ||
| 247 | ) !void { | ||
| 248 | var values_copy = try values.clone(); | ||
| 249 | defer values_copy.deinit(); | ||
| 250 | |||
| 251 | var any_errors = false; | ||
| 252 | var line_index: u32 = 0; | ||
| 253 | var line_it = std.mem.split(u8, contents, "\n"); | ||
| 254 | while (line_it.next()) |line| : (line_index += 1) { | ||
| 255 | if (!std.mem.startsWith(u8, line, "#")) { | ||
| 256 | try output.appendSlice(line); | ||
| 257 | try output.appendSlice("\n"); | ||
| 258 | continue; | ||
| 259 | } | ||
| 260 | var it = std.mem.tokenize(u8, line[1..], " \t\r"); | ||
| 261 | const undef = it.next().?; | ||
| 262 | if (!std.mem.eql(u8, undef, "undef")) { | ||
| 263 | try output.appendSlice(line); | ||
| 264 | try output.appendSlice("\n"); | ||
| 265 | continue; | ||
| 266 | } | ||
| 267 | const name = it.rest(); | ||
| 268 | const kv = values_copy.fetchSwapRemove(name) orelse { | ||
| 269 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | ||
| 270 | src_path, line_index + 1, name, | ||
| 271 | }); | ||
| 272 | any_errors = true; | ||
| 273 | continue; | ||
| 274 | }; | ||
| 275 | try renderValueC(output, name, kv.value); | ||
| 276 | } | ||
| 277 | |||
| 278 | for (values_copy.keys()) |name| { | ||
| 279 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); | ||
| 280 | any_errors = true; | ||
| 281 | } | ||
| 282 | |||
| 283 | if (any_errors) { | ||
| 284 | return error.MakeFailed; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | fn render_cmake( | ||
| 289 | step: *Step, | ||
| 290 | contents: []const u8, | ||
| 291 | output: *std.ArrayList(u8), | ||
| 292 | values: std.StringArrayHashMap(Value), | ||
| 293 | src_path: []const u8, | ||
| 294 | ) !void { | ||
| 295 | var values_copy = try values.clone(); | ||
| 296 | defer values_copy.deinit(); | ||
| 297 | |||
| 298 | var any_errors = false; | ||
| 299 | var line_index: u32 = 0; | ||
| 300 | var line_it = std.mem.split(u8, contents, "\n"); | ||
| 301 | while (line_it.next()) |line| : (line_index += 1) { | ||
| 302 | if (!std.mem.startsWith(u8, line, "#")) { | ||
| 303 | try output.appendSlice(line); | ||
| 304 | try output.appendSlice("\n"); | ||
| 305 | continue; | ||
| 306 | } | ||
| 307 | var it = std.mem.tokenize(u8, line[1..], " \t\r"); | ||
| 308 | const cmakedefine = it.next().?; | ||
| 309 | if (!std.mem.eql(u8, cmakedefine, "cmakedefine")) { | ||
| 310 | try output.appendSlice(line); | ||
| 311 | try output.appendSlice("\n"); | ||
| 312 | continue; | ||
| 313 | } | ||
| 314 | const name = it.next() orelse { | ||
| 315 | try step.addError("{s}:{d}: error: missing define name", .{ | ||
| 316 | src_path, line_index + 1, | ||
| 317 | }); | ||
| 318 | any_errors = true; | ||
| 319 | continue; | ||
| 320 | }; | ||
| 321 | const kv = values_copy.fetchSwapRemove(name) orelse { | ||
| 322 | try step.addError("{s}:{d}: error: unspecified config header value: '{s}'", .{ | ||
| 323 | src_path, line_index + 1, name, | ||
| 324 | }); | ||
| 325 | any_errors = true; | ||
| 326 | continue; | ||
| 327 | }; | ||
| 328 | try renderValueC(output, name, kv.value); | ||
| 329 | } | ||
| 330 | |||
| 331 | for (values_copy.keys()) |name| { | ||
| 332 | try step.addError("{s}: error: config header value unused: '{s}'", .{ src_path, name }); | ||
| 333 | any_errors = true; | ||
| 334 | } | ||
| 335 | |||
| 336 | if (any_errors) { | ||
| 337 | return error.HeaderConfigFailed; | ||
| 338 | } | ||
| 339 | } | ||
| 340 | |||
| 341 | fn render_blank( | ||
| 342 | output: *std.ArrayList(u8), | ||
| 343 | defines: std.StringArrayHashMap(Value), | ||
| 344 | include_path: []const u8, | ||
| 345 | ) !void { | ||
| 346 | const include_guard_name = try output.allocator.dupe(u8, include_path); | ||
| 347 | for (include_guard_name) |*byte| { | ||
| 348 | switch (byte.*) { | ||
| 349 | 'a'...'z' => byte.* = byte.* - 'a' + 'A', | ||
| 350 | 'A'...'Z', '0'...'9' => continue, | ||
| 351 | else => byte.* = '_', | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | try output.appendSlice("#ifndef "); | ||
| 356 | try output.appendSlice(include_guard_name); | ||
| 357 | try output.appendSlice("\n#define "); | ||
| 358 | try output.appendSlice(include_guard_name); | ||
| 359 | try output.appendSlice("\n"); | ||
| 360 | |||
| 361 | const values = defines.values(); | ||
| 362 | for (defines.keys(), 0..) |name, i| { | ||
| 363 | try renderValueC(output, name, values[i]); | ||
| 364 | } | ||
| 365 | |||
| 366 | try output.appendSlice("#endif /* "); | ||
| 367 | try output.appendSlice(include_guard_name); | ||
| 368 | try output.appendSlice(" */\n"); | ||
| 369 | } | ||
| 370 | |||
| 371 | fn render_nasm(output: *std.ArrayList(u8), defines: std.StringArrayHashMap(Value)) !void { | ||
| 372 | const values = defines.values(); | ||
| 373 | for (defines.keys(), 0..) |name, i| { | ||
| 374 | try renderValueNasm(output, name, values[i]); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | fn renderValueC(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { | ||
| 379 | switch (value) { | ||
| 380 | .undef => { | ||
| 381 | try output.appendSlice("/* #undef "); | ||
| 382 | try output.appendSlice(name); | ||
| 383 | try output.appendSlice(" */\n"); | ||
| 384 | }, | ||
| 385 | .defined => { | ||
| 386 | try output.appendSlice("#define "); | ||
| 387 | try output.appendSlice(name); | ||
| 388 | try output.appendSlice("\n"); | ||
| 389 | }, | ||
| 390 | .boolean => |b| { | ||
| 391 | try output.appendSlice("#define "); | ||
| 392 | try output.appendSlice(name); | ||
| 393 | try output.appendSlice(" "); | ||
| 394 | try output.appendSlice(if (b) "true\n" else "false\n"); | ||
| 395 | }, | ||
| 396 | .int => |i| { | ||
| 397 | try output.writer().print("#define {s} {d}\n", .{ name, i }); | ||
| 398 | }, | ||
| 399 | .ident => |ident| { | ||
| 400 | try output.writer().print("#define {s} {s}\n", .{ name, ident }); | ||
| 401 | }, | ||
| 402 | .string => |string| { | ||
| 403 | // TODO: use C-specific escaping instead of zig string literals | ||
| 404 | try output.writer().print("#define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); | ||
| 405 | }, | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | fn renderValueNasm(output: *std.ArrayList(u8), name: []const u8, value: Value) !void { | ||
| 410 | switch (value) { | ||
| 411 | .undef => { | ||
| 412 | try output.appendSlice("; %undef "); | ||
| 413 | try output.appendSlice(name); | ||
| 414 | try output.appendSlice("\n"); | ||
| 415 | }, | ||
| 416 | .defined => { | ||
| 417 | try output.appendSlice("%define "); | ||
| 418 | try output.appendSlice(name); | ||
| 419 | try output.appendSlice("\n"); | ||
| 420 | }, | ||
| 421 | .boolean => |b| { | ||
| 422 | try output.appendSlice("%define "); | ||
| 423 | try output.appendSlice(name); | ||
| 424 | try output.appendSlice(if (b) " 1\n" else " 0\n"); | ||
| 425 | }, | ||
| 426 | .int => |i| { | ||
| 427 | try output.writer().print("%define {s} {d}\n", .{ name, i }); | ||
| 428 | }, | ||
| 429 | .ident => |ident| { | ||
| 430 | try output.writer().print("%define {s} {s}\n", .{ name, ident }); | ||
| 431 | }, | ||
| 432 | .string => |string| { | ||
| 433 | // TODO: use nasm-specific escaping instead of zig string literals | ||
| 434 | try output.writer().print("%define {s} \"{}\"\n", .{ name, std.zig.fmtEscapes(string) }); | ||
| 435 | }, | ||
| 436 | } | ||
| 437 | } | ||
lib/std/Build/Step/Fmt.zig created+72| ... | @@ -0,0 +1,72 @@ | ||
| 1 | //! This step has two modes: | ||
| 2 | //! * Modify mode: directly modify source files, formatting them in place. | ||
| 3 | //! * Check mode: fail the step if a non-conforming file is found. | ||
| 4 | const std = @import("std"); | ||
| 5 | const Step = std.Build.Step; | ||
| 6 | const FmtStep = @This(); | ||
| 7 | |||
| 8 | step: Step, | ||
| 9 | paths: []const []const u8, | ||
| 10 | exclude_paths: []const []const u8, | ||
| 11 | check: bool, | ||
| 12 | |||
| 13 | pub const base_id = .fmt; | ||
| 14 | |||
| 15 | pub const Options = struct { | ||
| 16 | paths: []const []const u8 = &.{}, | ||
| 17 | exclude_paths: []const []const u8 = &.{}, | ||
| 18 | /// If true, fails the build step when any non-conforming files are encountered. | ||
| 19 | check: bool = false, | ||
| 20 | }; | ||
| 21 | |||
| 22 | pub fn create(owner: *std.Build, options: Options) *FmtStep { | ||
| 23 | const self = owner.allocator.create(FmtStep) catch @panic("OOM"); | ||
| 24 | const name = if (options.check) "zig fmt --check" else "zig fmt"; | ||
| 25 | self.* = .{ | ||
| 26 | .step = Step.init(.{ | ||
| 27 | .id = base_id, | ||
| 28 | .name = name, | ||
| 29 | .owner = owner, | ||
| 30 | .makeFn = make, | ||
| 31 | }), | ||
| 32 | .paths = options.paths, | ||
| 33 | .exclude_paths = options.exclude_paths, | ||
| 34 | .check = options.check, | ||
| 35 | }; | ||
| 36 | return self; | ||
| 37 | } | ||
| 38 | |||
| 39 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 40 | // zig fmt is fast enough that no progress is needed. | ||
| 41 | _ = prog_node; | ||
| 42 | |||
| 43 | // TODO: if check=false, this means we are modifying source files in place, which | ||
| 44 | // is an operation that could race against other operations also modifying source files | ||
| 45 | // in place. In this case, this step should obtain a write lock while making those | ||
| 46 | // modifications. | ||
| 47 | |||
| 48 | const b = step.owner; | ||
| 49 | const arena = b.allocator; | ||
| 50 | const self = @fieldParentPtr(FmtStep, "step", step); | ||
| 51 | |||
| 52 | var argv: std.ArrayListUnmanaged([]const u8) = .{}; | ||
| 53 | try argv.ensureUnusedCapacity(arena, 2 + 1 + self.paths.len + 2 * self.exclude_paths.len); | ||
| 54 | |||
| 55 | argv.appendAssumeCapacity(b.zig_exe); | ||
| 56 | argv.appendAssumeCapacity("fmt"); | ||
| 57 | |||
| 58 | if (self.check) { | ||
| 59 | argv.appendAssumeCapacity("--check"); | ||
| 60 | } | ||
| 61 | |||
| 62 | for (self.paths) |p| { | ||
| 63 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | ||
| 64 | } | ||
| 65 | |||
| 66 | for (self.exclude_paths) |p| { | ||
| 67 | argv.appendAssumeCapacity("--exclude"); | ||
| 68 | argv.appendAssumeCapacity(b.pathFromRoot(p)); | ||
| 69 | } | ||
| 70 | |||
| 71 | return step.evalChildProcess(argv.items); | ||
| 72 | } | ||
lib/std/Build/Step/InstallArtifact.zig created+130| ... | @@ -0,0 +1,130 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const CompileStep = std.Build.CompileStep; | ||
| 4 | const InstallDir = std.Build.InstallDir; | ||
| 5 | const InstallArtifactStep = @This(); | ||
| 6 | const fs = std.fs; | ||
| 7 | |||
| 8 | pub const base_id = .install_artifact; | ||
| 9 | |||
| 10 | step: Step, | ||
| 11 | artifact: *CompileStep, | ||
| 12 | dest_dir: InstallDir, | ||
| 13 | pdb_dir: ?InstallDir, | ||
| 14 | h_dir: ?InstallDir, | ||
| 15 | /// If non-null, adds additional path components relative to dest_dir, and | ||
| 16 | /// overrides the basename of the CompileStep. | ||
| 17 | dest_sub_path: ?[]const u8, | ||
| 18 | |||
| 19 | pub fn create(owner: *std.Build, artifact: *CompileStep) *InstallArtifactStep { | ||
| 20 | const self = owner.allocator.create(InstallArtifactStep) catch @panic("OOM"); | ||
| 21 | self.* = InstallArtifactStep{ | ||
| 22 | .step = Step.init(.{ | ||
| 23 | .id = base_id, | ||
| 24 | .name = owner.fmt("install {s}", .{artifact.name}), | ||
| 25 | .owner = owner, | ||
| 26 | .makeFn = make, | ||
| 27 | }), | ||
| 28 | .artifact = artifact, | ||
| 29 | .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) { | ||
| 30 | .obj => @panic("Cannot install a .obj build artifact."), | ||
| 31 | .exe, .@"test" => InstallDir{ .bin = {} }, | ||
| 32 | .lib => InstallDir{ .lib = {} }, | ||
| 33 | }, | ||
| 34 | .pdb_dir = if (artifact.producesPdbFile()) blk: { | ||
| 35 | if (artifact.kind == .exe or artifact.kind == .@"test") { | ||
| 36 | break :blk InstallDir{ .bin = {} }; | ||
| 37 | } else { | ||
| 38 | break :blk InstallDir{ .lib = {} }; | ||
| 39 | } | ||
| 40 | } else null, | ||
| 41 | .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null, | ||
| 42 | .dest_sub_path = null, | ||
| 43 | }; | ||
| 44 | self.step.dependOn(&artifact.step); | ||
| 45 | |||
| 46 | owner.pushInstalledFile(self.dest_dir, artifact.out_filename); | ||
| 47 | if (self.artifact.isDynamicLibrary()) { | ||
| 48 | if (artifact.major_only_filename) |name| { | ||
| 49 | owner.pushInstalledFile(.lib, name); | ||
| 50 | } | ||
| 51 | if (artifact.name_only_filename) |name| { | ||
| 52 | owner.pushInstalledFile(.lib, name); | ||
| 53 | } | ||
| 54 | if (self.artifact.target.isWindows()) { | ||
| 55 | owner.pushInstalledFile(.lib, artifact.out_lib_filename); | ||
| 56 | } | ||
| 57 | } | ||
| 58 | if (self.pdb_dir) |pdb_dir| { | ||
| 59 | owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename); | ||
| 60 | } | ||
| 61 | if (self.h_dir) |h_dir| { | ||
| 62 | owner.pushInstalledFile(h_dir, artifact.out_h_filename); | ||
| 63 | } | ||
| 64 | return self; | ||
| 65 | } | ||
| 66 | |||
| 67 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 68 | _ = prog_node; | ||
| 69 | const self = @fieldParentPtr(InstallArtifactStep, "step", step); | ||
| 70 | const src_builder = self.artifact.step.owner; | ||
| 71 | const dest_builder = step.owner; | ||
| 72 | |||
| 73 | const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename; | ||
| 74 | const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path); | ||
| 75 | const cwd = fs.cwd(); | ||
| 76 | |||
| 77 | var all_cached = true; | ||
| 78 | |||
| 79 | { | ||
| 80 | const full_src_path = self.artifact.getOutputSource().getPath(src_builder); | ||
| 81 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { | ||
| 82 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 83 | full_src_path, full_dest_path, @errorName(err), | ||
| 84 | }); | ||
| 85 | }; | ||
| 86 | all_cached = all_cached and p == .fresh; | ||
| 87 | } | ||
| 88 | |||
| 89 | if (self.artifact.isDynamicLibrary() and | ||
| 90 | self.artifact.version != null and | ||
| 91 | self.artifact.target.wantSharedLibSymLinks()) | ||
| 92 | { | ||
| 93 | try CompileStep.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?); | ||
| 94 | } | ||
| 95 | if (self.artifact.isDynamicLibrary() and | ||
| 96 | self.artifact.target.isWindows() and | ||
| 97 | self.artifact.emit_implib != .no_emit) | ||
| 98 | { | ||
| 99 | const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder); | ||
| 100 | const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename); | ||
| 101 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| { | ||
| 102 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 103 | full_src_path, full_implib_path, @errorName(err), | ||
| 104 | }); | ||
| 105 | }; | ||
| 106 | all_cached = all_cached and p == .fresh; | ||
| 107 | } | ||
| 108 | if (self.pdb_dir) |pdb_dir| { | ||
| 109 | const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder); | ||
| 110 | const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename); | ||
| 111 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| { | ||
| 112 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 113 | full_src_path, full_pdb_path, @errorName(err), | ||
| 114 | }); | ||
| 115 | }; | ||
| 116 | all_cached = all_cached and p == .fresh; | ||
| 117 | } | ||
| 118 | if (self.h_dir) |h_dir| { | ||
| 119 | const full_src_path = self.artifact.getOutputHSource().getPath(src_builder); | ||
| 120 | const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename); | ||
| 121 | const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| { | ||
| 122 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 123 | full_src_path, full_h_path, @errorName(err), | ||
| 124 | }); | ||
| 125 | }; | ||
| 126 | all_cached = all_cached and p == .fresh; | ||
| 127 | } | ||
| 128 | self.artifact.installed_path = full_dest_path; | ||
| 129 | step.result_cached = all_cached; | ||
| 130 | } | ||
lib/std/Build/Step/InstallDir.zig created+110| ... | @@ -0,0 +1,110 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const mem = std.mem; | ||
| 3 | const fs = std.fs; | ||
| 4 | const Step = std.Build.Step; | ||
| 5 | const InstallDir = std.Build.InstallDir; | ||
| 6 | const InstallDirStep = @This(); | ||
| 7 | |||
| 8 | step: Step, | ||
| 9 | options: Options, | ||
| 10 | /// This is used by the build system when a file being installed comes from one | ||
| 11 | /// package but is being installed by another. | ||
| 12 | dest_builder: *std.Build, | ||
| 13 | |||
| 14 | pub const base_id = .install_dir; | ||
| 15 | |||
| 16 | pub const Options = struct { | ||
| 17 | source_dir: []const u8, | ||
| 18 | install_dir: InstallDir, | ||
| 19 | install_subdir: []const u8, | ||
| 20 | /// File paths which end in any of these suffixes will be excluded | ||
| 21 | /// from being installed. | ||
| 22 | exclude_extensions: []const []const u8 = &.{}, | ||
| 23 | /// File paths which end in any of these suffixes will result in | ||
| 24 | /// empty files being installed. This is mainly intended for large | ||
| 25 | /// test.zig files in order to prevent needless installation bloat. | ||
| 26 | /// However if the files were not present at all, then | ||
| 27 | /// `@import("test.zig")` would be a compile error. | ||
| 28 | blank_extensions: []const []const u8 = &.{}, | ||
| 29 | |||
| 30 | fn dupe(self: Options, b: *std.Build) Options { | ||
| 31 | return .{ | ||
| 32 | .source_dir = b.dupe(self.source_dir), | ||
| 33 | .install_dir = self.install_dir.dupe(b), | ||
| 34 | .install_subdir = b.dupe(self.install_subdir), | ||
| 35 | .exclude_extensions = b.dupeStrings(self.exclude_extensions), | ||
| 36 | .blank_extensions = b.dupeStrings(self.blank_extensions), | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | |||
| 41 | pub fn init(owner: *std.Build, options: Options) InstallDirStep { | ||
| 42 | owner.pushInstalledFile(options.install_dir, options.install_subdir); | ||
| 43 | return .{ | ||
| 44 | .step = Step.init(.{ | ||
| 45 | .id = .install_dir, | ||
| 46 | .name = owner.fmt("install {s}/", .{options.source_dir}), | ||
| 47 | .owner = owner, | ||
| 48 | .makeFn = make, | ||
| 49 | }), | ||
| 50 | .options = options.dupe(owner), | ||
| 51 | .dest_builder = owner, | ||
| 52 | }; | ||
| 53 | } | ||
| 54 | |||
| 55 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 56 | _ = prog_node; | ||
| 57 | const self = @fieldParentPtr(InstallDirStep, "step", step); | ||
| 58 | const dest_builder = self.dest_builder; | ||
| 59 | const arena = dest_builder.allocator; | ||
| 60 | const dest_prefix = dest_builder.getInstallPath(self.options.install_dir, self.options.install_subdir); | ||
| 61 | const src_builder = self.step.owner; | ||
| 62 | var src_dir = src_builder.build_root.handle.openIterableDir(self.options.source_dir, .{}) catch |err| { | ||
| 63 | return step.fail("unable to open source directory '{}{s}': {s}", .{ | ||
| 64 | src_builder.build_root, self.options.source_dir, @errorName(err), | ||
| 65 | }); | ||
| 66 | }; | ||
| 67 | defer src_dir.close(); | ||
| 68 | var it = try src_dir.walk(arena); | ||
| 69 | var all_cached = true; | ||
| 70 | next_entry: while (try it.next()) |entry| { | ||
| 71 | for (self.options.exclude_extensions) |ext| { | ||
| 72 | if (mem.endsWith(u8, entry.path, ext)) { | ||
| 73 | continue :next_entry; | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | // relative to src build root | ||
| 78 | const src_sub_path = try fs.path.join(arena, &.{ self.options.source_dir, entry.path }); | ||
| 79 | const dest_path = try fs.path.join(arena, &.{ dest_prefix, entry.path }); | ||
| 80 | const cwd = fs.cwd(); | ||
| 81 | |||
| 82 | switch (entry.kind) { | ||
| 83 | .Directory => try cwd.makePath(dest_path), | ||
| 84 | .File => { | ||
| 85 | for (self.options.blank_extensions) |ext| { | ||
| 86 | if (mem.endsWith(u8, entry.path, ext)) { | ||
| 87 | try dest_builder.truncateFile(dest_path); | ||
| 88 | continue :next_entry; | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | const prev_status = fs.Dir.updateFile( | ||
| 93 | src_builder.build_root.handle, | ||
| 94 | src_sub_path, | ||
| 95 | cwd, | ||
| 96 | dest_path, | ||
| 97 | .{}, | ||
| 98 | ) catch |err| { | ||
| 99 | return step.fail("unable to update file from '{}{s}' to '{s}': {s}", .{ | ||
| 100 | src_builder.build_root, src_sub_path, dest_path, @errorName(err), | ||
| 101 | }); | ||
| 102 | }; | ||
| 103 | all_cached = all_cached and prev_status == .fresh; | ||
| 104 | }, | ||
| 105 | else => continue, | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | step.result_cached = all_cached; | ||
| 110 | } | ||
lib/std/Build/Step/InstallFile.zig created+57| ... | @@ -0,0 +1,57 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const FileSource = std.Build.FileSource; | ||
| 4 | const InstallDir = std.Build.InstallDir; | ||
| 5 | const InstallFileStep = @This(); | ||
| 6 | const assert = std.debug.assert; | ||
| 7 | |||
| 8 | pub const base_id = .install_file; | ||
| 9 | |||
| 10 | step: Step, | ||
| 11 | source: FileSource, | ||
| 12 | dir: InstallDir, | ||
| 13 | dest_rel_path: []const u8, | ||
| 14 | /// This is used by the build system when a file being installed comes from one | ||
| 15 | /// package but is being installed by another. | ||
| 16 | dest_builder: *std.Build, | ||
| 17 | |||
| 18 | pub fn create( | ||
| 19 | owner: *std.Build, | ||
| 20 | source: FileSource, | ||
| 21 | dir: InstallDir, | ||
| 22 | dest_rel_path: []const u8, | ||
| 23 | ) *InstallFileStep { | ||
| 24 | assert(dest_rel_path.len != 0); | ||
| 25 | owner.pushInstalledFile(dir, dest_rel_path); | ||
| 26 | const self = owner.allocator.create(InstallFileStep) catch @panic("OOM"); | ||
| 27 | self.* = .{ | ||
| 28 | .step = Step.init(.{ | ||
| 29 | .id = base_id, | ||
| 30 | .name = owner.fmt("install {s} to {s}", .{ source.getDisplayName(), dest_rel_path }), | ||
| 31 | .owner = owner, | ||
| 32 | .makeFn = make, | ||
| 33 | }), | ||
| 34 | .source = source.dupe(owner), | ||
| 35 | .dir = dir.dupe(owner), | ||
| 36 | .dest_rel_path = owner.dupePath(dest_rel_path), | ||
| 37 | .dest_builder = owner, | ||
| 38 | }; | ||
| 39 | source.addStepDependencies(&self.step); | ||
| 40 | return self; | ||
| 41 | } | ||
| 42 | |||
| 43 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 44 | _ = prog_node; | ||
| 45 | const src_builder = step.owner; | ||
| 46 | const self = @fieldParentPtr(InstallFileStep, "step", step); | ||
| 47 | const dest_builder = self.dest_builder; | ||
| 48 | const full_src_path = self.source.getPath2(src_builder, step); | ||
| 49 | const full_dest_path = dest_builder.getInstallPath(self.dir, self.dest_rel_path); | ||
| 50 | const cwd = std.fs.cwd(); | ||
| 51 | const prev = std.fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| { | ||
| 52 | return step.fail("unable to update file from '{s}' to '{s}': {s}", .{ | ||
| 53 | full_src_path, full_dest_path, @errorName(err), | ||
| 54 | }); | ||
| 55 | }; | ||
| 56 | step.result_cached = prev == .fresh; | ||
| 57 | } | ||
lib/std/Build/Step/ObjCopy.zig created+122| ... | @@ -0,0 +1,122 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const ObjCopyStep = @This(); | ||
| 3 | |||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const ArenaAllocator = std.heap.ArenaAllocator; | ||
| 6 | const ArrayListUnmanaged = std.ArrayListUnmanaged; | ||
| 7 | const File = std.fs.File; | ||
| 8 | const InstallDir = std.Build.InstallDir; | ||
| 9 | const CompileStep = std.Build.CompileStep; | ||
| 10 | const Step = std.Build.Step; | ||
| 11 | const elf = std.elf; | ||
| 12 | const fs = std.fs; | ||
| 13 | const io = std.io; | ||
| 14 | const sort = std.sort; | ||
| 15 | |||
| 16 | pub const base_id: Step.Id = .objcopy; | ||
| 17 | |||
| 18 | pub const RawFormat = enum { | ||
| 19 | bin, | ||
| 20 | hex, | ||
| 21 | }; | ||
| 22 | |||
| 23 | step: Step, | ||
| 24 | file_source: std.Build.FileSource, | ||
| 25 | basename: []const u8, | ||
| 26 | output_file: std.Build.GeneratedFile, | ||
| 27 | |||
| 28 | format: ?RawFormat, | ||
| 29 | only_section: ?[]const u8, | ||
| 30 | pad_to: ?u64, | ||
| 31 | |||
| 32 | pub const Options = struct { | ||
| 33 | basename: ?[]const u8 = null, | ||
| 34 | format: ?RawFormat = null, | ||
| 35 | only_section: ?[]const u8 = null, | ||
| 36 | pad_to: ?u64 = null, | ||
| 37 | }; | ||
| 38 | |||
| 39 | pub fn create( | ||
| 40 | owner: *std.Build, | ||
| 41 | file_source: std.Build.FileSource, | ||
| 42 | options: Options, | ||
| 43 | ) *ObjCopyStep { | ||
| 44 | const self = owner.allocator.create(ObjCopyStep) catch @panic("OOM"); | ||
| 45 | self.* = ObjCopyStep{ | ||
| 46 | .step = Step.init(.{ | ||
| 47 | .id = base_id, | ||
| 48 | .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}), | ||
| 49 | .owner = owner, | ||
| 50 | .makeFn = make, | ||
| 51 | }), | ||
| 52 | .file_source = file_source, | ||
| 53 | .basename = options.basename orelse file_source.getDisplayName(), | ||
| 54 | .output_file = std.Build.GeneratedFile{ .step = &self.step }, | ||
| 55 | |||
| 56 | .format = options.format, | ||
| 57 | .only_section = options.only_section, | ||
| 58 | .pad_to = options.pad_to, | ||
| 59 | }; | ||
| 60 | file_source.addStepDependencies(&self.step); | ||
| 61 | return self; | ||
| 62 | } | ||
| 63 | |||
| 64 | pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource { | ||
| 65 | return .{ .generated = &self.output_file }; | ||
| 66 | } | ||
| 67 | |||
| 68 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 69 | const b = step.owner; | ||
| 70 | const self = @fieldParentPtr(ObjCopyStep, "step", step); | ||
| 71 | |||
| 72 | var man = b.cache.obtain(); | ||
| 73 | defer man.deinit(); | ||
| 74 | |||
| 75 | // Random bytes to make ObjCopyStep unique. Refresh this with new random | ||
| 76 | // bytes when ObjCopyStep implementation is modified incompatibly. | ||
| 77 | man.hash.add(@as(u32, 0xe18b7baf)); | ||
| 78 | |||
| 79 | const full_src_path = self.file_source.getPath(b); | ||
| 80 | _ = try man.addFile(full_src_path, null); | ||
| 81 | man.hash.addOptionalBytes(self.only_section); | ||
| 82 | man.hash.addOptional(self.pad_to); | ||
| 83 | man.hash.addOptional(self.format); | ||
| 84 | |||
| 85 | if (try step.cacheHit(&man)) { | ||
| 86 | // Cache hit, skip subprocess execution. | ||
| 87 | const digest = man.final(); | ||
| 88 | self.output_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 89 | "o", &digest, self.basename, | ||
| 90 | }); | ||
| 91 | return; | ||
| 92 | } | ||
| 93 | |||
| 94 | const digest = man.final(); | ||
| 95 | const full_dest_path = try b.cache_root.join(b.allocator, &.{ "o", &digest, self.basename }); | ||
| 96 | const cache_path = "o" ++ fs.path.sep_str ++ digest; | ||
| 97 | b.cache_root.handle.makePath(cache_path) catch |err| { | ||
| 98 | return step.fail("unable to make path {s}: {s}", .{ cache_path, @errorName(err) }); | ||
| 99 | }; | ||
| 100 | |||
| 101 | var argv = std.ArrayList([]const u8).init(b.allocator); | ||
| 102 | try argv.appendSlice(&.{ b.zig_exe, "objcopy" }); | ||
| 103 | |||
| 104 | if (self.only_section) |only_section| { | ||
| 105 | try argv.appendSlice(&.{ "-j", only_section }); | ||
| 106 | } | ||
| 107 | if (self.pad_to) |pad_to| { | ||
| 108 | try argv.appendSlice(&.{ "--pad-to", b.fmt("{d}", .{pad_to}) }); | ||
| 109 | } | ||
| 110 | if (self.format) |format| switch (format) { | ||
| 111 | .bin => try argv.appendSlice(&.{ "-O", "binary" }), | ||
| 112 | .hex => try argv.appendSlice(&.{ "-O", "hex" }), | ||
| 113 | }; | ||
| 114 | |||
| 115 | try argv.appendSlice(&.{ full_src_path, full_dest_path }); | ||
| 116 | |||
| 117 | try argv.append("--listen=-"); | ||
| 118 | _ = try step.evalZigProcess(argv.items, prog_node); | ||
| 119 | |||
| 120 | self.output_file.path = full_dest_path; | ||
| 121 | try man.writeManifest(); | ||
| 122 | } | ||
lib/std/Build/Step/Options.zig created+421| ... | @@ -0,0 +1,421 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const fs = std.fs; | ||
| 4 | const Step = std.Build.Step; | ||
| 5 | const GeneratedFile = std.Build.GeneratedFile; | ||
| 6 | const CompileStep = std.Build.CompileStep; | ||
| 7 | const FileSource = std.Build.FileSource; | ||
| 8 | |||
| 9 | const OptionsStep = @This(); | ||
| 10 | |||
| 11 | pub const base_id = .options; | ||
| 12 | |||
| 13 | step: Step, | ||
| 14 | generated_file: GeneratedFile, | ||
| 15 | |||
| 16 | contents: std.ArrayList(u8), | ||
| 17 | artifact_args: std.ArrayList(OptionArtifactArg), | ||
| 18 | file_source_args: std.ArrayList(OptionFileSourceArg), | ||
| 19 | |||
| 20 | pub fn create(owner: *std.Build) *OptionsStep { | ||
| 21 | const self = owner.allocator.create(OptionsStep) catch @panic("OOM"); | ||
| 22 | self.* = .{ | ||
| 23 | .step = Step.init(.{ | ||
| 24 | .id = base_id, | ||
| 25 | .name = "options", | ||
| 26 | .owner = owner, | ||
| 27 | .makeFn = make, | ||
| 28 | }), | ||
| 29 | .generated_file = undefined, | ||
| 30 | .contents = std.ArrayList(u8).init(owner.allocator), | ||
| 31 | .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator), | ||
| 32 | .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator), | ||
| 33 | }; | ||
| 34 | self.generated_file = .{ .step = &self.step }; | ||
| 35 | |||
| 36 | return self; | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn addOption(self: *OptionsStep, comptime T: type, name: []const u8, value: T) void { | ||
| 40 | return addOptionFallible(self, T, name, value) catch @panic("unhandled error"); | ||
| 41 | } | ||
| 42 | |||
| 43 | fn addOptionFallible(self: *OptionsStep, comptime T: type, name: []const u8, value: T) !void { | ||
| 44 | const out = self.contents.writer(); | ||
| 45 | switch (T) { | ||
| 46 | []const []const u8 => { | ||
| 47 | try out.print("pub const {}: []const []const u8 = &[_][]const u8{{\n", .{std.zig.fmtId(name)}); | ||
| 48 | for (value) |slice| { | ||
| 49 | try out.print(" \"{}\",\n", .{std.zig.fmtEscapes(slice)}); | ||
| 50 | } | ||
| 51 | try out.writeAll("};\n"); | ||
| 52 | return; | ||
| 53 | }, | ||
| 54 | [:0]const u8 => { | ||
| 55 | try out.print("pub const {}: [:0]const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); | ||
| 56 | return; | ||
| 57 | }, | ||
| 58 | []const u8 => { | ||
| 59 | try out.print("pub const {}: []const u8 = \"{}\";\n", .{ std.zig.fmtId(name), std.zig.fmtEscapes(value) }); | ||
| 60 | return; | ||
| 61 | }, | ||
| 62 | ?[:0]const u8 => { | ||
| 63 | try out.print("pub const {}: ?[:0]const u8 = ", .{std.zig.fmtId(name)}); | ||
| 64 | if (value) |payload| { | ||
| 65 | try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); | ||
| 66 | } else { | ||
| 67 | try out.writeAll("null;\n"); | ||
| 68 | } | ||
| 69 | return; | ||
| 70 | }, | ||
| 71 | ?[]const u8 => { | ||
| 72 | try out.print("pub const {}: ?[]const u8 = ", .{std.zig.fmtId(name)}); | ||
| 73 | if (value) |payload| { | ||
| 74 | try out.print("\"{}\";\n", .{std.zig.fmtEscapes(payload)}); | ||
| 75 | } else { | ||
| 76 | try out.writeAll("null;\n"); | ||
| 77 | } | ||
| 78 | return; | ||
| 79 | }, | ||
| 80 | std.builtin.Version => { | ||
| 81 | try out.print( | ||
| 82 | \\pub const {}: @import("std").builtin.Version = .{{ | ||
| 83 | \\ .major = {d}, | ||
| 84 | \\ .minor = {d}, | ||
| 85 | \\ .patch = {d}, | ||
| 86 | \\}}; | ||
| 87 | \\ | ||
| 88 | , .{ | ||
| 89 | std.zig.fmtId(name), | ||
| 90 | |||
| 91 | value.major, | ||
| 92 | value.minor, | ||
| 93 | value.patch, | ||
| 94 | }); | ||
| 95 | return; | ||
| 96 | }, | ||
| 97 | std.SemanticVersion => { | ||
| 98 | try out.print( | ||
| 99 | \\pub const {}: @import("std").SemanticVersion = .{{ | ||
| 100 | \\ .major = {d}, | ||
| 101 | \\ .minor = {d}, | ||
| 102 | \\ .patch = {d}, | ||
| 103 | \\ | ||
| 104 | , .{ | ||
| 105 | std.zig.fmtId(name), | ||
| 106 | |||
| 107 | value.major, | ||
| 108 | value.minor, | ||
| 109 | value.patch, | ||
| 110 | }); | ||
| 111 | if (value.pre) |some| { | ||
| 112 | try out.print(" .pre = \"{}\",\n", .{std.zig.fmtEscapes(some)}); | ||
| 113 | } | ||
| 114 | if (value.build) |some| { | ||
| 115 | try out.print(" .build = \"{}\",\n", .{std.zig.fmtEscapes(some)}); | ||
| 116 | } | ||
| 117 | try out.writeAll("};\n"); | ||
| 118 | return; | ||
| 119 | }, | ||
| 120 | else => {}, | ||
| 121 | } | ||
| 122 | switch (@typeInfo(T)) { | ||
| 123 | .Enum => |enum_info| { | ||
| 124 | try out.print("pub const {} = enum {{\n", .{std.zig.fmtId(@typeName(T))}); | ||
| 125 | inline for (enum_info.fields) |field| { | ||
| 126 | try out.print(" {},\n", .{std.zig.fmtId(field.name)}); | ||
| 127 | } | ||
| 128 | try out.writeAll("};\n"); | ||
| 129 | try out.print("pub const {}: {s} = {s}.{s};\n", .{ | ||
| 130 | std.zig.fmtId(name), | ||
| 131 | std.zig.fmtId(@typeName(T)), | ||
| 132 | std.zig.fmtId(@typeName(T)), | ||
| 133 | std.zig.fmtId(@tagName(value)), | ||
| 134 | }); | ||
| 135 | return; | ||
| 136 | }, | ||
| 137 | else => {}, | ||
| 138 | } | ||
| 139 | try out.print("pub const {}: {s} = ", .{ std.zig.fmtId(name), @typeName(T) }); | ||
| 140 | try printLiteral(out, value, 0); | ||
| 141 | try out.writeAll(";\n"); | ||
| 142 | } | ||
| 143 | |||
| 144 | // TODO: non-recursive? | ||
| 145 | fn printLiteral(out: anytype, val: anytype, indent: u8) !void { | ||
| 146 | const T = @TypeOf(val); | ||
| 147 | switch (@typeInfo(T)) { | ||
| 148 | .Array => { | ||
| 149 | try out.print("{s} {{\n", .{@typeName(T)}); | ||
| 150 | for (val) |item| { | ||
| 151 | try out.writeByteNTimes(' ', indent + 4); | ||
| 152 | try printLiteral(out, item, indent + 4); | ||
| 153 | try out.writeAll(",\n"); | ||
| 154 | } | ||
| 155 | try out.writeByteNTimes(' ', indent); | ||
| 156 | try out.writeAll("}"); | ||
| 157 | }, | ||
| 158 | .Pointer => |p| { | ||
| 159 | if (p.size != .Slice) { | ||
| 160 | @compileError("Non-slice pointers are not yet supported in build options"); | ||
| 161 | } | ||
| 162 | try out.print("&[_]{s} {{\n", .{@typeName(p.child)}); | ||
| 163 | for (val) |item| { | ||
| 164 | try out.writeByteNTimes(' ', indent + 4); | ||
| 165 | try printLiteral(out, item, indent + 4); | ||
| 166 | try out.writeAll(",\n"); | ||
| 167 | } | ||
| 168 | try out.writeByteNTimes(' ', indent); | ||
| 169 | try out.writeAll("}"); | ||
| 170 | }, | ||
| 171 | .Optional => { | ||
| 172 | if (val) |inner| { | ||
| 173 | return printLiteral(out, inner, indent); | ||
| 174 | } else { | ||
| 175 | return out.writeAll("null"); | ||
| 176 | } | ||
| 177 | }, | ||
| 178 | .Void, | ||
| 179 | .Bool, | ||
| 180 | .Int, | ||
| 181 | .ComptimeInt, | ||
| 182 | .Float, | ||
| 183 | .Null, | ||
| 184 | => try out.print("{any}", .{val}), | ||
| 185 | else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})), | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | /// The value is the path in the cache dir. | ||
| 190 | /// Adds a dependency automatically. | ||
| 191 | pub fn addOptionFileSource( | ||
| 192 | self: *OptionsStep, | ||
| 193 | name: []const u8, | ||
| 194 | source: FileSource, | ||
| 195 | ) void { | ||
| 196 | self.file_source_args.append(.{ | ||
| 197 | .name = name, | ||
| 198 | .source = source.dupe(self.step.owner), | ||
| 199 | }) catch @panic("OOM"); | ||
| 200 | source.addStepDependencies(&self.step); | ||
| 201 | } | ||
| 202 | |||
| 203 | /// The value is the path in the cache dir. | ||
| 204 | /// Adds a dependency automatically. | ||
| 205 | pub fn addOptionArtifact(self: *OptionsStep, name: []const u8, artifact: *CompileStep) void { | ||
| 206 | self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM"); | ||
| 207 | self.step.dependOn(&artifact.step); | ||
| 208 | } | ||
| 209 | |||
| 210 | pub fn createModule(self: *OptionsStep) *std.Build.Module { | ||
| 211 | return self.step.owner.createModule(.{ | ||
| 212 | .source_file = self.getSource(), | ||
| 213 | .dependencies = &.{}, | ||
| 214 | }); | ||
| 215 | } | ||
| 216 | |||
| 217 | pub fn getSource(self: *OptionsStep) FileSource { | ||
| 218 | return .{ .generated = &self.generated_file }; | ||
| 219 | } | ||
| 220 | |||
| 221 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 222 | // This step completes so quickly that no progress is necessary. | ||
| 223 | _ = prog_node; | ||
| 224 | |||
| 225 | const b = step.owner; | ||
| 226 | const self = @fieldParentPtr(OptionsStep, "step", step); | ||
| 227 | |||
| 228 | for (self.artifact_args.items) |item| { | ||
| 229 | self.addOption( | ||
| 230 | []const u8, | ||
| 231 | item.name, | ||
| 232 | b.pathFromRoot(item.artifact.getOutputSource().getPath(b)), | ||
| 233 | ); | ||
| 234 | } | ||
| 235 | |||
| 236 | for (self.file_source_args.items) |item| { | ||
| 237 | self.addOption( | ||
| 238 | []const u8, | ||
| 239 | item.name, | ||
| 240 | item.source.getPath(b), | ||
| 241 | ); | ||
| 242 | } | ||
| 243 | |||
| 244 | const basename = "options.zig"; | ||
| 245 | |||
| 246 | // Hash contents to file name. | ||
| 247 | var hash = b.cache.hash; | ||
| 248 | // Random bytes to make unique. Refresh this with new random bytes when | ||
| 249 | // implementation is modified in a non-backwards-compatible way. | ||
| 250 | hash.add(@as(u32, 0x38845ef8)); | ||
| 251 | hash.addBytes(self.contents.items); | ||
| 252 | const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename; | ||
| 253 | |||
| 254 | self.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path}); | ||
| 255 | |||
| 256 | // Optimize for the hot path. Stat the file, and if it already exists, | ||
| 257 | // cache hit. | ||
| 258 | if (b.cache_root.handle.access(sub_path, .{})) |_| { | ||
| 259 | // This is the hot path, success. | ||
| 260 | step.result_cached = true; | ||
| 261 | return; | ||
| 262 | } else |outer_err| switch (outer_err) { | ||
| 263 | error.FileNotFound => { | ||
| 264 | const sub_dirname = fs.path.dirname(sub_path).?; | ||
| 265 | b.cache_root.handle.makePath(sub_dirname) catch |e| { | ||
| 266 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 267 | b.cache_root, sub_dirname, @errorName(e), | ||
| 268 | }); | ||
| 269 | }; | ||
| 270 | |||
| 271 | const rand_int = std.crypto.random.int(u64); | ||
| 272 | const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ | ||
| 273 | std.Build.hex64(rand_int) ++ fs.path.sep_str ++ | ||
| 274 | basename; | ||
| 275 | const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; | ||
| 276 | |||
| 277 | b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| { | ||
| 278 | return step.fail("unable to make temporary directory '{}{s}': {s}", .{ | ||
| 279 | b.cache_root, tmp_sub_path_dirname, @errorName(err), | ||
| 280 | }); | ||
| 281 | }; | ||
| 282 | |||
| 283 | b.cache_root.handle.writeFile(tmp_sub_path, self.contents.items) catch |err| { | ||
| 284 | return step.fail("unable to write options to '{}{s}': {s}", .{ | ||
| 285 | b.cache_root, tmp_sub_path, @errorName(err), | ||
| 286 | }); | ||
| 287 | }; | ||
| 288 | |||
| 289 | b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) { | ||
| 290 | error.PathAlreadyExists => { | ||
| 291 | // Other process beat us to it. Clean up the temp file. | ||
| 292 | b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| { | ||
| 293 | try step.addError("warning: unable to delete temp file '{}{s}': {s}", .{ | ||
| 294 | b.cache_root, tmp_sub_path, @errorName(e), | ||
| 295 | }); | ||
| 296 | }; | ||
| 297 | step.result_cached = true; | ||
| 298 | return; | ||
| 299 | }, | ||
| 300 | else => { | ||
| 301 | return step.fail("unable to rename options from '{}{s}' to '{}{s}': {s}", .{ | ||
| 302 | b.cache_root, tmp_sub_path, | ||
| 303 | b.cache_root, sub_path, | ||
| 304 | @errorName(err), | ||
| 305 | }); | ||
| 306 | }, | ||
| 307 | }; | ||
| 308 | }, | ||
| 309 | else => |e| return step.fail("unable to access options file '{}{s}': {s}", .{ | ||
| 310 | b.cache_root, sub_path, @errorName(e), | ||
| 311 | }), | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | const OptionArtifactArg = struct { | ||
| 316 | name: []const u8, | ||
| 317 | artifact: *CompileStep, | ||
| 318 | }; | ||
| 319 | |||
| 320 | const OptionFileSourceArg = struct { | ||
| 321 | name: []const u8, | ||
| 322 | source: FileSource, | ||
| 323 | }; | ||
| 324 | |||
| 325 | test "OptionsStep" { | ||
| 326 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | ||
| 327 | |||
| 328 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); | ||
| 329 | defer arena.deinit(); | ||
| 330 | |||
| 331 | const host = try std.zig.system.NativeTargetInfo.detect(.{}); | ||
| 332 | |||
| 333 | var cache: std.Build.Cache = .{ | ||
| 334 | .gpa = arena.allocator(), | ||
| 335 | .manifest_dir = std.fs.cwd(), | ||
| 336 | }; | ||
| 337 | |||
| 338 | var builder = try std.Build.create( | ||
| 339 | arena.allocator(), | ||
| 340 | "test", | ||
| 341 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 342 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 343 | .{ .path = "test", .handle = std.fs.cwd() }, | ||
| 344 | host, | ||
| 345 | &cache, | ||
| 346 | ); | ||
| 347 | defer builder.destroy(); | ||
| 348 | |||
| 349 | const options = builder.addOptions(); | ||
| 350 | |||
| 351 | // TODO this regressed at some point | ||
| 352 | //const KeywordEnum = enum { | ||
| 353 | // @"0.8.1", | ||
| 354 | //}; | ||
| 355 | |||
| 356 | const nested_array = [2][2]u16{ | ||
| 357 | [2]u16{ 300, 200 }, | ||
| 358 | [2]u16{ 300, 200 }, | ||
| 359 | }; | ||
| 360 | const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] }; | ||
| 361 | |||
| 362 | options.addOption(usize, "option1", 1); | ||
| 363 | options.addOption(?usize, "option2", null); | ||
| 364 | options.addOption(?usize, "option3", 3); | ||
| 365 | options.addOption(comptime_int, "option4", 4); | ||
| 366 | options.addOption([]const u8, "string", "zigisthebest"); | ||
| 367 | options.addOption(?[]const u8, "optional_string", null); | ||
| 368 | options.addOption([2][2]u16, "nested_array", nested_array); | ||
| 369 | options.addOption([]const []const u16, "nested_slice", nested_slice); | ||
| 370 | //options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1"); | ||
| 371 | options.addOption(std.builtin.Version, "version", try std.builtin.Version.parse("0.1.2")); | ||
| 372 | options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar")); | ||
| 373 | |||
| 374 | try std.testing.expectEqualStrings( | ||
| 375 | \\pub const option1: usize = 1; | ||
| 376 | \\pub const option2: ?usize = null; | ||
| 377 | \\pub const option3: ?usize = 3; | ||
| 378 | \\pub const option4: comptime_int = 4; | ||
| 379 | \\pub const string: []const u8 = "zigisthebest"; | ||
| 380 | \\pub const optional_string: ?[]const u8 = null; | ||
| 381 | \\pub const nested_array: [2][2]u16 = [2][2]u16 { | ||
| 382 | \\ [2]u16 { | ||
| 383 | \\ 300, | ||
| 384 | \\ 200, | ||
| 385 | \\ }, | ||
| 386 | \\ [2]u16 { | ||
| 387 | \\ 300, | ||
| 388 | \\ 200, | ||
| 389 | \\ }, | ||
| 390 | \\}; | ||
| 391 | \\pub const nested_slice: []const []const u16 = &[_][]const u16 { | ||
| 392 | \\ &[_]u16 { | ||
| 393 | \\ 300, | ||
| 394 | \\ 200, | ||
| 395 | \\ }, | ||
| 396 | \\ &[_]u16 { | ||
| 397 | \\ 300, | ||
| 398 | \\ 200, | ||
| 399 | \\ }, | ||
| 400 | \\}; | ||
| 401 | //\\pub const KeywordEnum = enum { | ||
| 402 | //\\ @"0.8.1", | ||
| 403 | //\\}; | ||
| 404 | //\\pub const keyword_enum: KeywordEnum = KeywordEnum.@"0.8.1"; | ||
| 405 | \\pub const version: @import("std").builtin.Version = .{ | ||
| 406 | \\ .major = 0, | ||
| 407 | \\ .minor = 1, | ||
| 408 | \\ .patch = 2, | ||
| 409 | \\}; | ||
| 410 | \\pub const semantic_version: @import("std").SemanticVersion = .{ | ||
| 411 | \\ .major = 0, | ||
| 412 | \\ .minor = 1, | ||
| 413 | \\ .patch = 2, | ||
| 414 | \\ .pre = "foo", | ||
| 415 | \\ .build = "bar", | ||
| 416 | \\}; | ||
| 417 | \\ | ||
| 418 | , options.contents.items); | ||
| 419 | |||
| 420 | _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(0), .zig); | ||
| 421 | } | ||
lib/std/Build/Step/RemoveDir.zig created+42| ... | @@ -0,0 +1,42 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const fs = std.fs; | ||
| 3 | const Step = std.Build.Step; | ||
| 4 | const RemoveDirStep = @This(); | ||
| 5 | |||
| 6 | pub const base_id = .remove_dir; | ||
| 7 | |||
| 8 | step: Step, | ||
| 9 | dir_path: []const u8, | ||
| 10 | |||
| 11 | pub fn init(owner: *std.Build, dir_path: []const u8) RemoveDirStep { | ||
| 12 | return RemoveDirStep{ | ||
| 13 | .step = Step.init(.{ | ||
| 14 | .id = .remove_dir, | ||
| 15 | .name = owner.fmt("RemoveDir {s}", .{dir_path}), | ||
| 16 | .owner = owner, | ||
| 17 | .makeFn = make, | ||
| 18 | }), | ||
| 19 | .dir_path = owner.dupePath(dir_path), | ||
| 20 | }; | ||
| 21 | } | ||
| 22 | |||
| 23 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 24 | // TODO update progress node while walking file system. | ||
| 25 | // Should the standard library support this use case?? | ||
| 26 | _ = prog_node; | ||
| 27 | |||
| 28 | const b = step.owner; | ||
| 29 | const self = @fieldParentPtr(RemoveDirStep, "step", step); | ||
| 30 | |||
| 31 | b.build_root.handle.deleteTree(self.dir_path) catch |err| { | ||
| 32 | if (b.build_root.path) |base| { | ||
| 33 | return step.fail("unable to recursively delete path '{s}/{s}': {s}", .{ | ||
| 34 | base, self.dir_path, @errorName(err), | ||
| 35 | }); | ||
| 36 | } else { | ||
| 37 | return step.fail("unable to recursively delete path '{s}': {s}", .{ | ||
| 38 | self.dir_path, @errorName(err), | ||
| 39 | }); | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | } | ||
lib/std/Build/Step/Run.zig created+1254| ... | @@ -0,0 +1,1254 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const Step = std.Build.Step; | ||
| 4 | const CompileStep = std.Build.CompileStep; | ||
| 5 | const WriteFileStep = std.Build.WriteFileStep; | ||
| 6 | const fs = std.fs; | ||
| 7 | const mem = std.mem; | ||
| 8 | const process = std.process; | ||
| 9 | const ArrayList = std.ArrayList; | ||
| 10 | const EnvMap = process.EnvMap; | ||
| 11 | const Allocator = mem.Allocator; | ||
| 12 | const ExecError = std.Build.ExecError; | ||
| 13 | const assert = std.debug.assert; | ||
| 14 | |||
| 15 | const RunStep = @This(); | ||
| 16 | |||
| 17 | pub const base_id: Step.Id = .run; | ||
| 18 | |||
| 19 | step: Step, | ||
| 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 | /// TODO change this to a Build.Cache.Directory to better integrate with | ||
| 26 | /// future child process cwd API. | ||
| 27 | cwd: ?[]const u8, | ||
| 28 | |||
| 29 | /// Override this field to modify the environment, or use setEnvironmentVariable | ||
| 30 | env_map: ?*EnvMap, | ||
| 31 | |||
| 32 | /// Configures whether the RunStep is considered to have side-effects, and also | ||
| 33 | /// whether the RunStep will inherit stdio streams, forwarding them to the | ||
| 34 | /// parent process, in which case will require a global lock to prevent other | ||
| 35 | /// steps from interfering with stdio while the subprocess associated with this | ||
| 36 | /// RunStep is running. | ||
| 37 | /// If the RunStep is determined to not have side-effects, then execution will | ||
| 38 | /// be skipped if all output files are up-to-date and input files are | ||
| 39 | /// unchanged. | ||
| 40 | stdio: StdIo = .infer_from_args, | ||
| 41 | /// This field must be `null` if stdio is `inherit`. | ||
| 42 | stdin: ?[]const u8 = null, | ||
| 43 | |||
| 44 | /// Additional file paths relative to build.zig that, when modified, indicate | ||
| 45 | /// that the RunStep should be re-executed. | ||
| 46 | /// If the RunStep is determined to have side-effects, this field is ignored | ||
| 47 | /// and the RunStep is always executed when it appears in the build graph. | ||
| 48 | extra_file_dependencies: []const []const u8 = &.{}, | ||
| 49 | |||
| 50 | /// After adding an output argument, this step will by default rename itself | ||
| 51 | /// for a better display name in the build summary. | ||
| 52 | /// This can be disabled by setting this to false. | ||
| 53 | rename_step_with_output_arg: bool = true, | ||
| 54 | |||
| 55 | /// If this is true, a RunStep which is configured to check the output of the | ||
| 56 | /// executed binary will not fail the build if the binary cannot be executed | ||
| 57 | /// due to being for a foreign binary to the host system which is running the | ||
| 58 | /// build graph. | ||
| 59 | /// Command-line arguments such as -fqemu and -fwasmtime may affect whether a | ||
| 60 | /// binary is detected as foreign, as well as system configuration such as | ||
| 61 | /// Rosetta (macOS) and binfmt_misc (Linux). | ||
| 62 | /// If this RunStep is considered to have side-effects, then this flag does | ||
| 63 | /// nothing. | ||
| 64 | skip_foreign_checks: bool = false, | ||
| 65 | |||
| 66 | /// If stderr or stdout exceeds this amount, the child process is killed and | ||
| 67 | /// the step fails. | ||
| 68 | max_stdio_size: usize = 10 * 1024 * 1024, | ||
| 69 | |||
| 70 | captured_stdout: ?*Output = null, | ||
| 71 | captured_stderr: ?*Output = null, | ||
| 72 | |||
| 73 | has_side_effects: bool = false, | ||
| 74 | |||
| 75 | pub const StdIo = union(enum) { | ||
| 76 | /// Whether the RunStep has side-effects will be determined by whether or not one | ||
| 77 | /// of the args is an output file (added with `addOutputFileArg`). | ||
| 78 | /// If the RunStep is determined to have side-effects, this is the same as `inherit`. | ||
| 79 | /// The step will fail if the subprocess crashes or returns a non-zero exit code. | ||
| 80 | infer_from_args, | ||
| 81 | /// Causes the RunStep to be considered to have side-effects, and therefore | ||
| 82 | /// always execute when it appears in the build graph. | ||
| 83 | /// It also means that this step will obtain a global lock to prevent other | ||
| 84 | /// steps from running in the meantime. | ||
| 85 | /// The step will fail if the subprocess crashes or returns a non-zero exit code. | ||
| 86 | inherit, | ||
| 87 | /// Causes the RunStep to be considered to *not* have side-effects. The | ||
| 88 | /// process will be re-executed if any of the input dependencies are | ||
| 89 | /// modified. The exit code and standard I/O streams will be checked for | ||
| 90 | /// certain conditions, and the step will succeed or fail based on these | ||
| 91 | /// conditions. | ||
| 92 | /// Note that an explicit check for exit code 0 needs to be added to this | ||
| 93 | /// list if such a check is desirable. | ||
| 94 | check: std.ArrayList(Check), | ||
| 95 | /// This RunStep is running a zig unit test binary and will communicate | ||
| 96 | /// extra metadata over the IPC protocol. | ||
| 97 | zig_test, | ||
| 98 | |||
| 99 | pub const Check = union(enum) { | ||
| 100 | expect_stderr_exact: []const u8, | ||
| 101 | expect_stderr_match: []const u8, | ||
| 102 | expect_stdout_exact: []const u8, | ||
| 103 | expect_stdout_match: []const u8, | ||
| 104 | expect_term: std.process.Child.Term, | ||
| 105 | }; | ||
| 106 | }; | ||
| 107 | |||
| 108 | pub const Arg = union(enum) { | ||
| 109 | artifact: *CompileStep, | ||
| 110 | file_source: std.Build.FileSource, | ||
| 111 | directory_source: std.Build.FileSource, | ||
| 112 | bytes: []u8, | ||
| 113 | output: *Output, | ||
| 114 | }; | ||
| 115 | |||
| 116 | pub const Output = struct { | ||
| 117 | generated_file: std.Build.GeneratedFile, | ||
| 118 | prefix: []const u8, | ||
| 119 | basename: []const u8, | ||
| 120 | }; | ||
| 121 | |||
| 122 | pub fn create(owner: *std.Build, name: []const u8) *RunStep { | ||
| 123 | const self = owner.allocator.create(RunStep) catch @panic("OOM"); | ||
| 124 | self.* = .{ | ||
| 125 | .step = Step.init(.{ | ||
| 126 | .id = base_id, | ||
| 127 | .name = name, | ||
| 128 | .owner = owner, | ||
| 129 | .makeFn = make, | ||
| 130 | }), | ||
| 131 | .argv = ArrayList(Arg).init(owner.allocator), | ||
| 132 | .cwd = null, | ||
| 133 | .env_map = null, | ||
| 134 | }; | ||
| 135 | return self; | ||
| 136 | } | ||
| 137 | |||
| 138 | pub fn setName(self: *RunStep, name: []const u8) void { | ||
| 139 | self.step.name = name; | ||
| 140 | self.rename_step_with_output_arg = false; | ||
| 141 | } | ||
| 142 | |||
| 143 | pub fn enableTestRunnerMode(rs: *RunStep) void { | ||
| 144 | rs.stdio = .zig_test; | ||
| 145 | rs.addArgs(&.{"--listen=-"}); | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn addArtifactArg(self: *RunStep, artifact: *CompileStep) void { | ||
| 149 | self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM"); | ||
| 150 | self.step.dependOn(&artifact.step); | ||
| 151 | } | ||
| 152 | |||
| 153 | /// This provides file path as a command line argument to the command being | ||
| 154 | /// run, and returns a FileSource which can be used as inputs to other APIs | ||
| 155 | /// throughout the build system. | ||
| 156 | pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource { | ||
| 157 | return addPrefixedOutputFileArg(rs, "", basename); | ||
| 158 | } | ||
| 159 | |||
| 160 | pub fn addPrefixedOutputFileArg( | ||
| 161 | rs: *RunStep, | ||
| 162 | prefix: []const u8, | ||
| 163 | basename: []const u8, | ||
| 164 | ) std.Build.FileSource { | ||
| 165 | const b = rs.step.owner; | ||
| 166 | |||
| 167 | const output = b.allocator.create(Output) catch @panic("OOM"); | ||
| 168 | output.* = .{ | ||
| 169 | .prefix = prefix, | ||
| 170 | .basename = basename, | ||
| 171 | .generated_file = .{ .step = &rs.step }, | ||
| 172 | }; | ||
| 173 | rs.argv.append(.{ .output = output }) catch @panic("OOM"); | ||
| 174 | |||
| 175 | if (rs.rename_step_with_output_arg) { | ||
| 176 | rs.setName(b.fmt("{s} ({s})", .{ rs.step.name, basename })); | ||
| 177 | } | ||
| 178 | |||
| 179 | return .{ .generated = &output.generated_file }; | ||
| 180 | } | ||
| 181 | |||
| 182 | pub fn addFileSourceArg(self: *RunStep, file_source: std.Build.FileSource) void { | ||
| 183 | self.argv.append(.{ | ||
| 184 | .file_source = file_source.dupe(self.step.owner), | ||
| 185 | }) catch @panic("OOM"); | ||
| 186 | file_source.addStepDependencies(&self.step); | ||
| 187 | } | ||
| 188 | |||
| 189 | pub fn addDirectorySourceArg(self: *RunStep, directory_source: std.Build.FileSource) void { | ||
| 190 | self.argv.append(.{ | ||
| 191 | .directory_source = directory_source.dupe(self.step.owner), | ||
| 192 | }) catch @panic("OOM"); | ||
| 193 | directory_source.addStepDependencies(&self.step); | ||
| 194 | } | ||
| 195 | |||
| 196 | pub fn addArg(self: *RunStep, arg: []const u8) void { | ||
| 197 | self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM"); | ||
| 198 | } | ||
| 199 | |||
| 200 | pub fn addArgs(self: *RunStep, args: []const []const u8) void { | ||
| 201 | for (args) |arg| { | ||
| 202 | self.addArg(arg); | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | pub fn clearEnvironment(self: *RunStep) void { | ||
| 207 | const b = self.step.owner; | ||
| 208 | const new_env_map = b.allocator.create(EnvMap) catch @panic("OOM"); | ||
| 209 | new_env_map.* = EnvMap.init(b.allocator); | ||
| 210 | self.env_map = new_env_map; | ||
| 211 | } | ||
| 212 | |||
| 213 | pub fn addPathDir(self: *RunStep, search_path: []const u8) void { | ||
| 214 | const b = self.step.owner; | ||
| 215 | const env_map = getEnvMapInternal(self); | ||
| 216 | |||
| 217 | const key = "PATH"; | ||
| 218 | var prev_path = env_map.get(key); | ||
| 219 | |||
| 220 | if (prev_path) |pp| { | ||
| 221 | const new_path = b.fmt("{s}" ++ [1]u8{fs.path.delimiter} ++ "{s}", .{ pp, search_path }); | ||
| 222 | env_map.put(key, new_path) catch @panic("OOM"); | ||
| 223 | } else { | ||
| 224 | env_map.put(key, b.dupePath(search_path)) catch @panic("OOM"); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | pub fn getEnvMap(self: *RunStep) *EnvMap { | ||
| 229 | return getEnvMapInternal(self); | ||
| 230 | } | ||
| 231 | |||
| 232 | fn getEnvMapInternal(self: *RunStep) *EnvMap { | ||
| 233 | const arena = self.step.owner.allocator; | ||
| 234 | return self.env_map orelse { | ||
| 235 | const env_map = arena.create(EnvMap) catch @panic("OOM"); | ||
| 236 | env_map.* = process.getEnvMap(arena) catch @panic("unhandled error"); | ||
| 237 | self.env_map = env_map; | ||
| 238 | return env_map; | ||
| 239 | }; | ||
| 240 | } | ||
| 241 | |||
| 242 | pub fn setEnvironmentVariable(self: *RunStep, key: []const u8, value: []const u8) void { | ||
| 243 | const b = self.step.owner; | ||
| 244 | const env_map = self.getEnvMap(); | ||
| 245 | env_map.put(b.dupe(key), b.dupe(value)) catch @panic("unhandled error"); | ||
| 246 | } | ||
| 247 | |||
| 248 | pub fn removeEnvironmentVariable(self: *RunStep, key: []const u8) void { | ||
| 249 | self.getEnvMap().remove(key); | ||
| 250 | } | ||
| 251 | |||
| 252 | /// Adds a check for exact stderr match. Does not add any other checks. | ||
| 253 | pub fn expectStdErrEqual(self: *RunStep, bytes: []const u8) void { | ||
| 254 | const new_check: StdIo.Check = .{ .expect_stderr_exact = self.step.owner.dupe(bytes) }; | ||
| 255 | self.addCheck(new_check); | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Adds a check for exact stdout match as well as a check for exit code 0, if | ||
| 259 | /// there is not already an expected termination check. | ||
| 260 | pub fn expectStdOutEqual(self: *RunStep, bytes: []const u8) void { | ||
| 261 | const new_check: StdIo.Check = .{ .expect_stdout_exact = self.step.owner.dupe(bytes) }; | ||
| 262 | self.addCheck(new_check); | ||
| 263 | if (!self.hasTermCheck()) { | ||
| 264 | self.expectExitCode(0); | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | pub fn expectExitCode(self: *RunStep, code: u8) void { | ||
| 269 | const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } }; | ||
| 270 | self.addCheck(new_check); | ||
| 271 | } | ||
| 272 | |||
| 273 | pub fn hasTermCheck(self: RunStep) bool { | ||
| 274 | for (self.stdio.check.items) |check| switch (check) { | ||
| 275 | .expect_term => return true, | ||
| 276 | else => continue, | ||
| 277 | }; | ||
| 278 | return false; | ||
| 279 | } | ||
| 280 | |||
| 281 | pub fn addCheck(self: *RunStep, new_check: StdIo.Check) void { | ||
| 282 | switch (self.stdio) { | ||
| 283 | .infer_from_args => { | ||
| 284 | self.stdio = .{ .check = std.ArrayList(StdIo.Check).init(self.step.owner.allocator) }; | ||
| 285 | self.stdio.check.append(new_check) catch @panic("OOM"); | ||
| 286 | }, | ||
| 287 | .check => |*checks| checks.append(new_check) catch @panic("OOM"), | ||
| 288 | else => @panic("illegal call to addCheck: conflicting helper method calls. Suggest to directly set stdio field of RunStep instead"), | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | pub fn captureStdErr(self: *RunStep) std.Build.FileSource { | ||
| 293 | assert(self.stdio != .inherit); | ||
| 294 | |||
| 295 | if (self.captured_stderr) |output| return .{ .generated = &output.generated_file }; | ||
| 296 | |||
| 297 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); | ||
| 298 | output.* = .{ | ||
| 299 | .prefix = "", | ||
| 300 | .basename = "stderr", | ||
| 301 | .generated_file = .{ .step = &self.step }, | ||
| 302 | }; | ||
| 303 | self.captured_stderr = output; | ||
| 304 | return .{ .generated = &output.generated_file }; | ||
| 305 | } | ||
| 306 | |||
| 307 | pub fn captureStdOut(self: *RunStep) std.Build.FileSource { | ||
| 308 | assert(self.stdio != .inherit); | ||
| 309 | |||
| 310 | if (self.captured_stdout) |output| return .{ .generated = &output.generated_file }; | ||
| 311 | |||
| 312 | const output = self.step.owner.allocator.create(Output) catch @panic("OOM"); | ||
| 313 | output.* = .{ | ||
| 314 | .prefix = "", | ||
| 315 | .basename = "stdout", | ||
| 316 | .generated_file = .{ .step = &self.step }, | ||
| 317 | }; | ||
| 318 | self.captured_stdout = output; | ||
| 319 | return .{ .generated = &output.generated_file }; | ||
| 320 | } | ||
| 321 | |||
| 322 | /// Returns whether the RunStep has side effects *other than* updating the output arguments. | ||
| 323 | fn hasSideEffects(self: RunStep) bool { | ||
| 324 | if (self.has_side_effects) return true; | ||
| 325 | return switch (self.stdio) { | ||
| 326 | .infer_from_args => !self.hasAnyOutputArgs(), | ||
| 327 | .inherit => true, | ||
| 328 | .check => false, | ||
| 329 | .zig_test => false, | ||
| 330 | }; | ||
| 331 | } | ||
| 332 | |||
| 333 | fn hasAnyOutputArgs(self: RunStep) bool { | ||
| 334 | if (self.captured_stdout != null) return true; | ||
| 335 | if (self.captured_stderr != null) return true; | ||
| 336 | for (self.argv.items) |arg| switch (arg) { | ||
| 337 | .output => return true, | ||
| 338 | else => continue, | ||
| 339 | }; | ||
| 340 | return false; | ||
| 341 | } | ||
| 342 | |||
| 343 | fn checksContainStdout(checks: []const StdIo.Check) bool { | ||
| 344 | for (checks) |check| switch (check) { | ||
| 345 | .expect_stderr_exact, | ||
| 346 | .expect_stderr_match, | ||
| 347 | .expect_term, | ||
| 348 | => continue, | ||
| 349 | |||
| 350 | .expect_stdout_exact, | ||
| 351 | .expect_stdout_match, | ||
| 352 | => return true, | ||
| 353 | }; | ||
| 354 | return false; | ||
| 355 | } | ||
| 356 | |||
| 357 | fn checksContainStderr(checks: []const StdIo.Check) bool { | ||
| 358 | for (checks) |check| switch (check) { | ||
| 359 | .expect_stdout_exact, | ||
| 360 | .expect_stdout_match, | ||
| 361 | .expect_term, | ||
| 362 | => continue, | ||
| 363 | |||
| 364 | .expect_stderr_exact, | ||
| 365 | .expect_stderr_match, | ||
| 366 | => return true, | ||
| 367 | }; | ||
| 368 | return false; | ||
| 369 | } | ||
| 370 | |||
| 371 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 372 | const b = step.owner; | ||
| 373 | const arena = b.allocator; | ||
| 374 | const self = @fieldParentPtr(RunStep, "step", step); | ||
| 375 | const has_side_effects = self.hasSideEffects(); | ||
| 376 | |||
| 377 | var argv_list = ArrayList([]const u8).init(arena); | ||
| 378 | var output_placeholders = ArrayList(struct { | ||
| 379 | index: usize, | ||
| 380 | output: *Output, | ||
| 381 | }).init(arena); | ||
| 382 | |||
| 383 | var man = b.cache.obtain(); | ||
| 384 | defer man.deinit(); | ||
| 385 | |||
| 386 | for (self.argv.items) |arg| { | ||
| 387 | switch (arg) { | ||
| 388 | .bytes => |bytes| { | ||
| 389 | try argv_list.append(bytes); | ||
| 390 | man.hash.addBytes(bytes); | ||
| 391 | }, | ||
| 392 | .file_source => |file| { | ||
| 393 | const file_path = file.getPath(b); | ||
| 394 | try argv_list.append(file_path); | ||
| 395 | _ = try man.addFile(file_path, null); | ||
| 396 | }, | ||
| 397 | .directory_source => |file| { | ||
| 398 | const file_path = file.getPath(b); | ||
| 399 | try argv_list.append(file_path); | ||
| 400 | man.hash.addBytes(file_path); | ||
| 401 | }, | ||
| 402 | .artifact => |artifact| { | ||
| 403 | if (artifact.target.isWindows()) { | ||
| 404 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | ||
| 405 | self.addPathForDynLibs(artifact); | ||
| 406 | } | ||
| 407 | const file_path = artifact.installed_path orelse | ||
| 408 | artifact.getOutputSource().getPath(b); | ||
| 409 | |||
| 410 | try argv_list.append(file_path); | ||
| 411 | |||
| 412 | _ = try man.addFile(file_path, null); | ||
| 413 | }, | ||
| 414 | .output => |output| { | ||
| 415 | man.hash.addBytes(output.prefix); | ||
| 416 | man.hash.addBytes(output.basename); | ||
| 417 | // Add a placeholder into the argument list because we need the | ||
| 418 | // manifest hash to be updated with all arguments before the | ||
| 419 | // object directory is computed. | ||
| 420 | try argv_list.append(""); | ||
| 421 | try output_placeholders.append(.{ | ||
| 422 | .index = argv_list.items.len - 1, | ||
| 423 | .output = output, | ||
| 424 | }); | ||
| 425 | }, | ||
| 426 | } | ||
| 427 | } | ||
| 428 | |||
| 429 | if (self.captured_stdout) |output| { | ||
| 430 | man.hash.addBytes(output.basename); | ||
| 431 | } | ||
| 432 | |||
| 433 | if (self.captured_stderr) |output| { | ||
| 434 | man.hash.addBytes(output.basename); | ||
| 435 | } | ||
| 436 | |||
| 437 | hashStdIo(&man.hash, self.stdio); | ||
| 438 | |||
| 439 | if (has_side_effects) { | ||
| 440 | try runCommand(self, argv_list.items, has_side_effects, null, prog_node); | ||
| 441 | return; | ||
| 442 | } | ||
| 443 | |||
| 444 | for (self.extra_file_dependencies) |file_path| { | ||
| 445 | _ = try man.addFile(b.pathFromRoot(file_path), null); | ||
| 446 | } | ||
| 447 | |||
| 448 | if (try step.cacheHit(&man)) { | ||
| 449 | // cache hit, skip running command | ||
| 450 | const digest = man.final(); | ||
| 451 | for (output_placeholders.items) |placeholder| { | ||
| 452 | placeholder.output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 453 | "o", &digest, placeholder.output.basename, | ||
| 454 | }); | ||
| 455 | } | ||
| 456 | |||
| 457 | if (self.captured_stdout) |output| { | ||
| 458 | output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 459 | "o", &digest, output.basename, | ||
| 460 | }); | ||
| 461 | } | ||
| 462 | |||
| 463 | if (self.captured_stderr) |output| { | ||
| 464 | output.generated_file.path = try b.cache_root.join(arena, &.{ | ||
| 465 | "o", &digest, output.basename, | ||
| 466 | }); | ||
| 467 | } | ||
| 468 | |||
| 469 | step.result_cached = true; | ||
| 470 | return; | ||
| 471 | } | ||
| 472 | |||
| 473 | const digest = man.final(); | ||
| 474 | |||
| 475 | for (output_placeholders.items) |placeholder| { | ||
| 476 | const output_components = .{ "o", &digest, placeholder.output.basename }; | ||
| 477 | const output_sub_path = try fs.path.join(arena, &output_components); | ||
| 478 | const output_sub_dir_path = fs.path.dirname(output_sub_path).?; | ||
| 479 | b.cache_root.handle.makePath(output_sub_dir_path) catch |err| { | ||
| 480 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 481 | b.cache_root, output_sub_dir_path, @errorName(err), | ||
| 482 | }); | ||
| 483 | }; | ||
| 484 | const output_path = try b.cache_root.join(arena, &output_components); | ||
| 485 | placeholder.output.generated_file.path = output_path; | ||
| 486 | const cli_arg = if (placeholder.output.prefix.len == 0) | ||
| 487 | output_path | ||
| 488 | else | ||
| 489 | b.fmt("{s}{s}", .{ placeholder.output.prefix, output_path }); | ||
| 490 | argv_list.items[placeholder.index] = cli_arg; | ||
| 491 | } | ||
| 492 | |||
| 493 | try runCommand(self, argv_list.items, has_side_effects, &digest, prog_node); | ||
| 494 | |||
| 495 | try step.writeManifest(&man); | ||
| 496 | } | ||
| 497 | |||
| 498 | fn formatTerm( | ||
| 499 | term: ?std.process.Child.Term, | ||
| 500 | comptime fmt: []const u8, | ||
| 501 | options: std.fmt.FormatOptions, | ||
| 502 | writer: anytype, | ||
| 503 | ) !void { | ||
| 504 | _ = fmt; | ||
| 505 | _ = options; | ||
| 506 | if (term) |t| switch (t) { | ||
| 507 | .Exited => |code| try writer.print("exited with code {}", .{code}), | ||
| 508 | .Signal => |sig| try writer.print("terminated with signal {}", .{sig}), | ||
| 509 | .Stopped => |sig| try writer.print("stopped with signal {}", .{sig}), | ||
| 510 | .Unknown => |code| try writer.print("terminated for unknown reason with code {}", .{code}), | ||
| 511 | } else { | ||
| 512 | try writer.writeAll("exited with any code"); | ||
| 513 | } | ||
| 514 | } | ||
| 515 | fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) { | ||
| 516 | return .{ .data = term }; | ||
| 517 | } | ||
| 518 | |||
| 519 | fn termMatches(expected: ?std.process.Child.Term, actual: std.process.Child.Term) bool { | ||
| 520 | return if (expected) |e| switch (e) { | ||
| 521 | .Exited => |expected_code| switch (actual) { | ||
| 522 | .Exited => |actual_code| expected_code == actual_code, | ||
| 523 | else => false, | ||
| 524 | }, | ||
| 525 | .Signal => |expected_sig| switch (actual) { | ||
| 526 | .Signal => |actual_sig| expected_sig == actual_sig, | ||
| 527 | else => false, | ||
| 528 | }, | ||
| 529 | .Stopped => |expected_sig| switch (actual) { | ||
| 530 | .Stopped => |actual_sig| expected_sig == actual_sig, | ||
| 531 | else => false, | ||
| 532 | }, | ||
| 533 | .Unknown => |expected_code| switch (actual) { | ||
| 534 | .Unknown => |actual_code| expected_code == actual_code, | ||
| 535 | else => false, | ||
| 536 | }, | ||
| 537 | } else switch (actual) { | ||
| 538 | .Exited => true, | ||
| 539 | else => false, | ||
| 540 | }; | ||
| 541 | } | ||
| 542 | |||
| 543 | fn runCommand( | ||
| 544 | self: *RunStep, | ||
| 545 | argv: []const []const u8, | ||
| 546 | has_side_effects: bool, | ||
| 547 | digest: ?*const [std.Build.Cache.hex_digest_len]u8, | ||
| 548 | prog_node: *std.Progress.Node, | ||
| 549 | ) !void { | ||
| 550 | const step = &self.step; | ||
| 551 | const b = step.owner; | ||
| 552 | const arena = b.allocator; | ||
| 553 | |||
| 554 | try step.handleChildProcUnsupported(self.cwd, argv); | ||
| 555 | try Step.handleVerbose2(step.owner, self.cwd, self.env_map, argv); | ||
| 556 | |||
| 557 | const allow_skip = switch (self.stdio) { | ||
| 558 | .check, .zig_test => self.skip_foreign_checks, | ||
| 559 | else => false, | ||
| 560 | }; | ||
| 561 | |||
| 562 | var interp_argv = std.ArrayList([]const u8).init(b.allocator); | ||
| 563 | defer interp_argv.deinit(); | ||
| 564 | |||
| 565 | const result = spawnChildAndCollect(self, argv, has_side_effects, prog_node) catch |err| term: { | ||
| 566 | // InvalidExe: cpu arch mismatch | ||
| 567 | // FileNotFound: can happen with a wrong dynamic linker path | ||
| 568 | if (err == error.InvalidExe or err == error.FileNotFound) interpret: { | ||
| 569 | // TODO: learn the target from the binary directly rather than from | ||
| 570 | // relying on it being a CompileStep. This will make this logic | ||
| 571 | // work even for the edge case that the binary was produced by a | ||
| 572 | // third party. | ||
| 573 | const exe = switch (self.argv.items[0]) { | ||
| 574 | .artifact => |exe| exe, | ||
| 575 | else => break :interpret, | ||
| 576 | }; | ||
| 577 | switch (exe.kind) { | ||
| 578 | .exe, .@"test" => {}, | ||
| 579 | else => break :interpret, | ||
| 580 | } | ||
| 581 | |||
| 582 | const need_cross_glibc = exe.target.isGnuLibC() and exe.is_linking_libc; | ||
| 583 | switch (b.host.getExternalExecutor(exe.target_info, .{ | ||
| 584 | .qemu_fixes_dl = need_cross_glibc and b.glibc_runtimes_dir != null, | ||
| 585 | .link_libc = exe.is_linking_libc, | ||
| 586 | })) { | ||
| 587 | .native, .rosetta => { | ||
| 588 | if (allow_skip) return error.MakeSkipped; | ||
| 589 | break :interpret; | ||
| 590 | }, | ||
| 591 | .wine => |bin_name| { | ||
| 592 | if (b.enable_wine) { | ||
| 593 | try interp_argv.append(bin_name); | ||
| 594 | try interp_argv.appendSlice(argv); | ||
| 595 | } else { | ||
| 596 | return failForeign(self, "-fwine", argv[0], exe); | ||
| 597 | } | ||
| 598 | }, | ||
| 599 | .qemu => |bin_name| { | ||
| 600 | if (b.enable_qemu) { | ||
| 601 | const glibc_dir_arg = if (need_cross_glibc) | ||
| 602 | b.glibc_runtimes_dir orelse | ||
| 603 | return failForeign(self, "--glibc-runtimes", argv[0], exe) | ||
| 604 | else | ||
| 605 | null; | ||
| 606 | |||
| 607 | try interp_argv.append(bin_name); | ||
| 608 | |||
| 609 | if (glibc_dir_arg) |dir| { | ||
| 610 | // TODO look into making this a call to `linuxTriple`. This | ||
| 611 | // needs the directory to be called "i686" rather than | ||
| 612 | // "x86" which is why we do it manually here. | ||
| 613 | const fmt_str = "{s}" ++ fs.path.sep_str ++ "{s}-{s}-{s}"; | ||
| 614 | const cpu_arch = exe.target.getCpuArch(); | ||
| 615 | const os_tag = exe.target.getOsTag(); | ||
| 616 | const abi = exe.target.getAbi(); | ||
| 617 | const cpu_arch_name: []const u8 = if (cpu_arch == .x86) | ||
| 618 | "i686" | ||
| 619 | else | ||
| 620 | @tagName(cpu_arch); | ||
| 621 | const full_dir = try std.fmt.allocPrint(b.allocator, fmt_str, .{ | ||
| 622 | dir, cpu_arch_name, @tagName(os_tag), @tagName(abi), | ||
| 623 | }); | ||
| 624 | |||
| 625 | try interp_argv.append("-L"); | ||
| 626 | try interp_argv.append(full_dir); | ||
| 627 | } | ||
| 628 | |||
| 629 | try interp_argv.appendSlice(argv); | ||
| 630 | } else { | ||
| 631 | return failForeign(self, "-fqemu", argv[0], exe); | ||
| 632 | } | ||
| 633 | }, | ||
| 634 | .darling => |bin_name| { | ||
| 635 | if (b.enable_darling) { | ||
| 636 | try interp_argv.append(bin_name); | ||
| 637 | try interp_argv.appendSlice(argv); | ||
| 638 | } else { | ||
| 639 | return failForeign(self, "-fdarling", argv[0], exe); | ||
| 640 | } | ||
| 641 | }, | ||
| 642 | .wasmtime => |bin_name| { | ||
| 643 | if (b.enable_wasmtime) { | ||
| 644 | try interp_argv.append(bin_name); | ||
| 645 | try interp_argv.append("--dir=."); | ||
| 646 | try interp_argv.append(argv[0]); | ||
| 647 | try interp_argv.append("--"); | ||
| 648 | try interp_argv.appendSlice(argv[1..]); | ||
| 649 | } else { | ||
| 650 | return failForeign(self, "-fwasmtime", argv[0], exe); | ||
| 651 | } | ||
| 652 | }, | ||
| 653 | .bad_dl => |foreign_dl| { | ||
| 654 | if (allow_skip) return error.MakeSkipped; | ||
| 655 | |||
| 656 | const host_dl = b.host.dynamic_linker.get() orelse "(none)"; | ||
| 657 | |||
| 658 | return step.fail( | ||
| 659 | \\the host system is unable to execute binaries from the target | ||
| 660 | \\ because the host dynamic linker is '{s}', | ||
| 661 | \\ while the target dynamic linker is '{s}'. | ||
| 662 | \\ consider setting the dynamic linker or enabling skip_foreign_checks in the Run step | ||
| 663 | , .{ host_dl, foreign_dl }); | ||
| 664 | }, | ||
| 665 | .bad_os_or_cpu => { | ||
| 666 | if (allow_skip) return error.MakeSkipped; | ||
| 667 | |||
| 668 | const host_name = try b.host.target.zigTriple(b.allocator); | ||
| 669 | const foreign_name = try exe.target.zigTriple(b.allocator); | ||
| 670 | |||
| 671 | return step.fail("the host system ({s}) is unable to execute binaries from the target ({s})", .{ | ||
| 672 | host_name, foreign_name, | ||
| 673 | }); | ||
| 674 | }, | ||
| 675 | } | ||
| 676 | |||
| 677 | if (exe.target.isWindows()) { | ||
| 678 | // On Windows we don't have rpaths so we have to add .dll search paths to PATH | ||
| 679 | self.addPathForDynLibs(exe); | ||
| 680 | } | ||
| 681 | |||
| 682 | try Step.handleVerbose2(step.owner, self.cwd, self.env_map, interp_argv.items); | ||
| 683 | |||
| 684 | break :term spawnChildAndCollect(self, interp_argv.items, has_side_effects, prog_node) catch |e| { | ||
| 685 | return step.fail("unable to spawn interpreter {s}: {s}", .{ | ||
| 686 | interp_argv.items[0], @errorName(e), | ||
| 687 | }); | ||
| 688 | }; | ||
| 689 | } | ||
| 690 | |||
| 691 | return step.fail("unable to spawn {s}: {s}", .{ argv[0], @errorName(err) }); | ||
| 692 | }; | ||
| 693 | |||
| 694 | step.result_duration_ns = result.elapsed_ns; | ||
| 695 | step.result_peak_rss = result.peak_rss; | ||
| 696 | step.test_results = result.stdio.test_results; | ||
| 697 | |||
| 698 | // Capture stdout and stderr to GeneratedFile objects. | ||
| 699 | const Stream = struct { | ||
| 700 | captured: ?*Output, | ||
| 701 | is_null: bool, | ||
| 702 | bytes: []const u8, | ||
| 703 | }; | ||
| 704 | for ([_]Stream{ | ||
| 705 | .{ | ||
| 706 | .captured = self.captured_stdout, | ||
| 707 | .is_null = result.stdio.stdout_null, | ||
| 708 | .bytes = result.stdio.stdout, | ||
| 709 | }, | ||
| 710 | .{ | ||
| 711 | .captured = self.captured_stderr, | ||
| 712 | .is_null = result.stdio.stderr_null, | ||
| 713 | .bytes = result.stdio.stderr, | ||
| 714 | }, | ||
| 715 | }) |stream| { | ||
| 716 | if (stream.captured) |output| { | ||
| 717 | assert(!stream.is_null); | ||
| 718 | |||
| 719 | const output_components = .{ "o", digest.?, output.basename }; | ||
| 720 | const output_path = try b.cache_root.join(arena, &output_components); | ||
| 721 | output.generated_file.path = output_path; | ||
| 722 | |||
| 723 | const sub_path = try fs.path.join(arena, &output_components); | ||
| 724 | const sub_path_dirname = fs.path.dirname(sub_path).?; | ||
| 725 | b.cache_root.handle.makePath(sub_path_dirname) catch |err| { | ||
| 726 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 727 | b.cache_root, sub_path_dirname, @errorName(err), | ||
| 728 | }); | ||
| 729 | }; | ||
| 730 | b.cache_root.handle.writeFile(sub_path, stream.bytes) catch |err| { | ||
| 731 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 732 | b.cache_root, sub_path, @errorName(err), | ||
| 733 | }); | ||
| 734 | }; | ||
| 735 | } | ||
| 736 | } | ||
| 737 | |||
| 738 | const final_argv = if (interp_argv.items.len == 0) argv else interp_argv.items; | ||
| 739 | |||
| 740 | switch (self.stdio) { | ||
| 741 | .check => |checks| for (checks.items) |check| switch (check) { | ||
| 742 | .expect_stderr_exact => |expected_bytes| { | ||
| 743 | assert(!result.stdio.stderr_null); | ||
| 744 | if (!mem.eql(u8, expected_bytes, result.stdio.stderr)) { | ||
| 745 | return step.fail( | ||
| 746 | \\ | ||
| 747 | \\========= expected this stderr: ========= | ||
| 748 | \\{s} | ||
| 749 | \\========= but found: ==================== | ||
| 750 | \\{s} | ||
| 751 | \\========= from the following command: === | ||
| 752 | \\{s} | ||
| 753 | , .{ | ||
| 754 | expected_bytes, | ||
| 755 | result.stdio.stderr, | ||
| 756 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 757 | }); | ||
| 758 | } | ||
| 759 | }, | ||
| 760 | .expect_stderr_match => |match| { | ||
| 761 | assert(!result.stdio.stderr_null); | ||
| 762 | if (mem.indexOf(u8, result.stdio.stderr, match) == null) { | ||
| 763 | return step.fail( | ||
| 764 | \\ | ||
| 765 | \\========= expected to find in stderr: ========= | ||
| 766 | \\{s} | ||
| 767 | \\========= but stderr does not contain it: ===== | ||
| 768 | \\{s} | ||
| 769 | \\========= from the following command: ========= | ||
| 770 | \\{s} | ||
| 771 | , .{ | ||
| 772 | match, | ||
| 773 | result.stdio.stderr, | ||
| 774 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 775 | }); | ||
| 776 | } | ||
| 777 | }, | ||
| 778 | .expect_stdout_exact => |expected_bytes| { | ||
| 779 | assert(!result.stdio.stdout_null); | ||
| 780 | if (!mem.eql(u8, expected_bytes, result.stdio.stdout)) { | ||
| 781 | return step.fail( | ||
| 782 | \\ | ||
| 783 | \\========= expected this stdout: ========= | ||
| 784 | \\{s} | ||
| 785 | \\========= but found: ==================== | ||
| 786 | \\{s} | ||
| 787 | \\========= from the following command: === | ||
| 788 | \\{s} | ||
| 789 | , .{ | ||
| 790 | expected_bytes, | ||
| 791 | result.stdio.stdout, | ||
| 792 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 793 | }); | ||
| 794 | } | ||
| 795 | }, | ||
| 796 | .expect_stdout_match => |match| { | ||
| 797 | assert(!result.stdio.stdout_null); | ||
| 798 | if (mem.indexOf(u8, result.stdio.stdout, match) == null) { | ||
| 799 | return step.fail( | ||
| 800 | \\ | ||
| 801 | \\========= expected to find in stdout: ========= | ||
| 802 | \\{s} | ||
| 803 | \\========= but stdout does not contain it: ===== | ||
| 804 | \\{s} | ||
| 805 | \\========= from the following command: ========= | ||
| 806 | \\{s} | ||
| 807 | , .{ | ||
| 808 | match, | ||
| 809 | result.stdio.stdout, | ||
| 810 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 811 | }); | ||
| 812 | } | ||
| 813 | }, | ||
| 814 | .expect_term => |expected_term| { | ||
| 815 | if (!termMatches(expected_term, result.term)) { | ||
| 816 | return step.fail("the following command {} (expected {}):\n{s}", .{ | ||
| 817 | fmtTerm(result.term), | ||
| 818 | fmtTerm(expected_term), | ||
| 819 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 820 | }); | ||
| 821 | } | ||
| 822 | }, | ||
| 823 | }, | ||
| 824 | .zig_test => { | ||
| 825 | const prefix: []const u8 = p: { | ||
| 826 | if (result.stdio.test_metadata) |tm| { | ||
| 827 | if (tm.next_index <= tm.names.len) { | ||
| 828 | const name = tm.testName(tm.next_index - 1); | ||
| 829 | break :p b.fmt("while executing test '{s}', ", .{name}); | ||
| 830 | } | ||
| 831 | } | ||
| 832 | break :p ""; | ||
| 833 | }; | ||
| 834 | const expected_term: std.process.Child.Term = .{ .Exited = 0 }; | ||
| 835 | if (!termMatches(expected_term, result.term)) { | ||
| 836 | return step.fail("{s}the following command {} (expected {}):\n{s}", .{ | ||
| 837 | prefix, | ||
| 838 | fmtTerm(result.term), | ||
| 839 | fmtTerm(expected_term), | ||
| 840 | try Step.allocPrintCmd(arena, self.cwd, final_argv), | ||
| 841 | }); | ||
| 842 | } | ||
| 843 | if (!result.stdio.test_results.isSuccess()) { | ||
| 844 | return step.fail( | ||
| 845 | "{s}the following test command failed:\n{s}", | ||
| 846 | .{ prefix, try Step.allocPrintCmd(arena, self.cwd, final_argv) }, | ||
| 847 | ); | ||
| 848 | } | ||
| 849 | }, | ||
| 850 | else => { | ||
| 851 | try step.handleChildProcessTerm(result.term, self.cwd, final_argv); | ||
| 852 | }, | ||
| 853 | } | ||
| 854 | } | ||
| 855 | |||
| 856 | const ChildProcResult = struct { | ||
| 857 | term: std.process.Child.Term, | ||
| 858 | elapsed_ns: u64, | ||
| 859 | peak_rss: usize, | ||
| 860 | |||
| 861 | stdio: StdIoResult, | ||
| 862 | }; | ||
| 863 | |||
| 864 | fn spawnChildAndCollect( | ||
| 865 | self: *RunStep, | ||
| 866 | argv: []const []const u8, | ||
| 867 | has_side_effects: bool, | ||
| 868 | prog_node: *std.Progress.Node, | ||
| 869 | ) !ChildProcResult { | ||
| 870 | const b = self.step.owner; | ||
| 871 | const arena = b.allocator; | ||
| 872 | |||
| 873 | var child = std.process.Child.init(argv, arena); | ||
| 874 | if (self.cwd) |cwd| { | ||
| 875 | child.cwd = b.pathFromRoot(cwd); | ||
| 876 | } else { | ||
| 877 | child.cwd = b.build_root.path; | ||
| 878 | child.cwd_dir = b.build_root.handle; | ||
| 879 | } | ||
| 880 | child.env_map = self.env_map orelse b.env_map; | ||
| 881 | child.request_resource_usage_statistics = true; | ||
| 882 | |||
| 883 | child.stdin_behavior = switch (self.stdio) { | ||
| 884 | .infer_from_args => if (has_side_effects) .Inherit else .Ignore, | ||
| 885 | .inherit => .Inherit, | ||
| 886 | .check => .Ignore, | ||
| 887 | .zig_test => .Pipe, | ||
| 888 | }; | ||
| 889 | child.stdout_behavior = switch (self.stdio) { | ||
| 890 | .infer_from_args => if (has_side_effects) .Inherit else .Ignore, | ||
| 891 | .inherit => .Inherit, | ||
| 892 | .check => |checks| if (checksContainStdout(checks.items)) .Pipe else .Ignore, | ||
| 893 | .zig_test => .Pipe, | ||
| 894 | }; | ||
| 895 | child.stderr_behavior = switch (self.stdio) { | ||
| 896 | .infer_from_args => if (has_side_effects) .Inherit else .Pipe, | ||
| 897 | .inherit => .Inherit, | ||
| 898 | .check => .Pipe, | ||
| 899 | .zig_test => .Pipe, | ||
| 900 | }; | ||
| 901 | if (self.captured_stdout != null) child.stdout_behavior = .Pipe; | ||
| 902 | if (self.captured_stderr != null) child.stderr_behavior = .Pipe; | ||
| 903 | if (self.stdin != null) { | ||
| 904 | assert(child.stdin_behavior != .Inherit); | ||
| 905 | child.stdin_behavior = .Pipe; | ||
| 906 | } | ||
| 907 | |||
| 908 | try child.spawn(); | ||
| 909 | var timer = try std.time.Timer.start(); | ||
| 910 | |||
| 911 | const result = if (self.stdio == .zig_test) | ||
| 912 | evalZigTest(self, &child, prog_node) | ||
| 913 | else | ||
| 914 | evalGeneric(self, &child); | ||
| 915 | |||
| 916 | const term = try child.wait(); | ||
| 917 | const elapsed_ns = timer.read(); | ||
| 918 | |||
| 919 | return .{ | ||
| 920 | .stdio = try result, | ||
| 921 | .term = term, | ||
| 922 | .elapsed_ns = elapsed_ns, | ||
| 923 | .peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0, | ||
| 924 | }; | ||
| 925 | } | ||
| 926 | |||
| 927 | const StdIoResult = struct { | ||
| 928 | // These use boolean flags instead of optionals as a workaround for | ||
| 929 | // https://github.com/ziglang/zig/issues/14783 | ||
| 930 | stdout: []const u8, | ||
| 931 | stderr: []const u8, | ||
| 932 | stdout_null: bool, | ||
| 933 | stderr_null: bool, | ||
| 934 | test_results: Step.TestResults, | ||
| 935 | test_metadata: ?TestMetadata, | ||
| 936 | }; | ||
| 937 | |||
| 938 | fn evalZigTest( | ||
| 939 | self: *RunStep, | ||
| 940 | child: *std.process.Child, | ||
| 941 | prog_node: *std.Progress.Node, | ||
| 942 | ) !StdIoResult { | ||
| 943 | const gpa = self.step.owner.allocator; | ||
| 944 | const arena = self.step.owner.allocator; | ||
| 945 | |||
| 946 | var poller = std.io.poll(gpa, enum { stdout, stderr }, .{ | ||
| 947 | .stdout = child.stdout.?, | ||
| 948 | .stderr = child.stderr.?, | ||
| 949 | }); | ||
| 950 | defer poller.deinit(); | ||
| 951 | |||
| 952 | try sendMessage(child.stdin.?, .query_test_metadata); | ||
| 953 | |||
| 954 | const Header = std.zig.Server.Message.Header; | ||
| 955 | |||
| 956 | const stdout = poller.fifo(.stdout); | ||
| 957 | const stderr = poller.fifo(.stderr); | ||
| 958 | |||
| 959 | var fail_count: u32 = 0; | ||
| 960 | var skip_count: u32 = 0; | ||
| 961 | var leak_count: u32 = 0; | ||
| 962 | var test_count: u32 = 0; | ||
| 963 | |||
| 964 | var metadata: ?TestMetadata = null; | ||
| 965 | |||
| 966 | var sub_prog_node: ?std.Progress.Node = null; | ||
| 967 | defer if (sub_prog_node) |*n| n.end(); | ||
| 968 | |||
| 969 | poll: while (true) { | ||
| 970 | while (stdout.readableLength() < @sizeOf(Header)) { | ||
| 971 | if (!(try poller.poll())) break :poll; | ||
| 972 | } | ||
| 973 | const header = stdout.reader().readStruct(Header) catch unreachable; | ||
| 974 | while (stdout.readableLength() < header.bytes_len) { | ||
| 975 | if (!(try poller.poll())) break :poll; | ||
| 976 | } | ||
| 977 | const body = stdout.readableSliceOfLen(header.bytes_len); | ||
| 978 | |||
| 979 | switch (header.tag) { | ||
| 980 | .zig_version => { | ||
| 981 | if (!std.mem.eql(u8, builtin.zig_version_string, body)) { | ||
| 982 | return self.step.fail( | ||
| 983 | "zig version mismatch build runner vs compiler: '{s}' vs '{s}'", | ||
| 984 | .{ builtin.zig_version_string, body }, | ||
| 985 | ); | ||
| 986 | } | ||
| 987 | }, | ||
| 988 | .test_metadata => { | ||
| 989 | const TmHdr = std.zig.Server.Message.TestMetadata; | ||
| 990 | const tm_hdr = @ptrCast(*align(1) const TmHdr, body); | ||
| 991 | test_count = tm_hdr.tests_len; | ||
| 992 | |||
| 993 | const names_bytes = body[@sizeOf(TmHdr)..][0 .. test_count * @sizeOf(u32)]; | ||
| 994 | const async_frame_lens_bytes = body[@sizeOf(TmHdr) + names_bytes.len ..][0 .. test_count * @sizeOf(u32)]; | ||
| 995 | const expected_panic_msgs_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len ..][0 .. test_count * @sizeOf(u32)]; | ||
| 996 | const string_bytes = body[@sizeOf(TmHdr) + names_bytes.len + async_frame_lens_bytes.len + expected_panic_msgs_bytes.len ..][0..tm_hdr.string_bytes_len]; | ||
| 997 | |||
| 998 | const names = std.mem.bytesAsSlice(u32, names_bytes); | ||
| 999 | const async_frame_lens = std.mem.bytesAsSlice(u32, async_frame_lens_bytes); | ||
| 1000 | const expected_panic_msgs = std.mem.bytesAsSlice(u32, expected_panic_msgs_bytes); | ||
| 1001 | const names_aligned = try arena.alloc(u32, names.len); | ||
| 1002 | for (names_aligned, names) |*dest, src| dest.* = src; | ||
| 1003 | |||
| 1004 | const async_frame_lens_aligned = try arena.alloc(u32, async_frame_lens.len); | ||
| 1005 | for (async_frame_lens_aligned, async_frame_lens) |*dest, src| dest.* = src; | ||
| 1006 | |||
| 1007 | const expected_panic_msgs_aligned = try arena.alloc(u32, expected_panic_msgs.len); | ||
| 1008 | for (expected_panic_msgs_aligned, expected_panic_msgs) |*dest, src| dest.* = src; | ||
| 1009 | |||
| 1010 | prog_node.setEstimatedTotalItems(names.len); | ||
| 1011 | metadata = .{ | ||
| 1012 | .string_bytes = try arena.dupe(u8, string_bytes), | ||
| 1013 | .names = names_aligned, | ||
| 1014 | .async_frame_lens = async_frame_lens_aligned, | ||
| 1015 | .expected_panic_msgs = expected_panic_msgs_aligned, | ||
| 1016 | .next_index = 0, | ||
| 1017 | .prog_node = prog_node, | ||
| 1018 | }; | ||
| 1019 | |||
| 1020 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); | ||
| 1021 | }, | ||
| 1022 | .test_results => { | ||
| 1023 | const md = metadata.?; | ||
| 1024 | |||
| 1025 | const TrHdr = std.zig.Server.Message.TestResults; | ||
| 1026 | const tr_hdr = @ptrCast(*align(1) const TrHdr, body); | ||
| 1027 | fail_count += @boolToInt(tr_hdr.flags.fail); | ||
| 1028 | skip_count += @boolToInt(tr_hdr.flags.skip); | ||
| 1029 | leak_count += @boolToInt(tr_hdr.flags.leak); | ||
| 1030 | |||
| 1031 | if (tr_hdr.flags.fail or tr_hdr.flags.leak) { | ||
| 1032 | const name = std.mem.sliceTo(md.string_bytes[md.names[tr_hdr.index]..], 0); | ||
| 1033 | const msg = std.mem.trim(u8, stderr.readableSlice(0), "\n"); | ||
| 1034 | const label = if (tr_hdr.flags.fail) "failed" else "leaked"; | ||
| 1035 | if (msg.len > 0) { | ||
| 1036 | try self.step.addError("'{s}' {s}: {s}", .{ name, label, msg }); | ||
| 1037 | } else { | ||
| 1038 | try self.step.addError("'{s}' {s}", .{ name, label }); | ||
| 1039 | } | ||
| 1040 | stderr.discard(msg.len); | ||
| 1041 | } | ||
| 1042 | |||
| 1043 | try requestNextTest(child.stdin.?, &metadata.?, &sub_prog_node); | ||
| 1044 | }, | ||
| 1045 | else => {}, // ignore other messages | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | stdout.discard(body.len); | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | if (stderr.readableLength() > 0) { | ||
| 1052 | const msg = std.mem.trim(u8, try stderr.toOwnedSlice(), "\n"); | ||
| 1053 | if (msg.len > 0) try self.step.result_error_msgs.append(arena, msg); | ||
| 1054 | } | ||
| 1055 | |||
| 1056 | // Send EOF to stdin. | ||
| 1057 | child.stdin.?.close(); | ||
| 1058 | child.stdin = null; | ||
| 1059 | |||
| 1060 | return .{ | ||
| 1061 | .stdout = &.{}, | ||
| 1062 | .stderr = &.{}, | ||
| 1063 | .stdout_null = true, | ||
| 1064 | .stderr_null = true, | ||
| 1065 | .test_results = .{ | ||
| 1066 | .test_count = test_count, | ||
| 1067 | .fail_count = fail_count, | ||
| 1068 | .skip_count = skip_count, | ||
| 1069 | .leak_count = leak_count, | ||
| 1070 | }, | ||
| 1071 | .test_metadata = metadata, | ||
| 1072 | }; | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | const TestMetadata = struct { | ||
| 1076 | names: []const u32, | ||
| 1077 | async_frame_lens: []const u32, | ||
| 1078 | expected_panic_msgs: []const u32, | ||
| 1079 | string_bytes: []const u8, | ||
| 1080 | next_index: u32, | ||
| 1081 | prog_node: *std.Progress.Node, | ||
| 1082 | |||
| 1083 | fn testName(tm: TestMetadata, index: u32) []const u8 { | ||
| 1084 | return std.mem.sliceTo(tm.string_bytes[tm.names[index]..], 0); | ||
| 1085 | } | ||
| 1086 | }; | ||
| 1087 | |||
| 1088 | fn requestNextTest(in: fs.File, metadata: *TestMetadata, sub_prog_node: *?std.Progress.Node) !void { | ||
| 1089 | while (metadata.next_index < metadata.names.len) { | ||
| 1090 | const i = metadata.next_index; | ||
| 1091 | metadata.next_index += 1; | ||
| 1092 | |||
| 1093 | if (metadata.async_frame_lens[i] != 0) continue; | ||
| 1094 | if (metadata.expected_panic_msgs[i] != 0) continue; | ||
| 1095 | |||
| 1096 | const name = metadata.testName(i); | ||
| 1097 | if (sub_prog_node.*) |*n| n.end(); | ||
| 1098 | sub_prog_node.* = metadata.prog_node.start(name, 0); | ||
| 1099 | |||
| 1100 | try sendRunTestMessage(in, i); | ||
| 1101 | return; | ||
| 1102 | } else { | ||
| 1103 | try sendMessage(in, .exit); | ||
| 1104 | } | ||
| 1105 | } | ||
| 1106 | |||
| 1107 | fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void { | ||
| 1108 | const header: std.zig.Client.Message.Header = .{ | ||
| 1109 | .tag = tag, | ||
| 1110 | .bytes_len = 0, | ||
| 1111 | }; | ||
| 1112 | try file.writeAll(std.mem.asBytes(&header)); | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | fn sendRunTestMessage(file: std.fs.File, index: u32) !void { | ||
| 1116 | const header: std.zig.Client.Message.Header = .{ | ||
| 1117 | .tag = .run_test, | ||
| 1118 | .bytes_len = 4, | ||
| 1119 | }; | ||
| 1120 | const full_msg = std.mem.asBytes(&header) ++ std.mem.asBytes(&index); | ||
| 1121 | try file.writeAll(full_msg); | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | fn evalGeneric(self: *RunStep, child: *std.process.Child) !StdIoResult { | ||
| 1125 | const arena = self.step.owner.allocator; | ||
| 1126 | |||
| 1127 | if (self.stdin) |stdin| { | ||
| 1128 | child.stdin.?.writeAll(stdin) catch |err| { | ||
| 1129 | return self.step.fail("unable to write stdin: {s}", .{@errorName(err)}); | ||
| 1130 | }; | ||
| 1131 | child.stdin.?.close(); | ||
| 1132 | child.stdin = null; | ||
| 1133 | } | ||
| 1134 | |||
| 1135 | // These are not optionals, as a workaround for | ||
| 1136 | // https://github.com/ziglang/zig/issues/14783 | ||
| 1137 | var stdout_bytes: []const u8 = undefined; | ||
| 1138 | var stderr_bytes: []const u8 = undefined; | ||
| 1139 | var stdout_null = true; | ||
| 1140 | var stderr_null = true; | ||
| 1141 | |||
| 1142 | if (child.stdout) |stdout| { | ||
| 1143 | if (child.stderr) |stderr| { | ||
| 1144 | var poller = std.io.poll(arena, enum { stdout, stderr }, .{ | ||
| 1145 | .stdout = stdout, | ||
| 1146 | .stderr = stderr, | ||
| 1147 | }); | ||
| 1148 | defer poller.deinit(); | ||
| 1149 | |||
| 1150 | while (try poller.poll()) { | ||
| 1151 | if (poller.fifo(.stdout).count > self.max_stdio_size) | ||
| 1152 | return error.StdoutStreamTooLong; | ||
| 1153 | if (poller.fifo(.stderr).count > self.max_stdio_size) | ||
| 1154 | return error.StderrStreamTooLong; | ||
| 1155 | } | ||
| 1156 | |||
| 1157 | stdout_bytes = try poller.fifo(.stdout).toOwnedSlice(); | ||
| 1158 | stderr_bytes = try poller.fifo(.stderr).toOwnedSlice(); | ||
| 1159 | stdout_null = false; | ||
| 1160 | stderr_null = false; | ||
| 1161 | } else { | ||
| 1162 | stdout_bytes = try stdout.reader().readAllAlloc(arena, self.max_stdio_size); | ||
| 1163 | stdout_null = false; | ||
| 1164 | } | ||
| 1165 | } else if (child.stderr) |stderr| { | ||
| 1166 | stderr_bytes = try stderr.reader().readAllAlloc(arena, self.max_stdio_size); | ||
| 1167 | stderr_null = false; | ||
| 1168 | } | ||
| 1169 | |||
| 1170 | if (!stderr_null and stderr_bytes.len > 0) { | ||
| 1171 | // Treat stderr as an error message. | ||
| 1172 | const stderr_is_diagnostic = self.captured_stderr == null and switch (self.stdio) { | ||
| 1173 | .check => |checks| !checksContainStderr(checks.items), | ||
| 1174 | else => true, | ||
| 1175 | }; | ||
| 1176 | if (stderr_is_diagnostic) { | ||
| 1177 | try self.step.result_error_msgs.append(arena, stderr_bytes); | ||
| 1178 | } | ||
| 1179 | } | ||
| 1180 | |||
| 1181 | return .{ | ||
| 1182 | .stdout = stdout_bytes, | ||
| 1183 | .stderr = stderr_bytes, | ||
| 1184 | .stdout_null = stdout_null, | ||
| 1185 | .stderr_null = stderr_null, | ||
| 1186 | .test_results = .{}, | ||
| 1187 | .test_metadata = null, | ||
| 1188 | }; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | fn addPathForDynLibs(self: *RunStep, artifact: *CompileStep) void { | ||
| 1192 | const b = self.step.owner; | ||
| 1193 | for (artifact.link_objects.items) |link_object| { | ||
| 1194 | switch (link_object) { | ||
| 1195 | .other_step => |other| { | ||
| 1196 | if (other.target.isWindows() and other.isDynamicLibrary()) { | ||
| 1197 | addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?); | ||
| 1198 | addPathForDynLibs(self, other); | ||
| 1199 | } | ||
| 1200 | }, | ||
| 1201 | else => {}, | ||
| 1202 | } | ||
| 1203 | } | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | fn failForeign( | ||
| 1207 | self: *RunStep, | ||
| 1208 | suggested_flag: []const u8, | ||
| 1209 | argv0: []const u8, | ||
| 1210 | exe: *CompileStep, | ||
| 1211 | ) error{ MakeFailed, MakeSkipped, OutOfMemory } { | ||
| 1212 | switch (self.stdio) { | ||
| 1213 | .check, .zig_test => { | ||
| 1214 | if (self.skip_foreign_checks) | ||
| 1215 | return error.MakeSkipped; | ||
| 1216 | |||
| 1217 | const b = self.step.owner; | ||
| 1218 | const host_name = try b.host.target.zigTriple(b.allocator); | ||
| 1219 | const foreign_name = try exe.target.zigTriple(b.allocator); | ||
| 1220 | |||
| 1221 | return self.step.fail( | ||
| 1222 | \\unable to spawn foreign binary '{s}' ({s}) on host system ({s}) | ||
| 1223 | \\ consider using {s} or enabling skip_foreign_checks in the Run step | ||
| 1224 | , .{ argv0, foreign_name, host_name, suggested_flag }); | ||
| 1225 | }, | ||
| 1226 | else => { | ||
| 1227 | return self.step.fail("unable to spawn foreign binary '{s}'", .{argv0}); | ||
| 1228 | }, | ||
| 1229 | } | ||
| 1230 | } | ||
| 1231 | |||
| 1232 | fn hashStdIo(hh: *std.Build.Cache.HashHelper, stdio: StdIo) void { | ||
| 1233 | switch (stdio) { | ||
| 1234 | .infer_from_args, .inherit, .zig_test => {}, | ||
| 1235 | .check => |checks| for (checks.items) |check| { | ||
| 1236 | hh.add(@as(std.meta.Tag(StdIo.Check), check)); | ||
| 1237 | switch (check) { | ||
| 1238 | .expect_stderr_exact, | ||
| 1239 | .expect_stderr_match, | ||
| 1240 | .expect_stdout_exact, | ||
| 1241 | .expect_stdout_match, | ||
| 1242 | => |s| hh.addBytes(s), | ||
| 1243 | |||
| 1244 | .expect_term => |term| { | ||
| 1245 | hh.add(@as(std.meta.Tag(std.process.Child.Term), term)); | ||
| 1246 | switch (term) { | ||
| 1247 | .Exited => |x| hh.add(x), | ||
| 1248 | .Signal, .Stopped, .Unknown => |x| hh.add(x), | ||
| 1249 | } | ||
| 1250 | }, | ||
| 1251 | } | ||
| 1252 | }, | ||
| 1253 | } | ||
| 1254 | } | ||
lib/std/Build/Step/TranslateC.zig created+136| ... | @@ -0,0 +1,136 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const CompileStep = std.Build.CompileStep; | ||
| 4 | const CheckFileStep = std.Build.CheckFileStep; | ||
| 5 | const fs = std.fs; | ||
| 6 | const mem = std.mem; | ||
| 7 | const CrossTarget = std.zig.CrossTarget; | ||
| 8 | |||
| 9 | const TranslateCStep = @This(); | ||
| 10 | |||
| 11 | pub const base_id = .translate_c; | ||
| 12 | |||
| 13 | step: Step, | ||
| 14 | source: std.Build.FileSource, | ||
| 15 | include_dirs: std.ArrayList([]const u8), | ||
| 16 | c_macros: std.ArrayList([]const u8), | ||
| 17 | out_basename: []const u8, | ||
| 18 | target: CrossTarget, | ||
| 19 | optimize: std.builtin.OptimizeMode, | ||
| 20 | output_file: std.Build.GeneratedFile, | ||
| 21 | |||
| 22 | pub const Options = struct { | ||
| 23 | source_file: std.Build.FileSource, | ||
| 24 | target: CrossTarget, | ||
| 25 | optimize: std.builtin.OptimizeMode, | ||
| 26 | }; | ||
| 27 | |||
| 28 | pub fn create(owner: *std.Build, options: Options) *TranslateCStep { | ||
| 29 | const self = owner.allocator.create(TranslateCStep) catch @panic("OOM"); | ||
| 30 | const source = options.source_file.dupe(owner); | ||
| 31 | self.* = TranslateCStep{ | ||
| 32 | .step = Step.init(.{ | ||
| 33 | .id = .translate_c, | ||
| 34 | .name = "translate-c", | ||
| 35 | .owner = owner, | ||
| 36 | .makeFn = make, | ||
| 37 | }), | ||
| 38 | .source = source, | ||
| 39 | .include_dirs = std.ArrayList([]const u8).init(owner.allocator), | ||
| 40 | .c_macros = std.ArrayList([]const u8).init(owner.allocator), | ||
| 41 | .out_basename = undefined, | ||
| 42 | .target = options.target, | ||
| 43 | .optimize = options.optimize, | ||
| 44 | .output_file = std.Build.GeneratedFile{ .step = &self.step }, | ||
| 45 | }; | ||
| 46 | source.addStepDependencies(&self.step); | ||
| 47 | return self; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub const AddExecutableOptions = struct { | ||
| 51 | name: ?[]const u8 = null, | ||
| 52 | version: ?std.builtin.Version = null, | ||
| 53 | target: ?CrossTarget = null, | ||
| 54 | optimize: ?std.builtin.Mode = null, | ||
| 55 | linkage: ?CompileStep.Linkage = null, | ||
| 56 | }; | ||
| 57 | |||
| 58 | /// Creates a step to build an executable from the translated source. | ||
| 59 | pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep { | ||
| 60 | return self.step.owner.addExecutable(.{ | ||
| 61 | .root_source_file = .{ .generated = &self.output_file }, | ||
| 62 | .name = options.name orelse "translated_c", | ||
| 63 | .version = options.version, | ||
| 64 | .target = options.target orelse self.target, | ||
| 65 | .optimize = options.optimize orelse self.optimize, | ||
| 66 | .linkage = options.linkage, | ||
| 67 | }); | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { | ||
| 71 | self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM"); | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { | ||
| 75 | return CheckFileStep.create( | ||
| 76 | self.step.owner, | ||
| 77 | .{ .generated = &self.output_file }, | ||
| 78 | .{ .expected_matches = expected_matches }, | ||
| 79 | ); | ||
| 80 | } | ||
| 81 | |||
| 82 | /// If the value is omitted, it is set to 1. | ||
| 83 | /// `name` and `value` need not live longer than the function call. | ||
| 84 | pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void { | ||
| 85 | const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value); | ||
| 86 | self.c_macros.append(macro) catch @panic("OOM"); | ||
| 87 | } | ||
| 88 | |||
| 89 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. | ||
| 90 | pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { | ||
| 91 | self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM"); | ||
| 92 | } | ||
| 93 | |||
| 94 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 95 | const b = step.owner; | ||
| 96 | const self = @fieldParentPtr(TranslateCStep, "step", step); | ||
| 97 | |||
| 98 | var argv_list = std.ArrayList([]const u8).init(b.allocator); | ||
| 99 | try argv_list.append(b.zig_exe); | ||
| 100 | try argv_list.append("translate-c"); | ||
| 101 | try argv_list.append("-lc"); | ||
| 102 | |||
| 103 | try argv_list.append("--listen=-"); | ||
| 104 | |||
| 105 | if (!self.target.isNative()) { | ||
| 106 | try argv_list.append("-target"); | ||
| 107 | try argv_list.append(try self.target.zigTriple(b.allocator)); | ||
| 108 | } | ||
| 109 | |||
| 110 | switch (self.optimize) { | ||
| 111 | .Debug => {}, // Skip since it's the default. | ||
| 112 | else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), | ||
| 113 | } | ||
| 114 | |||
| 115 | for (self.include_dirs.items) |include_dir| { | ||
| 116 | try argv_list.append("-I"); | ||
| 117 | try argv_list.append(include_dir); | ||
| 118 | } | ||
| 119 | |||
| 120 | for (self.c_macros.items) |c_macro| { | ||
| 121 | try argv_list.append("-D"); | ||
| 122 | try argv_list.append(c_macro); | ||
| 123 | } | ||
| 124 | |||
| 125 | try argv_list.append(self.source.getPath(b)); | ||
| 126 | |||
| 127 | const output_path = try step.evalZigProcess(argv_list.items, prog_node); | ||
| 128 | |||
| 129 | self.out_basename = fs.path.basename(output_path); | ||
| 130 | const output_dir = fs.path.dirname(output_path).?; | ||
| 131 | |||
| 132 | self.output_file.path = try fs.path.join( | ||
| 133 | b.allocator, | ||
| 134 | &[_][]const u8{ output_dir, self.out_basename }, | ||
| 135 | ); | ||
| 136 | } | ||
lib/std/Build/Step/WriteFile.zig created+291| ... | @@ -0,0 +1,291 @@ | ||
| 1 | //! WriteFileStep is primarily used to create a directory in an appropriate | ||
| 2 | //! location inside the local cache which has a set of files that have either | ||
| 3 | //! been generated during the build, or are copied from the source package. | ||
| 4 | //! | ||
| 5 | //! However, this step has an additional capability of writing data to paths | ||
| 6 | //! relative to the package root, effectively mutating the package's source | ||
| 7 | //! files. Be careful with the latter functionality; it should not be used | ||
| 8 | //! during the normal build process, but as a utility run by a developer with | ||
| 9 | //! intention to update source files, which will then be committed to version | ||
| 10 | //! control. | ||
| 11 | const std = @import("std"); | ||
| 12 | const Step = std.Build.Step; | ||
| 13 | const fs = std.fs; | ||
| 14 | const ArrayList = std.ArrayList; | ||
| 15 | const WriteFileStep = @This(); | ||
| 16 | |||
| 17 | step: Step, | ||
| 18 | /// The elements here are pointers because we need stable pointers for the | ||
| 19 | /// GeneratedFile field. | ||
| 20 | files: std.ArrayListUnmanaged(*File), | ||
| 21 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), | ||
| 22 | generated_directory: std.Build.GeneratedFile, | ||
| 23 | |||
| 24 | pub const base_id = .write_file; | ||
| 25 | |||
| 26 | pub const File = struct { | ||
| 27 | generated_file: std.Build.GeneratedFile, | ||
| 28 | sub_path: []const u8, | ||
| 29 | contents: Contents, | ||
| 30 | }; | ||
| 31 | |||
| 32 | pub const OutputSourceFile = struct { | ||
| 33 | contents: Contents, | ||
| 34 | sub_path: []const u8, | ||
| 35 | }; | ||
| 36 | |||
| 37 | pub const Contents = union(enum) { | ||
| 38 | bytes: []const u8, | ||
| 39 | copy: std.Build.FileSource, | ||
| 40 | }; | ||
| 41 | |||
| 42 | pub fn create(owner: *std.Build) *WriteFileStep { | ||
| 43 | const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); | ||
| 44 | wf.* = .{ | ||
| 45 | .step = Step.init(.{ | ||
| 46 | .id = .write_file, | ||
| 47 | .name = "WriteFile", | ||
| 48 | .owner = owner, | ||
| 49 | .makeFn = make, | ||
| 50 | }), | ||
| 51 | .files = .{}, | ||
| 52 | .output_source_files = .{}, | ||
| 53 | .generated_directory = .{ .step = &wf.step }, | ||
| 54 | }; | ||
| 55 | return wf; | ||
| 56 | } | ||
| 57 | |||
| 58 | pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { | ||
| 59 | const b = wf.step.owner; | ||
| 60 | const gpa = b.allocator; | ||
| 61 | const file = gpa.create(File) catch @panic("OOM"); | ||
| 62 | file.* = .{ | ||
| 63 | .generated_file = .{ .step = &wf.step }, | ||
| 64 | .sub_path = b.dupePath(sub_path), | ||
| 65 | .contents = .{ .bytes = b.dupe(bytes) }, | ||
| 66 | }; | ||
| 67 | wf.files.append(gpa, file) catch @panic("OOM"); | ||
| 68 | |||
| 69 | wf.maybeUpdateName(); | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Place the file into the generated directory within the local cache, | ||
| 73 | /// along with all the rest of the files added to this step. The parameter | ||
| 74 | /// here is the destination path relative to the local cache directory | ||
| 75 | /// associated with this WriteFileStep. It may be a basename, or it may | ||
| 76 | /// include sub-directories, in which case this step will ensure the | ||
| 77 | /// required sub-path exists. | ||
| 78 | /// This is the option expected to be used most commonly with `addCopyFile`. | ||
| 79 | pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { | ||
| 80 | const b = wf.step.owner; | ||
| 81 | const gpa = b.allocator; | ||
| 82 | const file = gpa.create(File) catch @panic("OOM"); | ||
| 83 | file.* = .{ | ||
| 84 | .generated_file = .{ .step = &wf.step }, | ||
| 85 | .sub_path = b.dupePath(sub_path), | ||
| 86 | .contents = .{ .copy = source }, | ||
| 87 | }; | ||
| 88 | wf.files.append(gpa, file) catch @panic("OOM"); | ||
| 89 | |||
| 90 | wf.maybeUpdateName(); | ||
| 91 | source.addStepDependencies(&wf.step); | ||
| 92 | } | ||
| 93 | |||
| 94 | /// A path relative to the package root. | ||
| 95 | /// Be careful with this because it updates source files. This should not be | ||
| 96 | /// used as part of the normal build process, but as a utility occasionally | ||
| 97 | /// run by a developer with intent to modify source files and then commit | ||
| 98 | /// those changes to version control. | ||
| 99 | /// A file added this way is not available with `getFileSource`. | ||
| 100 | pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { | ||
| 101 | const b = wf.step.owner; | ||
| 102 | wf.output_source_files.append(b.allocator, .{ | ||
| 103 | .contents = .{ .copy = source }, | ||
| 104 | .sub_path = sub_path, | ||
| 105 | }) catch @panic("OOM"); | ||
| 106 | source.addStepDependencies(&wf.step); | ||
| 107 | } | ||
| 108 | |||
| 109 | /// A path relative to the package root. | ||
| 110 | /// Be careful with this because it updates source files. This should not be | ||
| 111 | /// used as part of the normal build process, but as a utility occasionally | ||
| 112 | /// run by a developer with intent to modify source files and then commit | ||
| 113 | /// those changes to version control. | ||
| 114 | /// A file added this way is not available with `getFileSource`. | ||
| 115 | pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { | ||
| 116 | const b = wf.step.owner; | ||
| 117 | wf.output_source_files.append(b.allocator, .{ | ||
| 118 | .contents = .{ .bytes = bytes }, | ||
| 119 | .sub_path = sub_path, | ||
| 120 | }) catch @panic("OOM"); | ||
| 121 | } | ||
| 122 | |||
| 123 | /// Gets a file source for the given sub_path. If the file does not exist, returns `null`. | ||
| 124 | pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { | ||
| 125 | for (wf.files.items) |file| { | ||
| 126 | if (std.mem.eql(u8, file.sub_path, sub_path)) { | ||
| 127 | return .{ .generated = &file.generated_file }; | ||
| 128 | } | ||
| 129 | } | ||
| 130 | return null; | ||
| 131 | } | ||
| 132 | |||
| 133 | /// Returns a `FileSource` representing the base directory that contains all the | ||
| 134 | /// files from this `WriteFileStep`. | ||
| 135 | pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { | ||
| 136 | return .{ .generated = &wf.generated_directory }; | ||
| 137 | } | ||
| 138 | |||
| 139 | fn maybeUpdateName(wf: *WriteFileStep) void { | ||
| 140 | if (wf.files.items.len == 1) { | ||
| 141 | // First time adding a file; update name. | ||
| 142 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { | ||
| 143 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 149 | _ = prog_node; | ||
| 150 | const b = step.owner; | ||
| 151 | const wf = @fieldParentPtr(WriteFileStep, "step", step); | ||
| 152 | |||
| 153 | // Writing to source files is kind of an extra capability of this | ||
| 154 | // WriteFileStep - arguably it should be a different step. But anyway here | ||
| 155 | // it is, it happens unconditionally and does not interact with the other | ||
| 156 | // files here. | ||
| 157 | var any_miss = false; | ||
| 158 | for (wf.output_source_files.items) |output_source_file| { | ||
| 159 | if (fs.path.dirname(output_source_file.sub_path)) |dirname| { | ||
| 160 | b.build_root.handle.makePath(dirname) catch |err| { | ||
| 161 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 162 | b.build_root, dirname, @errorName(err), | ||
| 163 | }); | ||
| 164 | }; | ||
| 165 | } | ||
| 166 | switch (output_source_file.contents) { | ||
| 167 | .bytes => |bytes| { | ||
| 168 | b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { | ||
| 169 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 170 | b.build_root, output_source_file.sub_path, @errorName(err), | ||
| 171 | }); | ||
| 172 | }; | ||
| 173 | any_miss = true; | ||
| 174 | }, | ||
| 175 | .copy => |file_source| { | ||
| 176 | const source_path = file_source.getPath(b); | ||
| 177 | const prev_status = fs.Dir.updateFile( | ||
| 178 | fs.cwd(), | ||
| 179 | source_path, | ||
| 180 | b.build_root.handle, | ||
| 181 | output_source_file.sub_path, | ||
| 182 | .{}, | ||
| 183 | ) catch |err| { | ||
| 184 | return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ | ||
| 185 | source_path, b.build_root, output_source_file.sub_path, @errorName(err), | ||
| 186 | }); | ||
| 187 | }; | ||
| 188 | any_miss = any_miss or prev_status == .stale; | ||
| 189 | }, | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | // The cache is used here not really as a way to speed things up - because writing | ||
| 194 | // the data to a file would probably be very fast - but as a way to find a canonical | ||
| 195 | // location to put build artifacts. | ||
| 196 | |||
| 197 | // If, for example, a hard-coded path was used as the location to put WriteFileStep | ||
| 198 | // files, then two WriteFileSteps executing in parallel might clobber each other. | ||
| 199 | |||
| 200 | var man = b.cache.obtain(); | ||
| 201 | defer man.deinit(); | ||
| 202 | |||
| 203 | // Random bytes to make WriteFileStep unique. Refresh this with | ||
| 204 | // new random bytes when WriteFileStep implementation is modified | ||
| 205 | // in a non-backwards-compatible way. | ||
| 206 | man.hash.add(@as(u32, 0xd767ee59)); | ||
| 207 | |||
| 208 | for (wf.files.items) |file| { | ||
| 209 | man.hash.addBytes(file.sub_path); | ||
| 210 | switch (file.contents) { | ||
| 211 | .bytes => |bytes| { | ||
| 212 | man.hash.addBytes(bytes); | ||
| 213 | }, | ||
| 214 | .copy => |file_source| { | ||
| 215 | _ = try man.addFile(file_source.getPath(b), null); | ||
| 216 | }, | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | if (try step.cacheHit(&man)) { | ||
| 221 | const digest = man.final(); | ||
| 222 | for (wf.files.items) |file| { | ||
| 223 | file.generated_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 224 | "o", &digest, file.sub_path, | ||
| 225 | }); | ||
| 226 | } | ||
| 227 | wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | ||
| 228 | return; | ||
| 229 | } | ||
| 230 | |||
| 231 | const digest = man.final(); | ||
| 232 | const cache_path = "o" ++ fs.path.sep_str ++ digest; | ||
| 233 | |||
| 234 | wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | ||
| 235 | |||
| 236 | var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { | ||
| 237 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 238 | b.cache_root, cache_path, @errorName(err), | ||
| 239 | }); | ||
| 240 | }; | ||
| 241 | defer cache_dir.close(); | ||
| 242 | |||
| 243 | for (wf.files.items) |file| { | ||
| 244 | if (fs.path.dirname(file.sub_path)) |dirname| { | ||
| 245 | cache_dir.makePath(dirname) catch |err| { | ||
| 246 | return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ | ||
| 247 | b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), | ||
| 248 | }); | ||
| 249 | }; | ||
| 250 | } | ||
| 251 | switch (file.contents) { | ||
| 252 | .bytes => |bytes| { | ||
| 253 | cache_dir.writeFile(file.sub_path, bytes) catch |err| { | ||
| 254 | return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ | ||
| 255 | b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), | ||
| 256 | }); | ||
| 257 | }; | ||
| 258 | }, | ||
| 259 | .copy => |file_source| { | ||
| 260 | const source_path = file_source.getPath(b); | ||
| 261 | const prev_status = fs.Dir.updateFile( | ||
| 262 | fs.cwd(), | ||
| 263 | source_path, | ||
| 264 | cache_dir, | ||
| 265 | file.sub_path, | ||
| 266 | .{}, | ||
| 267 | ) catch |err| { | ||
| 268 | return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ | ||
| 269 | source_path, | ||
| 270 | b.cache_root, | ||
| 271 | cache_path, | ||
| 272 | fs.path.sep, | ||
| 273 | file.sub_path, | ||
| 274 | @errorName(err), | ||
| 275 | }); | ||
| 276 | }; | ||
| 277 | // At this point we already will mark the step as a cache miss. | ||
| 278 | // But this is kind of a partial cache hit since individual | ||
| 279 | // file copies may be avoided. Oh well, this information is | ||
| 280 | // discarded. | ||
| 281 | _ = prev_status; | ||
| 282 | }, | ||
| 283 | } | ||
| 284 | |||
| 285 | file.generated_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 286 | cache_path, file.sub_path, | ||
| 287 | }); | ||
| 288 | } | ||
| 289 | |||
| 290 | try step.writeManifest(&man); | ||
| 291 | } | ||
lib/std/Build/TranslateCStep.zig deleted-136| ... | @@ -1,136 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const Step = std.Build.Step; | ||
| 3 | const CompileStep = std.Build.CompileStep; | ||
| 4 | const CheckFileStep = std.Build.CheckFileStep; | ||
| 5 | const fs = std.fs; | ||
| 6 | const mem = std.mem; | ||
| 7 | const CrossTarget = std.zig.CrossTarget; | ||
| 8 | |||
| 9 | const TranslateCStep = @This(); | ||
| 10 | |||
| 11 | pub const base_id = .translate_c; | ||
| 12 | |||
| 13 | step: Step, | ||
| 14 | source: std.Build.FileSource, | ||
| 15 | include_dirs: std.ArrayList([]const u8), | ||
| 16 | c_macros: std.ArrayList([]const u8), | ||
| 17 | out_basename: []const u8, | ||
| 18 | target: CrossTarget, | ||
| 19 | optimize: std.builtin.OptimizeMode, | ||
| 20 | output_file: std.Build.GeneratedFile, | ||
| 21 | |||
| 22 | pub const Options = struct { | ||
| 23 | source_file: std.Build.FileSource, | ||
| 24 | target: CrossTarget, | ||
| 25 | optimize: std.builtin.OptimizeMode, | ||
| 26 | }; | ||
| 27 | |||
| 28 | pub fn create(owner: *std.Build, options: Options) *TranslateCStep { | ||
| 29 | const self = owner.allocator.create(TranslateCStep) catch @panic("OOM"); | ||
| 30 | const source = options.source_file.dupe(owner); | ||
| 31 | self.* = TranslateCStep{ | ||
| 32 | .step = Step.init(.{ | ||
| 33 | .id = .translate_c, | ||
| 34 | .name = "translate-c", | ||
| 35 | .owner = owner, | ||
| 36 | .makeFn = make, | ||
| 37 | }), | ||
| 38 | .source = source, | ||
| 39 | .include_dirs = std.ArrayList([]const u8).init(owner.allocator), | ||
| 40 | .c_macros = std.ArrayList([]const u8).init(owner.allocator), | ||
| 41 | .out_basename = undefined, | ||
| 42 | .target = options.target, | ||
| 43 | .optimize = options.optimize, | ||
| 44 | .output_file = std.Build.GeneratedFile{ .step = &self.step }, | ||
| 45 | }; | ||
| 46 | source.addStepDependencies(&self.step); | ||
| 47 | return self; | ||
| 48 | } | ||
| 49 | |||
| 50 | pub const AddExecutableOptions = struct { | ||
| 51 | name: ?[]const u8 = null, | ||
| 52 | version: ?std.builtin.Version = null, | ||
| 53 | target: ?CrossTarget = null, | ||
| 54 | optimize: ?std.builtin.Mode = null, | ||
| 55 | linkage: ?CompileStep.Linkage = null, | ||
| 56 | }; | ||
| 57 | |||
| 58 | /// Creates a step to build an executable from the translated source. | ||
| 59 | pub fn addExecutable(self: *TranslateCStep, options: AddExecutableOptions) *CompileStep { | ||
| 60 | return self.step.owner.addExecutable(.{ | ||
| 61 | .root_source_file = .{ .generated = &self.output_file }, | ||
| 62 | .name = options.name orelse "translated_c", | ||
| 63 | .version = options.version, | ||
| 64 | .target = options.target orelse self.target, | ||
| 65 | .optimize = options.optimize orelse self.optimize, | ||
| 66 | .linkage = options.linkage, | ||
| 67 | }); | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn addIncludeDir(self: *TranslateCStep, include_dir: []const u8) void { | ||
| 71 | self.include_dirs.append(self.step.owner.dupePath(include_dir)) catch @panic("OOM"); | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn addCheckFile(self: *TranslateCStep, expected_matches: []const []const u8) *CheckFileStep { | ||
| 75 | return CheckFileStep.create( | ||
| 76 | self.step.owner, | ||
| 77 | .{ .generated = &self.output_file }, | ||
| 78 | .{ .expected_matches = expected_matches }, | ||
| 79 | ); | ||
| 80 | } | ||
| 81 | |||
| 82 | /// If the value is omitted, it is set to 1. | ||
| 83 | /// `name` and `value` need not live longer than the function call. | ||
| 84 | pub fn defineCMacro(self: *TranslateCStep, name: []const u8, value: ?[]const u8) void { | ||
| 85 | const macro = std.Build.constructCMacro(self.step.owner.allocator, name, value); | ||
| 86 | self.c_macros.append(macro) catch @panic("OOM"); | ||
| 87 | } | ||
| 88 | |||
| 89 | /// name_and_value looks like [name]=[value]. If the value is omitted, it is set to 1. | ||
| 90 | pub fn defineCMacroRaw(self: *TranslateCStep, name_and_value: []const u8) void { | ||
| 91 | self.c_macros.append(self.step.owner.dupe(name_and_value)) catch @panic("OOM"); | ||
| 92 | } | ||
| 93 | |||
| 94 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 95 | const b = step.owner; | ||
| 96 | const self = @fieldParentPtr(TranslateCStep, "step", step); | ||
| 97 | |||
| 98 | var argv_list = std.ArrayList([]const u8).init(b.allocator); | ||
| 99 | try argv_list.append(b.zig_exe); | ||
| 100 | try argv_list.append("translate-c"); | ||
| 101 | try argv_list.append("-lc"); | ||
| 102 | |||
| 103 | try argv_list.append("--listen=-"); | ||
| 104 | |||
| 105 | if (!self.target.isNative()) { | ||
| 106 | try argv_list.append("-target"); | ||
| 107 | try argv_list.append(try self.target.zigTriple(b.allocator)); | ||
| 108 | } | ||
| 109 | |||
| 110 | switch (self.optimize) { | ||
| 111 | .Debug => {}, // Skip since it's the default. | ||
| 112 | else => try argv_list.append(b.fmt("-O{s}", .{@tagName(self.optimize)})), | ||
| 113 | } | ||
| 114 | |||
| 115 | for (self.include_dirs.items) |include_dir| { | ||
| 116 | try argv_list.append("-I"); | ||
| 117 | try argv_list.append(include_dir); | ||
| 118 | } | ||
| 119 | |||
| 120 | for (self.c_macros.items) |c_macro| { | ||
| 121 | try argv_list.append("-D"); | ||
| 122 | try argv_list.append(c_macro); | ||
| 123 | } | ||
| 124 | |||
| 125 | try argv_list.append(self.source.getPath(b)); | ||
| 126 | |||
| 127 | const output_path = try step.evalZigProcess(argv_list.items, prog_node); | ||
| 128 | |||
| 129 | self.out_basename = fs.path.basename(output_path); | ||
| 130 | const output_dir = fs.path.dirname(output_path).?; | ||
| 131 | |||
| 132 | self.output_file.path = try fs.path.join( | ||
| 133 | b.allocator, | ||
| 134 | &[_][]const u8{ output_dir, self.out_basename }, | ||
| 135 | ); | ||
| 136 | } | ||
lib/std/Build/WriteFileStep.zig deleted-293| ... | @@ -1,293 +0,0 @@ | ||
| 1 | //! WriteFileStep is primarily used to create a directory in an appropriate | ||
| 2 | //! location inside the local cache which has a set of files that have either | ||
| 3 | //! been generated during the build, or are copied from the source package. | ||
| 4 | //! | ||
| 5 | //! However, this step has an additional capability of writing data to paths | ||
| 6 | //! relative to the package root, effectively mutating the package's source | ||
| 7 | //! files. Be careful with the latter functionality; it should not be used | ||
| 8 | //! during the normal build process, but as a utility run by a developer with | ||
| 9 | //! intention to update source files, which will then be committed to version | ||
| 10 | //! control. | ||
| 11 | |||
| 12 | step: Step, | ||
| 13 | /// The elements here are pointers because we need stable pointers for the | ||
| 14 | /// GeneratedFile field. | ||
| 15 | files: std.ArrayListUnmanaged(*File), | ||
| 16 | output_source_files: std.ArrayListUnmanaged(OutputSourceFile), | ||
| 17 | generated_directory: std.Build.GeneratedFile, | ||
| 18 | |||
| 19 | pub const base_id = .write_file; | ||
| 20 | |||
| 21 | pub const File = struct { | ||
| 22 | generated_file: std.Build.GeneratedFile, | ||
| 23 | sub_path: []const u8, | ||
| 24 | contents: Contents, | ||
| 25 | }; | ||
| 26 | |||
| 27 | pub const OutputSourceFile = struct { | ||
| 28 | contents: Contents, | ||
| 29 | sub_path: []const u8, | ||
| 30 | }; | ||
| 31 | |||
| 32 | pub const Contents = union(enum) { | ||
| 33 | bytes: []const u8, | ||
| 34 | copy: std.Build.FileSource, | ||
| 35 | }; | ||
| 36 | |||
| 37 | pub fn create(owner: *std.Build) *WriteFileStep { | ||
| 38 | const wf = owner.allocator.create(WriteFileStep) catch @panic("OOM"); | ||
| 39 | wf.* = .{ | ||
| 40 | .step = Step.init(.{ | ||
| 41 | .id = .write_file, | ||
| 42 | .name = "WriteFile", | ||
| 43 | .owner = owner, | ||
| 44 | .makeFn = make, | ||
| 45 | }), | ||
| 46 | .files = .{}, | ||
| 47 | .output_source_files = .{}, | ||
| 48 | .generated_directory = .{ .step = &wf.step }, | ||
| 49 | }; | ||
| 50 | return wf; | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn add(wf: *WriteFileStep, sub_path: []const u8, bytes: []const u8) void { | ||
| 54 | const b = wf.step.owner; | ||
| 55 | const gpa = b.allocator; | ||
| 56 | const file = gpa.create(File) catch @panic("OOM"); | ||
| 57 | file.* = .{ | ||
| 58 | .generated_file = .{ .step = &wf.step }, | ||
| 59 | .sub_path = b.dupePath(sub_path), | ||
| 60 | .contents = .{ .bytes = b.dupe(bytes) }, | ||
| 61 | }; | ||
| 62 | wf.files.append(gpa, file) catch @panic("OOM"); | ||
| 63 | |||
| 64 | wf.maybeUpdateName(); | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Place the file into the generated directory within the local cache, | ||
| 68 | /// along with all the rest of the files added to this step. The parameter | ||
| 69 | /// here is the destination path relative to the local cache directory | ||
| 70 | /// associated with this WriteFileStep. It may be a basename, or it may | ||
| 71 | /// include sub-directories, in which case this step will ensure the | ||
| 72 | /// required sub-path exists. | ||
| 73 | /// This is the option expected to be used most commonly with `addCopyFile`. | ||
| 74 | pub fn addCopyFile(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { | ||
| 75 | const b = wf.step.owner; | ||
| 76 | const gpa = b.allocator; | ||
| 77 | const file = gpa.create(File) catch @panic("OOM"); | ||
| 78 | file.* = .{ | ||
| 79 | .generated_file = .{ .step = &wf.step }, | ||
| 80 | .sub_path = b.dupePath(sub_path), | ||
| 81 | .contents = .{ .copy = source }, | ||
| 82 | }; | ||
| 83 | wf.files.append(gpa, file) catch @panic("OOM"); | ||
| 84 | |||
| 85 | wf.maybeUpdateName(); | ||
| 86 | source.addStepDependencies(&wf.step); | ||
| 87 | } | ||
| 88 | |||
| 89 | /// A path relative to the package root. | ||
| 90 | /// Be careful with this because it updates source files. This should not be | ||
| 91 | /// used as part of the normal build process, but as a utility occasionally | ||
| 92 | /// run by a developer with intent to modify source files and then commit | ||
| 93 | /// those changes to version control. | ||
| 94 | /// A file added this way is not available with `getFileSource`. | ||
| 95 | pub fn addCopyFileToSource(wf: *WriteFileStep, source: std.Build.FileSource, sub_path: []const u8) void { | ||
| 96 | const b = wf.step.owner; | ||
| 97 | wf.output_source_files.append(b.allocator, .{ | ||
| 98 | .contents = .{ .copy = source }, | ||
| 99 | .sub_path = sub_path, | ||
| 100 | }) catch @panic("OOM"); | ||
| 101 | source.addStepDependencies(&wf.step); | ||
| 102 | } | ||
| 103 | |||
| 104 | /// A path relative to the package root. | ||
| 105 | /// Be careful with this because it updates source files. This should not be | ||
| 106 | /// used as part of the normal build process, but as a utility occasionally | ||
| 107 | /// run by a developer with intent to modify source files and then commit | ||
| 108 | /// those changes to version control. | ||
| 109 | /// A file added this way is not available with `getFileSource`. | ||
| 110 | pub fn addBytesToSource(wf: *WriteFileStep, bytes: []const u8, sub_path: []const u8) void { | ||
| 111 | const b = wf.step.owner; | ||
| 112 | wf.output_source_files.append(b.allocator, .{ | ||
| 113 | .contents = .{ .bytes = bytes }, | ||
| 114 | .sub_path = sub_path, | ||
| 115 | }) catch @panic("OOM"); | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Gets a file source for the given sub_path. If the file does not exist, returns `null`. | ||
| 119 | pub fn getFileSource(wf: *WriteFileStep, sub_path: []const u8) ?std.Build.FileSource { | ||
| 120 | for (wf.files.items) |file| { | ||
| 121 | if (std.mem.eql(u8, file.sub_path, sub_path)) { | ||
| 122 | return .{ .generated = &file.generated_file }; | ||
| 123 | } | ||
| 124 | } | ||
| 125 | return null; | ||
| 126 | } | ||
| 127 | |||
| 128 | /// Returns a `FileSource` representing the base directory that contains all the | ||
| 129 | /// files from this `WriteFileStep`. | ||
| 130 | pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource { | ||
| 131 | return .{ .generated = &wf.generated_directory }; | ||
| 132 | } | ||
| 133 | |||
| 134 | fn maybeUpdateName(wf: *WriteFileStep) void { | ||
| 135 | if (wf.files.items.len == 1) { | ||
| 136 | // First time adding a file; update name. | ||
| 137 | if (std.mem.eql(u8, wf.step.name, "WriteFile")) { | ||
| 138 | wf.step.name = wf.step.owner.fmt("WriteFile {s}", .{wf.files.items[0].sub_path}); | ||
| 139 | } | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | fn make(step: *Step, prog_node: *std.Progress.Node) !void { | ||
| 144 | _ = prog_node; | ||
| 145 | const b = step.owner; | ||
| 146 | const wf = @fieldParentPtr(WriteFileStep, "step", step); | ||
| 147 | |||
| 148 | // Writing to source files is kind of an extra capability of this | ||
| 149 | // WriteFileStep - arguably it should be a different step. But anyway here | ||
| 150 | // it is, it happens unconditionally and does not interact with the other | ||
| 151 | // files here. | ||
| 152 | var any_miss = false; | ||
| 153 | for (wf.output_source_files.items) |output_source_file| { | ||
| 154 | if (fs.path.dirname(output_source_file.sub_path)) |dirname| { | ||
| 155 | b.build_root.handle.makePath(dirname) catch |err| { | ||
| 156 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 157 | b.build_root, dirname, @errorName(err), | ||
| 158 | }); | ||
| 159 | }; | ||
| 160 | } | ||
| 161 | switch (output_source_file.contents) { | ||
| 162 | .bytes => |bytes| { | ||
| 163 | b.build_root.handle.writeFile(output_source_file.sub_path, bytes) catch |err| { | ||
| 164 | return step.fail("unable to write file '{}{s}': {s}", .{ | ||
| 165 | b.build_root, output_source_file.sub_path, @errorName(err), | ||
| 166 | }); | ||
| 167 | }; | ||
| 168 | any_miss = true; | ||
| 169 | }, | ||
| 170 | .copy => |file_source| { | ||
| 171 | const source_path = file_source.getPath(b); | ||
| 172 | const prev_status = fs.Dir.updateFile( | ||
| 173 | fs.cwd(), | ||
| 174 | source_path, | ||
| 175 | b.build_root.handle, | ||
| 176 | output_source_file.sub_path, | ||
| 177 | .{}, | ||
| 178 | ) catch |err| { | ||
| 179 | return step.fail("unable to update file from '{s}' to '{}{s}': {s}", .{ | ||
| 180 | source_path, b.build_root, output_source_file.sub_path, @errorName(err), | ||
| 181 | }); | ||
| 182 | }; | ||
| 183 | any_miss = any_miss or prev_status == .stale; | ||
| 184 | }, | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | // The cache is used here not really as a way to speed things up - because writing | ||
| 189 | // the data to a file would probably be very fast - but as a way to find a canonical | ||
| 190 | // location to put build artifacts. | ||
| 191 | |||
| 192 | // If, for example, a hard-coded path was used as the location to put WriteFileStep | ||
| 193 | // files, then two WriteFileSteps executing in parallel might clobber each other. | ||
| 194 | |||
| 195 | var man = b.cache.obtain(); | ||
| 196 | defer man.deinit(); | ||
| 197 | |||
| 198 | // Random bytes to make WriteFileStep unique. Refresh this with | ||
| 199 | // new random bytes when WriteFileStep implementation is modified | ||
| 200 | // in a non-backwards-compatible way. | ||
| 201 | man.hash.add(@as(u32, 0xd767ee59)); | ||
| 202 | |||
| 203 | for (wf.files.items) |file| { | ||
| 204 | man.hash.addBytes(file.sub_path); | ||
| 205 | switch (file.contents) { | ||
| 206 | .bytes => |bytes| { | ||
| 207 | man.hash.addBytes(bytes); | ||
| 208 | }, | ||
| 209 | .copy => |file_source| { | ||
| 210 | _ = try man.addFile(file_source.getPath(b), null); | ||
| 211 | }, | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | if (try step.cacheHit(&man)) { | ||
| 216 | const digest = man.final(); | ||
| 217 | for (wf.files.items) |file| { | ||
| 218 | file.generated_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 219 | "o", &digest, file.sub_path, | ||
| 220 | }); | ||
| 221 | } | ||
| 222 | wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | ||
| 223 | return; | ||
| 224 | } | ||
| 225 | |||
| 226 | const digest = man.final(); | ||
| 227 | const cache_path = "o" ++ fs.path.sep_str ++ digest; | ||
| 228 | |||
| 229 | wf.generated_directory.path = try b.cache_root.join(b.allocator, &.{ "o", &digest }); | ||
| 230 | |||
| 231 | var cache_dir = b.cache_root.handle.makeOpenPath(cache_path, .{}) catch |err| { | ||
| 232 | return step.fail("unable to make path '{}{s}': {s}", .{ | ||
| 233 | b.cache_root, cache_path, @errorName(err), | ||
| 234 | }); | ||
| 235 | }; | ||
| 236 | defer cache_dir.close(); | ||
| 237 | |||
| 238 | for (wf.files.items) |file| { | ||
| 239 | if (fs.path.dirname(file.sub_path)) |dirname| { | ||
| 240 | cache_dir.makePath(dirname) catch |err| { | ||
| 241 | return step.fail("unable to make path '{}{s}{c}{s}': {s}", .{ | ||
| 242 | b.cache_root, cache_path, fs.path.sep, dirname, @errorName(err), | ||
| 243 | }); | ||
| 244 | }; | ||
| 245 | } | ||
| 246 | switch (file.contents) { | ||
| 247 | .bytes => |bytes| { | ||
| 248 | cache_dir.writeFile(file.sub_path, bytes) catch |err| { | ||
| 249 | return step.fail("unable to write file '{}{s}{c}{s}': {s}", .{ | ||
| 250 | b.cache_root, cache_path, fs.path.sep, file.sub_path, @errorName(err), | ||
| 251 | }); | ||
| 252 | }; | ||
| 253 | }, | ||
| 254 | .copy => |file_source| { | ||
| 255 | const source_path = file_source.getPath(b); | ||
| 256 | const prev_status = fs.Dir.updateFile( | ||
| 257 | fs.cwd(), | ||
| 258 | source_path, | ||
| 259 | cache_dir, | ||
| 260 | file.sub_path, | ||
| 261 | .{}, | ||
| 262 | ) catch |err| { | ||
| 263 | return step.fail("unable to update file from '{s}' to '{}{s}{c}{s}': {s}", .{ | ||
| 264 | source_path, | ||
| 265 | b.cache_root, | ||
| 266 | cache_path, | ||
| 267 | fs.path.sep, | ||
| 268 | file.sub_path, | ||
| 269 | @errorName(err), | ||
| 270 | }); | ||
| 271 | }; | ||
| 272 | // At this point we already will mark the step as a cache miss. | ||
| 273 | // But this is kind of a partial cache hit since individual | ||
| 274 | // file copies may be avoided. Oh well, this information is | ||
| 275 | // discarded. | ||
| 276 | _ = prev_status; | ||
| 277 | }, | ||
| 278 | } | ||
| 279 | |||
| 280 | file.generated_file.path = try b.cache_root.join(b.allocator, &.{ | ||
| 281 | cache_path, file.sub_path, | ||
| 282 | }); | ||
| 283 | } | ||
| 284 | |||
| 285 | try step.writeManifest(&man); | ||
| 286 | } | ||
| 287 | |||
| 288 | const std = @import("../std.zig"); | ||
| 289 | const Step = std.Build.Step; | ||
| 290 | const fs = std.fs; | ||
| 291 | const ArrayList = std.ArrayList; | ||
| 292 | |||
| 293 | const WriteFileStep = @This(); | ||