authorgravatar for nicolas@sterchelen.netNicolas Sterchele <nicolas@sterchelen.net> 2023-03-20 09:23:10+01:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-05-03 08:39:24+03:00
log13eb7251d37759bd47403db304c6120c706fe353
tree225d87ef968270968379e2d58b9791b0aa152aa7
parent855493bb8b395970921494d3a11ccfeaac30c2dc

build: rename std.Build.*Step to std.Build.Step.*

Follow-up actions from #14647 Fixes #14947

29 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;
29pub const InstallDirectoryOptions = InstallDirStep.Options;29pub const InstallDirectoryOptions = InstallDirStep.Options;
3030
31pub const Step = @import("Build/Step.zig");31pub const Step = @import("Build/Step.zig");
32pub const CheckFileStep = @import("Build/CheckFileStep.zig");32pub const CheckFileStep = @import("Build/Step/CheckFile.zig");
33pub const CheckObjectStep = @import("Build/CheckObjectStep.zig");33pub const CheckObjectStep = @import("Build/Step/CheckObject.zig");
34pub const ConfigHeaderStep = @import("Build/ConfigHeaderStep.zig");34pub const ConfigHeaderStep = @import("Build/Step/ConfigHeader.zig");
35pub const FmtStep = @import("Build/FmtStep.zig");35pub const FmtStep = @import("Build/Step/Fmt.zig");
36pub const InstallArtifactStep = @import("Build/InstallArtifactStep.zig");36pub const InstallArtifactStep = @import("Build/Step/InstallArtifact.zig");
37pub const InstallDirStep = @import("Build/InstallDirStep.zig");37pub const InstallDirStep = @import("Build/Step/InstallDir.zig");
38pub const InstallFileStep = @import("Build/InstallFileStep.zig");38pub const InstallFileStep = @import("Build/Step/InstallFile.zig");
39pub const ObjCopyStep = @import("Build/ObjCopyStep.zig");39pub const ObjCopyStep = @import("Build/Step/ObjCopy.zig");
40pub const CompileStep = @import("Build/CompileStep.zig");40pub const CompileStep = @import("Build/Step/Compile.zig");
41pub const OptionsStep = @import("Build/OptionsStep.zig");41pub const OptionsStep = @import("Build/Step/Options.zig");
42pub const RemoveDirStep = @import("Build/RemoveDirStep.zig");42pub const RemoveDirStep = @import("Build/Step/RemoveDir.zig");
43pub const RunStep = @import("Build/RunStep.zig");43pub const RunStep = @import("Build/Step/Run.zig");
44pub const TranslateCStep = @import("Build/TranslateCStep.zig");44pub const TranslateCStep = @import("Build/Step/TranslateC.zig");
45pub const WriteFileStep = @import("Build/WriteFileStep.zig");45pub const WriteFileStep = @import("Build/Step/WriteFile.zig");
4646
47install_tls: TopLevelStep,47install_tls: TopLevelStep,
48uninstall_tls: TopLevelStep,48uninstall_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
6step: Step,
7expected_matches: []const []const u8,
8expected_exact: ?[]const u8,
9source: std.Build.FileSource,
10max_bytes: usize = 20 * 1024 * 1024,
11
12pub const base_id = .check_file;
13
14pub const Options = struct {
15 expected_matches: []const []const u8 = &.{},
16 expected_exact: ?[]const u8 = null,
17};
18
19pub 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
40pub fn setName(self: *CheckFileStep, name: []const u8) void {
41 self.step.name = name;
42}
43
44fn 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
84const CheckFileStep = @This();
85const std = @import("../std.zig");
86const Step = std.Build.Step;
87const fs = std.fs;
88const mem = std.mem;
lib/std/Build/CheckObjectStep.zig deleted-1055
...@@ -1,1055 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const fs = std.fs;
4const macho = std.macho;
5const math = std.math;
6const mem = std.mem;
7const testing = std.testing;
8
9const CheckObjectStep = @This();
10
11const Allocator = mem.Allocator;
12const Step = std.Build.Step;
13
14pub const base_id = .check_object;
15
16step: Step,
17source: std.Build.FileSource,
18max_bytes: usize = 20 * 1024 * 1024,
19checks: std.ArrayList(Check),
20dump_symtab: bool = false,
21obj_format: std.Target.ObjectFormat,
22
23pub 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.
50pub 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
61const 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 +`.
83const 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
221const 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
244const 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.
277pub 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.
285pub 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()`.
293pub 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.
306pub 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.
315pub 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.
327pub 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
337fn 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
426const Opts = struct {
427 dump_symtab: bool = false,
428};
429
430const 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
714const 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 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const mem = std.mem;
4const fs = std.fs;
5const assert = std.debug.assert;
6const panic = std.debug.panic;
7const ArrayList = std.ArrayList;
8const StringHashMap = std.StringHashMap;
9const Sha256 = std.crypto.hash.sha2.Sha256;
10const Allocator = mem.Allocator;
11const Step = std.Build.Step;
12const CrossTarget = std.zig.CrossTarget;
13const NativeTargetInfo = std.zig.system.NativeTargetInfo;
14const FileSource = std.Build.FileSource;
15const PkgConfigPkg = std.Build.PkgConfigPkg;
16const PkgConfigError = std.Build.PkgConfigError;
17const ExecError = std.Build.ExecError;
18const Module = std.Build.Module;
19const VcpkgRoot = std.Build.VcpkgRoot;
20const InstallDir = std.Build.InstallDir;
21const InstallArtifactStep = std.Build.InstallArtifactStep;
22const GeneratedFile = std.Build.GeneratedFile;
23const ObjCopyStep = std.Build.ObjCopyStep;
24const CheckObjectStep = std.Build.CheckObjectStep;
25const RunStep = std.Build.RunStep;
26const OptionsStep = std.Build.OptionsStep;
27const ConfigHeaderStep = std.Build.ConfigHeaderStep;
28const CompileStep = @This();
29
30pub const base_id: Step.Id = .compile;
31
32step: Step,
33name: []const u8,
34target: CrossTarget,
35target_info: NativeTargetInfo,
36optimize: std.builtin.Mode,
37linker_script: ?FileSource = null,
38version_script: ?[]const u8 = null,
39out_filename: []const u8,
40linkage: ?Linkage = null,
41version: ?std.builtin.Version,
42kind: Kind,
43major_only_filename: ?[]const u8,
44name_only_filename: ?[]const u8,
45strip: ?bool,
46unwind_tables: ?bool,
47// keep in sync with src/link.zig:CompressDebugSections
48compress_debug_sections: enum { none, zlib } = .none,
49lib_paths: ArrayList(FileSource),
50rpaths: ArrayList(FileSource),
51framework_dirs: ArrayList(FileSource),
52frameworks: StringHashMap(FrameworkLinkInfo),
53verbose_link: bool,
54verbose_cc: bool,
55emit_analysis: EmitOption = .default,
56emit_asm: EmitOption = .default,
57emit_bin: EmitOption = .default,
58emit_docs: EmitOption = .default,
59emit_implib: EmitOption = .default,
60emit_llvm_bc: EmitOption = .default,
61emit_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.
64emit_h: bool = false,
65bundle_compiler_rt: ?bool = null,
66single_threaded: ?bool,
67stack_protector: ?bool = null,
68disable_stack_probing: bool,
69disable_sanitize_c: bool,
70sanitize_thread: bool,
71rdynamic: bool,
72dwarf_format: ?std.dwarf.Format = null,
73import_memory: bool = false,
74/// For WebAssembly targets, this will allow for undefined symbols to
75/// be imported from the host environment.
76import_symbols: bool = false,
77import_table: bool = false,
78export_table: bool = false,
79initial_memory: ?u64 = null,
80max_memory: ?u64 = null,
81shared_memory: bool = false,
82global_base: ?u64 = null,
83c_std: std.Build.CStd,
84zig_lib_dir: ?[]const u8,
85main_pkg_path: ?[]const u8,
86exec_cmd_args: ?[]const ?[]const u8,
87filter: ?[]const u8,
88test_evented_io: bool = false,
89test_runner: ?[]const u8,
90code_model: std.builtin.CodeModel = .default,
91wasi_exec_model: ?std.builtin.WasiExecModel = null,
92/// Symbols to be exported when compiling to wasm
93export_symbol_names: []const []const u8 = &.{},
94
95root_src: ?FileSource,
96out_h_filename: []const u8,
97out_lib_filename: []const u8,
98out_pdb_filename: []const u8,
99modules: std.StringArrayHashMap(*Module),
100
101link_objects: ArrayList(LinkObject),
102include_dirs: ArrayList(IncludeDir),
103c_macros: ArrayList([]const u8),
104installed_headers: ArrayList(*Step),
105is_linking_libc: bool,
106is_linking_libcpp: bool,
107vcpkg_bin_path: ?[]const u8 = null,
108
109/// This may be set in order to override the default install directory
110override_dest_dir: ?InstallDir,
111installed_path: ?[]const u8,
112
113/// Base address for an executable image.
114image_base: ?u64 = null,
115
116libc_file: ?FileSource = null,
117
118valgrind_support: ?bool = null,
119each_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.
125build_id: ?bool = null,
126
127/// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF
128/// file.
129link_eh_frame_hdr: bool = false,
130link_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.
134link_function_sections: bool = false,
135
136/// Remove functions and data that are unreachable by the entry point or
137/// exported symbols.
138link_gc_sections: ?bool = null,
139
140/// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument.
141linker_dynamicbase: bool = true,
142
143linker_allow_shlib_undefined: ?bool = null,
144
145/// Permit read-only relocations in read-only segments. Disallowed by default.
146link_z_notext: bool = false,
147
148/// Force all relocations to be read-only after processing.
149link_z_relro: bool = true,
150
151/// Allow relocations to be lazily processed after load.
152link_z_lazy: bool = false,
153
154/// Common page size
155link_z_common_page_size: ?u64 = null,
156
157/// Maximum page size
158link_z_max_page_size: ?u64 = null,
159
160/// (Darwin) Install name for the dylib
161install_name: ?[]const u8 = null,
162
163/// (Darwin) Path to entitlements file
164entitlements: ?[]const u8 = null,
165
166/// (Darwin) Size of the pagezero segment.
167pagezero_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.
174search_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.
178headerpad_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.
182headerpad_max_install_names: bool = false,
183
184/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
185dead_strip_dylibs: bool = false,
186
187/// Position Independent Code
188force_pic: ?bool = null,
189
190/// Position Independent Executable
191pie: ?bool = null,
192
193red_zone: ?bool = null,
194
195omit_frame_pointer: ?bool = null,
196dll_export_fns: ?bool = null,
197
198subsystem: ?std.Target.SubSystem = null,
199
200entry_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.
205force_undefined_symbols: std.StringHashMap(void),
206
207/// Overrides the default stack size
208stack_size: ?u64 = null,
209
210want_lto: ?bool = null,
211use_llvm: ?bool,
212use_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.
218expect_errors: []const []const u8 = &.{},
219
220output_path_source: GeneratedFile,
221output_lib_path_source: GeneratedFile,
222output_h_path_source: GeneratedFile,
223output_pdb_path_source: GeneratedFile,
224output_dirname_source: GeneratedFile,
225
226pub const CSourceFiles = struct {
227 files: []const []const u8,
228 flags: []const []const u8,
229};
230
231pub 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
243pub 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
252pub 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
268const FrameworkLinkInfo = struct {
269 needed: bool = false,
270 weak: bool = false,
271};
272
273pub 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
280pub 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
297pub const Kind = enum {
298 exe,
299 lib,
300 obj,
301 @"test",
302};
303
304pub const Linkage = enum { dynamic, static };
305
306pub 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
322pub 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
462pub 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
469pub const InstallConfigHeaderOptions = struct {
470 install_dir: InstallDir = .header,
471 dest_rel_path: ?[]const u8 = null,
472};
473
474pub 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
491pub 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
503pub 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
513pub 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
536pub 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.
551pub 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.
555pub const install = @compileError("deprecated; use std.Build.installArtifact");
556
557pub fn checkObject(self: *CompileStep) *CheckObjectStep {
558 return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt);
559}
560
561pub 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
567pub 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
572pub 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
577pub 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
584pub 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.
592pub 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
608pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void {
609 assert(lib.kind == .lib);
610 self.linkLibraryOrObject(lib);
611}
612
613pub fn isDynamicLibrary(self: *CompileStep) bool {
614 return self.kind == .lib and self.linkage == Linkage.dynamic;
615}
616
617pub fn isStaticLibrary(self: *CompileStep) bool {
618 return self.kind == .lib and self.linkage != Linkage.dynamic;
619}
620
621pub 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
628pub fn linkLibC(self: *CompileStep) void {
629 self.is_linking_libc = true;
630}
631
632pub 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.
638pub 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.
645pub 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.
652pub 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.
666pub 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.
680pub 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.
694pub 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.
708pub 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.
722fn 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
816pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void {
817 self.linkSystemLibraryInner(name, .{});
818}
819
820pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void {
821 self.linkSystemLibraryInner(name, .{ .needed = true });
822}
823
824pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void {
825 self.linkSystemLibraryInner(name, .{ .weak = true });
826}
827
828fn 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.
853pub 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
867pub 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
874pub 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
882pub fn setVerboseLink(self: *CompileStep, value: bool) void {
883 self.verbose_link = value;
884}
885
886pub fn setVerboseCC(self: *CompileStep, value: bool) void {
887 self.verbose_cc = value;
888}
889
890pub 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
895pub 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
900pub 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.
907pub fn getOutputSource(self: *CompileStep) FileSource {
908 return .{ .generated = &self.output_path_source };
909}
910
911pub 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.
916pub 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.
923pub 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.
930pub 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
936pub 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
943pub 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
950pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void {
951 self.addObjectFileSource(.{ .path = source_file });
952}
953
954pub 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
960pub fn addObject(self: *CompileStep, obj: *CompileStep) void {
961 assert(obj.kind == .obj);
962 self.linkLibraryOrObject(obj);
963}
964
965pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath");
966pub const addIncludeDir = @compileError("deprecated; use addIncludePath");
967pub const addLibPath = @compileError("deprecated, use addLibraryPath");
968pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath");
969
970pub 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
975pub 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
980pub 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
985pub 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
990pub 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
995pub 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
1000pub 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
1005pub 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
1010pub 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`.
1017pub 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.
1028pub 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
1034pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void {
1035 addModule(cs, module_name, options.createModule());
1036}
1037
1038fn 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.
1049pub 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
1085pub 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
1095fn 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
1105fn 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
1192fn 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
1215fn 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
1960fn 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
1969fn 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.
1979fn 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
1997pub 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
2022fn 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
2038fn 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
2061fn 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
2071const 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
2131fn 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 @@
1pub 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
21pub const Value = union(enum) {
22 undef,
23 defined,
24 boolean: bool,
25 int: i64,
26 ident: []const u8,
27 string: []const u8,
28};
29
30step: Step,
31values: std.StringArrayHashMap(Value),
32output_file: std.Build.GeneratedFile,
33
34style: Style,
35max_bytes: usize,
36include_path: []const u8,
37
38pub const base_id: Step.Id = .config_header;
39
40pub 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
47pub 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
92pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {
93 return addValuesInner(self, values) catch @panic("OOM");
94}
95
96pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource {
97 return .{ .generated = &self.output_file };
98}
99
100fn 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
106fn 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
156fn 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
237fn 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
284fn 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
337fn 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
367fn 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
374fn 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
405fn 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
435const std = @import("../std.zig");
436const ConfigHeaderStep = @This();
437const 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
5step: Step,
6paths: []const []const u8,
7exclude_paths: []const []const u8,
8check: bool,
9
10pub const base_id = .fmt;
11
12pub 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
19pub 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
36fn 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
71const std = @import("../std.zig");
72const Step = std.Build.Step;
73const FmtStep = @This();
lib/std/Build/InstallArtifactStep.zig deleted-130
...@@ -1,130 +0,0 @@
1const std = @import("../std.zig");
2const Step = std.Build.Step;
3const CompileStep = std.Build.CompileStep;
4const InstallDir = std.Build.InstallDir;
5const InstallArtifactStep = @This();
6const fs = std.fs;
7
8pub const base_id = .install_artifact;
9
10step: Step,
11artifact: *CompileStep,
12dest_dir: InstallDir,
13pdb_dir: ?InstallDir,
14h_dir: ?InstallDir,
15/// If non-null, adds additional path components relative to dest_dir, and
16/// overrides the basename of the CompileStep.
17dest_sub_path: ?[]const u8,
18
19pub 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
67fn 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 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3const fs = std.fs;
4const Step = std.Build.Step;
5const InstallDir = std.Build.InstallDir;
6const InstallDirStep = @This();
7
8step: Step,
9options: 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.
12dest_builder: *std.Build,
13
14pub const base_id = .install_dir;
15
16pub 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
41pub 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
55fn 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 @@
1const std = @import("../std.zig");
2const Step = std.Build.Step;
3const FileSource = std.Build.FileSource;
4const InstallDir = std.Build.InstallDir;
5const InstallFileStep = @This();
6const assert = std.debug.assert;
7
8pub const base_id = .install_file;
9
10step: Step,
11source: FileSource,
12dir: InstallDir,
13dest_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.
16dest_builder: *std.Build,
17
18pub 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
43fn 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 @@
1const std = @import("std");
2const ObjCopyStep = @This();
3
4const Allocator = std.mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;
6const ArrayListUnmanaged = std.ArrayListUnmanaged;
7const File = std.fs.File;
8const InstallDir = std.Build.InstallDir;
9const CompileStep = std.Build.CompileStep;
10const Step = std.Build.Step;
11const elf = std.elf;
12const fs = std.fs;
13const io = std.io;
14const sort = std.sort;
15
16pub const base_id: Step.Id = .objcopy;
17
18pub const RawFormat = enum {
19 bin,
20 hex,
21};
22
23step: Step,
24file_source: std.Build.FileSource,
25basename: []const u8,
26output_file: std.Build.GeneratedFile,
27
28format: ?RawFormat,
29only_section: ?[]const u8,
30pad_to: ?u64,
31
32pub 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
39pub 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
64pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {
65 return .{ .generated = &self.output_file };
66}
67
68fn 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 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const fs = std.fs;
4const Step = std.Build.Step;
5const GeneratedFile = std.Build.GeneratedFile;
6const CompileStep = std.Build.CompileStep;
7const FileSource = std.Build.FileSource;
8
9const OptionsStep = @This();
10
11pub const base_id = .options;
12
13step: Step,
14generated_file: GeneratedFile,
15
16contents: std.ArrayList(u8),
17artifact_args: std.ArrayList(OptionArtifactArg),
18file_source_args: std.ArrayList(OptionFileSourceArg),
19
20pub 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
39pub 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
43fn 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?
145fn 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.
191pub 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.
205pub 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
210pub fn createModule(self: *OptionsStep) *std.Build.Module {
211 return self.step.owner.createModule(.{
212 .source_file = self.getSource(),
213 .dependencies = &.{},
214 });
215}
216
217pub fn getSource(self: *OptionsStep) FileSource {
218 return .{ .generated = &self.generated_file };
219}
220
221fn 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
315const OptionArtifactArg = struct {
316 name: []const u8,
317 artifact: *CompileStep,
318};
319
320const OptionFileSourceArg = struct {
321 name: []const u8,
322 source: FileSource,
323};
324
325test "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 @@
1const std = @import("../std.zig");
2const fs = std.fs;
3const Step = std.Build.Step;
4const RemoveDirStep = @This();
5
6pub const base_id = .remove_dir;
7
8step: Step,
9dir_path: []const u8,
10
11pub 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
23fn 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 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const Step = std.Build.Step;
4const CompileStep = std.Build.CompileStep;
5const WriteFileStep = std.Build.WriteFileStep;
6const fs = std.fs;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const EnvMap = process.EnvMap;
11const Allocator = mem.Allocator;
12const ExecError = std.Build.ExecError;
13const assert = std.debug.assert;
14
15const RunStep = @This();
16
17pub const base_id: Step.Id = .run;
18
19step: Step,
20
21/// See also addArg and addArgs to modifying this directly
22argv: 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.
27cwd: ?[]const u8,
28
29/// Override this field to modify the environment, or use setEnvironmentVariable
30env_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.
40stdio: StdIo = .infer_from_args,
41/// This field must be `null` if stdio is `inherit`.
42stdin: ?[]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.
48extra_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.
53rename_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.
64skip_foreign_checks: bool = false,
65
66/// If stderr or stdout exceeds this amount, the child process is killed and
67/// the step fails.
68max_stdio_size: usize = 10 * 1024 * 1024,
69
70captured_stdout: ?*Output = null,
71captured_stderr: ?*Output = null,
72
73has_side_effects: bool = false,
74
75pub 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
108pub 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
116pub const Output = struct {
117 generated_file: std.Build.GeneratedFile,
118 prefix: []const u8,
119 basename: []const u8,
120};
121
122pub 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
138pub fn setName(self: *RunStep, name: []const u8) void {
139 self.step.name = name;
140 self.rename_step_with_output_arg = false;
141}
142
143pub fn enableTestRunnerMode(rs: *RunStep) void {
144 rs.stdio = .zig_test;
145 rs.addArgs(&.{"--listen=-"});
146}
147
148pub 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.
156pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
157 return addPrefixedOutputFileArg(rs, "", basename);
158}
159
160pub 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
182pub 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
189pub 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
196pub fn addArg(self: *RunStep, arg: []const u8) void {
197 self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");
198}
199
200pub fn addArgs(self: *RunStep, args: []const []const u8) void {
201 for (args) |arg| {
202 self.addArg(arg);
203 }
204}
205
206pub 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
213pub 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
228pub fn getEnvMap(self: *RunStep) *EnvMap {
229 return getEnvMapInternal(self);
230}
231
232fn 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
242pub 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
248pub 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.
253pub 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.
260pub 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
268pub fn expectExitCode(self: *RunStep, code: u8) void {
269 const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
270 self.addCheck(new_check);
271}
272
273pub 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
281pub 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
292pub 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
307pub 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.
323fn 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
333fn 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
343fn 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
357fn 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
371fn 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
498fn 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}
515fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
516 return .{ .data = term };
517}
518
519fn 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
543fn 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
856const ChildProcResult = struct {
857 term: std.process.Child.Term,
858 elapsed_ns: u64,
859 peak_rss: usize,
860
861 stdio: StdIoResult,
862};
863
864fn 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
927const 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
938fn 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
1075const 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
1088fn 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
1107fn 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
1115fn 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
1124fn 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
1191fn 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
1206fn 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
1232fn 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.
5const CheckFileStep = @This();
6const std = @import("std");
7const Step = std.Build.Step;
8const fs = std.fs;
9const mem = std.mem;
10
11step: Step,
12expected_matches: []const []const u8,
13expected_exact: ?[]const u8,
14source: std.Build.FileSource,
15max_bytes: usize = 20 * 1024 * 1024,
16
17pub const base_id = .check_file;
18
19pub const Options = struct {
20 expected_matches: []const []const u8 = &.{},
21 expected_exact: ?[]const u8 = null,
22};
23
24pub 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
45pub fn setName(self: *CheckFileStep, name: []const u8) void {
46 self.step.name = name;
47}
48
49fn 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 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const fs = std.fs;
4const macho = std.macho;
5const math = std.math;
6const mem = std.mem;
7const testing = std.testing;
8
9const CheckObjectStep = @This();
10
11const Allocator = mem.Allocator;
12const Step = std.Build.Step;
13
14pub const base_id = .check_object;
15
16step: Step,
17source: std.Build.FileSource,
18max_bytes: usize = 20 * 1024 * 1024,
19checks: std.ArrayList(Check),
20dump_symtab: bool = false,
21obj_format: std.Target.ObjectFormat,
22
23pub 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.
50pub 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
61const 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 +`.
83const 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
221const 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
244const 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.
277pub 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.
285pub 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()`.
293pub 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.
306pub 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.
315pub 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.
327pub 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
337fn 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
426const Opts = struct {
427 dump_symtab: bool = false,
428};
429
430const 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
714const 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 @@
1const builtin = @import("builtin");
2const std = @import("std");
3const mem = std.mem;
4const fs = std.fs;
5const assert = std.debug.assert;
6const panic = std.debug.panic;
7const ArrayList = std.ArrayList;
8const StringHashMap = std.StringHashMap;
9const Sha256 = std.crypto.hash.sha2.Sha256;
10const Allocator = mem.Allocator;
11const Step = std.Build.Step;
12const CrossTarget = std.zig.CrossTarget;
13const NativeTargetInfo = std.zig.system.NativeTargetInfo;
14const FileSource = std.Build.FileSource;
15const PkgConfigPkg = std.Build.PkgConfigPkg;
16const PkgConfigError = std.Build.PkgConfigError;
17const ExecError = std.Build.ExecError;
18const Module = std.Build.Module;
19const VcpkgRoot = std.Build.VcpkgRoot;
20const InstallDir = std.Build.InstallDir;
21const InstallArtifactStep = std.Build.InstallArtifactStep;
22const GeneratedFile = std.Build.GeneratedFile;
23const ObjCopyStep = std.Build.ObjCopyStep;
24const CheckObjectStep = std.Build.CheckObjectStep;
25const RunStep = std.Build.RunStep;
26const OptionsStep = std.Build.OptionsStep;
27const ConfigHeaderStep = std.Build.ConfigHeaderStep;
28const CompileStep = @This();
29
30pub const base_id: Step.Id = .compile;
31
32step: Step,
33name: []const u8,
34target: CrossTarget,
35target_info: NativeTargetInfo,
36optimize: std.builtin.Mode,
37linker_script: ?FileSource = null,
38version_script: ?[]const u8 = null,
39out_filename: []const u8,
40linkage: ?Linkage = null,
41version: ?std.builtin.Version,
42kind: Kind,
43major_only_filename: ?[]const u8,
44name_only_filename: ?[]const u8,
45strip: ?bool,
46unwind_tables: ?bool,
47// keep in sync with src/link.zig:CompressDebugSections
48compress_debug_sections: enum { none, zlib } = .none,
49lib_paths: ArrayList(FileSource),
50rpaths: ArrayList(FileSource),
51framework_dirs: ArrayList(FileSource),
52frameworks: StringHashMap(FrameworkLinkInfo),
53verbose_link: bool,
54verbose_cc: bool,
55emit_analysis: EmitOption = .default,
56emit_asm: EmitOption = .default,
57emit_bin: EmitOption = .default,
58emit_docs: EmitOption = .default,
59emit_implib: EmitOption = .default,
60emit_llvm_bc: EmitOption = .default,
61emit_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.
64emit_h: bool = false,
65bundle_compiler_rt: ?bool = null,
66single_threaded: ?bool,
67stack_protector: ?bool = null,
68disable_stack_probing: bool,
69disable_sanitize_c: bool,
70sanitize_thread: bool,
71rdynamic: bool,
72dwarf_format: ?std.dwarf.Format = null,
73import_memory: bool = false,
74/// For WebAssembly targets, this will allow for undefined symbols to
75/// be imported from the host environment.
76import_symbols: bool = false,
77import_table: bool = false,
78export_table: bool = false,
79initial_memory: ?u64 = null,
80max_memory: ?u64 = null,
81shared_memory: bool = false,
82global_base: ?u64 = null,
83c_std: std.Build.CStd,
84zig_lib_dir: ?[]const u8,
85main_pkg_path: ?[]const u8,
86exec_cmd_args: ?[]const ?[]const u8,
87filter: ?[]const u8,
88test_evented_io: bool = false,
89test_runner: ?[]const u8,
90code_model: std.builtin.CodeModel = .default,
91wasi_exec_model: ?std.builtin.WasiExecModel = null,
92/// Symbols to be exported when compiling to wasm
93export_symbol_names: []const []const u8 = &.{},
94
95root_src: ?FileSource,
96out_h_filename: []const u8,
97out_lib_filename: []const u8,
98out_pdb_filename: []const u8,
99modules: std.StringArrayHashMap(*Module),
100
101link_objects: ArrayList(LinkObject),
102include_dirs: ArrayList(IncludeDir),
103c_macros: ArrayList([]const u8),
104installed_headers: ArrayList(*Step),
105is_linking_libc: bool,
106is_linking_libcpp: bool,
107vcpkg_bin_path: ?[]const u8 = null,
108
109/// This may be set in order to override the default install directory
110override_dest_dir: ?InstallDir,
111installed_path: ?[]const u8,
112
113/// Base address for an executable image.
114image_base: ?u64 = null,
115
116libc_file: ?FileSource = null,
117
118valgrind_support: ?bool = null,
119each_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.
125build_id: ?bool = null,
126
127/// Create a .eh_frame_hdr section and a PT_GNU_EH_FRAME segment in the ELF
128/// file.
129link_eh_frame_hdr: bool = false,
130link_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.
134link_function_sections: bool = false,
135
136/// Remove functions and data that are unreachable by the entry point or
137/// exported symbols.
138link_gc_sections: ?bool = null,
139
140/// (Windows) Whether or not to enable ASLR. Maps to the /DYNAMICBASE[:NO] linker argument.
141linker_dynamicbase: bool = true,
142
143linker_allow_shlib_undefined: ?bool = null,
144
145/// Permit read-only relocations in read-only segments. Disallowed by default.
146link_z_notext: bool = false,
147
148/// Force all relocations to be read-only after processing.
149link_z_relro: bool = true,
150
151/// Allow relocations to be lazily processed after load.
152link_z_lazy: bool = false,
153
154/// Common page size
155link_z_common_page_size: ?u64 = null,
156
157/// Maximum page size
158link_z_max_page_size: ?u64 = null,
159
160/// (Darwin) Install name for the dylib
161install_name: ?[]const u8 = null,
162
163/// (Darwin) Path to entitlements file
164entitlements: ?[]const u8 = null,
165
166/// (Darwin) Size of the pagezero segment.
167pagezero_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.
174search_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.
178headerpad_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.
182headerpad_max_install_names: bool = false,
183
184/// (Darwin) Remove dylibs that are unreachable by the entry point or exported symbols.
185dead_strip_dylibs: bool = false,
186
187/// Position Independent Code
188force_pic: ?bool = null,
189
190/// Position Independent Executable
191pie: ?bool = null,
192
193red_zone: ?bool = null,
194
195omit_frame_pointer: ?bool = null,
196dll_export_fns: ?bool = null,
197
198subsystem: ?std.Target.SubSystem = null,
199
200entry_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.
205force_undefined_symbols: std.StringHashMap(void),
206
207/// Overrides the default stack size
208stack_size: ?u64 = null,
209
210want_lto: ?bool = null,
211use_llvm: ?bool,
212use_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.
218expect_errors: []const []const u8 = &.{},
219
220output_path_source: GeneratedFile,
221output_lib_path_source: GeneratedFile,
222output_h_path_source: GeneratedFile,
223output_pdb_path_source: GeneratedFile,
224output_dirname_source: GeneratedFile,
225
226pub const CSourceFiles = struct {
227 files: []const []const u8,
228 flags: []const []const u8,
229};
230
231pub 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
243pub 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
252pub 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
268const FrameworkLinkInfo = struct {
269 needed: bool = false,
270 weak: bool = false,
271};
272
273pub 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
280pub 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
297pub const Kind = enum {
298 exe,
299 lib,
300 obj,
301 @"test",
302};
303
304pub const Linkage = enum { dynamic, static };
305
306pub 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
322pub 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
462pub 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
469pub const InstallConfigHeaderOptions = struct {
470 install_dir: InstallDir = .header,
471 dest_rel_path: ?[]const u8 = null,
472};
473
474pub 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
491pub 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
503pub 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
513pub 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
536pub 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.
551pub 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.
555pub const install = @compileError("deprecated; use std.Build.installArtifact");
556
557pub fn checkObject(self: *CompileStep) *CheckObjectStep {
558 return CheckObjectStep.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt);
559}
560
561pub 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
567pub 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
572pub 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
577pub 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
584pub 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.
592pub 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
608pub fn linkLibrary(self: *CompileStep, lib: *CompileStep) void {
609 assert(lib.kind == .lib);
610 self.linkLibraryOrObject(lib);
611}
612
613pub fn isDynamicLibrary(self: *CompileStep) bool {
614 return self.kind == .lib and self.linkage == Linkage.dynamic;
615}
616
617pub fn isStaticLibrary(self: *CompileStep) bool {
618 return self.kind == .lib and self.linkage != Linkage.dynamic;
619}
620
621pub 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
628pub fn linkLibC(self: *CompileStep) void {
629 self.is_linking_libc = true;
630}
631
632pub 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.
638pub 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.
645pub 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.
652pub 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.
666pub 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.
680pub 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.
694pub 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.
708pub 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.
722fn 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
816pub fn linkSystemLibrary(self: *CompileStep, name: []const u8) void {
817 self.linkSystemLibraryInner(name, .{});
818}
819
820pub fn linkSystemLibraryNeeded(self: *CompileStep, name: []const u8) void {
821 self.linkSystemLibraryInner(name, .{ .needed = true });
822}
823
824pub fn linkSystemLibraryWeak(self: *CompileStep, name: []const u8) void {
825 self.linkSystemLibraryInner(name, .{ .weak = true });
826}
827
828fn 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.
853pub 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
867pub 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
874pub 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
882pub fn setVerboseLink(self: *CompileStep, value: bool) void {
883 self.verbose_link = value;
884}
885
886pub fn setVerboseCC(self: *CompileStep, value: bool) void {
887 self.verbose_cc = value;
888}
889
890pub 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
895pub 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
900pub 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.
907pub fn getOutputSource(self: *CompileStep) FileSource {
908 return .{ .generated = &self.output_path_source };
909}
910
911pub 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.
916pub 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.
923pub 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.
930pub 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
936pub 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
943pub 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
950pub fn addObjectFile(self: *CompileStep, source_file: []const u8) void {
951 self.addObjectFileSource(.{ .path = source_file });
952}
953
954pub 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
960pub fn addObject(self: *CompileStep, obj: *CompileStep) void {
961 assert(obj.kind == .obj);
962 self.linkLibraryOrObject(obj);
963}
964
965pub const addSystemIncludeDir = @compileError("deprecated; use addSystemIncludePath");
966pub const addIncludeDir = @compileError("deprecated; use addIncludePath");
967pub const addLibPath = @compileError("deprecated, use addLibraryPath");
968pub const addFrameworkDir = @compileError("deprecated, use addFrameworkPath");
969
970pub 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
975pub 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
980pub 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
985pub 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
990pub 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
995pub 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
1000pub 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
1005pub 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
1010pub 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`.
1017pub 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.
1028pub 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
1034pub fn addOptions(cs: *CompileStep, module_name: []const u8, options: *OptionsStep) void {
1035 addModule(cs, module_name, options.createModule());
1036}
1037
1038fn 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.
1049pub 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
1085pub 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
1095fn 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
1105fn 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
1192fn 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
1215fn 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
1960fn 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
1969fn 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.
1979fn 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
1997pub 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
2022fn 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
2038fn 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
2061fn 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
2071const 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
2131fn 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 @@
1const std = @import("std");
2const ConfigHeaderStep = @This();
3const Step = std.Build.Step;
4
5pub 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
25pub const Value = union(enum) {
26 undef,
27 defined,
28 boolean: bool,
29 int: i64,
30 ident: []const u8,
31 string: []const u8,
32};
33
34step: Step,
35values: std.StringArrayHashMap(Value),
36output_file: std.Build.GeneratedFile,
37
38style: Style,
39max_bytes: usize,
40include_path: []const u8,
41
42pub const base_id: Step.Id = .config_header;
43
44pub 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
51pub 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
96pub fn addValues(self: *ConfigHeaderStep, values: anytype) void {
97 return addValuesInner(self, values) catch @panic("OOM");
98}
99
100pub fn getFileSource(self: *ConfigHeaderStep) std.Build.FileSource {
101 return .{ .generated = &self.output_file };
102}
103
104fn 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
110fn 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
160fn 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
241fn 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
288fn 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
341fn 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
371fn 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
378fn 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
409fn 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.
4const std = @import("std");
5const Step = std.Build.Step;
6const FmtStep = @This();
7
8step: Step,
9paths: []const []const u8,
10exclude_paths: []const []const u8,
11check: bool,
12
13pub const base_id = .fmt;
14
15pub 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
22pub 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
39fn 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 @@
1const std = @import("std");
2const Step = std.Build.Step;
3const CompileStep = std.Build.CompileStep;
4const InstallDir = std.Build.InstallDir;
5const InstallArtifactStep = @This();
6const fs = std.fs;
7
8pub const base_id = .install_artifact;
9
10step: Step,
11artifact: *CompileStep,
12dest_dir: InstallDir,
13pdb_dir: ?InstallDir,
14h_dir: ?InstallDir,
15/// If non-null, adds additional path components relative to dest_dir, and
16/// overrides the basename of the CompileStep.
17dest_sub_path: ?[]const u8,
18
19pub 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
67fn 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 @@
1const std = @import("std");
2const mem = std.mem;
3const fs = std.fs;
4const Step = std.Build.Step;
5const InstallDir = std.Build.InstallDir;
6const InstallDirStep = @This();
7
8step: Step,
9options: 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.
12dest_builder: *std.Build,
13
14pub const base_id = .install_dir;
15
16pub 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
41pub 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
55fn 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 @@
1const std = @import("std");
2const Step = std.Build.Step;
3const FileSource = std.Build.FileSource;
4const InstallDir = std.Build.InstallDir;
5const InstallFileStep = @This();
6const assert = std.debug.assert;
7
8pub const base_id = .install_file;
9
10step: Step,
11source: FileSource,
12dir: InstallDir,
13dest_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.
16dest_builder: *std.Build,
17
18pub 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
43fn 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 @@
1const std = @import("std");
2const ObjCopyStep = @This();
3
4const Allocator = std.mem.Allocator;
5const ArenaAllocator = std.heap.ArenaAllocator;
6const ArrayListUnmanaged = std.ArrayListUnmanaged;
7const File = std.fs.File;
8const InstallDir = std.Build.InstallDir;
9const CompileStep = std.Build.CompileStep;
10const Step = std.Build.Step;
11const elf = std.elf;
12const fs = std.fs;
13const io = std.io;
14const sort = std.sort;
15
16pub const base_id: Step.Id = .objcopy;
17
18pub const RawFormat = enum {
19 bin,
20 hex,
21};
22
23step: Step,
24file_source: std.Build.FileSource,
25basename: []const u8,
26output_file: std.Build.GeneratedFile,
27
28format: ?RawFormat,
29only_section: ?[]const u8,
30pad_to: ?u64,
31
32pub 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
39pub 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
64pub fn getOutputSource(self: *const ObjCopyStep) std.Build.FileSource {
65 return .{ .generated = &self.output_file };
66}
67
68fn 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 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const fs = std.fs;
4const Step = std.Build.Step;
5const GeneratedFile = std.Build.GeneratedFile;
6const CompileStep = std.Build.CompileStep;
7const FileSource = std.Build.FileSource;
8
9const OptionsStep = @This();
10
11pub const base_id = .options;
12
13step: Step,
14generated_file: GeneratedFile,
15
16contents: std.ArrayList(u8),
17artifact_args: std.ArrayList(OptionArtifactArg),
18file_source_args: std.ArrayList(OptionFileSourceArg),
19
20pub 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
39pub 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
43fn 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?
145fn 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.
191pub 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.
205pub 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
210pub fn createModule(self: *OptionsStep) *std.Build.Module {
211 return self.step.owner.createModule(.{
212 .source_file = self.getSource(),
213 .dependencies = &.{},
214 });
215}
216
217pub fn getSource(self: *OptionsStep) FileSource {
218 return .{ .generated = &self.generated_file };
219}
220
221fn 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
315const OptionArtifactArg = struct {
316 name: []const u8,
317 artifact: *CompileStep,
318};
319
320const OptionFileSourceArg = struct {
321 name: []const u8,
322 source: FileSource,
323};
324
325test "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 @@
1const std = @import("std");
2const fs = std.fs;
3const Step = std.Build.Step;
4const RemoveDirStep = @This();
5
6pub const base_id = .remove_dir;
7
8step: Step,
9dir_path: []const u8,
10
11pub 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
23fn 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 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const Step = std.Build.Step;
4const CompileStep = std.Build.CompileStep;
5const WriteFileStep = std.Build.WriteFileStep;
6const fs = std.fs;
7const mem = std.mem;
8const process = std.process;
9const ArrayList = std.ArrayList;
10const EnvMap = process.EnvMap;
11const Allocator = mem.Allocator;
12const ExecError = std.Build.ExecError;
13const assert = std.debug.assert;
14
15const RunStep = @This();
16
17pub const base_id: Step.Id = .run;
18
19step: Step,
20
21/// See also addArg and addArgs to modifying this directly
22argv: 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.
27cwd: ?[]const u8,
28
29/// Override this field to modify the environment, or use setEnvironmentVariable
30env_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.
40stdio: StdIo = .infer_from_args,
41/// This field must be `null` if stdio is `inherit`.
42stdin: ?[]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.
48extra_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.
53rename_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.
64skip_foreign_checks: bool = false,
65
66/// If stderr or stdout exceeds this amount, the child process is killed and
67/// the step fails.
68max_stdio_size: usize = 10 * 1024 * 1024,
69
70captured_stdout: ?*Output = null,
71captured_stderr: ?*Output = null,
72
73has_side_effects: bool = false,
74
75pub 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
108pub 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
116pub const Output = struct {
117 generated_file: std.Build.GeneratedFile,
118 prefix: []const u8,
119 basename: []const u8,
120};
121
122pub 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
138pub fn setName(self: *RunStep, name: []const u8) void {
139 self.step.name = name;
140 self.rename_step_with_output_arg = false;
141}
142
143pub fn enableTestRunnerMode(rs: *RunStep) void {
144 rs.stdio = .zig_test;
145 rs.addArgs(&.{"--listen=-"});
146}
147
148pub 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.
156pub fn addOutputFileArg(rs: *RunStep, basename: []const u8) std.Build.FileSource {
157 return addPrefixedOutputFileArg(rs, "", basename);
158}
159
160pub 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
182pub 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
189pub 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
196pub fn addArg(self: *RunStep, arg: []const u8) void {
197 self.argv.append(.{ .bytes = self.step.owner.dupe(arg) }) catch @panic("OOM");
198}
199
200pub fn addArgs(self: *RunStep, args: []const []const u8) void {
201 for (args) |arg| {
202 self.addArg(arg);
203 }
204}
205
206pub 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
213pub 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
228pub fn getEnvMap(self: *RunStep) *EnvMap {
229 return getEnvMapInternal(self);
230}
231
232fn 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
242pub 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
248pub 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.
253pub 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.
260pub 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
268pub fn expectExitCode(self: *RunStep, code: u8) void {
269 const new_check: StdIo.Check = .{ .expect_term = .{ .Exited = code } };
270 self.addCheck(new_check);
271}
272
273pub 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
281pub 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
292pub 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
307pub 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.
323fn 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
333fn 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
343fn 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
357fn 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
371fn 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
498fn 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}
515fn fmtTerm(term: ?std.process.Child.Term) std.fmt.Formatter(formatTerm) {
516 return .{ .data = term };
517}
518
519fn 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
543fn 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
856const ChildProcResult = struct {
857 term: std.process.Child.Term,
858 elapsed_ns: u64,
859 peak_rss: usize,
860
861 stdio: StdIoResult,
862};
863
864fn 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
927const 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
938fn 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
1075const 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
1088fn 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
1107fn 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
1115fn 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
1124fn 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
1191fn 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
1206fn 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
1232fn 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 @@
1const std = @import("std");
2const Step = std.Build.Step;
3const CompileStep = std.Build.CompileStep;
4const CheckFileStep = std.Build.CheckFileStep;
5const fs = std.fs;
6const mem = std.mem;
7const CrossTarget = std.zig.CrossTarget;
8
9const TranslateCStep = @This();
10
11pub const base_id = .translate_c;
12
13step: Step,
14source: std.Build.FileSource,
15include_dirs: std.ArrayList([]const u8),
16c_macros: std.ArrayList([]const u8),
17out_basename: []const u8,
18target: CrossTarget,
19optimize: std.builtin.OptimizeMode,
20output_file: std.Build.GeneratedFile,
21
22pub const Options = struct {
23 source_file: std.Build.FileSource,
24 target: CrossTarget,
25 optimize: std.builtin.OptimizeMode,
26};
27
28pub 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
50pub 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.
59pub 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
70pub 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
74pub 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.
84pub 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.
90pub 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
94fn 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.
11const std = @import("std");
12const Step = std.Build.Step;
13const fs = std.fs;
14const ArrayList = std.ArrayList;
15const WriteFileStep = @This();
16
17step: Step,
18/// The elements here are pointers because we need stable pointers for the
19/// GeneratedFile field.
20files: std.ArrayListUnmanaged(*File),
21output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
22generated_directory: std.Build.GeneratedFile,
23
24pub const base_id = .write_file;
25
26pub const File = struct {
27 generated_file: std.Build.GeneratedFile,
28 sub_path: []const u8,
29 contents: Contents,
30};
31
32pub const OutputSourceFile = struct {
33 contents: Contents,
34 sub_path: []const u8,
35};
36
37pub const Contents = union(enum) {
38 bytes: []const u8,
39 copy: std.Build.FileSource,
40};
41
42pub 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
58pub 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`.
79pub 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`.
100pub 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`.
115pub 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`.
124pub 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`.
135pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource {
136 return .{ .generated = &wf.generated_directory };
137}
138
139fn 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
148fn 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 @@
1const std = @import("../std.zig");
2const Step = std.Build.Step;
3const CompileStep = std.Build.CompileStep;
4const CheckFileStep = std.Build.CheckFileStep;
5const fs = std.fs;
6const mem = std.mem;
7const CrossTarget = std.zig.CrossTarget;
8
9const TranslateCStep = @This();
10
11pub const base_id = .translate_c;
12
13step: Step,
14source: std.Build.FileSource,
15include_dirs: std.ArrayList([]const u8),
16c_macros: std.ArrayList([]const u8),
17out_basename: []const u8,
18target: CrossTarget,
19optimize: std.builtin.OptimizeMode,
20output_file: std.Build.GeneratedFile,
21
22pub const Options = struct {
23 source_file: std.Build.FileSource,
24 target: CrossTarget,
25 optimize: std.builtin.OptimizeMode,
26};
27
28pub 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
50pub 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.
59pub 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
70pub 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
74pub 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.
84pub 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.
90pub 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
94fn 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
12step: Step,
13/// The elements here are pointers because we need stable pointers for the
14/// GeneratedFile field.
15files: std.ArrayListUnmanaged(*File),
16output_source_files: std.ArrayListUnmanaged(OutputSourceFile),
17generated_directory: std.Build.GeneratedFile,
18
19pub const base_id = .write_file;
20
21pub const File = struct {
22 generated_file: std.Build.GeneratedFile,
23 sub_path: []const u8,
24 contents: Contents,
25};
26
27pub const OutputSourceFile = struct {
28 contents: Contents,
29 sub_path: []const u8,
30};
31
32pub const Contents = union(enum) {
33 bytes: []const u8,
34 copy: std.Build.FileSource,
35};
36
37pub 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
53pub 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`.
74pub 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`.
95pub 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`.
110pub 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`.
119pub 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`.
130pub fn getDirectorySource(wf: *WriteFileStep) std.Build.FileSource {
131 return .{ .generated = &wf.generated_directory };
132}
133
134fn 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
143fn 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
288const std = @import("../std.zig");
289const Step = std.Build.Step;
290const fs = std.fs;
291const ArrayList = std.ArrayList;
292
293const WriteFileStep = @This();