authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-07-30 21:27:29-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-07-30 21:27:29-07:00
log43b830415368ac4fb08bf5e154a222a38baf4a24
treeeaf6206e2038cbf7af17098162458b78cac91058
parent235e6ac05df95e5dc2656cfb1a33d7b18c45a603
parentacbb6418c3899eb79aea98e7f5d3173298716377
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #16446 from MasterQ32/buildsystem_rename_orgy

Build.zig rename orgy. Renames FileSource to LazyPath and others

74 files changed, 3023 insertions(+), 2824 deletions(-)

build.zig+20-25
......@@ -36,7 +36,7 @@ pub fn build(b: *std.Build) !void {
3636
3737 const docgen_exe = b.addExecutable(.{
3838 .name = "docgen",
39 .root_source_file = .{ .path = "doc/docgen.zig" },
39 .root_source_file = .{ .path = "tools/docgen.zig" },
4040 .target = .{},
4141 .optimize = .Debug,
4242 });
......@@ -45,9 +45,10 @@ pub fn build(b: *std.Build) !void {
4545 const docgen_cmd = b.addRunArtifact(docgen_exe);
4646 docgen_cmd.addArgs(&.{ "--zig", b.zig_exe });
4747 if (b.zig_lib_dir) |p| {
48 docgen_cmd.addArgs(&.{ "--zig-lib-dir", p });
48 docgen_cmd.addArg("--zig-lib-dir");
49 docgen_cmd.addDirectoryArg(p);
4950 }
50 docgen_cmd.addFileSourceArg(.{ .path = "doc/langref.html.in" });
51 docgen_cmd.addFileArg(.{ .path = "doc/langref.html.in" });
5152 const langref_file = docgen_cmd.addOutputFileArg("langref.html");
5253 const install_langref = b.addInstallFileWithDir(langref_file, .prefix, "doc/langref.html");
5354 if (!skip_install_langref) {
......@@ -57,9 +58,8 @@ pub fn build(b: *std.Build) !void {
5758 const autodoc_test = b.addTest(.{
5859 .root_source_file = .{ .path = "lib/std/std.zig" },
5960 .target = target,
61 .zig_lib_dir = .{ .path = "lib" },
6062 });
61 autodoc_test.overrideZigLibDir("lib");
62 autodoc_test.emit_bin = .no_emit; // https://github.com/ziglang/zig/issues/16351
6363 const install_std_docs = b.addInstallDirectory(.{
6464 .source_dir = autodoc_test.getEmittedDocs(),
6565 .install_dir = .prefix,
......@@ -88,8 +88,8 @@ pub fn build(b: *std.Build) !void {
8888 .name = "check-case",
8989 .root_source_file = .{ .path = "test/src/Cases.zig" },
9090 .optimize = optimize,
91 .main_pkg_path = .{ .path = "." },
9192 });
92 check_case_exe.main_pkg_path = ".";
9393 check_case_exe.stack_size = stack_size;
9494 check_case_exe.single_threaded = single_threaded;
9595
......@@ -196,10 +196,6 @@ pub fn build(b: *std.Build) !void {
196196 exe.pie = pie;
197197 exe.sanitize_thread = sanitize_thread;
198198 exe.entitlements = entitlements;
199 // TODO -femit-bin/-fno-emit-bin should be inferred by the build system
200 // based on whether or not the exe is run or installed.
201 // https://github.com/ziglang/zig/issues/16351
202 if (no_bin) exe.emit_bin = .no_emit;
203199
204200 exe.build_id = b.option(
205201 std.Build.Step.Compile.BuildId,
......@@ -208,7 +204,7 @@ pub fn build(b: *std.Build) !void {
208204 );
209205
210206 if (!no_bin) {
211 const install_exe = b.addInstallArtifact(exe);
207 const install_exe = b.addInstallArtifact(exe, .{});
212208 if (flat) {
213209 install_exe.dest_dir = .prefix;
214210 }
......@@ -352,10 +348,9 @@ pub fn build(b: *std.Build) !void {
352348 exe_options.addOption(bool, "enable_tracy_allocation", tracy_allocation);
353349 exe_options.addOption(bool, "value_tracing", value_tracing);
354350 if (tracy) |tracy_path| {
355 const client_cpp = fs.path.join(
356 b.allocator,
351 const client_cpp = b.pathJoin(
357352 &[_][]const u8{ tracy_path, "public", "TracyClient.cpp" },
358 ) catch unreachable;
353 );
359354
360355 // On mingw, we need to opt into windows 7+ to get some features required by tracy.
361356 const tracy_c_flags: []const []const u8 = if (target.isWindows() and target.getAbi() == .gnu)
......@@ -363,8 +358,8 @@ pub fn build(b: *std.Build) !void {
363358 else
364359 &[_][]const u8{ "-DTRACY_ENABLE=1", "-fno-sanitize=undefined" };
365360
366 exe.addIncludePath(tracy_path);
367 exe.addCSourceFile(client_cpp, tracy_c_flags);
361 exe.addIncludePath(.{ .cwd_relative = tracy_path });
362 exe.addCSourceFile(.{ .file = .{ .cwd_relative = client_cpp }, .flags = tracy_c_flags });
368363 if (!enable_llvm) {
369364 exe.linkSystemLibraryName("c++");
370365 }
......@@ -554,7 +549,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
554549 });
555550 run_opt.addArtifactArg(exe);
556551 run_opt.addArg("-o");
557 run_opt.addFileSourceArg(.{ .path = "stage1/zig1.wasm" });
552 run_opt.addFileArg(.{ .path = "stage1/zig1.wasm" });
558553
559554 const copy_zig_h = b.addWriteFiles();
560555 copy_zig_h.addCopyFileToSource(.{ .path = "lib/zig.h" }, "stage1/zig.h");
......@@ -603,7 +598,7 @@ fn addCmakeCfgOptionsToExe(
603598 // useful for package maintainers
604599 exe.headerpad_max_install_names = true;
605600 }
606 exe.addObjectFile(fs.path.join(b.allocator, &[_][]const u8{
601 exe.addObjectFile(.{ .cwd_relative = b.pathJoin(&[_][]const u8{
607602 cfg.cmake_binary_dir,
608603 "zigcpp",
609604 b.fmt("{s}{s}{s}", .{
......@@ -611,11 +606,11 @@ fn addCmakeCfgOptionsToExe(
611606 "zigcpp",
612607 cfg.cmake_static_library_suffix,
613608 }),
614 }) catch unreachable);
609 }) });
615610 assert(cfg.lld_include_dir.len != 0);
616 exe.addIncludePath(cfg.lld_include_dir);
617 exe.addIncludePath(cfg.llvm_include_dir);
618 exe.addLibraryPath(cfg.llvm_lib_dir);
611 exe.addIncludePath(.{ .cwd_relative = cfg.lld_include_dir });
612 exe.addIncludePath(.{ .cwd_relative = cfg.llvm_include_dir });
613 exe.addLibraryPath(.{ .cwd_relative = cfg.llvm_lib_dir });
619614 addCMakeLibraryList(exe, cfg.clang_libraries);
620615 addCMakeLibraryList(exe, cfg.lld_libraries);
621616 addCMakeLibraryList(exe, cfg.llvm_libraries);
......@@ -671,7 +666,7 @@ fn addCmakeCfgOptionsToExe(
671666 }
672667
673668 if (cfg.dia_guids_lib.len != 0) {
674 exe.addObjectFile(cfg.dia_guids_lib);
669 exe.addObjectFile(.{ .cwd_relative = cfg.dia_guids_lib });
675670 }
676671}
677672
......@@ -732,7 +727,7 @@ fn addCxxKnownPath(
732727 }
733728 return error.RequiredLibraryNotFound;
734729 }
735 exe.addObjectFile(path_unpadded);
730 exe.addObjectFile(.{ .cwd_relative = path_unpadded });
736731
737732 // TODO a way to integrate with system c++ include files here
738733 // c++ -E -Wp,-v -xc++ /dev/null
......@@ -752,7 +747,7 @@ fn addCMakeLibraryList(exe: *std.Build.Step.Compile, list: []const u8) void {
752747 } else if (exe.target.isWindows() and mem.endsWith(u8, lib, ".lib") and !fs.path.isAbsolute(lib)) {
753748 exe.linkSystemLibrary(lib[0 .. lib.len - ".lib".len]);
754749 } else {
755 exe.addObjectFile(lib);
750 exe.addObjectFile(.{ .cwd_relative = lib });
756751 }
757752 }
758753}
doc/docgen.zig deleted-2276
......@@ -1,2276 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const io = std.io;
4const fs = std.fs;
5const process = std.process;
6const ChildProcess = std.ChildProcess;
7const Progress = std.Progress;
8const print = std.debug.print;
9const mem = std.mem;
10const testing = std.testing;
11const Allocator = std.mem.Allocator;
12
13const max_doc_file_size = 10 * 1024 * 1024;
14
15const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt();
16const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
17const tmp_dir_name = "docgen_tmp";
18const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext;
19
20const usage =
21 \\Usage: docgen [--zig] [--skip-code-tests] input output"
22 \\
23 \\ Generates an HTML document from a docgen template.
24 \\
25 \\Options:
26 \\ -h, --help Print this help and exit
27 \\ --skip-code-tests Skip the doctests
28 \\
29;
30
31fn fatal(comptime format: []const u8, args: anytype) noreturn {
32 const stderr = io.getStdErr().writer();
33
34 stderr.print("error: " ++ format ++ "\n", args) catch {};
35 process.exit(1);
36}
37
38pub fn main() !void {
39 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
40 defer arena.deinit();
41
42 const allocator = arena.allocator();
43
44 var args_it = try process.argsWithAllocator(allocator);
45 if (!args_it.skip()) @panic("expected self arg");
46
47 var zig_exe: []const u8 = "zig";
48 var opt_zig_lib_dir: ?[]const u8 = null;
49 var do_code_tests = true;
50 var files = [_][]const u8{ "", "" };
51
52 var i: usize = 0;
53 while (args_it.next()) |arg| {
54 if (mem.startsWith(u8, arg, "-")) {
55 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
56 const stdout = io.getStdOut().writer();
57 try stdout.writeAll(usage);
58 process.exit(0);
59 } else if (mem.eql(u8, arg, "--zig")) {
60 if (args_it.next()) |param| {
61 zig_exe = param;
62 } else {
63 fatal("expected parameter after --zig", .{});
64 }
65 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
66 if (args_it.next()) |param| {
67 opt_zig_lib_dir = param;
68 } else {
69 fatal("expected parameter after --zig-lib-dir", .{});
70 }
71 } else if (mem.eql(u8, arg, "--skip-code-tests")) {
72 do_code_tests = false;
73 } else {
74 fatal("unrecognized option: '{s}'", .{arg});
75 }
76 } else {
77 if (i > 1) {
78 fatal("too many arguments", .{});
79 }
80 files[i] = arg;
81 i += 1;
82 }
83 }
84 if (i < 2) {
85 fatal("not enough arguments", .{});
86 }
87
88 var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only });
89 defer in_file.close();
90
91 var out_file = try fs.cwd().createFile(files[1], .{});
92 defer out_file.close();
93
94 const input_file_bytes = try in_file.reader().readAllAlloc(allocator, max_doc_file_size);
95
96 var buffered_writer = io.bufferedWriter(out_file.writer());
97
98 var tokenizer = Tokenizer.init(files[0], input_file_bytes);
99 var toc = try genToc(allocator, &tokenizer);
100
101 try fs.cwd().makePath(tmp_dir_name);
102 defer fs.cwd().deleteTree(tmp_dir_name) catch {};
103
104 try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests);
105 try buffered_writer.flush();
106}
107
108const Token = struct {
109 id: Id,
110 start: usize,
111 end: usize,
112
113 const Id = enum {
114 invalid,
115 content,
116 bracket_open,
117 tag_content,
118 separator,
119 bracket_close,
120 eof,
121 };
122};
123
124const Tokenizer = struct {
125 buffer: []const u8,
126 index: usize,
127 state: State,
128 source_file_name: []const u8,
129 code_node_count: usize,
130
131 const State = enum {
132 start,
133 l_bracket,
134 hash,
135 tag_name,
136 eof,
137 };
138
139 fn init(source_file_name: []const u8, buffer: []const u8) Tokenizer {
140 return Tokenizer{
141 .buffer = buffer,
142 .index = 0,
143 .state = .start,
144 .source_file_name = source_file_name,
145 .code_node_count = 0,
146 };
147 }
148
149 fn next(self: *Tokenizer) Token {
150 var result = Token{
151 .id = .eof,
152 .start = self.index,
153 .end = undefined,
154 };
155 while (self.index < self.buffer.len) : (self.index += 1) {
156 const c = self.buffer[self.index];
157 switch (self.state) {
158 .start => switch (c) {
159 '{' => {
160 self.state = .l_bracket;
161 },
162 else => {
163 result.id = .content;
164 },
165 },
166 .l_bracket => switch (c) {
167 '#' => {
168 if (result.id != .eof) {
169 self.index -= 1;
170 self.state = .start;
171 break;
172 } else {
173 result.id = .bracket_open;
174 self.index += 1;
175 self.state = .tag_name;
176 break;
177 }
178 },
179 else => {
180 result.id = .content;
181 self.state = .start;
182 },
183 },
184 .tag_name => switch (c) {
185 '|' => {
186 if (result.id != .eof) {
187 break;
188 } else {
189 result.id = .separator;
190 self.index += 1;
191 break;
192 }
193 },
194 '#' => {
195 self.state = .hash;
196 },
197 else => {
198 result.id = .tag_content;
199 },
200 },
201 .hash => switch (c) {
202 '}' => {
203 if (result.id != .eof) {
204 self.index -= 1;
205 self.state = .tag_name;
206 break;
207 } else {
208 result.id = .bracket_close;
209 self.index += 1;
210 self.state = .start;
211 break;
212 }
213 },
214 else => {
215 result.id = .tag_content;
216 self.state = .tag_name;
217 },
218 },
219 .eof => unreachable,
220 }
221 } else {
222 switch (self.state) {
223 .start, .l_bracket, .eof => {},
224 else => {
225 result.id = .invalid;
226 },
227 }
228 self.state = .eof;
229 }
230 result.end = self.index;
231 return result;
232 }
233
234 const Location = struct {
235 line: usize,
236 column: usize,
237 line_start: usize,
238 line_end: usize,
239 };
240
241 fn getTokenLocation(self: *Tokenizer, token: Token) Location {
242 var loc = Location{
243 .line = 0,
244 .column = 0,
245 .line_start = 0,
246 .line_end = 0,
247 };
248 for (self.buffer, 0..) |c, i| {
249 if (i == token.start) {
250 loc.line_end = i;
251 while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {}
252 return loc;
253 }
254 if (c == '\n') {
255 loc.line += 1;
256 loc.column = 0;
257 loc.line_start = i + 1;
258 } else {
259 loc.column += 1;
260 }
261 }
262 return loc;
263 }
264};
265
266fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: anytype) anyerror {
267 const loc = tokenizer.getTokenLocation(token);
268 const args_prefix = .{ tokenizer.source_file_name, loc.line + 1, loc.column + 1 };
269 print("{s}:{d}:{d}: error: " ++ fmt ++ "\n", args_prefix ++ args);
270 if (loc.line_start <= loc.line_end) {
271 print("{s}\n", .{tokenizer.buffer[loc.line_start..loc.line_end]});
272 {
273 var i: usize = 0;
274 while (i < loc.column) : (i += 1) {
275 print(" ", .{});
276 }
277 }
278 {
279 const caret_count = @min(token.end, loc.line_end) - token.start;
280 var i: usize = 0;
281 while (i < caret_count) : (i += 1) {
282 print("~", .{});
283 }
284 }
285 print("\n", .{});
286 }
287 return error.ParseError;
288}
289
290fn assertToken(tokenizer: *Tokenizer, token: Token, id: Token.Id) !void {
291 if (token.id != id) {
292 return parseError(tokenizer, token, "expected {s}, found {s}", .{ @tagName(id), @tagName(token.id) });
293 }
294}
295
296fn eatToken(tokenizer: *Tokenizer, id: Token.Id) !Token {
297 const token = tokenizer.next();
298 try assertToken(tokenizer, token, id);
299 return token;
300}
301
302const HeaderOpen = struct {
303 name: []const u8,
304 url: []const u8,
305 n: usize,
306};
307
308const SeeAlsoItem = struct {
309 name: []const u8,
310 token: Token,
311};
312
313const ExpectedOutcome = enum {
314 succeed,
315 fail,
316 build_fail,
317};
318
319const Code = struct {
320 id: Id,
321 name: []const u8,
322 source_token: Token,
323 just_check_syntax: bool,
324 mode: std.builtin.Mode,
325 link_objects: []const []const u8,
326 target_str: ?[]const u8,
327 link_libc: bool,
328 link_mode: ?std.builtin.LinkMode,
329 disable_cache: bool,
330 verbose_cimport: bool,
331 additional_options: []const []const u8,
332
333 const Id = union(enum) {
334 @"test",
335 test_error: []const u8,
336 test_safety: []const u8,
337 exe: ExpectedOutcome,
338 obj: ?[]const u8,
339 lib,
340 };
341};
342
343const Link = struct {
344 url: []const u8,
345 name: []const u8,
346 token: Token,
347};
348
349const SyntaxBlock = struct {
350 source_type: SourceType,
351 name: []const u8,
352 source_token: Token,
353
354 const SourceType = enum {
355 zig,
356 c,
357 peg,
358 javascript,
359 };
360};
361
362const Node = union(enum) {
363 Content: []const u8,
364 Nav,
365 Builtin: Token,
366 HeaderOpen: HeaderOpen,
367 SeeAlso: []const SeeAlsoItem,
368 Code: Code,
369 Link: Link,
370 InlineSyntax: Token,
371 Shell: Token,
372 SyntaxBlock: SyntaxBlock,
373};
374
375const Toc = struct {
376 nodes: []Node,
377 toc: []u8,
378 urls: std.StringHashMap(Token),
379};
380
381const Action = enum {
382 open,
383 close,
384};
385
386fn genToc(allocator: Allocator, tokenizer: *Tokenizer) !Toc {
387 var urls = std.StringHashMap(Token).init(allocator);
388 errdefer urls.deinit();
389
390 var header_stack_size: usize = 0;
391 var last_action: Action = .open;
392 var last_columns: ?u8 = null;
393
394 var toc_buf = std.ArrayList(u8).init(allocator);
395 defer toc_buf.deinit();
396
397 var toc = toc_buf.writer();
398
399 var nodes = std.ArrayList(Node).init(allocator);
400 defer nodes.deinit();
401
402 try toc.writeByte('\n');
403
404 while (true) {
405 const token = tokenizer.next();
406 switch (token.id) {
407 .eof => {
408 if (header_stack_size != 0) {
409 return parseError(tokenizer, token, "unbalanced headers", .{});
410 }
411 try toc.writeAll(" </ul>\n");
412 break;
413 },
414 .content => {
415 try nodes.append(Node{ .Content = tokenizer.buffer[token.start..token.end] });
416 },
417 .bracket_open => {
418 const tag_token = try eatToken(tokenizer, .tag_content);
419 const tag_name = tokenizer.buffer[tag_token.start..tag_token.end];
420
421 if (mem.eql(u8, tag_name, "nav")) {
422 _ = try eatToken(tokenizer, .bracket_close);
423
424 try nodes.append(Node.Nav);
425 } else if (mem.eql(u8, tag_name, "builtin")) {
426 _ = try eatToken(tokenizer, .bracket_close);
427 try nodes.append(Node{ .Builtin = tag_token });
428 } else if (mem.eql(u8, tag_name, "header_open")) {
429 _ = try eatToken(tokenizer, .separator);
430 const content_token = try eatToken(tokenizer, .tag_content);
431 const content = tokenizer.buffer[content_token.start..content_token.end];
432 var columns: ?u8 = null;
433 while (true) {
434 const bracket_tok = tokenizer.next();
435 switch (bracket_tok.id) {
436 .bracket_close => break,
437 .separator => continue,
438 .tag_content => {
439 const param = tokenizer.buffer[bracket_tok.start..bracket_tok.end];
440 if (mem.eql(u8, param, "2col")) {
441 columns = 2;
442 } else {
443 return parseError(
444 tokenizer,
445 bracket_tok,
446 "unrecognized header_open param: {s}",
447 .{param},
448 );
449 }
450 },
451 else => return parseError(tokenizer, bracket_tok, "invalid header_open token", .{}),
452 }
453 }
454
455 header_stack_size += 1;
456
457 const urlized = try urlize(allocator, content);
458 try nodes.append(Node{
459 .HeaderOpen = HeaderOpen{
460 .name = content,
461 .url = urlized,
462 .n = header_stack_size + 1, // highest-level section headers start at h2
463 },
464 });
465 if (try urls.fetchPut(urlized, tag_token)) |kv| {
466 parseError(tokenizer, tag_token, "duplicate header url: #{s}", .{urlized}) catch {};
467 parseError(tokenizer, kv.value, "other tag here", .{}) catch {};
468 return error.ParseError;
469 }
470 if (last_action == .open) {
471 try toc.writeByte('\n');
472 try toc.writeByteNTimes(' ', header_stack_size * 4);
473 if (last_columns) |n| {
474 try toc.print("<ul style=\"columns: {}\">\n", .{n});
475 } else {
476 try toc.writeAll("<ul>\n");
477 }
478 } else {
479 last_action = .open;
480 }
481 last_columns = columns;
482 try toc.writeByteNTimes(' ', 4 + header_stack_size * 4);
483 try toc.print("<li><a id=\"toc-{s}\" href=\"#{s}\">{s}</a>", .{ urlized, urlized, content });
484 } else if (mem.eql(u8, tag_name, "header_close")) {
485 if (header_stack_size == 0) {
486 return parseError(tokenizer, tag_token, "unbalanced close header", .{});
487 }
488 header_stack_size -= 1;
489 _ = try eatToken(tokenizer, .bracket_close);
490
491 if (last_action == .close) {
492 try toc.writeByteNTimes(' ', 8 + header_stack_size * 4);
493 try toc.writeAll("</ul></li>\n");
494 } else {
495 try toc.writeAll("</li>\n");
496 last_action = .close;
497 }
498 } else if (mem.eql(u8, tag_name, "see_also")) {
499 var list = std.ArrayList(SeeAlsoItem).init(allocator);
500 errdefer list.deinit();
501
502 while (true) {
503 const see_also_tok = tokenizer.next();
504 switch (see_also_tok.id) {
505 .tag_content => {
506 const content = tokenizer.buffer[see_also_tok.start..see_also_tok.end];
507 try list.append(SeeAlsoItem{
508 .name = content,
509 .token = see_also_tok,
510 });
511 },
512 .separator => {},
513 .bracket_close => {
514 try nodes.append(Node{ .SeeAlso = try list.toOwnedSlice() });
515 break;
516 },
517 else => return parseError(tokenizer, see_also_tok, "invalid see_also token", .{}),
518 }
519 }
520 } else if (mem.eql(u8, tag_name, "link")) {
521 _ = try eatToken(tokenizer, .separator);
522 const name_tok = try eatToken(tokenizer, .tag_content);
523 const name = tokenizer.buffer[name_tok.start..name_tok.end];
524
525 const url_name = blk: {
526 const tok = tokenizer.next();
527 switch (tok.id) {
528 .bracket_close => break :blk name,
529 .separator => {
530 const explicit_text = try eatToken(tokenizer, .tag_content);
531 _ = try eatToken(tokenizer, .bracket_close);
532 break :blk tokenizer.buffer[explicit_text.start..explicit_text.end];
533 },
534 else => return parseError(tokenizer, tok, "invalid link token", .{}),
535 }
536 };
537
538 try nodes.append(Node{
539 .Link = Link{
540 .url = try urlize(allocator, url_name),
541 .name = name,
542 .token = name_tok,
543 },
544 });
545 } else if (mem.eql(u8, tag_name, "code_begin")) {
546 _ = try eatToken(tokenizer, .separator);
547 const code_kind_tok = try eatToken(tokenizer, .tag_content);
548 _ = try eatToken(tokenizer, .separator);
549 const name_tok = try eatToken(tokenizer, .tag_content);
550 const name = tokenizer.buffer[name_tok.start..name_tok.end];
551 var error_str: []const u8 = "";
552 const maybe_sep = tokenizer.next();
553 switch (maybe_sep.id) {
554 .separator => {
555 const error_tok = try eatToken(tokenizer, .tag_content);
556 error_str = tokenizer.buffer[error_tok.start..error_tok.end];
557 _ = try eatToken(tokenizer, .bracket_close);
558 },
559 .bracket_close => {},
560 else => return parseError(tokenizer, token, "invalid token", .{}),
561 }
562 const code_kind_str = tokenizer.buffer[code_kind_tok.start..code_kind_tok.end];
563 var code_kind_id: Code.Id = undefined;
564 var just_check_syntax = false;
565 if (mem.eql(u8, code_kind_str, "exe")) {
566 code_kind_id = Code.Id{ .exe = .succeed };
567 } else if (mem.eql(u8, code_kind_str, "exe_err")) {
568 code_kind_id = Code.Id{ .exe = .fail };
569 } else if (mem.eql(u8, code_kind_str, "exe_build_err")) {
570 code_kind_id = Code.Id{ .exe = .build_fail };
571 } else if (mem.eql(u8, code_kind_str, "test")) {
572 code_kind_id = .@"test";
573 } else if (mem.eql(u8, code_kind_str, "test_err")) {
574 code_kind_id = Code.Id{ .test_error = error_str };
575 } else if (mem.eql(u8, code_kind_str, "test_safety")) {
576 code_kind_id = Code.Id{ .test_safety = error_str };
577 } else if (mem.eql(u8, code_kind_str, "obj")) {
578 code_kind_id = Code.Id{ .obj = null };
579 } else if (mem.eql(u8, code_kind_str, "obj_err")) {
580 code_kind_id = Code.Id{ .obj = error_str };
581 } else if (mem.eql(u8, code_kind_str, "lib")) {
582 code_kind_id = Code.Id.lib;
583 } else if (mem.eql(u8, code_kind_str, "syntax")) {
584 code_kind_id = Code.Id{ .obj = null };
585 just_check_syntax = true;
586 } else {
587 return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str});
588 }
589
590 var mode: std.builtin.Mode = .Debug;
591 var link_objects = std.ArrayList([]const u8).init(allocator);
592 defer link_objects.deinit();
593 var target_str: ?[]const u8 = null;
594 var link_libc = false;
595 var link_mode: ?std.builtin.LinkMode = null;
596 var disable_cache = false;
597 var verbose_cimport = false;
598 var additional_options = std.ArrayList([]const u8).init(allocator);
599 defer additional_options.deinit();
600
601 const source_token = while (true) {
602 const content_tok = try eatToken(tokenizer, .content);
603 _ = try eatToken(tokenizer, .bracket_open);
604 const end_code_tag = try eatToken(tokenizer, .tag_content);
605 const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end];
606 if (mem.eql(u8, end_tag_name, "code_release_fast")) {
607 mode = .ReleaseFast;
608 } else if (mem.eql(u8, end_tag_name, "code_release_safe")) {
609 mode = .ReleaseSafe;
610 } else if (mem.eql(u8, end_tag_name, "code_disable_cache")) {
611 disable_cache = true;
612 } else if (mem.eql(u8, end_tag_name, "code_verbose_cimport")) {
613 verbose_cimport = true;
614 } else if (mem.eql(u8, end_tag_name, "code_link_object")) {
615 _ = try eatToken(tokenizer, .separator);
616 const obj_tok = try eatToken(tokenizer, .tag_content);
617 try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]);
618 } else if (mem.eql(u8, end_tag_name, "target_windows")) {
619 target_str = "x86_64-windows";
620 } else if (mem.eql(u8, end_tag_name, "target_linux_x86_64")) {
621 target_str = "x86_64-linux";
622 } else if (mem.eql(u8, end_tag_name, "target_linux_riscv64")) {
623 target_str = "riscv64-linux";
624 } else if (mem.eql(u8, end_tag_name, "target_wasm")) {
625 target_str = "wasm32-freestanding";
626 } else if (mem.eql(u8, end_tag_name, "target_wasi")) {
627 target_str = "wasm32-wasi";
628 } else if (mem.eql(u8, end_tag_name, "link_libc")) {
629 link_libc = true;
630 } else if (mem.eql(u8, end_tag_name, "link_mode_dynamic")) {
631 link_mode = .Dynamic;
632 } else if (mem.eql(u8, end_tag_name, "additonal_option")) {
633 _ = try eatToken(tokenizer, .separator);
634 const option = try eatToken(tokenizer, .tag_content);
635 try additional_options.append(tokenizer.buffer[option.start..option.end]);
636 } else if (mem.eql(u8, end_tag_name, "code_end")) {
637 _ = try eatToken(tokenizer, .bracket_close);
638 break content_tok;
639 } else {
640 return parseError(
641 tokenizer,
642 end_code_tag,
643 "invalid token inside code_begin: {s}",
644 .{end_tag_name},
645 );
646 }
647 _ = try eatToken(tokenizer, .bracket_close);
648 } else unreachable; // TODO issue #707
649 try nodes.append(Node{
650 .Code = Code{
651 .id = code_kind_id,
652 .name = name,
653 .source_token = source_token,
654 .just_check_syntax = just_check_syntax,
655 .mode = mode,
656 .link_objects = try link_objects.toOwnedSlice(),
657 .target_str = target_str,
658 .link_libc = link_libc,
659 .link_mode = link_mode,
660 .disable_cache = disable_cache,
661 .verbose_cimport = verbose_cimport,
662 .additional_options = try additional_options.toOwnedSlice(),
663 },
664 });
665 tokenizer.code_node_count += 1;
666 } else if (mem.eql(u8, tag_name, "syntax")) {
667 _ = try eatToken(tokenizer, .bracket_close);
668 const content_tok = try eatToken(tokenizer, .content);
669 _ = try eatToken(tokenizer, .bracket_open);
670 const end_syntax_tag = try eatToken(tokenizer, .tag_content);
671 const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end];
672 if (!mem.eql(u8, end_tag_name, "endsyntax")) {
673 return parseError(
674 tokenizer,
675 end_syntax_tag,
676 "invalid token inside syntax: {s}",
677 .{end_tag_name},
678 );
679 }
680 _ = try eatToken(tokenizer, .bracket_close);
681 try nodes.append(Node{ .InlineSyntax = content_tok });
682 } else if (mem.eql(u8, tag_name, "shell_samp")) {
683 _ = try eatToken(tokenizer, .bracket_close);
684 const content_tok = try eatToken(tokenizer, .content);
685 _ = try eatToken(tokenizer, .bracket_open);
686 const end_syntax_tag = try eatToken(tokenizer, .tag_content);
687 const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end];
688 if (!mem.eql(u8, end_tag_name, "end_shell_samp")) {
689 return parseError(
690 tokenizer,
691 end_syntax_tag,
692 "invalid token inside syntax: {s}",
693 .{end_tag_name},
694 );
695 }
696 _ = try eatToken(tokenizer, .bracket_close);
697 try nodes.append(Node{ .Shell = content_tok });
698 } else if (mem.eql(u8, tag_name, "syntax_block")) {
699 _ = try eatToken(tokenizer, .separator);
700 const source_type_tok = try eatToken(tokenizer, .tag_content);
701 var name: []const u8 = "sample_code";
702 const maybe_sep = tokenizer.next();
703 switch (maybe_sep.id) {
704 .separator => {
705 const name_tok = try eatToken(tokenizer, .tag_content);
706 name = tokenizer.buffer[name_tok.start..name_tok.end];
707 _ = try eatToken(tokenizer, .bracket_close);
708 },
709 .bracket_close => {},
710 else => return parseError(tokenizer, token, "invalid token", .{}),
711 }
712 const source_type_str = tokenizer.buffer[source_type_tok.start..source_type_tok.end];
713 var source_type: SyntaxBlock.SourceType = undefined;
714 if (mem.eql(u8, source_type_str, "zig")) {
715 source_type = SyntaxBlock.SourceType.zig;
716 } else if (mem.eql(u8, source_type_str, "c")) {
717 source_type = SyntaxBlock.SourceType.c;
718 } else if (mem.eql(u8, source_type_str, "peg")) {
719 source_type = SyntaxBlock.SourceType.peg;
720 } else if (mem.eql(u8, source_type_str, "javascript")) {
721 source_type = SyntaxBlock.SourceType.javascript;
722 } else {
723 return parseError(tokenizer, source_type_tok, "unrecognized code kind: {s}", .{source_type_str});
724 }
725 const source_token = while (true) {
726 const content_tok = try eatToken(tokenizer, .content);
727 _ = try eatToken(tokenizer, .bracket_open);
728 const end_code_tag = try eatToken(tokenizer, .tag_content);
729 const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end];
730 if (mem.eql(u8, end_tag_name, "end_syntax_block")) {
731 _ = try eatToken(tokenizer, .bracket_close);
732 break content_tok;
733 } else {
734 return parseError(
735 tokenizer,
736 end_code_tag,
737 "invalid token inside code_begin: {s}",
738 .{end_tag_name},
739 );
740 }
741 _ = try eatToken(tokenizer, .bracket_close);
742 };
743 try nodes.append(Node{ .SyntaxBlock = SyntaxBlock{ .source_type = source_type, .name = name, .source_token = source_token } });
744 } else {
745 return parseError(tokenizer, tag_token, "unrecognized tag name: {s}", .{tag_name});
746 }
747 },
748 else => return parseError(tokenizer, token, "invalid token", .{}),
749 }
750 }
751
752 return Toc{
753 .nodes = try nodes.toOwnedSlice(),
754 .toc = try toc_buf.toOwnedSlice(),
755 .urls = urls,
756 };
757}
758
759fn urlize(allocator: Allocator, input: []const u8) ![]u8 {
760 var buf = std.ArrayList(u8).init(allocator);
761 defer buf.deinit();
762
763 const out = buf.writer();
764 for (input) |c| {
765 switch (c) {
766 'a'...'z', 'A'...'Z', '_', '-', '0'...'9' => {
767 try out.writeByte(c);
768 },
769 ' ' => {
770 try out.writeByte('-');
771 },
772 else => {},
773 }
774 }
775 return try buf.toOwnedSlice();
776}
777
778fn escapeHtml(allocator: Allocator, input: []const u8) ![]u8 {
779 var buf = std.ArrayList(u8).init(allocator);
780 defer buf.deinit();
781
782 const out = buf.writer();
783 try writeEscaped(out, input);
784 return try buf.toOwnedSlice();
785}
786
787fn writeEscaped(out: anytype, input: []const u8) !void {
788 for (input) |c| {
789 try switch (c) {
790 '&' => out.writeAll("&amp;"),
791 '<' => out.writeAll("&lt;"),
792 '>' => out.writeAll("&gt;"),
793 '"' => out.writeAll("&quot;"),
794 else => out.writeByte(c),
795 };
796 }
797}
798
799// Returns true if number is in slice.
800fn in(slice: []const u8, number: u8) bool {
801 for (slice) |n| {
802 if (number == n) return true;
803 }
804 return false;
805}
806
807fn termColor(allocator: Allocator, input: []const u8) ![]u8 {
808 // The SRG sequences generates by the Zig compiler are in the format:
809 // ESC [ <foreground-color> ; <n> m
810 // or
811 // ESC [ <n> m
812 //
813 // where
814 // foreground-color is 31 (red), 32 (green), 36 (cyan)
815 // n is 0 (reset), 1 (bold), 2 (dim)
816 //
817 // Note that 37 (white) is currently not used by the compiler.
818 //
819 // See std.debug.TTY.Color.
820 const supported_sgr_colors = [_]u8{ 31, 32, 36 };
821 const supported_sgr_numbers = [_]u8{ 0, 1, 2 };
822
823 var buf = std.ArrayList(u8).init(allocator);
824 defer buf.deinit();
825
826 var out = buf.writer();
827 var sgr_param_start_index: usize = undefined;
828 var sgr_num: u8 = undefined;
829 var sgr_color: u8 = undefined;
830 var i: usize = 0;
831 var state: enum {
832 start,
833 escape,
834 lbracket,
835 number,
836 after_number,
837 arg,
838 arg_number,
839 expect_end,
840 } = .start;
841 var last_new_line: usize = 0;
842 var open_span_count: usize = 0;
843 while (i < input.len) : (i += 1) {
844 const c = input[i];
845 switch (state) {
846 .start => switch (c) {
847 '\x1b' => state = .escape,
848 '\n' => {
849 try out.writeByte(c);
850 last_new_line = buf.items.len;
851 },
852 else => try out.writeByte(c),
853 },
854 .escape => switch (c) {
855 '[' => state = .lbracket,
856 else => return error.UnsupportedEscape,
857 },
858 .lbracket => switch (c) {
859 '0'...'9' => {
860 sgr_param_start_index = i;
861 state = .number;
862 },
863 else => return error.UnsupportedEscape,
864 },
865 .number => switch (c) {
866 '0'...'9' => {},
867 else => {
868 sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10);
869 sgr_color = 0;
870 state = .after_number;
871 i -= 1;
872 },
873 },
874 .after_number => switch (c) {
875 ';' => state = .arg,
876 'D' => state = .start,
877 'K' => {
878 buf.items.len = last_new_line;
879 state = .start;
880 },
881 else => {
882 state = .expect_end;
883 i -= 1;
884 },
885 },
886 .arg => switch (c) {
887 '0'...'9' => {
888 sgr_param_start_index = i;
889 state = .arg_number;
890 },
891 else => return error.UnsupportedEscape,
892 },
893 .arg_number => switch (c) {
894 '0'...'9' => {},
895 else => {
896 // Keep the sequence consistent, foreground color first.
897 // 32;1m is equivalent to 1;32m, but the latter will
898 // generate an incorrect HTML class without notice.
899 sgr_color = sgr_num;
900 if (!in(&supported_sgr_colors, sgr_color)) return error.UnsupportedForegroundColor;
901
902 sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10);
903 if (!in(&supported_sgr_numbers, sgr_num)) return error.UnsupportedNumber;
904
905 state = .expect_end;
906 i -= 1;
907 },
908 },
909 .expect_end => switch (c) {
910 'm' => {
911 state = .start;
912 while (open_span_count != 0) : (open_span_count -= 1) {
913 try out.writeAll("</span>");
914 }
915 if (sgr_num == 0) {
916 if (sgr_color != 0) return error.UnsupportedColor;
917 continue;
918 }
919 if (sgr_color != 0) {
920 try out.print("<span class=\"sgr-{d}_{d}m\">", .{ sgr_color, sgr_num });
921 } else {
922 try out.print("<span class=\"sgr-{d}m\">", .{sgr_num});
923 }
924 open_span_count += 1;
925 },
926 else => return error.UnsupportedEscape,
927 },
928 }
929 }
930 return try buf.toOwnedSlice();
931}
932
933const builtin_types = [_][]const u8{
934 "f16", "f32", "f64", "f80", "f128",
935 "c_longdouble", "c_short", "c_ushort", "c_int", "c_uint",
936 "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char",
937 "anyopaque", "void", "bool", "isize", "usize",
938 "noreturn", "type", "anyerror", "comptime_int", "comptime_float",
939};
940
941fn isType(name: []const u8) bool {
942 for (builtin_types) |t| {
943 if (mem.eql(u8, t, name))
944 return true;
945 }
946 return false;
947}
948
949const start_line = "<span class=\"line\">";
950const end_line = "</span>";
951
952fn writeEscapedLines(out: anytype, text: []const u8) !void {
953 for (text) |char| {
954 if (char == '\n') {
955 try out.writeAll(end_line);
956 try out.writeAll("\n");
957 try out.writeAll(start_line);
958 } else {
959 try writeEscaped(out, &[_]u8{char});
960 }
961 }
962}
963
964fn tokenizeAndPrintRaw(
965 allocator: Allocator,
966 docgen_tokenizer: *Tokenizer,
967 out: anytype,
968 source_token: Token,
969 raw_src: []const u8,
970) !void {
971 const src_non_terminated = mem.trim(u8, raw_src, " \n");
972 const src = try allocator.dupeZ(u8, src_non_terminated);
973
974 try out.writeAll("<code>" ++ start_line);
975 var tokenizer = std.zig.Tokenizer.init(src);
976 var index: usize = 0;
977 var next_tok_is_fn = false;
978 while (true) {
979 const prev_tok_was_fn = next_tok_is_fn;
980 next_tok_is_fn = false;
981
982 const token = tokenizer.next();
983 if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
984 // render one comment
985 const comment_start = index + comment_start_off;
986 const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
987 const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
988
989 try writeEscapedLines(out, src[index..comment_start]);
990 try out.writeAll("<span class=\"tok-comment\">");
991 try writeEscaped(out, src[comment_start..comment_end]);
992 try out.writeAll("</span>");
993 index = comment_end;
994 tokenizer.index = index;
995 continue;
996 }
997
998 try writeEscapedLines(out, src[index..token.loc.start]);
999 switch (token.tag) {
1000 .eof => break,
1001
1002 .keyword_addrspace,
1003 .keyword_align,
1004 .keyword_and,
1005 .keyword_asm,
1006 .keyword_async,
1007 .keyword_await,
1008 .keyword_break,
1009 .keyword_catch,
1010 .keyword_comptime,
1011 .keyword_const,
1012 .keyword_continue,
1013 .keyword_defer,
1014 .keyword_else,
1015 .keyword_enum,
1016 .keyword_errdefer,
1017 .keyword_error,
1018 .keyword_export,
1019 .keyword_extern,
1020 .keyword_for,
1021 .keyword_if,
1022 .keyword_inline,
1023 .keyword_noalias,
1024 .keyword_noinline,
1025 .keyword_nosuspend,
1026 .keyword_opaque,
1027 .keyword_or,
1028 .keyword_orelse,
1029 .keyword_packed,
1030 .keyword_anyframe,
1031 .keyword_pub,
1032 .keyword_resume,
1033 .keyword_return,
1034 .keyword_linksection,
1035 .keyword_callconv,
1036 .keyword_struct,
1037 .keyword_suspend,
1038 .keyword_switch,
1039 .keyword_test,
1040 .keyword_threadlocal,
1041 .keyword_try,
1042 .keyword_union,
1043 .keyword_unreachable,
1044 .keyword_usingnamespace,
1045 .keyword_var,
1046 .keyword_volatile,
1047 .keyword_allowzero,
1048 .keyword_while,
1049 .keyword_anytype,
1050 => {
1051 try out.writeAll("<span class=\"tok-kw\">");
1052 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1053 try out.writeAll("</span>");
1054 },
1055
1056 .keyword_fn => {
1057 try out.writeAll("<span class=\"tok-kw\">");
1058 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1059 try out.writeAll("</span>");
1060 next_tok_is_fn = true;
1061 },
1062
1063 .string_literal,
1064 .char_literal,
1065 => {
1066 try out.writeAll("<span class=\"tok-str\">");
1067 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1068 try out.writeAll("</span>");
1069 },
1070
1071 .multiline_string_literal_line => {
1072 if (src[token.loc.end - 1] == '\n') {
1073 try out.writeAll("<span class=\"tok-str\">");
1074 try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
1075 try out.writeAll("</span>" ++ end_line ++ "\n" ++ start_line);
1076 } else {
1077 try out.writeAll("<span class=\"tok-str\">");
1078 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1079 try out.writeAll("</span>");
1080 }
1081 },
1082
1083 .builtin => {
1084 try out.writeAll("<span class=\"tok-builtin\">");
1085 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1086 try out.writeAll("</span>");
1087 },
1088
1089 .doc_comment,
1090 .container_doc_comment,
1091 => {
1092 try out.writeAll("<span class=\"tok-comment\">");
1093 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1094 try out.writeAll("</span>");
1095 },
1096
1097 .identifier => {
1098 const tok_bytes = src[token.loc.start..token.loc.end];
1099 if (mem.eql(u8, tok_bytes, "undefined") or
1100 mem.eql(u8, tok_bytes, "null") or
1101 mem.eql(u8, tok_bytes, "true") or
1102 mem.eql(u8, tok_bytes, "false"))
1103 {
1104 try out.writeAll("<span class=\"tok-null\">");
1105 try writeEscaped(out, tok_bytes);
1106 try out.writeAll("</span>");
1107 } else if (prev_tok_was_fn) {
1108 try out.writeAll("<span class=\"tok-fn\">");
1109 try writeEscaped(out, tok_bytes);
1110 try out.writeAll("</span>");
1111 } else {
1112 const is_int = blk: {
1113 if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
1114 break :blk false;
1115 var i = token.loc.start + 1;
1116 if (i == token.loc.end)
1117 break :blk false;
1118 while (i != token.loc.end) : (i += 1) {
1119 if (src[i] < '0' or src[i] > '9')
1120 break :blk false;
1121 }
1122 break :blk true;
1123 };
1124 if (is_int or isType(tok_bytes)) {
1125 try out.writeAll("<span class=\"tok-type\">");
1126 try writeEscaped(out, tok_bytes);
1127 try out.writeAll("</span>");
1128 } else {
1129 try writeEscaped(out, tok_bytes);
1130 }
1131 }
1132 },
1133
1134 .number_literal => {
1135 try out.writeAll("<span class=\"tok-number\">");
1136 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1137 try out.writeAll("</span>");
1138 },
1139
1140 .bang,
1141 .pipe,
1142 .pipe_pipe,
1143 .pipe_equal,
1144 .equal,
1145 .equal_equal,
1146 .equal_angle_bracket_right,
1147 .bang_equal,
1148 .l_paren,
1149 .r_paren,
1150 .semicolon,
1151 .percent,
1152 .percent_equal,
1153 .l_brace,
1154 .r_brace,
1155 .l_bracket,
1156 .r_bracket,
1157 .period,
1158 .period_asterisk,
1159 .ellipsis2,
1160 .ellipsis3,
1161 .caret,
1162 .caret_equal,
1163 .plus,
1164 .plus_plus,
1165 .plus_equal,
1166 .plus_percent,
1167 .plus_percent_equal,
1168 .plus_pipe,
1169 .plus_pipe_equal,
1170 .minus,
1171 .minus_equal,
1172 .minus_percent,
1173 .minus_percent_equal,
1174 .minus_pipe,
1175 .minus_pipe_equal,
1176 .asterisk,
1177 .asterisk_equal,
1178 .asterisk_asterisk,
1179 .asterisk_percent,
1180 .asterisk_percent_equal,
1181 .asterisk_pipe,
1182 .asterisk_pipe_equal,
1183 .arrow,
1184 .colon,
1185 .slash,
1186 .slash_equal,
1187 .comma,
1188 .ampersand,
1189 .ampersand_equal,
1190 .question_mark,
1191 .angle_bracket_left,
1192 .angle_bracket_left_equal,
1193 .angle_bracket_angle_bracket_left,
1194 .angle_bracket_angle_bracket_left_equal,
1195 .angle_bracket_angle_bracket_left_pipe,
1196 .angle_bracket_angle_bracket_left_pipe_equal,
1197 .angle_bracket_right,
1198 .angle_bracket_right_equal,
1199 .angle_bracket_angle_bracket_right,
1200 .angle_bracket_angle_bracket_right_equal,
1201 .tilde,
1202 => try writeEscaped(out, src[token.loc.start..token.loc.end]),
1203
1204 .invalid, .invalid_periodasterisks => return parseError(
1205 docgen_tokenizer,
1206 source_token,
1207 "syntax error",
1208 .{},
1209 ),
1210 }
1211 index = token.loc.end;
1212 }
1213 try out.writeAll(end_line ++ "</code>");
1214}
1215
1216fn tokenizeAndPrint(
1217 allocator: Allocator,
1218 docgen_tokenizer: *Tokenizer,
1219 out: anytype,
1220 source_token: Token,
1221) !void {
1222 const raw_src = docgen_tokenizer.buffer[source_token.start..source_token.end];
1223 return tokenizeAndPrintRaw(allocator, docgen_tokenizer, out, source_token, raw_src);
1224}
1225
1226fn printSourceBlock(allocator: Allocator, docgen_tokenizer: *Tokenizer, out: anytype, syntax_block: SyntaxBlock) !void {
1227 const source_type = @tagName(syntax_block.source_type);
1228
1229 try out.print("<figure><figcaption class=\"{s}-cap\"><cite class=\"file\">{s}</cite></figcaption><pre>", .{ source_type, syntax_block.name });
1230 switch (syntax_block.source_type) {
1231 .zig => try tokenizeAndPrint(allocator, docgen_tokenizer, out, syntax_block.source_token),
1232 else => {
1233 const raw_source = docgen_tokenizer.buffer[syntax_block.source_token.start..syntax_block.source_token.end];
1234 const trimmed_raw_source = mem.trim(u8, raw_source, " \n");
1235
1236 try out.writeAll("<code>" ++ start_line);
1237 try writeEscapedLines(out, trimmed_raw_source);
1238 try out.writeAll(end_line ++ "</code>");
1239 },
1240 }
1241 try out.writeAll("</pre></figure>");
1242}
1243
1244fn printShell(out: anytype, shell_content: []const u8, escape: bool) !void {
1245 const trimmed_shell_content = mem.trim(u8, shell_content, " \n");
1246 try out.writeAll("<figure><figcaption class=\"shell-cap\">Shell</figcaption><pre><samp>");
1247 var cmd_cont: bool = false;
1248 var iter = std.mem.splitScalar(u8, trimmed_shell_content, '\n');
1249 while (iter.next()) |orig_line| {
1250 const line = mem.trimRight(u8, orig_line, " ");
1251 if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] != '\\') {
1252 try out.writeAll("$ <kbd>");
1253 const s = std.mem.trimLeft(u8, line[1..], " ");
1254 if (escape) {
1255 try writeEscaped(out, s);
1256 } else {
1257 try out.writeAll(s);
1258 }
1259 try out.writeAll("</kbd>" ++ "\n");
1260 } else if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] == '\\') {
1261 try out.writeAll("$ <kbd>");
1262 const s = std.mem.trimLeft(u8, line[1..], " ");
1263 if (escape) {
1264 try writeEscaped(out, s);
1265 } else {
1266 try out.writeAll(s);
1267 }
1268 try out.writeAll("\n");
1269 cmd_cont = true;
1270 } else if (line.len > 0 and line[line.len - 1] != '\\' and cmd_cont) {
1271 if (escape) {
1272 try writeEscaped(out, line);
1273 } else {
1274 try out.writeAll(line);
1275 }
1276 try out.writeAll("</kbd>" ++ "\n");
1277 cmd_cont = false;
1278 } else {
1279 if (escape) {
1280 try writeEscaped(out, line);
1281 } else {
1282 try out.writeAll(line);
1283 }
1284 try out.writeAll("\n");
1285 }
1286 }
1287
1288 try out.writeAll("</samp></pre></figure>");
1289}
1290
1291// Override this to skip to later tests
1292const debug_start_line = 0;
1293
1294fn genHtml(
1295 allocator: Allocator,
1296 tokenizer: *Tokenizer,
1297 toc: *Toc,
1298 out: anytype,
1299 zig_exe: []const u8,
1300 opt_zig_lib_dir: ?[]const u8,
1301 do_code_tests: bool,
1302) !void {
1303 var progress = Progress{ .dont_print_on_dumb = true };
1304 const root_node = progress.start("Generating docgen examples", toc.nodes.len);
1305 defer root_node.end();
1306
1307 var env_map = try process.getEnvMap(allocator);
1308 try env_map.put("YES_COLOR", "1");
1309
1310 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1311 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir);
1312
1313 for (toc.nodes) |node| {
1314 defer root_node.completeOne();
1315 switch (node) {
1316 .Content => |data| {
1317 try out.writeAll(data);
1318 },
1319 .Link => |info| {
1320 if (!toc.urls.contains(info.url)) {
1321 return parseError(tokenizer, info.token, "url not found: {s}", .{info.url});
1322 }
1323 try out.print("<a href=\"#{s}\">{s}</a>", .{ info.url, info.name });
1324 },
1325 .Nav => {
1326 try out.writeAll(toc.toc);
1327 },
1328 .Builtin => |tok| {
1329 try out.writeAll("<figure><figcaption class=\"zig-cap\"><cite>@import(\"builtin\")</cite></figcaption><pre>");
1330 try tokenizeAndPrintRaw(allocator, tokenizer, out, tok, builtin_code);
1331 try out.writeAll("</pre></figure>");
1332 },
1333 .HeaderOpen => |info| {
1334 try out.print(
1335 "<h{d} id=\"{s}\"><a href=\"#toc-{s}\">{s}</a> <a class=\"hdr\" href=\"#{s}\">§</a></h{d}>\n",
1336 .{ info.n, info.url, info.url, info.name, info.url, info.n },
1337 );
1338 },
1339 .SeeAlso => |items| {
1340 try out.writeAll("<p>See also:</p><ul>\n");
1341 for (items) |item| {
1342 const url = try urlize(allocator, item.name);
1343 if (!toc.urls.contains(url)) {
1344 return parseError(tokenizer, item.token, "url not found: {s}", .{url});
1345 }
1346 try out.print("<li><a href=\"#{s}\">{s}</a></li>\n", .{ url, item.name });
1347 }
1348 try out.writeAll("</ul>\n");
1349 },
1350 .InlineSyntax => |content_tok| {
1351 try tokenizeAndPrint(allocator, tokenizer, out, content_tok);
1352 },
1353 .Shell => |content_tok| {
1354 const raw_shell_content = tokenizer.buffer[content_tok.start..content_tok.end];
1355 try printShell(out, raw_shell_content, true);
1356 },
1357 .SyntaxBlock => |syntax_block| {
1358 try printSourceBlock(allocator, tokenizer, out, syntax_block);
1359 },
1360 .Code => |code| {
1361 const name_plus_ext = try std.fmt.allocPrint(allocator, "{s}.zig", .{code.name});
1362 const syntax_block = SyntaxBlock{
1363 .source_type = .zig,
1364 .name = name_plus_ext,
1365 .source_token = code.source_token,
1366 };
1367
1368 try printSourceBlock(allocator, tokenizer, out, syntax_block);
1369
1370 if (!do_code_tests) {
1371 continue;
1372 }
1373
1374 if (debug_start_line > 0) {
1375 const loc = tokenizer.getTokenLocation(code.source_token);
1376 if (debug_start_line > loc.line) {
1377 continue;
1378 }
1379 }
1380
1381 const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end];
1382 const trimmed_raw_source = mem.trim(u8, raw_source, " \n");
1383 const tmp_source_file_name = try fs.path.join(
1384 allocator,
1385 &[_][]const u8{ tmp_dir_name, name_plus_ext },
1386 );
1387 try fs.cwd().writeFile(tmp_source_file_name, trimmed_raw_source);
1388
1389 var shell_buffer = std.ArrayList(u8).init(allocator);
1390 defer shell_buffer.deinit();
1391 var shell_out = shell_buffer.writer();
1392
1393 switch (code.id) {
1394 .exe => |expected_outcome| code_block: {
1395 var build_args = std.ArrayList([]const u8).init(allocator);
1396 defer build_args.deinit();
1397 try build_args.appendSlice(&[_][]const u8{
1398 zig_exe, "build-exe",
1399 "--name", code.name,
1400 "--color", "on",
1401 name_plus_ext,
1402 });
1403 if (opt_zig_lib_dir) |zig_lib_dir| {
1404 try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1405 }
1406
1407 try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext});
1408
1409 switch (code.mode) {
1410 .Debug => {},
1411 else => {
1412 try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1413 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1414 },
1415 }
1416 for (code.link_objects) |link_object| {
1417 const name_with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ link_object, obj_ext });
1418 try build_args.append(name_with_ext);
1419 try shell_out.print("{s} ", .{name_with_ext});
1420 }
1421 if (code.link_libc) {
1422 try build_args.append("-lc");
1423 try shell_out.print("-lc ", .{});
1424 }
1425 const target = try std.zig.CrossTarget.parse(.{
1426 .arch_os_abi = code.target_str orelse "native",
1427 });
1428 if (code.target_str) |triple| {
1429 try build_args.appendSlice(&[_][]const u8{ "-target", triple });
1430 try shell_out.print("-target {s} ", .{triple});
1431 }
1432 if (code.verbose_cimport) {
1433 try build_args.append("--verbose-cimport");
1434 try shell_out.print("--verbose-cimport ", .{});
1435 }
1436 for (code.additional_options) |option| {
1437 try build_args.append(option);
1438 try shell_out.print("{s} ", .{option});
1439 }
1440
1441 try shell_out.print("\n", .{});
1442
1443 if (expected_outcome == .build_fail) {
1444 const result = try ChildProcess.exec(.{
1445 .allocator = allocator,
1446 .argv = build_args.items,
1447 .cwd = tmp_dir_name,
1448 .env_map = &env_map,
1449 .max_output_bytes = max_doc_file_size,
1450 });
1451 switch (result.term) {
1452 .Exited => |exit_code| {
1453 if (exit_code == 0) {
1454 progress.log("", .{});
1455 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1456 dumpArgs(build_args.items);
1457 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1458 }
1459 },
1460 else => {
1461 progress.log("", .{});
1462 print("{s}\nThe following command crashed:\n", .{result.stderr});
1463 dumpArgs(build_args.items);
1464 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1465 },
1466 }
1467 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1468 const colored_stderr = try termColor(allocator, escaped_stderr);
1469 try shell_out.writeAll(colored_stderr);
1470 break :code_block;
1471 }
1472 const exec_result = exec(allocator, &env_map, tmp_dir_name, build_args.items) catch
1473 return parseError(tokenizer, code.source_token, "example failed to compile", .{});
1474
1475 if (code.verbose_cimport) {
1476 const escaped_build_stderr = try escapeHtml(allocator, exec_result.stderr);
1477 try shell_out.writeAll(escaped_build_stderr);
1478 }
1479
1480 if (code.target_str) |triple| {
1481 if (mem.startsWith(u8, triple, "wasm32") or
1482 mem.startsWith(u8, triple, "riscv64-linux") or
1483 (mem.startsWith(u8, triple, "x86_64-linux") and
1484 builtin.os.tag != .linux or builtin.cpu.arch != .x86_64))
1485 {
1486 // skip execution
1487 break :code_block;
1488 }
1489 }
1490
1491 const path_to_exe = try std.fmt.allocPrint(allocator, "./{s}{s}", .{
1492 code.name,
1493 target.exeFileExt(),
1494 });
1495 const run_args = &[_][]const u8{path_to_exe};
1496
1497 var exited_with_signal = false;
1498
1499 const result = if (expected_outcome == .fail) blk: {
1500 const result = try ChildProcess.exec(.{
1501 .allocator = allocator,
1502 .argv = run_args,
1503 .env_map = &env_map,
1504 .cwd = tmp_dir_name,
1505 .max_output_bytes = max_doc_file_size,
1506 });
1507 switch (result.term) {
1508 .Exited => |exit_code| {
1509 if (exit_code == 0) {
1510 progress.log("", .{});
1511 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1512 dumpArgs(run_args);
1513 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1514 }
1515 },
1516 .Signal => exited_with_signal = true,
1517 else => {},
1518 }
1519 break :blk result;
1520 } else blk: {
1521 break :blk exec(allocator, &env_map, tmp_dir_name, run_args) catch return parseError(tokenizer, code.source_token, "example crashed", .{});
1522 };
1523
1524 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1525 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1526
1527 const colored_stderr = try termColor(allocator, escaped_stderr);
1528 const colored_stdout = try termColor(allocator, escaped_stdout);
1529
1530 try shell_out.print("$ ./{s}\n{s}{s}", .{ code.name, colored_stdout, colored_stderr });
1531 if (exited_with_signal) {
1532 try shell_out.print("(process terminated by signal)", .{});
1533 }
1534 try shell_out.writeAll("\n");
1535 },
1536 .@"test" => {
1537 var test_args = std.ArrayList([]const u8).init(allocator);
1538 defer test_args.deinit();
1539
1540 try test_args.appendSlice(&[_][]const u8{
1541 zig_exe, "test",
1542 tmp_source_file_name,
1543 });
1544 if (opt_zig_lib_dir) |zig_lib_dir| {
1545 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1546 }
1547 try shell_out.print("$ zig test {s}.zig ", .{code.name});
1548
1549 switch (code.mode) {
1550 .Debug => {},
1551 else => {
1552 try test_args.appendSlice(&[_][]const u8{
1553 "-O", @tagName(code.mode),
1554 });
1555 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1556 },
1557 }
1558 if (code.link_libc) {
1559 try test_args.append("-lc");
1560 try shell_out.print("-lc ", .{});
1561 }
1562 if (code.target_str) |triple| {
1563 try test_args.appendSlice(&[_][]const u8{ "-target", triple });
1564 try shell_out.print("-target {s} ", .{triple});
1565
1566 const cross_target = try std.zig.CrossTarget.parse(.{
1567 .arch_os_abi = triple,
1568 });
1569 const target_info = try std.zig.system.NativeTargetInfo.detect(
1570 cross_target,
1571 );
1572 switch (host.getExternalExecutor(target_info, .{
1573 .link_libc = code.link_libc,
1574 })) {
1575 .native => {},
1576 else => {
1577 try test_args.appendSlice(&[_][]const u8{"--test-no-exec"});
1578 try shell_out.writeAll("--test-no-exec");
1579 },
1580 }
1581 }
1582 const result = exec(allocator, &env_map, null, test_args.items) catch
1583 return parseError(tokenizer, code.source_token, "test failed", .{});
1584 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1585 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1586 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
1587 },
1588 .test_error => |error_match| {
1589 var test_args = std.ArrayList([]const u8).init(allocator);
1590 defer test_args.deinit();
1591
1592 try test_args.appendSlice(&[_][]const u8{
1593 zig_exe, "test",
1594 "--color", "on",
1595 tmp_source_file_name,
1596 });
1597 if (opt_zig_lib_dir) |zig_lib_dir| {
1598 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1599 }
1600 try shell_out.print("$ zig test {s}.zig ", .{code.name});
1601
1602 switch (code.mode) {
1603 .Debug => {},
1604 else => {
1605 try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1606 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1607 },
1608 }
1609 if (code.link_libc) {
1610 try test_args.append("-lc");
1611 try shell_out.print("-lc ", .{});
1612 }
1613 const result = try ChildProcess.exec(.{
1614 .allocator = allocator,
1615 .argv = test_args.items,
1616 .env_map = &env_map,
1617 .max_output_bytes = max_doc_file_size,
1618 });
1619 switch (result.term) {
1620 .Exited => |exit_code| {
1621 if (exit_code == 0) {
1622 progress.log("", .{});
1623 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1624 dumpArgs(test_args.items);
1625 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1626 }
1627 },
1628 else => {
1629 progress.log("", .{});
1630 print("{s}\nThe following command crashed:\n", .{result.stderr});
1631 dumpArgs(test_args.items);
1632 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1633 },
1634 }
1635 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1636 progress.log("", .{});
1637 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1638 return parseError(tokenizer, code.source_token, "example did not have expected compile error", .{});
1639 }
1640 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1641 const colored_stderr = try termColor(allocator, escaped_stderr);
1642 try shell_out.print("\n{s}\n", .{colored_stderr});
1643 },
1644 .test_safety => |error_match| {
1645 var test_args = std.ArrayList([]const u8).init(allocator);
1646 defer test_args.deinit();
1647
1648 try test_args.appendSlice(&[_][]const u8{
1649 zig_exe, "test",
1650 tmp_source_file_name,
1651 });
1652 if (opt_zig_lib_dir) |zig_lib_dir| {
1653 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1654 }
1655 var mode_arg: []const u8 = "";
1656 switch (code.mode) {
1657 .Debug => {},
1658 .ReleaseSafe => {
1659 try test_args.append("-OReleaseSafe");
1660 mode_arg = "-OReleaseSafe";
1661 },
1662 .ReleaseFast => {
1663 try test_args.append("-OReleaseFast");
1664 mode_arg = "-OReleaseFast";
1665 },
1666 .ReleaseSmall => {
1667 try test_args.append("-OReleaseSmall");
1668 mode_arg = "-OReleaseSmall";
1669 },
1670 }
1671
1672 const result = try ChildProcess.exec(.{
1673 .allocator = allocator,
1674 .argv = test_args.items,
1675 .env_map = &env_map,
1676 .max_output_bytes = max_doc_file_size,
1677 });
1678 switch (result.term) {
1679 .Exited => |exit_code| {
1680 if (exit_code == 0) {
1681 progress.log("", .{});
1682 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1683 dumpArgs(test_args.items);
1684 return parseError(tokenizer, code.source_token, "example test incorrectly succeeded", .{});
1685 }
1686 },
1687 else => {
1688 progress.log("", .{});
1689 print("{s}\nThe following command crashed:\n", .{result.stderr});
1690 dumpArgs(test_args.items);
1691 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1692 },
1693 }
1694 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1695 progress.log("", .{});
1696 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1697 return parseError(tokenizer, code.source_token, "example did not have expected runtime safety error message", .{});
1698 }
1699 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1700 const colored_stderr = try termColor(allocator, escaped_stderr);
1701 try shell_out.print("$ zig test {s}.zig {s}\n{s}\n", .{
1702 code.name,
1703 mode_arg,
1704 colored_stderr,
1705 });
1706 },
1707 .obj => |maybe_error_match| {
1708 const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ code.name, obj_ext });
1709 var build_args = std.ArrayList([]const u8).init(allocator);
1710 defer build_args.deinit();
1711
1712 try build_args.appendSlice(&[_][]const u8{
1713 zig_exe, "build-obj",
1714 "--color", "on",
1715 "--name", code.name,
1716 tmp_source_file_name,
1717 try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{
1718 tmp_dir_name, fs.path.sep, name_plus_obj_ext,
1719 }),
1720 });
1721 if (opt_zig_lib_dir) |zig_lib_dir| {
1722 try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1723 }
1724
1725 try shell_out.print("$ zig build-obj {s}.zig ", .{code.name});
1726
1727 switch (code.mode) {
1728 .Debug => {},
1729 else => {
1730 try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1731 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1732 },
1733 }
1734
1735 if (code.target_str) |triple| {
1736 try build_args.appendSlice(&[_][]const u8{ "-target", triple });
1737 try shell_out.print("-target {s} ", .{triple});
1738 }
1739 for (code.additional_options) |option| {
1740 try build_args.append(option);
1741 try shell_out.print("{s} ", .{option});
1742 }
1743
1744 if (maybe_error_match) |error_match| {
1745 const result = try ChildProcess.exec(.{
1746 .allocator = allocator,
1747 .argv = build_args.items,
1748 .env_map = &env_map,
1749 .max_output_bytes = max_doc_file_size,
1750 });
1751 switch (result.term) {
1752 .Exited => |exit_code| {
1753 if (exit_code == 0) {
1754 progress.log("", .{});
1755 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1756 dumpArgs(build_args.items);
1757 return parseError(tokenizer, code.source_token, "example build incorrectly succeeded", .{});
1758 }
1759 },
1760 else => {
1761 progress.log("", .{});
1762 print("{s}\nThe following command crashed:\n", .{result.stderr});
1763 dumpArgs(build_args.items);
1764 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1765 },
1766 }
1767 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1768 progress.log("", .{});
1769 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1770 return parseError(tokenizer, code.source_token, "example did not have expected compile error message", .{});
1771 }
1772 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1773 const colored_stderr = try termColor(allocator, escaped_stderr);
1774 try shell_out.print("\n{s} ", .{colored_stderr});
1775 } else {
1776 _ = exec(allocator, &env_map, null, build_args.items) catch return parseError(tokenizer, code.source_token, "example failed to compile", .{});
1777 }
1778 try shell_out.writeAll("\n");
1779 },
1780 .lib => {
1781 const bin_basename = try std.zig.binNameAlloc(allocator, .{
1782 .root_name = code.name,
1783 .target = builtin.target,
1784 .output_mode = .Lib,
1785 });
1786
1787 var test_args = std.ArrayList([]const u8).init(allocator);
1788 defer test_args.deinit();
1789
1790 try test_args.appendSlice(&[_][]const u8{
1791 zig_exe, "build-lib",
1792 tmp_source_file_name,
1793 try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{
1794 tmp_dir_name, fs.path.sep_str, bin_basename,
1795 }),
1796 });
1797 if (opt_zig_lib_dir) |zig_lib_dir| {
1798 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1799 }
1800 try shell_out.print("$ zig build-lib {s}.zig ", .{code.name});
1801
1802 switch (code.mode) {
1803 .Debug => {},
1804 else => {
1805 try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1806 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1807 },
1808 }
1809 if (code.target_str) |triple| {
1810 try test_args.appendSlice(&[_][]const u8{ "-target", triple });
1811 try shell_out.print("-target {s} ", .{triple});
1812 }
1813 if (code.link_mode) |link_mode| {
1814 switch (link_mode) {
1815 .Static => {
1816 try test_args.append("-static");
1817 try shell_out.print("-static ", .{});
1818 },
1819 .Dynamic => {
1820 try test_args.append("-dynamic");
1821 try shell_out.print("-dynamic ", .{});
1822 },
1823 }
1824 }
1825 for (code.additional_options) |option| {
1826 try test_args.append(option);
1827 try shell_out.print("{s} ", .{option});
1828 }
1829 const result = exec(allocator, &env_map, null, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{});
1830 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1831 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1832 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
1833 },
1834 }
1835
1836 if (!code.just_check_syntax) {
1837 try printShell(out, shell_buffer.items, false);
1838 }
1839 },
1840 }
1841 }
1842}
1843
1844fn exec(
1845 allocator: Allocator,
1846 env_map: *process.EnvMap,
1847 cwd: ?[]const u8,
1848 args: []const []const u8,
1849) !ChildProcess.ExecResult {
1850 const result = try ChildProcess.exec(.{
1851 .allocator = allocator,
1852 .argv = args,
1853 .env_map = env_map,
1854 .cwd = cwd,
1855 .max_output_bytes = max_doc_file_size,
1856 });
1857 switch (result.term) {
1858 .Exited => |exit_code| {
1859 if (exit_code != 0) {
1860 print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code });
1861 dumpArgs(args);
1862 return error.ChildExitError;
1863 }
1864 },
1865 else => {
1866 print("{s}\nThe following command crashed:\n", .{result.stderr});
1867 dumpArgs(args);
1868 return error.ChildCrashed;
1869 },
1870 }
1871 return result;
1872}
1873
1874fn getBuiltinCode(
1875 allocator: Allocator,
1876 env_map: *process.EnvMap,
1877 zig_exe: []const u8,
1878 opt_zig_lib_dir: ?[]const u8,
1879) ![]const u8 {
1880 if (opt_zig_lib_dir) |zig_lib_dir| {
1881 const result = try exec(allocator, env_map, null, &.{
1882 zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir,
1883 });
1884 return result.stdout;
1885 } else {
1886 const result = try exec(allocator, env_map, null, &.{
1887 zig_exe, "build-obj", "--show-builtin",
1888 });
1889 return result.stdout;
1890 }
1891}
1892
1893fn dumpArgs(args: []const []const u8) void {
1894 for (args) |arg|
1895 print("{s} ", .{arg})
1896 else
1897 print("\n", .{});
1898}
1899
1900test "term supported colors" {
1901 const test_allocator = testing.allocator;
1902
1903 {
1904 const input = "A\x1b[31;1mred\x1b[0mB";
1905 const expect = "A<span class=\"sgr-31_1m\">red</span>B";
1906
1907 const result = try termColor(test_allocator, input);
1908 defer test_allocator.free(result);
1909 try testing.expectEqualSlices(u8, expect, result);
1910 }
1911
1912 {
1913 const input = "A\x1b[32;1mgreen\x1b[0mB";
1914 const expect = "A<span class=\"sgr-32_1m\">green</span>B";
1915
1916 const result = try termColor(test_allocator, input);
1917 defer test_allocator.free(result);
1918 try testing.expectEqualSlices(u8, expect, result);
1919 }
1920
1921 {
1922 const input = "A\x1b[36;1mcyan\x1b[0mB";
1923 const expect = "A<span class=\"sgr-36_1m\">cyan</span>B";
1924
1925 const result = try termColor(test_allocator, input);
1926 defer test_allocator.free(result);
1927 try testing.expectEqualSlices(u8, expect, result);
1928 }
1929
1930 {
1931 const input = "A\x1b[1mbold\x1b[0mB";
1932 const expect = "A<span class=\"sgr-1m\">bold</span>B";
1933
1934 const result = try termColor(test_allocator, input);
1935 defer test_allocator.free(result);
1936 try testing.expectEqualSlices(u8, expect, result);
1937 }
1938
1939 {
1940 const input = "A\x1b[2mdim\x1b[0mB";
1941 const expect = "A<span class=\"sgr-2m\">dim</span>B";
1942
1943 const result = try termColor(test_allocator, input);
1944 defer test_allocator.free(result);
1945 try testing.expectEqualSlices(u8, expect, result);
1946 }
1947}
1948
1949test "term output from zig" {
1950 // Use data generated by https://github.com/perillo/zig-tty-test-data,
1951 // with zig version 0.11.0-dev.1898+36d47dd19.
1952 const test_allocator = testing.allocator;
1953
1954 {
1955 // 1.1-with-build-progress.out
1956 const input = "Semantic Analysis [1324] \x1b[25D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLD Link... \x1b[12D\x1b[0K";
1957 const expect = "";
1958
1959 const result = try termColor(test_allocator, input);
1960 defer test_allocator.free(result);
1961 try testing.expectEqualSlices(u8, expect, result);
1962 }
1963
1964 {
1965 // 2.1-with-reference-traces.out
1966 const input = "\x1b[1msrc/2.1-with-reference-traces.zig:3:7: \x1b[31;1merror: \x1b[0m\x1b[1mcannot assign to constant\n\x1b[0m x += 1;\n \x1b[32;1m~~^~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.1-with-reference-traces.zig:7:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m";
1967 const expect =
1968 \\<span class="sgr-1m">src/2.1-with-reference-traces.zig:3:7: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">cannot assign to constant
1969 \\</span> x += 1;
1970 \\ <span class="sgr-32_1m">~~^~~~
1971 \\</span><span class="sgr-2m">referenced by:
1972 \\ main: src/2.1-with-reference-traces.zig:7:5
1973 \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17
1974 \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces
1975 \\
1976 \\</span>
1977 ;
1978
1979 const result = try termColor(test_allocator, input);
1980 defer test_allocator.free(result);
1981 try testing.expectEqualSlices(u8, expect, result);
1982 }
1983
1984 {
1985 // 2.2-without-reference-traces.out
1986 const input = "\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: \x1b[31;1merror: \x1b[0m\x1b[1minvalid type given to fixedBufferStream\n\x1b[0m else => @compileError(\"invalid type given to fixedBufferStream\"),\n \x1b[32;1m^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\x1b[0m\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: \x1b[36;1mnote: \x1b[0m\x1b[1mcalled from here\n\x1b[0mpub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {\n; \x1b[32;1m~~~~~^~~~~~~~~~~~~~~~~\n\x1b[0m";
1987 const expect =
1988 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">invalid type given to fixedBufferStream
1989 \\</span> else => @compileError("invalid type given to fixedBufferStream"),
1990 \\ <span class="sgr-32_1m">^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1991 \\</span><span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">called from here
1992 \\</span>pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
1993 \\; <span class="sgr-32_1m">~~~~~^~~~~~~~~~~~~~~~~
1994 \\</span>
1995 ;
1996
1997 const result = try termColor(test_allocator, input);
1998 defer test_allocator.free(result);
1999 try testing.expectEqualSlices(u8, expect, result);
2000 }
2001
2002 {
2003 // 2.3-with-notes.out
2004 const input = "\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[31;1merror: \x1b[0m\x1b[1mexpected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'\n\x1b[0m bar(w);\n \x1b[32;1m^\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[36;1mnote: \x1b[0m\x1b[1mpointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:2:13: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Wat = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:1:14: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Derp = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:4:18: \x1b[36;1mnote: \x1b[0m\x1b[1mparameter type declared here\n\x1b[0mextern fn bar(d: *Derp) void;\n \x1b[32;1m^~~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.3-with-notes.zig:10:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m";
2005 const expect =
2006 \\<span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">expected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'
2007 \\</span> bar(w);
2008 \\ <span class="sgr-32_1m">^
2009 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">pointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'
2010 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:2:13: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here
2011 \\</span>const Wat = opaque {};
2012 \\ <span class="sgr-32_1m">^~~~~~~~~
2013 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:1:14: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here
2014 \\</span>const Derp = opaque {};
2015 \\ <span class="sgr-32_1m">^~~~~~~~~
2016 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:4:18: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">parameter type declared here
2017 \\</span>extern fn bar(d: *Derp) void;
2018 \\ <span class="sgr-32_1m">^~~~~
2019 \\</span><span class="sgr-2m">referenced by:
2020 \\ main: src/2.3-with-notes.zig:10:5
2021 \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17
2022 \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces
2023 \\
2024 \\</span>
2025 ;
2026
2027 const result = try termColor(test_allocator, input);
2028 defer test_allocator.free(result);
2029 try testing.expectEqualSlices(u8, expect, result);
2030 }
2031
2032 {
2033 // 3.1-with-error-return-traces.out
2034
2035 const input = "error: Error\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:5:5\x1b[0m: \x1b[2m0x20b008 in callee (3.1-with-error-return-traces)\x1b[0m\n return error.Error;\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:9:5\x1b[0m: \x1b[2m0x20b113 in caller (3.1-with-error-return-traces)\x1b[0m\n try callee();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:13:5\x1b[0m: \x1b[2m0x20b153 in main (3.1-with-error-return-traces)\x1b[0m\n try caller();\n \x1b[32;1m^\x1b[0m\n";
2036 const expect =
2037 \\error: Error
2038 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:5:5</span>: <span class="sgr-2m">0x20b008 in callee (3.1-with-error-return-traces)</span>
2039 \\ return error.Error;
2040 \\ <span class="sgr-32_1m">^</span>
2041 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:9:5</span>: <span class="sgr-2m">0x20b113 in caller (3.1-with-error-return-traces)</span>
2042 \\ try callee();
2043 \\ <span class="sgr-32_1m">^</span>
2044 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:13:5</span>: <span class="sgr-2m">0x20b153 in main (3.1-with-error-return-traces)</span>
2045 \\ try caller();
2046 \\ <span class="sgr-32_1m">^</span>
2047 \\
2048 ;
2049
2050 const result = try termColor(test_allocator, input);
2051 defer test_allocator.free(result);
2052 try testing.expectEqualSlices(u8, expect, result);
2053 }
2054
2055 {
2056 // 3.2-with-stack-trace.out
2057 const input = "\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:561:19\x1b[0m: \x1b[2m0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)\x1b[0m\n while (it.next()) |return_address| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:157:80\x1b[0m: \x1b[2m0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)\x1b[0m\n writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:5:36\x1b[0m: \x1b[2m0x20d3b2 in foo (3.2-with-stack-trace)\x1b[0m\n std.debug.dumpCurrentStackTrace(null);\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:9:8\x1b[0m: \x1b[2m0x20b458 in main (3.2-with-stack-trace)\x1b[0m\n foo();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:607:22\x1b[0m: \x1b[2m0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)\x1b[0m\n root.main();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:376:5\x1b[0m: \x1b[2m0x20a411 in _start (3.2-with-stack-trace)\x1b[0m\n @call(.never_inline, posixCallMainAndExit, .{});\n \x1b[32;1m^\x1b[0m\n";
2058 const expect =
2059 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:561:19</span>: <span class="sgr-2m">0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)</span>
2060 \\ while (it.next()) |return_address| {
2061 \\ <span class="sgr-32_1m">^</span>
2062 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:157:80</span>: <span class="sgr-2m">0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)</span>
2063 \\ writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {
2064 \\ <span class="sgr-32_1m">^</span>
2065 \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:5:36</span>: <span class="sgr-2m">0x20d3b2 in foo (3.2-with-stack-trace)</span>
2066 \\ std.debug.dumpCurrentStackTrace(null);
2067 \\ <span class="sgr-32_1m">^</span>
2068 \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:9:8</span>: <span class="sgr-2m">0x20b458 in main (3.2-with-stack-trace)</span>
2069 \\ foo();
2070 \\ <span class="sgr-32_1m">^</span>
2071 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:607:22</span>: <span class="sgr-2m">0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)</span>
2072 \\ root.main();
2073 \\ <span class="sgr-32_1m">^</span>
2074 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:376:5</span>: <span class="sgr-2m">0x20a411 in _start (3.2-with-stack-trace)</span>
2075 \\ @call(.never_inline, posixCallMainAndExit, .{});
2076 \\ <span class="sgr-32_1m">^</span>
2077 \\
2078 ;
2079
2080 const result = try termColor(test_allocator, input);
2081 defer test_allocator.free(result);
2082 try testing.expectEqualSlices(u8, expect, result);
2083 }
2084}
2085
2086test "printShell" {
2087 const test_allocator = std.testing.allocator;
2088
2089 {
2090 const shell_out =
2091 \\$ zig build test.zig
2092 ;
2093 const expected =
2094 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2095 \\</samp></pre></figure>
2096 ;
2097
2098 var buffer = std.ArrayList(u8).init(test_allocator);
2099 defer buffer.deinit();
2100
2101 try printShell(buffer.writer(), shell_out, false);
2102 try testing.expectEqualSlices(u8, expected, buffer.items);
2103 }
2104 {
2105 const shell_out =
2106 \\$ zig build test.zig
2107 \\build output
2108 ;
2109 const expected =
2110 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2111 \\build output
2112 \\</samp></pre></figure>
2113 ;
2114
2115 var buffer = std.ArrayList(u8).init(test_allocator);
2116 defer buffer.deinit();
2117
2118 try printShell(buffer.writer(), shell_out, false);
2119 try testing.expectEqualSlices(u8, expected, buffer.items);
2120 }
2121 {
2122 const shell_out =
2123 \\$ zig build test.zig
2124 \\build output
2125 \\$ ./test
2126 ;
2127 const expected =
2128 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2129 \\build output
2130 \\$ <kbd>./test</kbd>
2131 \\</samp></pre></figure>
2132 ;
2133
2134 var buffer = std.ArrayList(u8).init(test_allocator);
2135 defer buffer.deinit();
2136
2137 try printShell(buffer.writer(), shell_out, false);
2138 try testing.expectEqualSlices(u8, expected, buffer.items);
2139 }
2140 {
2141 const shell_out =
2142 \\$ zig build test.zig
2143 \\
2144 \\$ ./test
2145 \\output
2146 ;
2147 const expected =
2148 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2149 \\
2150 \\$ <kbd>./test</kbd>
2151 \\output
2152 \\</samp></pre></figure>
2153 ;
2154
2155 var buffer = std.ArrayList(u8).init(test_allocator);
2156 defer buffer.deinit();
2157
2158 try printShell(buffer.writer(), shell_out, false);
2159 try testing.expectEqualSlices(u8, expected, buffer.items);
2160 }
2161 {
2162 const shell_out =
2163 \\$ zig build test.zig
2164 \\$ ./test
2165 \\output
2166 ;
2167 const expected =
2168 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2169 \\$ <kbd>./test</kbd>
2170 \\output
2171 \\</samp></pre></figure>
2172 ;
2173
2174 var buffer = std.ArrayList(u8).init(test_allocator);
2175 defer buffer.deinit();
2176
2177 try printShell(buffer.writer(), shell_out, false);
2178 try testing.expectEqualSlices(u8, expected, buffer.items);
2179 }
2180 {
2181 const shell_out =
2182 \\$ zig build test.zig \
2183 \\ --build-option
2184 \\build output
2185 \\$ ./test
2186 \\output
2187 ;
2188 const expected =
2189 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2190 \\ --build-option</kbd>
2191 \\build output
2192 \\$ <kbd>./test</kbd>
2193 \\output
2194 \\</samp></pre></figure>
2195 ;
2196
2197 var buffer = std.ArrayList(u8).init(test_allocator);
2198 defer buffer.deinit();
2199
2200 try printShell(buffer.writer(), shell_out, false);
2201 try testing.expectEqualSlices(u8, expected, buffer.items);
2202 }
2203 {
2204 // intentional space after "--build-option1 \"
2205 const shell_out =
2206 \\$ zig build test.zig \
2207 \\ --build-option1 \
2208 \\ --build-option2
2209 \\$ ./test
2210 ;
2211 const expected =
2212 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2213 \\ --build-option1 \
2214 \\ --build-option2</kbd>
2215 \\$ <kbd>./test</kbd>
2216 \\</samp></pre></figure>
2217 ;
2218
2219 var buffer = std.ArrayList(u8).init(test_allocator);
2220 defer buffer.deinit();
2221
2222 try printShell(buffer.writer(), shell_out, false);
2223 try testing.expectEqualSlices(u8, expected, buffer.items);
2224 }
2225 {
2226 const shell_out =
2227 \\$ zig build test.zig \
2228 \\$ ./test
2229 ;
2230 const expected =
2231 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2232 \\$ ./test</kbd>
2233 \\</samp></pre></figure>
2234 ;
2235
2236 var buffer = std.ArrayList(u8).init(test_allocator);
2237 defer buffer.deinit();
2238
2239 try printShell(buffer.writer(), shell_out, false);
2240 try testing.expectEqualSlices(u8, expected, buffer.items);
2241 }
2242 {
2243 const shell_out =
2244 \\$ zig build test.zig
2245 \\$ ./test
2246 \\$1
2247 ;
2248 const expected =
2249 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2250 \\$ <kbd>./test</kbd>
2251 \\$1
2252 \\</samp></pre></figure>
2253 ;
2254
2255 var buffer = std.ArrayList(u8).init(test_allocator);
2256 defer buffer.deinit();
2257
2258 try printShell(buffer.writer(), shell_out, false);
2259 try testing.expectEqualSlices(u8, expected, buffer.items);
2260 }
2261 {
2262 const shell_out =
2263 \\$zig build test.zig
2264 ;
2265 const expected =
2266 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$zig build test.zig
2267 \\</samp></pre></figure>
2268 ;
2269
2270 var buffer = std.ArrayList(u8).init(test_allocator);
2271 defer buffer.deinit();
2272
2273 try printShell(buffer.writer(), shell_out, false);
2274 try testing.expectEqualSlices(u8, expected, buffer.items);
2275 }
2276}
lib/build_runner.zig+2-2
......@@ -188,10 +188,10 @@ pub fn main() !void {
188188 usageAndErr(builder, false, stderr_stream);
189189 };
190190 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
191 builder.zig_lib_dir = nextArg(args, &arg_idx) orelse {
191 builder.zig_lib_dir = .{ .cwd_relative = nextArg(args, &arg_idx) orelse {
192192 std.debug.print("Expected argument after {s}\n\n", .{arg});
193193 usageAndErr(builder, false, stderr_stream);
194 };
194 } };
195195 } else if (mem.eql(u8, arg, "--debug-log")) {
196196 const next_arg = nextArg(args, &arg_idx) orelse {
197197 std.debug.print("Expected argument after {s}\n\n", .{arg});
lib/std/Build.zig+91-38
......@@ -57,6 +57,8 @@ pub const RunStep = @import("Build/Step/Run.zig");
5757pub const TranslateCStep = @import("Build/Step/TranslateC.zig");
5858/// deprecated: use `Step.WriteFile`.
5959pub const WriteFileStep = @import("Build/Step/WriteFile.zig");
60/// deprecated: use `LazyPath`.
61pub const FileSource = LazyPath;
6062
6163install_tls: TopLevelStep,
6264uninstall_tls: TopLevelStep,
......@@ -93,8 +95,7 @@ build_root: Cache.Directory,
9395cache_root: Cache.Directory,
9496global_cache_root: Cache.Directory,
9597cache: *Cache,
96/// If non-null, overrides the default zig lib dir.
97zig_lib_dir: ?[]const u8,
98zig_lib_dir: ?LazyPath,
9899vcpkg_root: VcpkgRoot = .unattempted,
99100pkg_config_pkg_list: ?(PkgConfigError![]const PkgConfigPkg) = null,
100101args: ?[][]const u8 = null,
......@@ -471,7 +472,7 @@ pub fn addOptions(self: *Build) *Step.Options {
471472
472473pub const ExecutableOptions = struct {
473474 name: []const u8,
474 root_source_file: ?FileSource = null,
475 root_source_file: ?LazyPath = null,
475476 version: ?std.SemanticVersion = null,
476477 target: CrossTarget = .{},
477478 optimize: std.builtin.Mode = .Debug,
......@@ -481,6 +482,8 @@ pub const ExecutableOptions = struct {
481482 single_threaded: ?bool = null,
482483 use_llvm: ?bool = null,
483484 use_lld: ?bool = null,
485 zig_lib_dir: ?LazyPath = null,
486 main_pkg_path: ?LazyPath = null,
484487};
485488
486489pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
......@@ -497,12 +500,14 @@ pub fn addExecutable(b: *Build, options: ExecutableOptions) *Step.Compile {
497500 .single_threaded = options.single_threaded,
498501 .use_llvm = options.use_llvm,
499502 .use_lld = options.use_lld,
503 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
504 .main_pkg_path = options.main_pkg_path,
500505 });
501506}
502507
503508pub const ObjectOptions = struct {
504509 name: []const u8,
505 root_source_file: ?FileSource = null,
510 root_source_file: ?LazyPath = null,
506511 target: CrossTarget,
507512 optimize: std.builtin.Mode,
508513 max_rss: usize = 0,
......@@ -510,6 +515,8 @@ pub const ObjectOptions = struct {
510515 single_threaded: ?bool = null,
511516 use_llvm: ?bool = null,
512517 use_lld: ?bool = null,
518 zig_lib_dir: ?LazyPath = null,
519 main_pkg_path: ?LazyPath = null,
513520};
514521
515522pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
......@@ -524,12 +531,14 @@ pub fn addObject(b: *Build, options: ObjectOptions) *Step.Compile {
524531 .single_threaded = options.single_threaded,
525532 .use_llvm = options.use_llvm,
526533 .use_lld = options.use_lld,
534 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
535 .main_pkg_path = options.main_pkg_path,
527536 });
528537}
529538
530539pub const SharedLibraryOptions = struct {
531540 name: []const u8,
532 root_source_file: ?FileSource = null,
541 root_source_file: ?LazyPath = null,
533542 version: ?std.SemanticVersion = null,
534543 target: CrossTarget,
535544 optimize: std.builtin.Mode,
......@@ -538,6 +547,8 @@ pub const SharedLibraryOptions = struct {
538547 single_threaded: ?bool = null,
539548 use_llvm: ?bool = null,
540549 use_lld: ?bool = null,
550 zig_lib_dir: ?LazyPath = null,
551 main_pkg_path: ?LazyPath = null,
541552};
542553
543554pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile {
......@@ -554,12 +565,14 @@ pub fn addSharedLibrary(b: *Build, options: SharedLibraryOptions) *Step.Compile
554565 .single_threaded = options.single_threaded,
555566 .use_llvm = options.use_llvm,
556567 .use_lld = options.use_lld,
568 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
569 .main_pkg_path = options.main_pkg_path,
557570 });
558571}
559572
560573pub const StaticLibraryOptions = struct {
561574 name: []const u8,
562 root_source_file: ?FileSource = null,
575 root_source_file: ?LazyPath = null,
563576 target: CrossTarget,
564577 optimize: std.builtin.Mode,
565578 version: ?std.SemanticVersion = null,
......@@ -568,6 +581,8 @@ pub const StaticLibraryOptions = struct {
568581 single_threaded: ?bool = null,
569582 use_llvm: ?bool = null,
570583 use_lld: ?bool = null,
584 zig_lib_dir: ?LazyPath = null,
585 main_pkg_path: ?LazyPath = null,
571586};
572587
573588pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile {
......@@ -584,12 +599,14 @@ pub fn addStaticLibrary(b: *Build, options: StaticLibraryOptions) *Step.Compile
584599 .single_threaded = options.single_threaded,
585600 .use_llvm = options.use_llvm,
586601 .use_lld = options.use_lld,
602 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
603 .main_pkg_path = options.main_pkg_path,
587604 });
588605}
589606
590607pub const TestOptions = struct {
591608 name: []const u8 = "test",
592 root_source_file: FileSource,
609 root_source_file: LazyPath,
593610 target: CrossTarget = .{},
594611 optimize: std.builtin.Mode = .Debug,
595612 version: ?std.SemanticVersion = null,
......@@ -600,6 +617,8 @@ pub const TestOptions = struct {
600617 single_threaded: ?bool = null,
601618 use_llvm: ?bool = null,
602619 use_lld: ?bool = null,
620 zig_lib_dir: ?LazyPath = null,
621 main_pkg_path: ?LazyPath = null,
603622};
604623
605624pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
......@@ -616,15 +635,18 @@ pub fn addTest(b: *Build, options: TestOptions) *Step.Compile {
616635 .single_threaded = options.single_threaded,
617636 .use_llvm = options.use_llvm,
618637 .use_lld = options.use_lld,
638 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
639 .main_pkg_path = options.main_pkg_path,
619640 });
620641}
621642
622643pub const AssemblyOptions = struct {
623644 name: []const u8,
624 source_file: FileSource,
645 source_file: LazyPath,
625646 target: CrossTarget,
626647 optimize: std.builtin.Mode,
627648 max_rss: usize = 0,
649 zig_lib_dir: ?LazyPath = null,
628650};
629651
630652pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {
......@@ -635,8 +657,9 @@ pub fn addAssembly(b: *Build, options: AssemblyOptions) *Step.Compile {
635657 .target = options.target,
636658 .optimize = options.optimize,
637659 .max_rss = options.max_rss,
660 .zig_lib_dir = options.zig_lib_dir orelse b.zig_lib_dir,
638661 });
639 obj_step.addAssemblyFileSource(options.source_file.dupe(b));
662 obj_step.addAssemblyLazyPath(options.source_file.dupe(b));
640663 return obj_step;
641664}
642665
......@@ -655,7 +678,7 @@ pub const ModuleDependency = struct {
655678};
656679
657680pub const CreateModuleOptions = struct {
658 source_file: FileSource,
681 source_file: LazyPath,
659682 dependencies: []const ModuleDependency = &.{},
660683};
661684
......@@ -1257,12 +1280,21 @@ fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
12571280 std.debug.print("{s}\n", .{text});
12581281}
12591282
1283/// This creates the install step and adds it to the dependencies of the
1284/// top-level install step, using all the default options.
1285/// See `addInstallArtifact` for a more flexible function.
12601286pub fn installArtifact(self: *Build, artifact: *Step.Compile) void {
1261 self.getInstallStep().dependOn(&self.addInstallArtifact(artifact).step);
1287 self.getInstallStep().dependOn(&self.addInstallArtifact(artifact, .{}).step);
12621288}
12631289
1264pub fn addInstallArtifact(self: *Build, artifact: *Step.Compile) *Step.InstallArtifact {
1265 return Step.InstallArtifact.create(self, artifact);
1290/// This merely creates the step; it does not add it to the dependencies of the
1291/// top-level install step.
1292pub fn addInstallArtifact(
1293 self: *Build,
1294 artifact: *Step.Compile,
1295 options: Step.InstallArtifact.Options,
1296) *Step.InstallArtifact {
1297 return Step.InstallArtifact.create(self, artifact, options);
12661298}
12671299
12681300///`dest_rel_path` is relative to prefix path
......@@ -1284,22 +1316,22 @@ pub fn installLibFile(self: *Build, src_path: []const u8, dest_rel_path: []const
12841316 self.getInstallStep().dependOn(&self.addInstallFileWithDir(.{ .path = src_path }, .lib, dest_rel_path).step);
12851317}
12861318
1287pub fn addObjCopy(b: *Build, source: FileSource, options: Step.ObjCopy.Options) *Step.ObjCopy {
1319pub fn addObjCopy(b: *Build, source: LazyPath, options: Step.ObjCopy.Options) *Step.ObjCopy {
12881320 return Step.ObjCopy.create(b, source, options);
12891321}
12901322
12911323///`dest_rel_path` is relative to install prefix path
1292pub fn addInstallFile(self: *Build, source: FileSource, dest_rel_path: []const u8) *Step.InstallFile {
1324pub fn addInstallFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
12931325 return self.addInstallFileWithDir(source.dupe(self), .prefix, dest_rel_path);
12941326}
12951327
12961328///`dest_rel_path` is relative to bin path
1297pub fn addInstallBinFile(self: *Build, source: FileSource, dest_rel_path: []const u8) *Step.InstallFile {
1329pub fn addInstallBinFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
12981330 return self.addInstallFileWithDir(source.dupe(self), .bin, dest_rel_path);
12991331}
13001332
13011333///`dest_rel_path` is relative to lib path
1302pub fn addInstallLibFile(self: *Build, source: FileSource, dest_rel_path: []const u8) *Step.InstallFile {
1334pub fn addInstallLibFile(self: *Build, source: LazyPath, dest_rel_path: []const u8) *Step.InstallFile {
13031335 return self.addInstallFileWithDir(source.dupe(self), .lib, dest_rel_path);
13041336}
13051337
......@@ -1309,7 +1341,7 @@ pub fn addInstallHeaderFile(b: *Build, src_path: []const u8, dest_rel_path: []co
13091341
13101342pub fn addInstallFileWithDir(
13111343 self: *Build,
1312 source: FileSource,
1344 source: LazyPath,
13131345 install_dir: InstallDir,
13141346 dest_rel_path: []const u8,
13151347) *Step.InstallFile {
......@@ -1322,12 +1354,13 @@ pub fn addInstallDirectory(self: *Build, options: InstallDirectoryOptions) *Step
13221354
13231355pub fn addCheckFile(
13241356 b: *Build,
1325 file_source: FileSource,
1357 file_source: LazyPath,
13261358 options: Step.CheckFile.Options,
13271359) *Step.CheckFile {
13281360 return Step.CheckFile.create(b, file_source, options);
13291361}
13301362
1363/// deprecated: https://github.com/ziglang/zig/issues/14943
13311364pub fn pushInstalledFile(self: *Build, dir: InstallDir, dest_rel_path: []const u8) void {
13321365 const file = InstalledFile{
13331366 .dir = dir,
......@@ -1357,6 +1390,11 @@ pub fn pathFromRoot(b: *Build, p: []const u8) []u8 {
13571390 return fs.path.resolve(b.allocator, &.{ b.build_root.path orelse ".", p }) catch @panic("OOM");
13581391}
13591392
1393fn pathFromCwd(b: *Build, p: []const u8) []u8 {
1394 const cwd = process.getCwdAlloc(b.allocator) catch @panic("OOM");
1395 return fs.path.resolve(b.allocator, &.{ cwd, p }) catch @panic("OOM");
1396}
1397
13601398pub fn pathJoin(self: *Build, paths: []const []const u8) []u8 {
13611399 return fs.path.join(self.allocator, paths) catch @panic("OOM");
13621400}
......@@ -1608,7 +1646,7 @@ pub const Module = struct {
16081646 /// This could either be a generated file, in which case the module
16091647 /// contains exactly one file, or it could be a path to the root source
16101648 /// file of directory of files which constitute the module.
1611 source_file: FileSource,
1649 source_file: LazyPath,
16121650 dependencies: std.StringArrayHashMap(*Module),
16131651};
16141652
......@@ -1630,50 +1668,64 @@ pub const GeneratedFile = struct {
16301668 }
16311669};
16321670
1633/// A file source is a reference to an existing or future file.
1634pub const FileSource = union(enum) {
1635 /// A plain file path, relative to build root or absolute.
1671/// A reference to an existing or future path.
1672pub const LazyPath = union(enum) {
1673 /// A source file path relative to build root.
1674 /// This should not be an absolute path, but in an older iteration of the zig build
1675 /// system API, it was allowed to be absolute. Absolute paths should use `cwd_relative`.
16361676 path: []const u8,
16371677
16381678 /// A file that is generated by an interface. Those files usually are
16391679 /// not available until built by a build step.
16401680 generated: *const GeneratedFile,
16411681
1682 /// An absolute path or a path relative to the current working directory of
1683 /// the build runner process.
1684 /// This is uncommon but used for system environment paths such as `--zig-lib-dir` which
1685 /// ignore the file system path of build.zig and instead are relative to the directory from
1686 /// which `zig build` was invoked.
1687 /// Use of this tag indicates a dependency on the host system.
1688 cwd_relative: []const u8,
1689
16421690 /// Returns a new file source that will have a relative path to the build root guaranteed.
1643 /// This should be preferred over setting `.path` directly as it documents that the files are in the project directory.
1644 pub fn relative(path: []const u8) FileSource {
1691 /// Asserts the parameter is not an absolute path.
1692 pub fn relative(path: []const u8) LazyPath {
16451693 std.debug.assert(!std.fs.path.isAbsolute(path));
1646 return FileSource{ .path = path };
1694 return LazyPath{ .path = path };
16471695 }
16481696
16491697 /// Returns a string that can be shown to represent the file source.
16501698 /// Either returns the path or `"generated"`.
1651 pub fn getDisplayName(self: FileSource) []const u8 {
1699 pub fn getDisplayName(self: LazyPath) []const u8 {
16521700 return switch (self) {
1653 .path => self.path,
1701 .path, .cwd_relative => self.path,
16541702 .generated => "generated",
16551703 };
16561704 }
16571705
16581706 /// Adds dependencies this file source implies to the given step.
1659 pub fn addStepDependencies(self: FileSource, other_step: *Step) void {
1707 pub fn addStepDependencies(self: LazyPath, other_step: *Step) void {
16601708 switch (self) {
1661 .path => {},
1709 .path, .cwd_relative => {},
16621710 .generated => |gen| other_step.dependOn(gen.step),
16631711 }
16641712 }
16651713
1666 /// Should only be called during make(), returns a path relative to the build root or absolute.
1667 pub fn getPath(self: FileSource, src_builder: *Build) []const u8 {
1714 /// Returns an absolute path.
1715 /// Intended to be used during the make phase only.
1716 pub fn getPath(self: LazyPath, src_builder: *Build) []const u8 {
16681717 return getPath2(self, src_builder, null);
16691718 }
16701719
1671 /// Should only be called during make(), returns a path relative to the build root or absolute.
1672 /// asking_step is only used for debugging purposes; it's the step being run that is asking for
1673 /// the path.
1674 pub fn getPath2(self: FileSource, src_builder: *Build, asking_step: ?*Step) []const u8 {
1720 /// Returns an absolute path.
1721 /// Intended to be used during the make phase only.
1722 ///
1723 /// `asking_step` is only used for debugging purposes; it's the step being
1724 /// run that is asking for the path.
1725 pub fn getPath2(self: LazyPath, src_builder: *Build, asking_step: ?*Step) []const u8 {
16751726 switch (self) {
16761727 .path => |p| return src_builder.pathFromRoot(p),
1728 .cwd_relative => |p| return src_builder.pathFromCwd(p),
16771729 .generated => |gen| return gen.path orelse {
16781730 std.debug.getStderrMutex().lock();
16791731 const stderr = std.io.getStdErr();
......@@ -1684,16 +1736,17 @@ pub const FileSource = union(enum) {
16841736 }
16851737
16861738 /// Duplicates the file source for a given builder.
1687 pub fn dupe(self: FileSource, b: *Build) FileSource {
1739 pub fn dupe(self: LazyPath, b: *Build) LazyPath {
16881740 return switch (self) {
16891741 .path => |p| .{ .path = b.dupePath(p) },
1742 .cwd_relative => |p| .{ .cwd_relative = b.dupePath(p) },
16901743 .generated => |gen| .{ .generated = gen },
16911744 };
16921745 }
16931746};
16941747
16951748/// In this function the stderr mutex has already been locked.
1696fn dumpBadGetPathHelp(
1749pub fn dumpBadGetPathHelp(
16971750 s: *Step,
16981751 stderr: fs.File,
16991752 src_builder: *Build,
lib/std/Build/Step.zig+1-6
......@@ -423,12 +423,7 @@ pub fn evalZigProcess(
423423 });
424424 }
425425
426 if (s.cast(Compile)) |compile| if (compile.emit_bin == .no_emit) return result;
427
428 return result orelse return s.fail(
429 "the following command failed to communicate the compilation result:\n{s}",
430 .{try allocPrintCmd(arena, null, argv)},
431 );
426 return result;
432427}
433428
434429fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
lib/std/Build/Step/CheckFile.zig+2-2
......@@ -11,7 +11,7 @@ const mem = std.mem;
1111step: Step,
1212expected_matches: []const []const u8,
1313expected_exact: ?[]const u8,
14source: std.Build.FileSource,
14source: std.Build.LazyPath,
1515max_bytes: usize = 20 * 1024 * 1024,
1616
1717pub const base_id = .check_file;
......@@ -23,7 +23,7 @@ pub const Options = struct {
2323
2424pub fn create(
2525 owner: *std.Build,
26 source: std.Build.FileSource,
26 source: std.Build.LazyPath,
2727 options: Options,
2828) *CheckFile {
2929 const self = owner.allocator.create(CheckFile) catch @panic("OOM");
lib/std/Build/Step/CheckObject.zig+7-7
......@@ -15,14 +15,14 @@ const Step = std.Build.Step;
1515pub const base_id = .check_object;
1616
1717step: Step,
18source: std.Build.FileSource,
18source: std.Build.LazyPath,
1919max_bytes: usize = 20 * 1024 * 1024,
2020checks: std.ArrayList(Check),
2121obj_format: std.Target.ObjectFormat,
2222
2323pub fn create(
2424 owner: *std.Build,
25 source: std.Build.FileSource,
25 source: std.Build.LazyPath,
2626 obj_format: std.Target.ObjectFormat,
2727) *CheckObject {
2828 const gpa = owner.allocator;
......@@ -44,7 +44,7 @@ pub fn create(
4444
4545const SearchPhrase = struct {
4646 string: []const u8,
47 file_source: ?std.Build.FileSource = null,
47 file_source: ?std.Build.LazyPath = null,
4848
4949 fn resolve(phrase: SearchPhrase, b: *std.Build, step: *Step) []const u8 {
5050 const file_source = phrase.file_source orelse return phrase.string;
......@@ -302,13 +302,13 @@ pub fn checkExact(self: *CheckObject, phrase: []const u8) void {
302302 self.checkExactInner(phrase, null);
303303}
304304
305/// Like `checkExact()` but takes an additional argument `FileSource` which will be
305/// Like `checkExact()` but takes an additional argument `LazyPath` which will be
306306/// resolved to a full search query in `make()`.
307pub fn checkExactFileSource(self: *CheckObject, phrase: []const u8, file_source: std.Build.FileSource) void {
307pub fn checkExactPath(self: *CheckObject, phrase: []const u8, file_source: std.Build.LazyPath) void {
308308 self.checkExactInner(phrase, file_source);
309309}
310310
311fn checkExactInner(self: *CheckObject, phrase: []const u8, file_source: ?std.Build.FileSource) void {
311fn checkExactInner(self: *CheckObject, phrase: []const u8, file_source: ?std.Build.LazyPath) void {
312312 assert(self.checks.items.len > 0);
313313 const last = &self.checks.items[self.checks.items.len - 1];
314314 last.exact(.{ .string = self.step.owner.dupe(phrase), .file_source = file_source });
......@@ -321,7 +321,7 @@ pub fn checkContains(self: *CheckObject, phrase: []const u8) void {
321321
322322/// Like `checkContains()` but takes an additional argument `FileSource` which will be
323323/// resolved to a full search query in `make()`.
324pub fn checkContainsFileSource(self: *CheckObject, phrase: []const u8, file_source: std.Build.FileSource) void {
324pub fn checkContainsPath(self: *CheckObject, phrase: []const u8, file_source: std.Build.LazyPath) void {
325325 self.checkContainsInner(phrase, file_source);
326326}
327327
lib/std/Build/Step/Compile.zig+230-185
......@@ -11,7 +11,7 @@ const Allocator = mem.Allocator;
1111const Step = std.Build.Step;
1212const CrossTarget = std.zig.CrossTarget;
1313const NativeTargetInfo = std.zig.system.NativeTargetInfo;
14const FileSource = std.Build.FileSource;
14const LazyPath = std.Build.LazyPath;
1515const PkgConfigPkg = std.Build.PkgConfigPkg;
1616const PkgConfigError = std.Build.PkgConfigError;
1717const ExecError = std.Build.ExecError;
......@@ -28,7 +28,7 @@ name: []const u8,
2828target: CrossTarget,
2929target_info: NativeTargetInfo,
3030optimize: std.builtin.Mode,
31linker_script: ?FileSource = null,
31linker_script: ?LazyPath = null,
3232version_script: ?[]const u8 = null,
3333out_filename: []const u8,
3434linkage: ?Linkage = null,
......@@ -40,20 +40,12 @@ strip: ?bool,
4040unwind_tables: ?bool,
4141// keep in sync with src/link.zig:CompressDebugSections
4242compress_debug_sections: enum { none, zlib } = .none,
43lib_paths: ArrayList(FileSource),
44rpaths: ArrayList(FileSource),
45framework_dirs: ArrayList(FileSource),
43lib_paths: ArrayList(LazyPath),
44rpaths: ArrayList(LazyPath),
45framework_dirs: ArrayList(LazyPath),
4646frameworks: StringHashMap(FrameworkLinkInfo),
4747verbose_link: bool,
4848verbose_cc: bool,
49emit_asm: EmitOption = .default,
50emit_bin: EmitOption = .default,
51emit_implib: EmitOption = .default,
52emit_llvm_bc: EmitOption = .default,
53emit_llvm_ir: EmitOption = .default,
54// Lots of things depend on emit_h having a consistent path,
55// so it is not an EmitOption for now.
56emit_h: bool = false,
5749bundle_compiler_rt: ?bool = null,
5850single_threaded: ?bool,
5951stack_protector: ?bool = null,
......@@ -74,8 +66,10 @@ max_memory: ?u64 = null,
7466shared_memory: bool = false,
7567global_base: ?u64 = null,
7668c_std: std.Build.CStd,
77zig_lib_dir: ?[]const u8,
78main_pkg_path: ?[]const u8,
69/// Set via options; intended to be read-only after that.
70zig_lib_dir: ?LazyPath,
71/// Set via options; intended to be read-only after that.
72main_pkg_path: ?LazyPath,
7973exec_cmd_args: ?[]const ?[]const u8,
8074filter: ?[]const u8,
8175test_evented_io: bool = false,
......@@ -85,10 +79,8 @@ wasi_exec_model: ?std.builtin.WasiExecModel = null,
8579/// Symbols to be exported when compiling to wasm
8680export_symbol_names: []const []const u8 = &.{},
8781
88root_src: ?FileSource,
89out_h_filename: []const u8,
82root_src: ?LazyPath,
9083out_lib_filename: []const u8,
91out_pdb_filename: []const u8,
9284modules: std.StringArrayHashMap(*Module),
9385
9486link_objects: ArrayList(LinkObject),
......@@ -99,14 +91,12 @@ is_linking_libc: bool,
9991is_linking_libcpp: bool,
10092vcpkg_bin_path: ?[]const u8 = null,
10193
102/// This may be set in order to override the default install directory
103override_dest_dir: ?InstallDir,
10494installed_path: ?[]const u8,
10595
10696/// Base address for an executable image.
10797image_base: ?u64 = null,
10898
109libc_file: ?FileSource = null,
99libc_file: ?LazyPath = null,
110100
111101valgrind_support: ?bool = null,
112102each_lib_rpath: ?bool = null,
......@@ -210,35 +200,40 @@ use_lld: ?bool,
210200/// otherwise.
211201expect_errors: []const []const u8 = &.{},
212202
213output_path_source: GeneratedFile,
214output_lib_path_source: GeneratedFile,
215output_h_path_source: GeneratedFile,
216output_pdb_path_source: GeneratedFile,
217output_dirname_source: GeneratedFile,
203emit_directory: ?*GeneratedFile,
204
218205generated_docs: ?*GeneratedFile,
206generated_asm: ?*GeneratedFile,
207generated_bin: ?*GeneratedFile,
208generated_pdb: ?*GeneratedFile,
209generated_implib: ?*GeneratedFile,
210generated_llvm_bc: ?*GeneratedFile,
211generated_llvm_ir: ?*GeneratedFile,
212generated_h: ?*GeneratedFile,
219213
220214pub const CSourceFiles = struct {
215 /// Relative to the build root.
221216 files: []const []const u8,
222217 flags: []const []const u8,
223218};
224219
225220pub const CSourceFile = struct {
226 source: FileSource,
227 args: []const []const u8,
221 file: LazyPath,
222 flags: []const []const u8,
228223
229224 pub fn dupe(self: CSourceFile, b: *std.Build) CSourceFile {
230225 return .{
231 .source = self.source.dupe(b),
232 .args = b.dupeStrings(self.args),
226 .file = self.file.dupe(b),
227 .flags = b.dupeStrings(self.flags),
233228 };
234229 }
235230};
236231
237232pub const LinkObject = union(enum) {
238 static_path: FileSource,
233 static_path: LazyPath,
239234 other_step: *Compile,
240235 system_lib: SystemLib,
241 assembly_file: FileSource,
236 assembly_file: LazyPath,
242237 c_source_file: *CSourceFile,
243238 c_source_files: *CSourceFiles,
244239};
......@@ -265,15 +260,15 @@ const FrameworkLinkInfo = struct {
265260};
266261
267262pub const IncludeDir = union(enum) {
268 raw_path: []const u8,
269 raw_path_system: []const u8,
263 path: LazyPath,
264 path_system: LazyPath,
270265 other_step: *Compile,
271266 config_header_step: *Step.ConfigHeader,
272267};
273268
274269pub const Options = struct {
275270 name: []const u8,
276 root_source_file: ?FileSource = null,
271 root_source_file: ?LazyPath = null,
277272 target: CrossTarget,
278273 optimize: std.builtin.Mode,
279274 kind: Kind,
......@@ -286,6 +281,8 @@ pub const Options = struct {
286281 single_threaded: ?bool = null,
287282 use_llvm: ?bool = null,
288283 use_lld: ?bool = null,
284 zig_lib_dir: ?LazyPath = null,
285 main_pkg_path: ?LazyPath = null,
289286};
290287
291288pub const BuildId = union(enum) {
......@@ -373,25 +370,9 @@ pub const Kind = enum {
373370
374371pub const Linkage = enum { dynamic, static };
375372
376pub const EmitOption = union(enum) {
377 default: void,
378 no_emit: void,
379 emit: void,
380 emit_to: []const u8,
381
382 fn getArg(self: @This(), b: *std.Build, arg_name: []const u8) ?[]const u8 {
383 return switch (self) {
384 .no_emit => b.fmt("-fno-{s}", .{arg_name}),
385 .default => null,
386 .emit => b.fmt("-f{s}", .{arg_name}),
387 .emit_to => |path| b.fmt("-f{s}={s}", .{ arg_name, path }),
388 };
389 }
390};
391
392373pub fn create(owner: *std.Build, options: Options) *Compile {
393374 const name = owner.dupe(options.name);
394 const root_src: ?FileSource = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null;
375 const root_src: ?LazyPath = if (options.root_source_file) |rsrc| rsrc.dupe(owner) else null;
395376 if (mem.indexOf(u8, name, "/") != null or mem.indexOf(u8, name, "\\") != null) {
396377 panic("invalid name: '{s}'. It looks like a file path, but it is supposed to be the library or application name.", .{name});
397378 }
......@@ -453,18 +434,16 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
453434 }),
454435 .version = options.version,
455436 .out_filename = out_filename,
456 .out_h_filename = owner.fmt("{s}.h", .{name}),
457437 .out_lib_filename = undefined,
458 .out_pdb_filename = owner.fmt("{s}.pdb", .{name}),
459438 .major_only_filename = null,
460439 .name_only_filename = null,
461440 .modules = std.StringArrayHashMap(*Module).init(owner.allocator),
462441 .include_dirs = ArrayList(IncludeDir).init(owner.allocator),
463442 .link_objects = ArrayList(LinkObject).init(owner.allocator),
464443 .c_macros = ArrayList([]const u8).init(owner.allocator),
465 .lib_paths = ArrayList(FileSource).init(owner.allocator),
466 .rpaths = ArrayList(FileSource).init(owner.allocator),
467 .framework_dirs = ArrayList(FileSource).init(owner.allocator),
444 .lib_paths = ArrayList(LazyPath).init(owner.allocator),
445 .rpaths = ArrayList(LazyPath).init(owner.allocator),
446 .framework_dirs = ArrayList(LazyPath).init(owner.allocator),
468447 .installed_headers = ArrayList(*Step).init(owner.allocator),
469448 .c_std = std.Build.CStd.C99,
470449 .zig_lib_dir = null,
......@@ -476,16 +455,18 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
476455 .disable_sanitize_c = false,
477456 .sanitize_thread = false,
478457 .rdynamic = false,
479 .override_dest_dir = null,
480458 .installed_path = null,
481459 .force_undefined_symbols = StringHashMap(void).init(owner.allocator),
482460
483 .output_path_source = GeneratedFile{ .step = &self.step },
484 .output_lib_path_source = GeneratedFile{ .step = &self.step },
485 .output_h_path_source = GeneratedFile{ .step = &self.step },
486 .output_pdb_path_source = GeneratedFile{ .step = &self.step },
487 .output_dirname_source = GeneratedFile{ .step = &self.step },
461 .emit_directory = null,
488462 .generated_docs = null,
463 .generated_asm = null,
464 .generated_bin = null,
465 .generated_pdb = null,
466 .generated_implib = null,
467 .generated_llvm_bc = null,
468 .generated_llvm_ir = null,
469 .generated_h = null,
489470
490471 .target_info = target_info,
491472
......@@ -496,6 +477,16 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
496477 .use_lld = options.use_lld,
497478 };
498479
480 if (options.zig_lib_dir) |lp| {
481 self.zig_lib_dir = lp.dupe(self.step.owner);
482 lp.addStepDependencies(&self.step);
483 }
484
485 if (options.main_pkg_path) |lp| {
486 self.main_pkg_path = lp.dupe(self.step.owner);
487 lp.addStepDependencies(&self.step);
488 }
489
499490 if (self.kind == .lib) {
500491 if (self.linkage != null and self.linkage.? == .static) {
501492 self.out_lib_filename = self.out_filename;
......@@ -614,7 +605,7 @@ pub fn addObjCopy(cs: *Compile, options: Step.ObjCopy.Options) *Step.ObjCopy {
614605 copy.basename = cs.name;
615606 }
616607 }
617 return b.addObjCopy(cs.getOutputSource(), copy);
608 return b.addObjCopy(cs.getEmittedBin(), copy);
618609}
619610
620611/// This function would run in the context of the package that created the executable,
......@@ -626,10 +617,13 @@ pub const run = @compileError("deprecated; use std.Build.addRunArtifact");
626617pub const install = @compileError("deprecated; use std.Build.installArtifact");
627618
628619pub fn checkObject(self: *Compile) *Step.CheckObject {
629 return Step.CheckObject.create(self.step.owner, self.getOutputSource(), self.target_info.target.ofmt);
620 return Step.CheckObject.create(self.step.owner, self.getEmittedBin(), self.target_info.target.ofmt);
630621}
631622
632pub fn setLinkerScriptPath(self: *Compile, source: FileSource) void {
623/// deprecated: use `setLinkerScript`
624pub const setLinkerScriptPath = setLinkerScript;
625
626pub fn setLinkerScript(self: *Compile, source: LazyPath) void {
633627 const b = self.step.owner;
634628 self.linker_script = source.dupe(b);
635629 source.addStepDependencies(&self.step);
......@@ -690,12 +684,18 @@ pub fn isStaticLibrary(self: *Compile) bool {
690684}
691685
692686pub fn producesPdbFile(self: *Compile) bool {
687 // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
688 // TODO: just share this logic with the compiler, silly!
693689 if (!self.target.isWindows() and !self.target.isUefi()) return false;
694690 if (self.target.getObjectFormat() == .c) return false;
695691 if (self.strip == true or (self.strip == null and self.optimize == .ReleaseSmall)) return false;
696692 return self.isDynamicLibrary() or self.kind == .exe or self.kind == .@"test";
697693}
698694
695pub fn producesImplib(self: *Compile) bool {
696 return self.isDynamicLibrary() and self.target.isWindows();
697}
698
699699pub fn linkLibC(self: *Compile) void {
700700 self.is_linking_libc = true;
701701}
......@@ -935,19 +935,12 @@ pub fn addCSourceFiles(self: *Compile, files: []const []const u8, flags: []const
935935 self.link_objects.append(.{ .c_source_files = c_source_files }) catch @panic("OOM");
936936}
937937
938pub fn addCSourceFile(self: *Compile, file: []const u8, flags: []const []const u8) void {
939 self.addCSourceFileSource(.{
940 .args = flags,
941 .source = .{ .path = file },
942 });
943}
944
945pub fn addCSourceFileSource(self: *Compile, source: CSourceFile) void {
938pub fn addCSourceFile(self: *Compile, source: CSourceFile) void {
946939 const b = self.step.owner;
947940 const c_source_file = b.allocator.create(CSourceFile) catch @panic("OOM");
948941 c_source_file.* = source.dupe(b);
949942 self.link_objects.append(.{ .c_source_file = c_source_file }) catch @panic("OOM");
950 source.source.addStepDependencies(&self.step);
943 source.file.addStepDependencies(&self.step);
951944}
952945
953946pub fn setVerboseLink(self: *Compile, value: bool) void {
......@@ -958,80 +951,99 @@ pub fn setVerboseCC(self: *Compile, value: bool) void {
958951 self.verbose_cc = value;
959952}
960953
961pub fn overrideZigLibDir(self: *Compile, dir_path: []const u8) void {
954pub fn setLibCFile(self: *Compile, libc_file: ?LazyPath) void {
962955 const b = self.step.owner;
963 self.zig_lib_dir = b.dupePath(dir_path);
956 self.libc_file = if (libc_file) |f| f.dupe(b) else null;
964957}
965958
966pub fn setMainPkgPath(self: *Compile, dir_path: []const u8) void {
967 const b = self.step.owner;
968 self.main_pkg_path = b.dupePath(dir_path);
959fn getEmittedFileGeneric(self: *Compile, output_file: *?*GeneratedFile) LazyPath {
960 if (output_file.*) |g| {
961 return .{ .generated = g };
962 }
963 const arena = self.step.owner.allocator;
964 const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
965 generated_file.* = .{ .step = &self.step };
966 output_file.* = generated_file;
967 return .{ .generated = generated_file };
969968}
970969
971pub fn setLibCFile(self: *Compile, libc_file: ?FileSource) void {
972 const b = self.step.owner;
973 self.libc_file = if (libc_file) |f| f.dupe(b) else null;
970/// deprecated: use `getEmittedBinDirectory`
971pub const getOutputDirectorySource = getEmittedBinDirectory;
972
973/// Returns the path to the directory that contains the emitted binary file.
974pub fn getEmittedBinDirectory(self: *Compile) LazyPath {
975 _ = self.getEmittedBin();
976 return self.getEmittedFileGeneric(&self.emit_directory);
974977}
975978
976/// Returns the generated executable, library or object file.
979/// deprecated: use `getEmittedBin`
980pub const getOutputSource = getEmittedBin;
981
982/// Returns the path to the generated executable, library or object file.
977983/// To run an executable built with zig build, use `run`, or create an install step and invoke it.
978pub fn getOutputSource(self: *Compile) FileSource {
979 return .{ .generated = &self.output_path_source };
984pub fn getEmittedBin(self: *Compile) LazyPath {
985 return self.getEmittedFileGeneric(&self.generated_bin);
980986}
981987
982pub fn getOutputDirectorySource(self: *Compile) FileSource {
983 return .{ .generated = &self.output_dirname_source };
984}
988/// deprecated: use `getEmittedImplib`
989pub const getOutputLibSource = getEmittedImplib;
985990
986/// Returns the generated import library. This function can only be called for libraries.
987pub fn getOutputLibSource(self: *Compile) FileSource {
991/// Returns the path to the generated import library.
992/// This function can only be called for libraries.
993pub fn getEmittedImplib(self: *Compile) LazyPath {
988994 assert(self.kind == .lib);
989 return .{ .generated = &self.output_lib_path_source };
995 return self.getEmittedFileGeneric(&self.generated_implib);
990996}
991997
992/// Returns the generated header file.
993/// This function can only be called for libraries or object files which have `emit_h` set.
994pub fn getOutputHSource(self: *Compile) FileSource {
998/// deprecated: use `getEmittedH`
999pub const getOutputHSource = getEmittedH;
1000
1001/// Returns the path to the generated header file.
1002/// This function can only be called for libraries or objects.
1003pub fn getEmittedH(self: *Compile) LazyPath {
9951004 assert(self.kind != .exe and self.kind != .@"test");
996 assert(self.emit_h);
997 return .{ .generated = &self.output_h_path_source };
1005 return self.getEmittedFileGeneric(&self.generated_h);
9981006}
9991007
1000/// Returns the generated PDB file. This function can only be called for Windows and UEFI.
1001pub fn getOutputPdbSource(self: *Compile) FileSource {
1002 // TODO: Is this right? Isn't PDB for *any* PE/COFF file?
1003 assert(self.target.isWindows() or self.target.isUefi());
1004 return .{ .generated = &self.output_pdb_path_source };
1008/// deprecated: use `getEmittedPdb`.
1009pub const getOutputPdbSource = getEmittedPdb;
1010
1011/// Returns the generated PDB file.
1012/// If the compilation does not produce a PDB file, this causes a FileNotFound error
1013/// at build time.
1014pub fn getEmittedPdb(self: *Compile) LazyPath {
1015 _ = self.getEmittedBin();
1016 return self.getEmittedFileGeneric(&self.generated_pdb);
10051017}
10061018
1007pub fn getEmittedDocs(self: *Compile) FileSource {
1008 if (self.generated_docs) |g| return .{ .generated = g };
1009 const arena = self.step.owner.allocator;
1010 const generated_file = arena.create(GeneratedFile) catch @panic("OOM");
1011 generated_file.* = .{ .step = &self.step };
1012 self.generated_docs = generated_file;
1013 return .{ .generated = generated_file };
1019/// Returns the path to the generated documentation directory.
1020pub fn getEmittedDocs(self: *Compile) LazyPath {
1021 return self.getEmittedFileGeneric(&self.generated_docs);
10141022}
10151023
1016pub fn addAssemblyFile(self: *Compile, path: []const u8) void {
1017 const b = self.step.owner;
1018 self.link_objects.append(.{
1019 .assembly_file = .{ .path = b.dupe(path) },
1020 }) catch @panic("OOM");
1024/// Returns the path to the generated assembly code.
1025pub fn getEmittedAsm(self: *Compile) LazyPath {
1026 return self.getEmittedFileGeneric(&self.generated_asm);
10211027}
10221028
1023pub fn addAssemblyFileSource(self: *Compile, source: FileSource) void {
1029/// Returns the path to the generated LLVM IR.
1030pub fn getEmittedLlvmIr(self: *Compile) LazyPath {
1031 return self.getEmittedFileGeneric(&self.generated_llvm_ir);
1032}
1033
1034/// Returns the path to the generated LLVM BC.
1035pub fn getEmittedLlvmBc(self: *Compile) LazyPath {
1036 return self.getEmittedFileGeneric(&self.generated_llvm_bc);
1037}
1038
1039pub fn addAssemblyFile(self: *Compile, source: LazyPath) void {
10241040 const b = self.step.owner;
10251041 const source_duped = source.dupe(b);
10261042 self.link_objects.append(.{ .assembly_file = source_duped }) catch @panic("OOM");
10271043 source_duped.addStepDependencies(&self.step);
10281044}
10291045
1030pub fn addObjectFile(self: *Compile, source_file: []const u8) void {
1031 self.addObjectFileSource(.{ .path = source_file });
1032}
1033
1034pub fn addObjectFileSource(self: *Compile, source: FileSource) void {
1046pub fn addObjectFile(self: *Compile, source: LazyPath) void {
10351047 const b = self.step.owner;
10361048 self.link_objects.append(.{ .static_path = source.dupe(b) }) catch @panic("OOM");
10371049 source.addStepDependencies(&self.step);
......@@ -1042,14 +1054,16 @@ pub fn addObject(self: *Compile, obj: *Compile) void {
10421054 self.linkLibraryOrObject(obj);
10431055}
10441056
1045pub fn addSystemIncludePath(self: *Compile, path: []const u8) void {
1057pub fn addSystemIncludePath(self: *Compile, path: LazyPath) void {
10461058 const b = self.step.owner;
1047 self.include_dirs.append(IncludeDir{ .raw_path_system = b.dupe(path) }) catch @panic("OOM");
1059 self.include_dirs.append(IncludeDir{ .path_system = path.dupe(b) }) catch @panic("OOM");
1060 path.addStepDependencies(&self.step);
10481061}
10491062
1050pub fn addIncludePath(self: *Compile, path: []const u8) void {
1063pub fn addIncludePath(self: *Compile, path: LazyPath) void {
10511064 const b = self.step.owner;
1052 self.include_dirs.append(IncludeDir{ .raw_path = b.dupe(path) }) catch @panic("OOM");
1065 self.include_dirs.append(IncludeDir{ .path = path.dupe(b) }) catch @panic("OOM");
1066 path.addStepDependencies(&self.step);
10531067}
10541068
10551069pub fn addConfigHeader(self: *Compile, config_header: *Step.ConfigHeader) void {
......@@ -1057,32 +1071,17 @@ pub fn addConfigHeader(self: *Compile, config_header: *Step.ConfigHeader) void {
10571071 self.include_dirs.append(.{ .config_header_step = config_header }) catch @panic("OOM");
10581072}
10591073
1060pub fn addLibraryPath(self: *Compile, path: []const u8) void {
1061 const b = self.step.owner;
1062 self.lib_paths.append(.{ .path = b.dupe(path) }) catch @panic("OOM");
1063}
1064
1065pub fn addLibraryPathDirectorySource(self: *Compile, directory_source: FileSource) void {
1074pub fn addLibraryPath(self: *Compile, directory_source: LazyPath) void {
10661075 self.lib_paths.append(directory_source) catch @panic("OOM");
10671076 directory_source.addStepDependencies(&self.step);
10681077}
10691078
1070pub fn addRPath(self: *Compile, path: []const u8) void {
1071 const b = self.step.owner;
1072 self.rpaths.append(.{ .path = b.dupe(path) }) catch @panic("OOM");
1073}
1074
1075pub fn addRPathDirectorySource(self: *Compile, directory_source: FileSource) void {
1079pub fn addRPath(self: *Compile, directory_source: LazyPath) void {
10761080 self.rpaths.append(directory_source) catch @panic("OOM");
10771081 directory_source.addStepDependencies(&self.step);
10781082}
10791083
1080pub fn addFrameworkPath(self: *Compile, dir_path: []const u8) void {
1081 const b = self.step.owner;
1082 self.framework_dirs.append(.{ .path = b.dupe(dir_path) }) catch @panic("OOM");
1083}
1084
1085pub fn addFrameworkPathDirectorySource(self: *Compile, directory_source: FileSource) void {
1084pub fn addFrameworkPath(self: *Compile, directory_source: LazyPath) void {
10861085 self.framework_dirs.append(directory_source) catch @panic("OOM");
10871086 directory_source.addStepDependencies(&self.step);
10881087}
......@@ -1168,7 +1167,11 @@ pub fn setExecCmd(self: *Compile, args: []const ?[]const u8) void {
11681167}
11691168
11701169fn linkLibraryOrObject(self: *Compile, other: *Compile) void {
1171 self.step.dependOn(&other.step);
1170 other.getEmittedBin().addStepDependencies(&self.step);
1171 if (other.target.isWindows() and other.isDynamicLibrary()) {
1172 other.getEmittedImplib().addStepDependencies(&self.step);
1173 }
1174
11721175 self.link_objects.append(.{ .other_step = other }) catch @panic("OOM");
11731176 self.include_dirs.append(.{ .other_step = other }) catch @panic("OOM");
11741177
......@@ -1287,6 +1290,30 @@ fn constructDepString(
12871290 }
12881291}
12891292
1293fn getGeneratedFilePath(self: *Compile, comptime tag_name: []const u8, asking_step: ?*Step) []const u8 {
1294 const maybe_path: ?*GeneratedFile = @field(self, tag_name);
1295
1296 const generated_file = maybe_path orelse {
1297 std.debug.getStderrMutex().lock();
1298 const stderr = std.io.getStdErr();
1299
1300 std.Build.dumpBadGetPathHelp(&self.step, stderr, self.step.owner, asking_step) catch {};
1301
1302 @panic("missing emit option for " ++ tag_name);
1303 };
1304
1305 const path = generated_file.path orelse {
1306 std.debug.getStderrMutex().lock();
1307 const stderr = std.io.getStdErr();
1308
1309 std.Build.dumpBadGetPathHelp(&self.step, stderr, self.step.owner, asking_step) catch {};
1310
1311 @panic(tag_name ++ " is null. Is there a missing step dependency?");
1312 };
1313
1314 return path;
1315}
1316
12901317fn make(step: *Step, prog_node: *std.Progress.Node) !void {
12911318 const b = step.owner;
12921319 const self = @fieldParentPtr(Compile, "step", step);
......@@ -1364,7 +1391,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
13641391 .exe => @panic("Cannot link with an executable build artifact"),
13651392 .@"test" => @panic("Cannot link with a test"),
13661393 .obj => {
1367 try zig_args.append(other.getOutputSource().getPath(b));
1394 try zig_args.append(other.getEmittedBin().getPath(b));
13681395 },
13691396 .lib => l: {
13701397 if (self.isStaticLibrary() and other.isStaticLibrary()) {
......@@ -1372,7 +1399,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
13721399 break :l;
13731400 }
13741401
1375 const full_path_lib = other.getOutputLibSource().getPath(b);
1402 // For DLLs, we gotta link against the implib. For
1403 // everything else, we directly link against the library file.
1404 const full_path_lib = if (other.producesImplib())
1405 other.getGeneratedFilePath("generated_implib", &self.step)
1406 else
1407 other.getGeneratedFilePath("generated_bin", &self.step);
13761408 try zig_args.append(full_path_lib);
13771409
13781410 if (other.linkage == Linkage.dynamic and !self.target.isWindows()) {
......@@ -1432,7 +1464,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
14321464 },
14331465
14341466 .c_source_file => |c_source_file| {
1435 if (c_source_file.args.len == 0) {
1467 if (c_source_file.flags.len == 0) {
14361468 if (prev_has_cflags) {
14371469 try zig_args.append("-cflags");
14381470 try zig_args.append("--");
......@@ -1440,13 +1472,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
14401472 }
14411473 } else {
14421474 try zig_args.append("-cflags");
1443 for (c_source_file.args) |arg| {
1475 for (c_source_file.flags) |arg| {
14441476 try zig_args.append(arg);
14451477 }
14461478 try zig_args.append("--");
14471479 prev_has_cflags = true;
14481480 }
1449 try zig_args.append(c_source_file.source.getPath(b));
1481 try zig_args.append(c_source_file.file.getPath(b));
14501482 },
14511483
14521484 .c_source_files => |c_source_files| {
......@@ -1515,14 +1547,13 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
15151547 if (b.verbose_cc or self.verbose_cc) try zig_args.append("--verbose-cc");
15161548 if (b.verbose_llvm_cpu_features) try zig_args.append("--verbose-llvm-cpu-features");
15171549
1518 if (self.emit_asm.getArg(b, "emit-asm")) |arg| try zig_args.append(arg);
1519 if (self.emit_bin.getArg(b, "emit-bin")) |arg| try zig_args.append(arg);
1550 if (self.generated_asm != null) try zig_args.append("-femit-asm");
1551 if (self.generated_bin == null) try zig_args.append("-fno-emit-bin");
15201552 if (self.generated_docs != null) try zig_args.append("-femit-docs");
1521 if (self.emit_implib.getArg(b, "emit-implib")) |arg| try zig_args.append(arg);
1522 if (self.emit_llvm_bc.getArg(b, "emit-llvm-bc")) |arg| try zig_args.append(arg);
1523 if (self.emit_llvm_ir.getArg(b, "emit-llvm-ir")) |arg| try zig_args.append(arg);
1524
1525 if (self.emit_h) try zig_args.append("-femit-h");
1553 if (self.generated_implib != null) try zig_args.append("-femit-implib");
1554 if (self.generated_llvm_bc != null) try zig_args.append("-femit-llvm-bc");
1555 if (self.generated_llvm_ir != null) try zig_args.append("-femit-llvm-ir");
1556 if (self.generated_h != null) try zig_args.append("-femit-h");
15261557
15271558 try addFlag(&zig_args, "strip", self.strip);
15281559 try addFlag(&zig_args, "unwind-tables", self.unwind_tables);
......@@ -1746,18 +1777,18 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
17461777
17471778 for (self.include_dirs.items) |include_dir| {
17481779 switch (include_dir) {
1749 .raw_path => |include_path| {
1780 .path => |include_path| {
17501781 try zig_args.append("-I");
1751 try zig_args.append(b.pathFromRoot(include_path));
1782 try zig_args.append(include_path.getPath(b));
17521783 },
1753 .raw_path_system => |include_path| {
1784 .path_system => |include_path| {
17541785 if (b.sysroot != null) {
17551786 try zig_args.append("-iwithsysroot");
17561787 } else {
17571788 try zig_args.append("-isystem");
17581789 }
17591790
1760 const resolved_include_path = b.pathFromRoot(include_path);
1791 const resolved_include_path = include_path.getPath(b);
17611792
17621793 const common_include_path = if (builtin.os.tag == .windows and b.sysroot != null and fs.path.isAbsolute(resolved_include_path)) blk: {
17631794 // We need to check for disk designator and strip it out from dir path so
......@@ -1774,10 +1805,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
17741805 try zig_args.append(common_include_path);
17751806 },
17761807 .other_step => |other| {
1777 if (other.emit_h) {
1778 const h_path = other.getOutputHSource().getPath(b);
1808 if (other.generated_h) |header| {
17791809 try zig_args.append("-isystem");
1780 try zig_args.append(fs.path.dirname(h_path).?);
1810 try zig_args.append(fs.path.dirname(header.path.?).?);
17811811 }
17821812 if (other.installed_headers.items.len > 0) {
17831813 try zig_args.append("-I");
......@@ -1810,7 +1840,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
18101840 zig_args.appendAssumeCapacity("-rpath");
18111841
18121842 if (self.target_info.target.isDarwin()) switch (rpath) {
1813 .path => |path| {
1843 .path, .cwd_relative => |path| {
18141844 // On Darwin, we should not try to expand special runtime paths such as
18151845 // * @executable_path
18161846 // * @loader_path
......@@ -1907,15 +1937,12 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
19071937
19081938 if (self.zig_lib_dir) |dir| {
19091939 try zig_args.append("--zig-lib-dir");
1910 try zig_args.append(b.pathFromRoot(dir));
1911 } else if (b.zig_lib_dir) |dir| {
1912 try zig_args.append("--zig-lib-dir");
1913 try zig_args.append(dir);
1940 try zig_args.append(dir.getPath(b));
19141941 }
19151942
19161943 if (self.main_pkg_path) |dir| {
19171944 try zig_args.append("--main-pkg-path");
1918 try zig_args.append(b.pathFromRoot(dir));
1945 try zig_args.append(dir.getPath(b));
19191946 }
19201947
19211948 try addFlag(&zig_args, "PIC", self.force_pic);
......@@ -2008,33 +2035,51 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
20082035 if (maybe_output_bin_path) |output_bin_path| {
20092036 const output_dir = fs.path.dirname(output_bin_path).?;
20102037
2011 self.output_dirname_source.path = output_dir;
2038 if (self.emit_directory) |lp| {
2039 lp.path = output_dir;
2040 }
20122041
2013 self.output_path_source.path = b.pathJoin(
2014 &.{ output_dir, self.out_filename },
2015 );
2042 // -femit-bin[=path] (default) Output machine code
2043 if (self.generated_bin) |bin| {
2044 bin.path = b.pathJoin(&.{ output_dir, self.out_filename });
2045 }
2046
2047 const sep = std.fs.path.sep;
20162048
2017 if (self.kind == .lib) {
2018 self.output_lib_path_source.path = b.pathJoin(
2019 &.{ output_dir, self.out_lib_filename },
2020 );
2049 // output PDB if someone requested it
2050 if (self.generated_pdb) |pdb| {
2051 pdb.path = b.fmt("{s}{c}{s}.pdb", .{ output_dir, sep, self.name });
20212052 }
20222053
2023 if (self.emit_h) {
2024 self.output_h_path_source.path = b.pathJoin(
2025 &.{ output_dir, self.out_h_filename },
2026 );
2054 // -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL
2055 if (self.generated_implib) |implib| {
2056 implib.path = b.fmt("{s}{c}{s}.lib", .{ output_dir, sep, self.name });
20272057 }
20282058
2029 if (self.target.isWindows() or self.target.isUefi()) {
2030 self.output_pdb_path_source.path = b.pathJoin(
2031 &.{ output_dir, self.out_pdb_filename },
2032 );
2059 // -femit-h[=path] Generate a C header file (.h)
2060 if (self.generated_h) |lp| {
2061 lp.path = b.fmt("{s}{c}{s}.h", .{ output_dir, sep, self.name });
20332062 }
20342063
2064 // -femit-docs[=path] Create a docs/ dir with html documentation
20352065 if (self.generated_docs) |generated_docs| {
20362066 generated_docs.path = b.pathJoin(&.{ output_dir, "docs" });
20372067 }
2068
2069 // -femit-asm[=path] Output .s (assembly code)
2070 if (self.generated_asm) |lp| {
2071 lp.path = b.fmt("{s}{c}{s}.s", .{ output_dir, sep, self.name });
2072 }
2073
2074 // -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions)
2075 if (self.generated_llvm_ir) |lp| {
2076 lp.path = b.fmt("{s}{c}{s}.ll", .{ output_dir, sep, self.name });
2077 }
2078
2079 // -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions)
2080 if (self.generated_llvm_bc) |lp| {
2081 lp.path = b.fmt("{s}{c}{s}.bc", .{ output_dir, sep, self.name });
2082 }
20382083 }
20392084
20402085 if (self.kind == .lib and self.linkage != null and self.linkage.? == .dynamic and
......@@ -2042,7 +2087,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
20422087 {
20432088 try doAtomicSymLinks(
20442089 step,
2045 self.getOutputSource().getPath(b),
2090 self.getEmittedBin().getPath(b),
20462091 self.major_only_filename.?,
20472092 self.name_only_filename.?,
20482093 );
lib/std/Build/Step/ConfigHeader.zig+12-6
......@@ -6,16 +6,19 @@ const Allocator = std.mem.Allocator;
66pub const Style = union(enum) {
77 /// The configure format supported by autotools. It uses `#undef foo` to
88 /// mark lines that can be substituted with different values.
9 autoconf: std.Build.FileSource,
9 autoconf: std.Build.LazyPath,
1010 /// The configure format supported by CMake. It uses `@@FOO@@` and
1111 /// `#cmakedefine` for template substitution.
12 cmake: std.Build.FileSource,
12 cmake: std.Build.LazyPath,
1313 /// Instead of starting with an input file, start with nothing.
1414 blank,
1515 /// Start with nothing, like blank, and output a nasm .asm file.
1616 nasm,
1717
18 pub fn getFileSource(style: Style) ?std.Build.FileSource {
18 /// deprecated: use `getPath`
19 pub const getFileSource = getPath;
20
21 pub fn getPath(style: Style) ?std.Build.LazyPath {
1922 switch (style) {
2023 .autoconf, .cmake => |s| return s,
2124 .blank, .nasm => return null,
......@@ -54,7 +57,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
5457
5558 var include_path: []const u8 = "config.h";
5659
57 if (options.style.getFileSource()) |s| switch (s) {
60 if (options.style.getPath()) |s| switch (s) {
5861 .path => |p| {
5962 const basename = std.fs.path.basename(p);
6063 if (std.mem.endsWith(u8, basename, ".h.in")) {
......@@ -68,7 +71,7 @@ pub fn create(owner: *std.Build, options: Options) *ConfigHeader {
6871 include_path = p;
6972 }
7073
71 const name = if (options.style.getFileSource()) |s|
74 const name = if (options.style.getPath()) |s|
7275 owner.fmt("configure {s} header {s} to {s}", .{
7376 @tagName(options.style), s.getDisplayName(), include_path,
7477 })
......@@ -98,7 +101,10 @@ pub fn addValues(self: *ConfigHeader, values: anytype) void {
98101 return addValuesInner(self, values) catch @panic("OOM");
99102}
100103
101pub fn getFileSource(self: *ConfigHeader) std.Build.FileSource {
104/// deprecated: use `getOutput`
105pub const getFileSource = getOutput;
106
107pub fn getOutput(self: *ConfigHeader) std.Build.LazyPath {
102108 return .{ .generated = &self.output_file };
103109}
104110
lib/std/Build/Step/InstallArtifact.zig+115-63
......@@ -3,100 +3,150 @@ const Step = std.Build.Step;
33const InstallDir = std.Build.InstallDir;
44const InstallArtifact = @This();
55const fs = std.fs;
6
7pub const base_id = .install_artifact;
6const LazyPath = std.Build.LazyPath;
87
98step: Step,
10artifact: *Step.Compile,
11dest_dir: InstallDir,
9
10dest_dir: ?InstallDir,
11dest_sub_path: []const u8,
12emitted_bin: ?LazyPath,
13
14implib_dir: ?InstallDir,
15emitted_implib: ?LazyPath,
16
1217pdb_dir: ?InstallDir,
18emitted_pdb: ?LazyPath,
19
1320h_dir: ?InstallDir,
14/// If non-null, adds additional path components relative to dest_dir, and
15/// overrides the basename of the Compile step.
16dest_sub_path: ?[]const u8,
21emitted_h: ?LazyPath,
22
23dylib_symlinks: ?DylibSymlinkInfo,
24
25artifact: *Step.Compile,
26
27const DylibSymlinkInfo = struct {
28 major_only_filename: []const u8,
29 name_only_filename: []const u8,
30};
31
32pub const base_id = .install_artifact;
1733
18pub fn create(owner: *std.Build, artifact: *Step.Compile) *InstallArtifact {
34pub const Options = struct {
35 /// Which installation directory to put the main output file into.
36 dest_dir: Dir = .default,
37 pdb_dir: Dir = .default,
38 h_dir: Dir = .default,
39 implib_dir: Dir = .default,
40
41 /// Whether to install symlinks along with dynamic libraries.
42 dylib_symlinks: ?bool = null,
43 /// If non-null, adds additional path components relative to bin dir, and
44 /// overrides the basename of the Compile step for installation purposes.
45 dest_sub_path: ?[]const u8 = null,
46
47 pub const Dir = union(enum) {
48 disabled,
49 default,
50 override: InstallDir,
51 };
52};
53
54pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *InstallArtifact {
1955 const self = owner.allocator.create(InstallArtifact) catch @panic("OOM");
20 self.* = InstallArtifact{
56 const dest_dir: ?InstallDir = switch (options.dest_dir) {
57 .disabled => null,
58 .default => switch (artifact.kind) {
59 .obj => @panic("object files have no standard installation procedure"),
60 .exe, .@"test" => InstallDir{ .bin = {} },
61 .lib => InstallDir{ .lib = {} },
62 },
63 .override => |o| o,
64 };
65 self.* = .{
2166 .step = Step.init(.{
2267 .id = base_id,
2368 .name = owner.fmt("install {s}", .{artifact.name}),
2469 .owner = owner,
2570 .makeFn = make,
2671 }),
27 .artifact = artifact,
28 .dest_dir = artifact.override_dest_dir orelse switch (artifact.kind) {
29 .obj => @panic("Cannot install a .obj build artifact."),
30 .exe, .@"test" => InstallDir{ .bin = {} },
31 .lib => InstallDir{ .lib = {} },
72 .dest_dir = dest_dir,
73 .pdb_dir = switch (options.pdb_dir) {
74 .disabled => null,
75 .default => if (artifact.producesPdbFile()) dest_dir else null,
76 .override => |o| o,
77 },
78 .h_dir = switch (options.h_dir) {
79 .disabled => null,
80 // https://github.com/ziglang/zig/issues/9698
81 .default => null,
82 //.default => switch (artifact.kind) {
83 // .lib => .header,
84 // else => null,
85 //},
86 .override => |o| o,
3287 },
33 .pdb_dir = if (artifact.producesPdbFile()) blk: {
34 if (artifact.kind == .exe or artifact.kind == .@"test") {
35 break :blk InstallDir{ .bin = {} };
36 } else {
37 break :blk InstallDir{ .lib = {} };
38 }
88 .implib_dir = switch (options.implib_dir) {
89 .disabled => null,
90 .default => if (artifact.producesImplib()) dest_dir else null,
91 .override => |o| o,
92 },
93
94 .dylib_symlinks = if (options.dylib_symlinks orelse (dest_dir != null and
95 artifact.isDynamicLibrary() and
96 artifact.version != null and
97 artifact.target.wantSharedLibSymLinks())) .{
98 .major_only_filename = artifact.major_only_filename.?,
99 .name_only_filename = artifact.name_only_filename.?,
39100 } else null,
40 .h_dir = if (artifact.kind == .lib and artifact.emit_h) .header else null,
41 .dest_sub_path = null,
101
102 .dest_sub_path = options.dest_sub_path orelse artifact.out_filename,
103
104 .emitted_bin = null,
105 .emitted_pdb = null,
106 .emitted_h = null,
107 .emitted_implib = null,
108
109 .artifact = artifact,
42110 };
111
43112 self.step.dependOn(&artifact.step);
44113
45 owner.pushInstalledFile(self.dest_dir, artifact.out_filename);
46 if (self.artifact.isDynamicLibrary()) {
47 if (artifact.major_only_filename) |name| {
48 owner.pushInstalledFile(.lib, name);
49 }
50 if (artifact.name_only_filename) |name| {
51 owner.pushInstalledFile(.lib, name);
52 }
53 if (self.artifact.target.isWindows()) {
54 owner.pushInstalledFile(.lib, artifact.out_lib_filename);
55 }
56 }
57 if (self.pdb_dir) |pdb_dir| {
58 owner.pushInstalledFile(pdb_dir, artifact.out_pdb_filename);
59 }
60 if (self.h_dir) |h_dir| {
61 owner.pushInstalledFile(h_dir, artifact.out_h_filename);
62 }
114 if (self.dest_dir != null) self.emitted_bin = artifact.getEmittedBin();
115 if (self.pdb_dir != null) self.emitted_pdb = artifact.getEmittedPdb();
116 if (self.h_dir != null) self.emitted_h = artifact.getEmittedH();
117 if (self.implib_dir != null) self.emitted_implib = artifact.getEmittedImplib();
118
63119 return self;
64120}
65121
66122fn make(step: *Step, prog_node: *std.Progress.Node) !void {
67123 _ = prog_node;
68124 const self = @fieldParentPtr(InstallArtifact, "step", step);
69 const src_builder = self.artifact.step.owner;
70125 const dest_builder = step.owner;
71
72 const dest_sub_path = if (self.dest_sub_path) |sub_path| sub_path else self.artifact.out_filename;
73 const full_dest_path = dest_builder.getInstallPath(self.dest_dir, dest_sub_path);
74126 const cwd = fs.cwd();
75127
76128 var all_cached = true;
77129
78 {
79 const full_src_path = self.artifact.getOutputSource().getPath(src_builder);
130 if (self.dest_dir) |dest_dir| {
131 const full_dest_path = dest_builder.getInstallPath(dest_dir, self.dest_sub_path);
132 const full_src_path = self.emitted_bin.?.getPath2(step.owner, step);
80133 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_dest_path, .{}) catch |err| {
81134 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
82135 full_src_path, full_dest_path, @errorName(err),
83136 });
84137 };
85138 all_cached = all_cached and p == .fresh;
86 }
87139
88 if (self.artifact.isDynamicLibrary() and
89 self.artifact.version != null and
90 self.artifact.target.wantSharedLibSymLinks())
91 {
92 try Step.Compile.doAtomicSymLinks(step, full_dest_path, self.artifact.major_only_filename.?, self.artifact.name_only_filename.?);
140 if (self.dylib_symlinks) |dls| {
141 try Step.Compile.doAtomicSymLinks(step, full_dest_path, dls.major_only_filename, dls.name_only_filename);
142 }
143
144 self.artifact.installed_path = full_dest_path;
93145 }
94 if (self.artifact.isDynamicLibrary() and
95 self.artifact.target.isWindows() and
96 self.artifact.emit_implib != .no_emit)
97 {
98 const full_src_path = self.artifact.getOutputLibSource().getPath(src_builder);
99 const full_implib_path = dest_builder.getInstallPath(self.dest_dir, self.artifact.out_lib_filename);
146
147 if (self.implib_dir) |implib_dir| {
148 const full_src_path = self.emitted_implib.?.getPath2(step.owner, step);
149 const full_implib_path = dest_builder.getInstallPath(implib_dir, fs.path.basename(full_src_path));
100150 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_implib_path, .{}) catch |err| {
101151 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
102152 full_src_path, full_implib_path, @errorName(err),
......@@ -104,9 +154,10 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
104154 };
105155 all_cached = all_cached and p == .fresh;
106156 }
157
107158 if (self.pdb_dir) |pdb_dir| {
108 const full_src_path = self.artifact.getOutputPdbSource().getPath(src_builder);
109 const full_pdb_path = dest_builder.getInstallPath(pdb_dir, self.artifact.out_pdb_filename);
159 const full_src_path = self.emitted_pdb.?.getPath2(step.owner, step);
160 const full_pdb_path = dest_builder.getInstallPath(pdb_dir, fs.path.basename(full_src_path));
110161 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_pdb_path, .{}) catch |err| {
111162 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
112163 full_src_path, full_pdb_path, @errorName(err),
......@@ -114,9 +165,10 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
114165 };
115166 all_cached = all_cached and p == .fresh;
116167 }
168
117169 if (self.h_dir) |h_dir| {
118 const full_src_path = self.artifact.getOutputHSource().getPath(src_builder);
119 const full_h_path = dest_builder.getInstallPath(h_dir, self.artifact.out_h_filename);
170 const full_src_path = self.emitted_h.?.getPath2(step.owner, step);
171 const full_h_path = dest_builder.getInstallPath(h_dir, fs.path.basename(full_src_path));
120172 const p = fs.Dir.updateFile(cwd, full_src_path, cwd, full_h_path, .{}) catch |err| {
121173 return step.fail("unable to update file from '{s}' to '{s}': {s}", .{
122174 full_src_path, full_h_path, @errorName(err),
......@@ -124,6 +176,6 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
124176 };
125177 all_cached = all_cached and p == .fresh;
126178 }
127 self.artifact.installed_path = full_dest_path;
179
128180 step.result_cached = all_cached;
129181}
lib/std/Build/Step/InstallDir.zig+2-2
......@@ -2,7 +2,7 @@ const std = @import("std");
22const mem = std.mem;
33const fs = std.fs;
44const Step = std.Build.Step;
5const FileSource = std.Build.FileSource;
5const LazyPath = std.Build.LazyPath;
66const InstallDir = std.Build.InstallDir;
77const InstallDirStep = @This();
88
......@@ -15,7 +15,7 @@ dest_builder: *std.Build,
1515pub const base_id = .install_dir;
1616
1717pub const Options = struct {
18 source_dir: FileSource,
18 source_dir: LazyPath,
1919 install_dir: InstallDir,
2020 install_subdir: []const u8,
2121 /// File paths which end in any of these suffixes will be excluded
lib/std/Build/Step/InstallFile.zig+3-3
......@@ -1,6 +1,6 @@
11const std = @import("std");
22const Step = std.Build.Step;
3const FileSource = std.Build.FileSource;
3const LazyPath = std.Build.LazyPath;
44const InstallDir = std.Build.InstallDir;
55const InstallFile = @This();
66const assert = std.debug.assert;
......@@ -8,7 +8,7 @@ const assert = std.debug.assert;
88pub const base_id = .install_file;
99
1010step: Step,
11source: FileSource,
11source: LazyPath,
1212dir: InstallDir,
1313dest_rel_path: []const u8,
1414/// This is used by the build system when a file being installed comes from one
......@@ -17,7 +17,7 @@ dest_builder: *std.Build,
1717
1818pub fn create(
1919 owner: *std.Build,
20 source: FileSource,
20 source: LazyPath,
2121 dir: InstallDir,
2222 dest_rel_path: []const u8,
2323) *InstallFile {
lib/std/Build/Step/ObjCopy.zig+11-8
......@@ -20,7 +20,7 @@ pub const RawFormat = enum {
2020};
2121
2222step: Step,
23file_source: std.Build.FileSource,
23input_file: std.Build.LazyPath,
2424basename: []const u8,
2525output_file: std.Build.GeneratedFile,
2626
......@@ -37,30 +37,33 @@ pub const Options = struct {
3737
3838pub fn create(
3939 owner: *std.Build,
40 file_source: std.Build.FileSource,
40 input_file: std.Build.LazyPath,
4141 options: Options,
4242) *ObjCopy {
4343 const self = owner.allocator.create(ObjCopy) catch @panic("OOM");
4444 self.* = ObjCopy{
4545 .step = Step.init(.{
4646 .id = base_id,
47 .name = owner.fmt("objcopy {s}", .{file_source.getDisplayName()}),
47 .name = owner.fmt("objcopy {s}", .{input_file.getDisplayName()}),
4848 .owner = owner,
4949 .makeFn = make,
5050 }),
51 .file_source = file_source,
52 .basename = options.basename orelse file_source.getDisplayName(),
51 .input_file = input_file,
52 .basename = options.basename orelse input_file.getDisplayName(),
5353 .output_file = std.Build.GeneratedFile{ .step = &self.step },
5454
5555 .format = options.format,
5656 .only_section = options.only_section,
5757 .pad_to = options.pad_to,
5858 };
59 file_source.addStepDependencies(&self.step);
59 input_file.addStepDependencies(&self.step);
6060 return self;
6161}
6262
63pub fn getOutputSource(self: *const ObjCopy) std.Build.FileSource {
63/// deprecated: use getOutput
64pub const getOutputSource = getOutput;
65
66pub fn getOutput(self: *const ObjCopy) std.Build.LazyPath {
6467 return .{ .generated = &self.output_file };
6568}
6669
......@@ -75,7 +78,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
7578 // bytes when ObjCopy implementation is modified incompatibly.
7679 man.hash.add(@as(u32, 0xe18b7baf));
7780
78 const full_src_path = self.file_source.getPath(b);
81 const full_src_path = self.input_file.getPath(b);
7982 _ = try man.addFile(full_src_path, null);
8083 man.hash.addOptionalBytes(self.only_section);
8184 man.hash.addOptional(self.pad_to);
lib/std/Build/Step/Options.zig+24-35
......@@ -3,7 +3,7 @@ const builtin = @import("builtin");
33const fs = std.fs;
44const Step = std.Build.Step;
55const GeneratedFile = std.Build.GeneratedFile;
6const FileSource = std.Build.FileSource;
6const LazyPath = std.Build.LazyPath;
77
88const Options = @This();
99
......@@ -13,8 +13,7 @@ step: Step,
1313generated_file: GeneratedFile,
1414
1515contents: std.ArrayList(u8),
16artifact_args: std.ArrayList(OptionArtifactArg),
17file_source_args: std.ArrayList(OptionFileSourceArg),
16args: std.ArrayList(Arg),
1817
1918pub fn create(owner: *std.Build) *Options {
2019 const self = owner.allocator.create(Options) catch @panic("OOM");
......@@ -27,8 +26,7 @@ pub fn create(owner: *std.Build) *Options {
2726 }),
2827 .generated_file = undefined,
2928 .contents = std.ArrayList(u8).init(owner.allocator),
30 .artifact_args = std.ArrayList(OptionArtifactArg).init(owner.allocator),
31 .file_source_args = std.ArrayList(OptionFileSourceArg).init(owner.allocator),
29 .args = std.ArrayList(Arg).init(owner.allocator),
3230 };
3331 self.generated_file = .{ .step = &self.step };
3432
......@@ -168,35 +166,39 @@ fn printLiteral(out: anytype, val: anytype, indent: u8) !void {
168166 }
169167}
170168
169/// deprecated: use `addOptionPath`
170pub const addOptionFileSource = addOptionPath;
171
171172/// The value is the path in the cache dir.
172173/// Adds a dependency automatically.
173pub fn addOptionFileSource(
174pub fn addOptionPath(
174175 self: *Options,
175176 name: []const u8,
176 source: FileSource,
177 path: LazyPath,
177178) void {
178 self.file_source_args.append(.{
179 .name = name,
180 .source = source.dupe(self.step.owner),
179 self.args.append(.{
180 .name = self.step.owner.dupe(name),
181 .path = path.dupe(self.step.owner),
181182 }) catch @panic("OOM");
182 source.addStepDependencies(&self.step);
183 path.addStepDependencies(&self.step);
183184}
184185
185/// The value is the path in the cache dir.
186/// Adds a dependency automatically.
186/// Deprecated: use `addOptionPath(options, name, artifact.getEmittedBin())` instead.
187187pub fn addOptionArtifact(self: *Options, name: []const u8, artifact: *Step.Compile) void {
188 self.artifact_args.append(.{ .name = self.step.owner.dupe(name), .artifact = artifact }) catch @panic("OOM");
189 self.step.dependOn(&artifact.step);
188 return addOptionPath(self, name, artifact.getEmittedBin());
190189}
191190
192191pub fn createModule(self: *Options) *std.Build.Module {
193192 return self.step.owner.createModule(.{
194 .source_file = self.getSource(),
193 .source_file = self.getOutput(),
195194 .dependencies = &.{},
196195 });
197196}
198197
199pub fn getSource(self: *Options) FileSource {
198/// deprecated: use `getOutput`
199pub const getSource = getOutput;
200
201pub fn getOutput(self: *Options) LazyPath {
200202 return .{ .generated = &self.generated_file };
201203}
202204
......@@ -207,19 +209,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
207209 const b = step.owner;
208210 const self = @fieldParentPtr(Options, "step", step);
209211
210 for (self.artifact_args.items) |item| {
212 for (self.args.items) |item| {
211213 self.addOption(
212214 []const u8,
213215 item.name,
214 b.pathFromRoot(item.artifact.getOutputSource().getPath(b)),
215 );
216 }
217
218 for (self.file_source_args.items) |item| {
219 self.addOption(
220 []const u8,
221 item.name,
222 item.source.getPath(b),
216 item.path.getPath(b),
223217 );
224218 }
225219
......@@ -229,7 +223,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
229223 var hash = b.cache.hash;
230224 // Random bytes to make unique. Refresh this with new random bytes when
231225 // implementation is modified in a non-backwards-compatible way.
232 hash.add(@as(u32, 0x38845ef8));
226 hash.add(@as(u32, 0xad95e922));
233227 hash.addBytes(self.contents.items);
234228 const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
235229
......@@ -294,14 +288,9 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
294288 }
295289}
296290
297const OptionArtifactArg = struct {
298 name: []const u8,
299 artifact: *Step.Compile,
300};
301
302const OptionFileSourceArg = struct {
291const Arg = struct {
303292 name: []const u8,
304 source: FileSource,
293 path: LazyPath,
305294};
306295
307296test Options {
lib/std/Build/Step/Run.zig+46-34
......@@ -82,7 +82,7 @@ has_side_effects: bool = false,
8282pub const StdIn = union(enum) {
8383 none,
8484 bytes: []const u8,
85 file_source: std.Build.FileSource,
85 lazy_path: std.Build.LazyPath,
8686};
8787
8888pub const StdIo = union(enum) {
......@@ -120,15 +120,15 @@ pub const StdIo = union(enum) {
120120
121121pub const Arg = union(enum) {
122122 artifact: *Step.Compile,
123 file_source: PrefixedFileSource,
124 directory_source: PrefixedFileSource,
123 lazy_path: PrefixedLazyPath,
124 directory_source: PrefixedLazyPath,
125125 bytes: []u8,
126126 output: *Output,
127127};
128128
129pub const PrefixedFileSource = struct {
129pub const PrefixedLazyPath = struct {
130130 prefix: []const u8,
131 file_source: std.Build.FileSource,
131 lazy_path: std.Build.LazyPath,
132132};
133133
134134pub const Output = struct {
......@@ -164,14 +164,15 @@ pub fn enableTestRunnerMode(self: *Run) void {
164164}
165165
166166pub fn addArtifactArg(self: *Run, artifact: *Step.Compile) void {
167 const bin_file = artifact.getEmittedBin();
168 bin_file.addStepDependencies(&self.step);
167169 self.argv.append(Arg{ .artifact = artifact }) catch @panic("OOM");
168 self.step.dependOn(&artifact.step);
169170}
170171
171172/// This provides file path as a command line argument to the command being
172/// run, and returns a FileSource which can be used as inputs to other APIs
173/// run, and returns a LazyPath which can be used as inputs to other APIs
173174/// throughout the build system.
174pub fn addOutputFileArg(self: *Run, basename: []const u8) std.Build.FileSource {
175pub fn addOutputFileArg(self: *Run, basename: []const u8) std.Build.LazyPath {
175176 return self.addPrefixedOutputFileArg("", basename);
176177}
177178
......@@ -179,7 +180,7 @@ pub fn addPrefixedOutputFileArg(
179180 self: *Run,
180181 prefix: []const u8,
181182 basename: []const u8,
182) std.Build.FileSource {
183) std.Build.LazyPath {
183184 const b = self.step.owner;
184185
185186 const output = b.allocator.create(Output) catch @panic("OOM");
......@@ -197,31 +198,43 @@ pub fn addPrefixedOutputFileArg(
197198 return .{ .generated = &output.generated_file };
198199}
199200
200pub fn addFileSourceArg(self: *Run, file_source: std.Build.FileSource) void {
201 self.addPrefixedFileSourceArg("", file_source);
201/// deprecated: use `addFileArg`
202pub const addFileSourceArg = addFileArg;
203
204pub fn addFileArg(self: *Run, lp: std.Build.LazyPath) void {
205 self.addPrefixedFileArg("", lp);
202206}
203207
204pub fn addPrefixedFileSourceArg(self: *Run, prefix: []const u8, file_source: std.Build.FileSource) void {
208// deprecated: use `addPrefixedFileArg`
209pub const addPrefixedFileSourceArg = addPrefixedFileArg;
210
211pub fn addPrefixedFileArg(self: *Run, prefix: []const u8, lp: std.Build.LazyPath) void {
205212 const b = self.step.owner;
206213
207 const prefixed_file_source: PrefixedFileSource = .{
214 const prefixed_file_source: PrefixedLazyPath = .{
208215 .prefix = b.dupe(prefix),
209 .file_source = file_source.dupe(b),
216 .lazy_path = lp.dupe(b),
210217 };
211 self.argv.append(.{ .file_source = prefixed_file_source }) catch @panic("OOM");
212 file_source.addStepDependencies(&self.step);
218 self.argv.append(.{ .lazy_path = prefixed_file_source }) catch @panic("OOM");
219 lp.addStepDependencies(&self.step);
213220}
214221
215pub fn addDirectorySourceArg(self: *Run, directory_source: std.Build.FileSource) void {
216 self.addPrefixedDirectorySourceArg("", directory_source);
222/// deprecated: use `addDirectoryArg`
223pub const addDirectorySourceArg = addDirectoryArg;
224
225pub fn addDirectoryArg(self: *Run, directory_source: std.Build.LazyPath) void {
226 self.addPrefixedDirectoryArg("", directory_source);
217227}
218228
219pub fn addPrefixedDirectorySourceArg(self: *Run, prefix: []const u8, directory_source: std.Build.FileSource) void {
229// deprecated: use `addPrefixedDirectoryArg`
230pub const addPrefixedDirectorySourceArg = addPrefixedDirectoryArg;
231
232pub fn addPrefixedDirectoryArg(self: *Run, prefix: []const u8, directory_source: std.Build.LazyPath) void {
220233 const b = self.step.owner;
221234
222 const prefixed_directory_source: PrefixedFileSource = .{
235 const prefixed_directory_source: PrefixedLazyPath = .{
223236 .prefix = b.dupe(prefix),
224 .file_source = directory_source.dupe(b),
237 .lazy_path = directory_source.dupe(b),
225238 };
226239 self.argv.append(.{ .directory_source = prefixed_directory_source }) catch @panic("OOM");
227240 directory_source.addStepDependencies(&self.step);
......@@ -239,7 +252,7 @@ pub fn addArgs(self: *Run, args: []const []const u8) void {
239252
240253pub fn setStdIn(self: *Run, stdin: StdIn) void {
241254 switch (stdin) {
242 .file_source => |file_source| file_source.addStepDependencies(&self.step),
255 .lazy_path => |lazy_path| lazy_path.addStepDependencies(&self.step),
243256 .bytes, .none => {},
244257 }
245258 self.stdin = stdin;
......@@ -331,7 +344,7 @@ pub fn addCheck(self: *Run, new_check: StdIo.Check) void {
331344 }
332345}
333346
334pub fn captureStdErr(self: *Run) std.Build.FileSource {
347pub fn captureStdErr(self: *Run) std.Build.LazyPath {
335348 assert(self.stdio != .inherit);
336349
337350 if (self.captured_stderr) |output| return .{ .generated = &output.generated_file };
......@@ -346,7 +359,7 @@ pub fn captureStdErr(self: *Run) std.Build.FileSource {
346359 return .{ .generated = &output.generated_file };
347360}
348361
349pub fn captureStdOut(self: *Run) std.Build.FileSource {
362pub fn captureStdOut(self: *Run) std.Build.LazyPath {
350363 assert(self.stdio != .inherit);
351364
352365 if (self.captured_stdout) |output| return .{ .generated = &output.generated_file };
......@@ -431,14 +444,14 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
431444 try argv_list.append(bytes);
432445 man.hash.addBytes(bytes);
433446 },
434 .file_source => |file| {
435 const file_path = file.file_source.getPath(b);
447 .lazy_path => |file| {
448 const file_path = file.lazy_path.getPath(b);
436449 try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, file_path }));
437450 man.hash.addBytes(file.prefix);
438451 _ = try man.addFile(file_path, null);
439452 },
440453 .directory_source => |file| {
441 const file_path = file.file_source.getPath(b);
454 const file_path = file.lazy_path.getPath(b);
442455 try argv_list.append(b.fmt("{s}{s}", .{ file.prefix, file_path }));
443456 man.hash.addBytes(file.prefix);
444457 man.hash.addBytes(file_path);
......@@ -448,8 +461,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
448461 // On Windows we don't have rpaths so we have to add .dll search paths to PATH
449462 self.addPathForDynLibs(artifact);
450463 }
451 const file_path = artifact.installed_path orelse
452 artifact.getOutputSource().getPath(b);
464 const file_path = artifact.installed_path orelse artifact.generated_bin.?.path.?; // the path is guaranteed to be set
453465
454466 try argv_list.append(file_path);
455467
......@@ -474,8 +486,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void {
474486 .bytes => |bytes| {
475487 man.hash.addBytes(bytes);
476488 },
477 .file_source => |file_source| {
478 const file_path = file_source.getPath(b);
489 .lazy_path => |lazy_path| {
490 const file_path = lazy_path.getPath(b);
479491 _ = try man.addFile(file_path, null);
480492 },
481493 .none => {},
......@@ -1174,8 +1186,8 @@ fn evalGeneric(self: *Run, child: *std.process.Child) !StdIoResult {
11741186 child.stdin.?.close();
11751187 child.stdin = null;
11761188 },
1177 .file_source => |file_source| {
1178 const path = file_source.getPath(self.step.owner);
1189 .lazy_path => |lazy_path| {
1190 const path = lazy_path.getPath(self.step.owner);
11791191 const file = self.step.owner.build_root.handle.openFile(path, .{}) catch |err| {
11801192 return self.step.fail("unable to open stdin file: {s}", .{@errorName(err)});
11811193 };
......@@ -1241,7 +1253,7 @@ fn addPathForDynLibs(self: *Run, artifact: *Step.Compile) void {
12411253 switch (link_object) {
12421254 .other_step => |other| {
12431255 if (other.target.isWindows() and other.isDynamicLibrary()) {
1244 addPathDir(self, fs.path.dirname(other.getOutputSource().getPath(b)).?);
1256 addPathDir(self, fs.path.dirname(other.getEmittedBin().getPath(b)).?);
12451257 addPathForDynLibs(self, other);
12461258 }
12471259 },
lib/std/Build/Step/TranslateC.zig+10-6
......@@ -9,7 +9,7 @@ const TranslateC = @This();
99pub const base_id = .translate_c;
1010
1111step: Step,
12source: std.Build.FileSource,
12source: std.Build.LazyPath,
1313include_dirs: std.ArrayList([]const u8),
1414c_macros: std.ArrayList([]const u8),
1515out_basename: []const u8,
......@@ -18,7 +18,7 @@ optimize: std.builtin.OptimizeMode,
1818output_file: std.Build.GeneratedFile,
1919
2020pub const Options = struct {
21 source_file: std.Build.FileSource,
21 source_file: std.Build.LazyPath,
2222 target: CrossTarget,
2323 optimize: std.builtin.OptimizeMode,
2424};
......@@ -53,10 +53,14 @@ pub const AddExecutableOptions = struct {
5353 linkage: ?Step.Compile.Linkage = null,
5454};
5555
56pub fn getOutput(self: *TranslateC) std.Build.LazyPath {
57 return .{ .generated = &self.output_file };
58}
59
5660/// Creates a step to build an executable from the translated source.
5761pub fn addExecutable(self: *TranslateC, options: AddExecutableOptions) *Step.Compile {
5862 return self.step.owner.addExecutable(.{
59 .root_source_file = .{ .generated = &self.output_file },
63 .root_source_file = self.getOutput(),
6064 .name = options.name orelse "translated_c",
6165 .version = options.version,
6266 .target = options.target orelse self.target,
......@@ -70,7 +74,7 @@ pub fn addExecutable(self: *TranslateC, options: AddExecutableOptions) *Step.Com
7074/// `createModule` can be used instead to create a private module.
7175pub fn addModule(self: *TranslateC, name: []const u8) *std.Build.Module {
7276 return self.step.owner.addModule(name, .{
73 .source_file = .{ .generated = &self.output_file },
77 .source_file = self.getOutput(),
7478 });
7579}
7680
......@@ -83,7 +87,7 @@ pub fn createModule(self: *TranslateC) *std.Build.Module {
8387
8488 module.* = .{
8589 .builder = b,
86 .source_file = .{ .generated = &self.output_file },
90 .source_file = self.getOutput(),
8791 .dependencies = std.StringArrayHashMap(*std.Build.Module).init(b.allocator),
8892 };
8993 return module;
......@@ -96,7 +100,7 @@ pub fn addIncludeDir(self: *TranslateC, include_dir: []const u8) void {
96100pub fn addCheckFile(self: *TranslateC, expected_matches: []const []const u8) *Step.CheckFile {
97101 return Step.CheckFile.create(
98102 self.step.owner,
99 .{ .generated = &self.output_file },
103 self.getOutput(),
100104 .{ .expected_matches = expected_matches },
101105 );
102106}
lib/std/Build/Step/WriteFile.zig+15-10
......@@ -28,7 +28,10 @@ pub const File = struct {
2828 sub_path: []const u8,
2929 contents: Contents,
3030
31 pub fn getFileSource(self: *File) std.Build.FileSource {
31 /// deprecated: use `getPath`
32 pub const getFileSource = getPath;
33
34 pub fn getPath(self: *File) std.Build.LazyPath {
3235 return .{ .generated = &self.generated_file };
3336 }
3437};
......@@ -40,7 +43,7 @@ pub const OutputSourceFile = struct {
4043
4144pub const Contents = union(enum) {
4245 bytes: []const u8,
43 copy: std.Build.FileSource,
46 copy: std.Build.LazyPath,
4447};
4548
4649pub fn create(owner: *std.Build) *WriteFile {
......@@ -59,7 +62,7 @@ pub fn create(owner: *std.Build) *WriteFile {
5962 return wf;
6063}
6164
62pub fn add(wf: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.FileSource {
65pub fn add(wf: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.LazyPath {
6366 const b = wf.step.owner;
6467 const gpa = b.allocator;
6568 const file = gpa.create(File) catch @panic("OOM");
......@@ -70,7 +73,7 @@ pub fn add(wf: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.Fi
7073 };
7174 wf.files.append(gpa, file) catch @panic("OOM");
7275 wf.maybeUpdateName();
73 return file.getFileSource();
76 return file.getPath();
7477}
7578
7679/// Place the file into the generated directory within the local cache,
......@@ -80,7 +83,7 @@ pub fn add(wf: *WriteFile, sub_path: []const u8, bytes: []const u8) std.Build.Fi
8083/// include sub-directories, in which case this step will ensure the
8184/// required sub-path exists.
8285/// This is the option expected to be used most commonly with `addCopyFile`.
83pub fn addCopyFile(wf: *WriteFile, source: std.Build.FileSource, sub_path: []const u8) std.Build.FileSource {
86pub fn addCopyFile(wf: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) std.Build.LazyPath {
8487 const b = wf.step.owner;
8588 const gpa = b.allocator;
8689 const file = gpa.create(File) catch @panic("OOM");
......@@ -93,7 +96,7 @@ pub fn addCopyFile(wf: *WriteFile, source: std.Build.FileSource, sub_path: []con
9396
9497 wf.maybeUpdateName();
9598 source.addStepDependencies(&wf.step);
96 return file.getFileSource();
99 return file.getLazyPath();
97100}
98101
99102/// A path relative to the package root.
......@@ -101,7 +104,7 @@ pub fn addCopyFile(wf: *WriteFile, source: std.Build.FileSource, sub_path: []con
101104/// used as part of the normal build process, but as a utility occasionally
102105/// run by a developer with intent to modify source files and then commit
103106/// those changes to version control.
104pub fn addCopyFileToSource(wf: *WriteFile, source: std.Build.FileSource, sub_path: []const u8) void {
107pub fn addCopyFileToSource(wf: *WriteFile, source: std.Build.LazyPath, sub_path: []const u8) void {
105108 const b = wf.step.owner;
106109 wf.output_source_files.append(b.allocator, .{
107110 .contents = .{ .copy = source },
......@@ -123,11 +126,13 @@ pub fn addBytesToSource(wf: *WriteFile, bytes: []const u8, sub_path: []const u8)
123126 }) catch @panic("OOM");
124127}
125128
126pub const getFileSource = @compileError("Deprecated; use the return value from add()/addCopyFile(), or use files[i].getFileSource()");
129pub const getFileSource = @compileError("Deprecated; use the return value from add()/addCopyFile(), or use files[i].getPath()");
130
131pub const getDirectorySource = getDirectory;
127132
128/// Returns a `FileSource` representing the base directory that contains all the
133/// Returns a `LazyPath` representing the base directory that contains all the
129134/// files from this `WriteFile`.
130pub fn getDirectorySource(wf: *WriteFile) std.Build.FileSource {
135pub fn getDirectory(wf: *WriteFile) std.Build.LazyPath {
131136 return .{ .generated = &wf.generated_directory };
132137}
133138
src/link.zig+4-2
......@@ -966,6 +966,8 @@ pub const File = struct {
966966 }
967967
968968 pub fn linkAsArchive(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) FlushError!void {
969 const emit = base.options.emit orelse return;
970
969971 const tracy = trace(@src());
970972 defer tracy.end();
971973
......@@ -973,8 +975,8 @@ pub const File = struct {
973975 defer arena_allocator.deinit();
974976 const arena = arena_allocator.allocator();
975977
976 const directory = base.options.emit.?.directory; // Just an alias to make it shorter to type.
977 const full_out_path = try directory.join(arena, &[_][]const u8{base.options.emit.?.sub_path});
978 const directory = emit.directory; // Just an alias to make it shorter to type.
979 const full_out_path = try directory.join(arena, &[_][]const u8{emit.sub_path});
978980 const full_out_path_z = try arena.dupeZ(u8, full_out_path);
979981
980982 // If there is no Zig code to compile, then we should skip flushing the output file
src/link/Coff.zig+2
......@@ -1452,6 +1452,8 @@ pub fn updateDeclExports(
14521452 if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports);
14531453 }
14541454
1455 if (self.base.options.emit == null) return;
1456
14551457 const gpa = self.base.allocator;
14561458
14571459 const decl = mod.declPtr(decl_index);
src/link/Elf.zig+2
......@@ -2865,6 +2865,8 @@ pub fn updateDeclExports(
28652865 if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports);
28662866 }
28672867
2868 if (self.base.options.emit == null) return;
2869
28682870 const tracy = trace(@src());
28692871 defer tracy.end();
28702872
src/link/MachO.zig+2
......@@ -2386,6 +2386,8 @@ pub fn updateDeclExports(
23862386 return llvm_object.updateDeclExports(mod, decl_index, exports);
23872387 }
23882388
2389 if (self.base.options.emit == null) return;
2390
23892391 const tracy = trace(@src());
23902392 defer tracy.end();
23912393
src/link/NvPtx.zig+2-1
......@@ -106,10 +106,11 @@ pub fn flushModule(self: *NvPtx, comp: *Compilation, prog_node: *std.Progress.No
106106 if (build_options.skip_non_native) {
107107 @panic("Attempted to compile for architecture that was disabled by build configuration");
108108 }
109 const outfile = comp.bin_file.options.emit orelse return;
110
109111 const tracy = trace(@src());
110112 defer tracy.end();
111113
112 const outfile = comp.bin_file.options.emit.?;
113114 // We modify 'comp' before passing it to LLVM, but restore value afterwards.
114115 // We tell LLVM to not try to build a .o, only an "assembly" file.
115116 // This is required by the LLVM PTX backend.
src/link/Wasm.zig+2
......@@ -1712,6 +1712,8 @@ pub fn updateDeclExports(
17121712 if (wasm.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports);
17131713 }
17141714
1715 if (wasm.base.options.emit == null) return;
1716
17151717 const decl = mod.declPtr(decl_index);
17161718 const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
17171719 const atom = wasm.getAtom(atom_index);
test/link/glibc_compat/build.zig+3-1
......@@ -4,7 +4,7 @@ pub fn build(b: *std.Build) void {
44 const test_step = b.step("test", "Test");
55 b.default_step = test_step;
66
7 inline for (.{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| {
7 for ([_][]const u8{ "aarch64-linux-gnu.2.27", "aarch64-linux-gnu.2.34" }) |t| {
88 const exe = b.addExecutable(.{
99 .name = t,
1010 .root_source_file = .{ .path = "main.c" },
......@@ -13,6 +13,8 @@ pub fn build(b: *std.Build) void {
1313 ) catch unreachable,
1414 });
1515 exe.linkLibC();
16 // TODO: actually test the output
17 _ = exe.getEmittedBin();
1618 test_step.dependOn(&exe.step);
1719 }
1820}
test/link/interdependent_static_c_libs/build.zig+5-5
......@@ -16,16 +16,16 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1616 .optimize = optimize,
1717 .target = .{},
1818 });
19 lib_a.addCSourceFile("a.c", &[_][]const u8{});
20 lib_a.addIncludePath(".");
19 lib_a.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &[_][]const u8{} });
20 lib_a.addIncludePath(.{ .path = "." });
2121
2222 const lib_b = b.addStaticLibrary(.{
2323 .name = "b",
2424 .optimize = optimize,
2525 .target = .{},
2626 });
27 lib_b.addCSourceFile("b.c", &[_][]const u8{});
28 lib_b.addIncludePath(".");
27 lib_b.addCSourceFile(.{ .file = .{ .path = "b.c" }, .flags = &[_][]const u8{} });
28 lib_b.addIncludePath(.{ .path = "." });
2929
3030 const test_exe = b.addTest(.{
3131 .root_source_file = .{ .path = "main.zig" },
......@@ -33,7 +33,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3333 });
3434 test_exe.linkLibrary(lib_a);
3535 test_exe.linkLibrary(lib_b);
36 test_exe.addIncludePath(".");
36 test_exe.addIncludePath(.{ .path = "." });
3737
3838 test_step.dependOn(&b.addRunArtifact(test_exe).step);
3939}
test/link/macho/bugs/13056/build.zig+5-5
......@@ -23,13 +23,13 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2323 .name = "test",
2424 .optimize = optimize,
2525 });
26 exe.addSystemIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include" }) catch unreachable);
27 exe.addIncludePath(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/include/c++/v1" }) catch unreachable);
28 exe.addCSourceFile("test.cpp", &.{
26 exe.addSystemIncludePath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/include" }) });
27 exe.addIncludePath(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/include/c++/v1" }) });
28 exe.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &.{
2929 "-nostdlib++",
3030 "-nostdinc++",
31 });
32 exe.addObjectFile(std.fs.path.join(b.allocator, &.{ sdk.path, "/usr/lib/libc++.tbd" }) catch unreachable);
31 } });
32 exe.addObjectFile(.{ .path = b.pathJoin(&.{ sdk.path, "/usr/lib/libc++.tbd" }) });
3333
3434 const run_cmd = b.addRunArtifact(exe);
3535 run_cmd.expectStdErrEqual("x: 5\n");
test/link/macho/dead_strip/build.zig+1-1
......@@ -52,7 +52,7 @@ fn createScenario(
5252 .optimize = optimize,
5353 .target = target,
5454 });
55 exe.addCSourceFile("main.c", &[0][]const u8{});
55 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
5656 exe.linkLibC();
5757 return exe;
5858}
test/link/macho/dead_strip_dylibs/build.zig+1-1
......@@ -53,7 +53,7 @@ fn createScenario(
5353 .name = name,
5454 .optimize = optimize,
5555 });
56 exe.addCSourceFile("main.c", &[0][]const u8{});
56 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
5757 exe.linkLibC();
5858 exe.linkFramework("Cocoa");
5959 return exe;
test/link/macho/dylib/build.zig+5-5
......@@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 .optimize = optimize,
2222 .target = target,
2323 });
24 dylib.addCSourceFile("a.c", &.{});
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
2525 dylib.linkLibC();
2626
2727 const check_dylib = dylib.checkObject();
......@@ -39,10 +39,10 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3939 .optimize = optimize,
4040 .target = target,
4141 });
42 exe.addCSourceFile("main.c", &.{});
42 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
4343 exe.linkSystemLibrary("a");
44 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
45 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
44 exe.addLibraryPath(dylib.getEmittedBinDirectory());
45 exe.addRPath(dylib.getEmittedBinDirectory());
4646 exe.linkLibC();
4747
4848 const check_exe = exe.checkObject();
......@@ -55,7 +55,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
5555
5656 check_exe.checkStart();
5757 check_exe.checkExact("cmd RPATH");
58 check_exe.checkExactFileSource("path", dylib.getOutputDirectorySource());
58 check_exe.checkExactPath("path", dylib.getOutputDirectorySource());
5959 test_step.dependOn(&check_exe.step);
6060
6161 const run = b.addRunArtifact(exe);
test/link/macho/empty/build.zig+2-2
......@@ -20,8 +20,8 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 .optimize = optimize,
2121 .target = target,
2222 });
23 exe.addCSourceFile("main.c", &[0][]const u8{});
24 exe.addCSourceFile("empty.c", &[0][]const u8{});
23 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
24 exe.addCSourceFile(.{ .file = .{ .path = "empty.c" }, .flags = &[0][]const u8{} });
2525 exe.linkLibC();
2626
2727 const run_cmd = b.addRunArtifact(exe);
test/link/macho/entry/build.zig+1-1
......@@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 .optimize = optimize,
1919 .target = .{ .os_tag = .macos },
2020 });
21 exe.addCSourceFile("main.c", &.{});
21 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
2222 exe.linkLibC();
2323 exe.entry_symbol_name = "_non_main";
2424
test/link/macho/entry_in_archive/build.zig+1-1
......@@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 .optimize = optimize,
1919 .target = .{ .os_tag = .macos },
2020 });
21 lib.addCSourceFile("main.c", &.{});
21 lib.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
2222 lib.linkLibC();
2323
2424 const exe = b.addExecutable(.{
test/link/macho/entry_in_dylib/build.zig+2-2
......@@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 .optimize = optimize,
1919 .target = .{ .os_tag = .macos },
2020 });
21 lib.addCSourceFile("bootstrap.c", &.{});
21 lib.addCSourceFile(.{ .file = .{ .path = "bootstrap.c" }, .flags = &.{} });
2222 lib.linkLibC();
2323 lib.linker_allow_shlib_undefined = true;
2424
......@@ -27,7 +27,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2727 .optimize = optimize,
2828 .target = .{ .os_tag = .macos },
2929 });
30 exe.addCSourceFile("main.c", &.{});
30 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
3131 exe.linkLibrary(lib);
3232 exe.linkLibC();
3333 exe.entry_symbol_name = "_bootstrap";
test/link/macho/headerpad/build.zig+1-1
......@@ -113,7 +113,7 @@ fn simpleExe(
113113 .name = name,
114114 .optimize = optimize,
115115 });
116 exe.addCSourceFile("main.c", &.{});
116 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
117117 exe.linkLibC();
118118 exe.linkFramework("CoreFoundation");
119119 exe.linkFramework("Foundation");
test/link/macho/needed_framework/build.zig+1-1
......@@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 .name = "test",
2121 .optimize = optimize,
2222 });
23 exe.addCSourceFile("main.c", &[0][]const u8{});
23 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
2424 exe.linkLibC();
2525 exe.linkFrameworkNeeded("Cocoa");
2626 exe.dead_strip_dylibs = true;
test/link/macho/needed_library/build.zig+4-4
......@@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 .optimize = optimize,
2222 .target = target,
2323 });
24 dylib.addCSourceFile("a.c", &.{});
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
2525 dylib.linkLibC();
2626
2727 // -dead_strip_dylibs
......@@ -31,11 +31,11 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3131 .optimize = optimize,
3232 .target = target,
3333 });
34 exe.addCSourceFile("main.c", &[0][]const u8{});
34 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
3535 exe.linkLibC();
3636 exe.linkSystemLibraryNeeded("a");
37 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
38 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
37 exe.addLibraryPath(dylib.getEmittedBinDirectory());
38 exe.addRPath(dylib.getEmittedBinDirectory());
3939 exe.dead_strip_dylibs = true;
4040
4141 const check = exe.checkObject();
test/link/macho/objc/build.zig+3-3
......@@ -18,9 +18,9 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 .name = "test",
1919 .optimize = optimize,
2020 });
21 exe.addIncludePath(".");
22 exe.addCSourceFile("Foo.m", &[0][]const u8{});
23 exe.addCSourceFile("test.m", &[0][]const u8{});
21 exe.addIncludePath(.{ .path = "." });
22 exe.addCSourceFile(.{ .file = .{ .path = "Foo.m" }, .flags = &[0][]const u8{} });
23 exe.addCSourceFile(.{ .file = .{ .path = "test.m" }, .flags = &[0][]const u8{} });
2424 exe.linkLibC();
2525 // TODO when we figure out how to ship framework stubs for cross-compilation,
2626 // populate paths to the sysroot here.
test/link/macho/objcpp/build.zig+3-3
......@@ -19,9 +19,9 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1919 .optimize = optimize,
2020 });
2121 b.default_step.dependOn(&exe.step);
22 exe.addIncludePath(".");
23 exe.addCSourceFile("Foo.mm", &[0][]const u8{});
24 exe.addCSourceFile("test.mm", &[0][]const u8{});
22 exe.addIncludePath(.{ .path = "." });
23 exe.addCSourceFile(.{ .file = .{ .path = "Foo.mm" }, .flags = &[0][]const u8{} });
24 exe.addCSourceFile(.{ .file = .{ .path = "test.mm" }, .flags = &[0][]const u8{} });
2525 exe.linkLibCpp();
2626 // TODO when we figure out how to ship framework stubs for cross-compilation,
2727 // populate paths to the sysroot here.
test/link/macho/pagezero/build.zig+2-2
......@@ -15,7 +15,7 @@ pub fn build(b: *std.Build) void {
1515 .optimize = optimize,
1616 .target = target,
1717 });
18 exe.addCSourceFile("main.c", &.{});
18 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
1919 exe.linkLibC();
2020 exe.pagezero_size = 0x4000;
2121
......@@ -39,7 +39,7 @@ pub fn build(b: *std.Build) void {
3939 .optimize = optimize,
4040 .target = target,
4141 });
42 exe.addCSourceFile("main.c", &.{});
42 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
4343 exe.linkLibC();
4444 exe.pagezero_size = 0;
4545
test/link/macho/search_strategy/build.zig+6-12
......@@ -55,11 +55,8 @@ fn createScenario(
5555 .optimize = optimize,
5656 .target = target,
5757 });
58 static.addCSourceFile("a.c", &.{});
58 static.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
5959 static.linkLibC();
60 static.override_dest_dir = std.Build.InstallDir{
61 .custom = "static",
62 };
6360
6461 const dylib = b.addSharedLibrary(.{
6562 .name = name,
......@@ -67,22 +64,19 @@ fn createScenario(
6764 .optimize = optimize,
6865 .target = target,
6966 });
70 dylib.addCSourceFile("a.c", &.{});
67 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
7168 dylib.linkLibC();
72 dylib.override_dest_dir = std.Build.InstallDir{
73 .custom = "dynamic",
74 };
7569
7670 const exe = b.addExecutable(.{
7771 .name = name,
7872 .optimize = optimize,
7973 .target = target,
8074 });
81 exe.addCSourceFile("main.c", &.{});
75 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
8276 exe.linkSystemLibraryName(name);
8377 exe.linkLibC();
84 exe.addLibraryPathDirectorySource(static.getOutputDirectorySource());
85 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
86 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
78 exe.addLibraryPath(static.getEmittedBinDirectory());
79 exe.addLibraryPath(dylib.getEmittedBinDirectory());
80 exe.addRPath(dylib.getEmittedBinDirectory());
8781 return exe;
8882}
test/link/macho/stack_size/build.zig+1-1
......@@ -20,7 +20,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2020 .optimize = optimize,
2121 .target = target,
2222 });
23 exe.addCSourceFile("main.c", &.{});
23 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &.{} });
2424 exe.linkLibC();
2525 exe.stack_size = 0x100000000;
2626
test/link/macho/tbdv3/build.zig+4-4
......@@ -23,7 +23,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2323 .optimize = optimize,
2424 .target = target,
2525 });
26 lib.addCSourceFile("a.c", &.{});
26 lib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
2727 lib.linkLibC();
2828
2929 const tbd_file = b.addWriteFile("liba.tbd",
......@@ -43,10 +43,10 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
4343 .optimize = optimize,
4444 .target = target,
4545 });
46 exe.addCSourceFile("main.c", &[0][]const u8{});
46 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
4747 exe.linkSystemLibrary("a");
48 exe.addLibraryPathDirectorySource(tbd_file.getDirectorySource());
49 exe.addRPathDirectorySource(lib.getOutputDirectorySource());
48 exe.addLibraryPath(tbd_file.getDirectory());
49 exe.addRPath(lib.getEmittedBinDirectory());
5050 exe.linkLibC();
5151
5252 const run = b.addRunArtifact(exe);
test/link/macho/tls/build.zig+1-1
......@@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 .optimize = optimize,
2222 .target = target,
2323 });
24 lib.addCSourceFile("a.c", &.{});
24 lib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
2525 lib.linkLibC();
2626
2727 const test_exe = b.addTest(.{
test/link/macho/unwind_info/build.zig+1-1
......@@ -75,7 +75,7 @@ fn createScenario(
7575 .target = target,
7676 });
7777 b.default_step.dependOn(&exe.step);
78 exe.addIncludePath(".");
78 exe.addIncludePath(.{ .path = "." });
7979 exe.addCSourceFiles(&[_][]const u8{
8080 "main.cpp",
8181 "simple_string.cpp",
test/link/macho/weak_framework/build.zig+1-1
......@@ -18,7 +18,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1818 .name = "test",
1919 .optimize = optimize,
2020 });
21 exe.addCSourceFile("main.c", &[0][]const u8{});
21 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
2222 exe.linkLibC();
2323 exe.linkFrameworkWeak("Cocoa");
2424
test/link/macho/weak_library/build.zig+4-4
......@@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 .target = target,
2222 .optimize = optimize,
2323 });
24 dylib.addCSourceFile("a.c", &.{});
24 dylib.addCSourceFile(.{ .file = .{ .path = "a.c" }, .flags = &.{} });
2525 dylib.linkLibC();
2626 b.installArtifact(dylib);
2727
......@@ -30,11 +30,11 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
3030 .target = target,
3131 .optimize = optimize,
3232 });
33 exe.addCSourceFile("main.c", &[0][]const u8{});
33 exe.addCSourceFile(.{ .file = .{ .path = "main.c" }, .flags = &[0][]const u8{} });
3434 exe.linkLibC();
3535 exe.linkSystemLibraryWeak("a");
36 exe.addLibraryPathDirectorySource(dylib.getOutputDirectorySource());
37 exe.addRPathDirectorySource(dylib.getOutputDirectorySource());
36 exe.addLibraryPath(dylib.getEmittedBinDirectory());
37 exe.addRPath(dylib.getEmittedBinDirectory());
3838
3939 const check = exe.checkObject();
4040 check.checkStart();
test/link/static_libs_from_object_files/build.zig+6-6
......@@ -2,7 +2,7 @@ const std = @import("std");
22const builtin = @import("builtin");
33
44const Build = std.Build;
5const FileSource = Build.FileSource;
5const LazyPath = Build.LazyPath;
66const Step = Build.Step;
77const Run = Step.Run;
88const WriteFile = Step.WriteFile;
......@@ -14,7 +14,7 @@ pub fn build(b: *Build) void {
1414 b.default_step = test_step;
1515
1616 // generate c files
17 const files = b.allocator.alloc(std.Build.FileSource, nb_files) catch unreachable;
17 const files = b.allocator.alloc(LazyPath, nb_files) catch unreachable;
1818 defer b.allocator.free(files);
1919 {
2020 for (files[0 .. nb_files - 1], 1..nb_files) |*file, i| {
......@@ -47,7 +47,7 @@ pub fn build(b: *Build) void {
4747 add(b, test_step, files, .ReleaseFast);
4848}
4949
50fn add(b: *Build, test_step: *Step, files: []const std.Build.FileSource, optimize: std.builtin.OptimizeMode) void {
50fn add(b: *Build, test_step: *Step, files: []const LazyPath, optimize: std.builtin.OptimizeMode) void {
5151 const flags = [_][]const u8{
5252 "-Wall",
5353 "-std=c11",
......@@ -63,7 +63,7 @@ fn add(b: *Build, test_step: *Step, files: []const std.Build.FileSource, optimiz
6363 });
6464
6565 for (files) |file| {
66 exe.addCSourceFileSource(.{ .source = file, .args = &flags });
66 exe.addCSourceFile(.{ .file = file, .flags = &flags });
6767 }
6868
6969 const run_cmd = b.addRunArtifact(exe);
......@@ -88,7 +88,7 @@ fn add(b: *Build, test_step: *Step, files: []const std.Build.FileSource, optimiz
8888
8989 for (files, 1..) |file, i| {
9090 const lib = if (i & 1 == 0) lib_a else lib_b;
91 lib.addCSourceFileSource(.{ .source = file, .args = &flags });
91 lib.addCSourceFile(.{ .file = file, .flags = &flags });
9292 }
9393
9494 const exe = b.addExecutable(.{
......@@ -125,7 +125,7 @@ fn add(b: *Build, test_step: *Step, files: []const std.Build.FileSource, optimiz
125125 .target = .{},
126126 .optimize = optimize,
127127 });
128 obj.addCSourceFileSource(.{ .source = file, .args = &flags });
128 obj.addCSourceFile(.{ .file = file, .flags = &flags });
129129
130130 const lib = if (i & 1 == 0) lib_a else lib_b;
131131 lib.addObject(obj);
test/link/wasm/extern/build.zig+1-1
......@@ -19,7 +19,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1919 .optimize = optimize,
2020 .target = .{ .cpu_arch = .wasm32, .os_tag = .wasi },
2121 });
22 exe.addCSourceFile("foo.c", &.{});
22 exe.addCSourceFile(.{ .file = .{ .path = "foo.c" }, .flags = &.{} });
2323 exe.use_llvm = false;
2424 exe.use_lld = false;
2525
test/link/wasm/infer-features/build.zig+1-1
......@@ -13,7 +13,7 @@ pub fn build(b: *std.Build) void {
1313 .os_tag = .freestanding,
1414 },
1515 });
16 c_obj.addCSourceFile("foo.c", &.{});
16 c_obj.addCSourceFile(.{ .file = .{ .path = "foo.c" }, .flags = &.{} });
1717
1818 // Wasm library that doesn't have any features specified. This will
1919 // infer its featureset from other linked object files.
test/src/Cases.zig+3-5
......@@ -518,12 +518,12 @@ pub fn lowerToBuildSteps(
518518 }
519519
520520 const writefiles = b.addWriteFiles();
521 var file_sources = std.StringHashMap(std.Build.FileSource).init(b.allocator);
521 var file_sources = std.StringHashMap(std.Build.LazyPath).init(b.allocator);
522522 defer file_sources.deinit();
523523 for (update.files.items) |file| {
524524 file_sources.put(file.path, writefiles.add(file.path, file.src)) catch @panic("OOM");
525525 }
526 const root_source_file = writefiles.files.items[0].getFileSource();
526 const root_source_file = writefiles.files.items[0].getPath();
527527
528528 const artifact = if (case.is_test) b.addTest(.{
529529 .root_source_file = root_source_file,
......@@ -551,8 +551,6 @@ pub fn lowerToBuildSteps(
551551 }),
552552 };
553553
554 artifact.emit_bin = if (case.emit_bin) .default else .no_emit;
555
556554 if (case.link_libc) artifact.linkLibC();
557555
558556 switch (case.backend) {
......@@ -577,7 +575,7 @@ pub fn lowerToBuildSteps(
577575 parent_step.dependOn(&artifact.step);
578576 },
579577 .CompareObjectFile => |expected_output| {
580 const check = b.addCheckFile(artifact.getOutputSource(), .{
578 const check = b.addCheckFile(artifact.getEmittedBin(), .{
581579 .expected_exact = expected_output,
582580 });
583581
test/src/CompareOutput.zig+3-3
......@@ -99,7 +99,7 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void {
9999 .target = .{},
100100 .optimize = .Debug,
101101 });
102 exe.addAssemblyFileSource(write_src.files.items[0].getFileSource());
102 exe.addAssemblyFile(write_src.files.items[0].getPath());
103103
104104 const run = b.addRunArtifact(exe);
105105 run.setName(annotated_case_name);
......@@ -119,7 +119,7 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void {
119119
120120 const exe = b.addExecutable(.{
121121 .name = "test",
122 .root_source_file = write_src.files.items[0].getFileSource(),
122 .root_source_file = write_src.files.items[0].getPath(),
123123 .optimize = optimize,
124124 .target = .{},
125125 });
......@@ -145,7 +145,7 @@ pub fn addCase(self: *CompareOutput, case: TestCase) void {
145145
146146 const exe = b.addExecutable(.{
147147 .name = "test",
148 .root_source_file = write_src.files.items[0].getFileSource(),
148 .root_source_file = write_src.files.items[0].getPath(),
149149 .target = .{},
150150 .optimize = .Debug,
151151 });
test/src/StackTrace.zig+2-2
......@@ -75,7 +75,7 @@ fn addExpect(
7575 const write_src = b.addWriteFile("source.zig", source);
7676 const exe = b.addExecutable(.{
7777 .name = "test",
78 .root_source_file = write_src.files.items[0].getFileSource(),
78 .root_source_file = write_src.files.items[0].getPath(),
7979 .optimize = optimize_mode,
8080 .target = .{},
8181 });
......@@ -88,7 +88,7 @@ fn addExpect(
8888
8989 const check_run = b.addRunArtifact(self.check_exe);
9090 check_run.setName(annotated_case_name);
91 check_run.addFileSourceArg(run.captureStdErr());
91 check_run.addFileArg(run.captureStdErr());
9292 check_run.addArgs(&.{
9393 @tagName(optimize_mode),
9494 });
test/src/run_translated_c.zig+1-1
......@@ -85,7 +85,7 @@ pub const RunTranslatedCContext = struct {
8585 _ = write_src.add(src_file.filename, src_file.source);
8686 }
8787 const translate_c = b.addTranslateC(.{
88 .source_file = write_src.files.items[0].getFileSource(),
88 .source_file = write_src.files.items[0].getPath(),
8989 .target = .{},
9090 .optimize = .Debug,
9191 });
test/src/translate_c.zig+1-1
......@@ -108,7 +108,7 @@ pub const TranslateCContext = struct {
108108 }
109109
110110 const translate_c = b.addTranslateC(.{
111 .source_file = write_src.files.items[0].getFileSource(),
111 .source_file = write_src.files.items[0].getPath(),
112112 .target = case.target,
113113 .optimize = .Debug,
114114 });
test/standalone/c_compiler/build.zig+2-2
......@@ -19,7 +19,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
1919 .optimize = optimize,
2020 .target = target,
2121 });
22 exe_c.addCSourceFile("test.c", &[0][]const u8{});
22 exe_c.addCSourceFile(.{ .file = .{ .path = "test.c" }, .flags = &[0][]const u8{} });
2323 exe_c.linkLibC();
2424
2525 const exe_cpp = b.addExecutable(.{
......@@ -28,7 +28,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2828 .target = target,
2929 });
3030 b.default_step.dependOn(&exe_cpp.step);
31 exe_cpp.addCSourceFile("test.cpp", &[0][]const u8{});
31 exe_cpp.addCSourceFile(.{ .file = .{ .path = "test.cpp" }, .flags = &[0][]const u8{} });
3232 exe_cpp.linkLibCpp();
3333
3434 switch (target.getOsTag()) {
test/standalone/coff_dwarf/build.zig+1-1
......@@ -23,7 +23,7 @@ pub fn build(b: *std.Build) void {
2323 .optimize = optimize,
2424 .target = target,
2525 });
26 lib.addCSourceFile("shared_lib.c", &.{"-gdwarf"});
26 lib.addCSourceFile(.{ .file = .{ .path = "shared_lib.c" }, .flags = &.{"-gdwarf"} });
2727 lib.linkLibC();
2828 exe.linkLibrary(lib);
2929
test/standalone/compiler_rt_panic/build.zig+4-1
......@@ -16,7 +16,10 @@ pub fn build(b: *std.Build) void {
1616 .target = target,
1717 });
1818 exe.linkLibC();
19 exe.addCSourceFile("main.c", &.{});
19 exe.addCSourceFile(.{
20 .file = .{ .path = "main.c" },
21 .flags = &.{},
22 });
2023 exe.link_gc_sections = false;
2124 exe.bundle_compiler_rt = true;
2225
test/standalone/embed_generated_file/build.zig+4-1
......@@ -19,8 +19,11 @@ pub fn build(b: *std.Build) void {
1919 .optimize = .Debug,
2020 });
2121 exe.addAnonymousModule("bootloader.elf", .{
22 .source_file = bootloader.getOutputSource(),
22 .source_file = bootloader.getEmittedBin(),
2323 });
2424
25 // TODO: actually check the output
26 _ = exe.getEmittedBin();
27
2528 test_step.dependOn(&exe.step);
2629}
test/standalone/emit_asm_and_bin/build.zig+3-2
......@@ -8,8 +8,9 @@ pub fn build(b: *std.Build) void {
88 .root_source_file = .{ .path = "main.zig" },
99 .optimize = b.standardOptimizeOption(.{}),
1010 });
11 main.emit_asm = .{ .emit_to = b.pathFromRoot("main.s") };
12 main.emit_bin = .{ .emit_to = b.pathFromRoot("main") };
11 // TODO: actually check these two artifacts for correctness
12 _ = main.getEmittedBin();
13 _ = main.getEmittedAsm();
1314
1415 test_step.dependOn(&b.addRunArtifact(main).step);
1516}
test/standalone/issue_12588/build.zig+2-3
......@@ -13,9 +13,8 @@ pub fn build(b: *std.Build) void {
1313 .optimize = optimize,
1414 .target = target,
1515 });
16 obj.emit_llvm_ir = .{ .emit_to = b.pathFromRoot("main.ll") };
17 obj.emit_llvm_bc = .{ .emit_to = b.pathFromRoot("main.bc") };
18 obj.emit_bin = .no_emit;
16 _ = obj.getEmittedLlvmIr();
17 _ = obj.getEmittedLlvmBc();
1918 b.default_step.dependOn(&obj.step);
2019
2120 test_step.dependOn(&obj.step);
test/standalone/issue_339/build.zig+3
......@@ -14,5 +14,8 @@ pub fn build(b: *std.Build) void {
1414 .optimize = optimize,
1515 });
1616
17 // TODO: actually check the output
18 _ = obj.getEmittedBin();
19
1720 test_step.dependOn(&obj.step);
1821}
test/standalone/issue_5825/build.zig+3
......@@ -27,5 +27,8 @@ pub fn build(b: *std.Build) void {
2727 exe.linkSystemLibrary("ntdll");
2828 exe.addObject(obj);
2929
30 // TODO: actually check the output
31 _ = exe.getEmittedBin();
32
3033 test_step.dependOn(&exe.step);
3134}
test/standalone/issue_794/build.zig+4-1
......@@ -7,7 +7,10 @@ pub fn build(b: *std.Build) void {
77 const test_artifact = b.addTest(.{
88 .root_source_file = .{ .path = "main.zig" },
99 });
10 test_artifact.addIncludePath("a_directory");
10 test_artifact.addIncludePath(.{ .path = "a_directory" });
11
12 // TODO: actually check the output
13 _ = test_artifact.getEmittedBin();
1114
1215 test_step.dependOn(&test_artifact.step);
1316}
test/standalone/issue_8550/build.zig+2-2
......@@ -19,8 +19,8 @@ pub fn build(b: *std.Build) !void {
1919 .optimize = optimize,
2020 .target = target,
2121 });
22 kernel.addObjectFile("./boot.S");
23 kernel.setLinkerScriptPath(.{ .path = "./linker.ld" });
22 kernel.addObjectFile(.{ .path = "./boot.S" });
23 kernel.setLinkerScript(.{ .path = "./linker.ld" });
2424 b.installArtifact(kernel);
2525
2626 test_step.dependOn(&kernel.step);
test/standalone/main_pkg_path/build.zig+1-1
......@@ -6,8 +6,8 @@ pub fn build(b: *std.Build) void {
66
77 const test_exe = b.addTest(.{
88 .root_source_file = .{ .path = "a/test.zig" },
9 .main_pkg_path = .{ .path = "." },
910 });
10 test_exe.setMainPkgPath(".");
1111
1212 test_step.dependOn(&b.addRunArtifact(test_exe).step);
1313}
test/standalone/mix_c_files/build.zig+1-1
......@@ -21,7 +21,7 @@ fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.Optimize
2121 .root_source_file = .{ .path = "main.zig" },
2222 .optimize = optimize,
2323 });
24 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c11"});
24 exe.addCSourceFile(.{ .file = .{ .path = "test.c" }, .flags = &[_][]const u8{"-std=c11"} });
2525 exe.linkLibC();
2626
2727 const run_cmd = b.addRunArtifact(exe);
test/standalone/mix_o_files/build.zig+4-1
......@@ -19,7 +19,10 @@ pub fn build(b: *std.Build) void {
1919 .optimize = optimize,
2020 .target = target,
2121 });
22 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
22 exe.addCSourceFile(.{
23 .file = .{ .path = "test.c" },
24 .flags = &[_][]const u8{"-std=c99"},
25 });
2326 exe.addObject(obj);
2427 exe.linkSystemLibrary("c");
2528
test/standalone/shared_library/build.zig+4-1
......@@ -19,7 +19,10 @@ pub fn build(b: *std.Build) void {
1919 .target = target,
2020 .optimize = optimize,
2121 });
22 exe.addCSourceFile("test.c", &[_][]const u8{"-std=c99"});
22 exe.addCSourceFile(.{
23 .file = .{ .path = "test.c" },
24 .flags = &[_][]const u8{"-std=c99"},
25 });
2326 exe.linkLibrary(lib);
2427 exe.linkSystemLibrary("c");
2528
test/standalone/stack_iterator/build.zig+4-1
......@@ -74,7 +74,10 @@ pub fn build(b: *std.Build) void {
7474 if (target.isWindows()) c_shared_lib.defineCMacro("LIB_API", "__declspec(dllexport)");
7575
7676 c_shared_lib.strip = false;
77 c_shared_lib.addCSourceFile("shared_lib.c", &.{"-fomit-frame-pointer"});
77 c_shared_lib.addCSourceFile(.{
78 .file = .{ .path = "shared_lib.c" },
79 .flags = &.{"-fomit-frame-pointer"},
80 });
7881 c_shared_lib.linkLibC();
7982
8083 const exe = b.addExecutable(.{
test/standalone/static_c_lib/build.zig+3-3
......@@ -11,15 +11,15 @@ pub fn build(b: *std.Build) void {
1111 .optimize = optimize,
1212 .target = .{},
1313 });
14 foo.addCSourceFile("foo.c", &[_][]const u8{});
15 foo.addIncludePath(".");
14 foo.addCSourceFile(.{ .file = .{ .path = "foo.c" }, .flags = &[_][]const u8{} });
15 foo.addIncludePath(.{ .path = "." });
1616
1717 const test_exe = b.addTest(.{
1818 .root_source_file = .{ .path = "foo.zig" },
1919 .optimize = optimize,
2020 });
2121 test_exe.linkLibrary(foo);
22 test_exe.addIncludePath(".");
22 test_exe.addIncludePath(.{ .path = "." });
2323
2424 test_step.dependOn(&b.addRunArtifact(test_exe).step);
2525}
test/standalone/strip_empty_loop/build.zig+4
......@@ -14,5 +14,9 @@ pub fn build(b: *std.Build) void {
1414 .target = target,
1515 });
1616 main.strip = true;
17
18 // TODO: actually check the output
19 _ = main.getEmittedBin();
20
1721 test_step.dependOn(&main.step);
1822}
test/standalone/use_alias/build.zig+1-1
......@@ -10,7 +10,7 @@ pub fn build(b: *std.Build) void {
1010 .root_source_file = .{ .path = "main.zig" },
1111 .optimize = optimize,
1212 });
13 main.addIncludePath(".");
13 main.addIncludePath(.{ .path = "." });
1414
1515 test_step.dependOn(&b.addRunArtifact(main).step);
1616}
test/tests.zig+14-9
......@@ -588,6 +588,8 @@ pub fn addStandaloneTests(
588588 });
589589 if (case.link_libc) exe.linkLibC();
590590
591 _ = exe.getEmittedBin();
592
591593 step.dependOn(&exe.step);
592594 }
593595
......@@ -759,7 +761,7 @@ pub fn addCliTests(b: *std.Build) *Step {
759761 "-fno-emit-bin", "-fno-emit-h",
760762 "-fstrip", "-OReleaseFast",
761763 });
762 run.addFileSourceArg(writefile.files.items[0].getFileSource());
764 run.addFileArg(writefile.files.items[0].getPath());
763765 const example_s = run.addPrefixedOutputFileArg("-femit-asm=", "example.s");
764766
765767 const checkfile = b.addCheckFile(example_s, .{
......@@ -1006,6 +1008,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
10061008 .single_threaded = test_target.single_threaded,
10071009 .use_llvm = test_target.use_llvm,
10081010 .use_lld = test_target.use_lld,
1011 .zig_lib_dir = .{ .path = "lib" },
10091012 });
10101013 const single_threaded_suffix = if (test_target.single_threaded == true) "-single" else "";
10111014 const backend_suffix = if (test_target.use_llvm == true)
......@@ -1017,8 +1020,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
10171020 else
10181021 "";
10191022
1020 these_tests.overrideZigLibDir("lib");
1021 these_tests.addIncludePath("test");
1023 these_tests.addIncludePath(.{ .path = "test" });
10221024
10231025 const qualified_name = b.fmt("{s}-{s}-{s}{s}{s}{s}", .{
10241026 options.name,
......@@ -1037,11 +1039,11 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
10371039 .name = qualified_name,
10381040 .link_libc = test_target.link_libc,
10391041 .target = altered_target,
1042 .zig_lib_dir = .{ .path = "lib" },
10401043 });
1041 compile_c.overrideZigLibDir("lib");
1042 compile_c.addCSourceFileSource(.{
1043 .source = these_tests.getOutputSource(),
1044 .args = &.{
1044 compile_c.addCSourceFile(.{
1045 .file = these_tests.getEmittedBin(),
1046 .flags = &.{
10451047 // TODO output -std=c89 compatible C code
10461048 "-std=c99",
10471049 "-pedantic",
......@@ -1058,7 +1060,7 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
10581060 "-Wno-absolute-value",
10591061 },
10601062 });
1061 compile_c.addIncludePath("lib"); // for zig.h
1063 compile_c.addIncludePath(.{ .path = "lib" }); // for zig.h
10621064 if (test_target.target.getOsTag() == .windows) {
10631065 if (true) {
10641066 // Unfortunately this requires about 8G of RAM for clang to compile
......@@ -1131,7 +1133,10 @@ pub fn addCAbiTests(b: *std.Build, skip_non_native: bool, skip_release: bool) *S
11311133 test_step.target_info.dynamic_linker.max_byte = null;
11321134 }
11331135 test_step.linkLibC();
1134 test_step.addCSourceFile("test/c_abi/cfuncs.c", &.{"-std=c99"});
1136 test_step.addCSourceFile(.{
1137 .file = .{ .path = "test/c_abi/cfuncs.c" },
1138 .flags = &.{"-std=c99"},
1139 });
11351140
11361141 // This test is intentionally trying to check if the external ABI is
11371142 // done properly. LTO would be a hindrance to this.
tools/docgen.zig created+2278
......@@ -0,0 +1,2278 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const io = std.io;
4const fs = std.fs;
5const process = std.process;
6const ChildProcess = std.ChildProcess;
7const Progress = std.Progress;
8const print = std.debug.print;
9const mem = std.mem;
10const testing = std.testing;
11const Allocator = std.mem.Allocator;
12
13const max_doc_file_size = 10 * 1024 * 1024;
14
15const exe_ext = @as(std.zig.CrossTarget, .{}).exeFileExt();
16const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch);
17const tmp_dir_name = "docgen_tmp";
18const test_out_path = tmp_dir_name ++ fs.path.sep_str ++ "test" ++ exe_ext;
19
20const usage =
21 \\Usage: docgen [--zig] [--skip-code-tests] input output"
22 \\
23 \\ Generates an HTML document from a docgen template.
24 \\
25 \\Options:
26 \\ -h, --help Print this help and exit
27 \\ --skip-code-tests Skip the doctests
28 \\
29;
30
31fn fatal(comptime format: []const u8, args: anytype) noreturn {
32 const stderr = io.getStdErr().writer();
33
34 stderr.print("error: " ++ format ++ "\n", args) catch {};
35 process.exit(1);
36}
37
38pub fn main() !void {
39 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
40 defer arena.deinit();
41
42 const allocator = arena.allocator();
43
44 var args_it = try process.argsWithAllocator(allocator);
45 if (!args_it.skip()) @panic("expected self arg");
46
47 var zig_exe: []const u8 = "zig";
48 var opt_zig_lib_dir: ?[]const u8 = null;
49 var do_code_tests = true;
50 var files = [_][]const u8{ "", "" };
51
52 var i: usize = 0;
53 while (args_it.next()) |arg| {
54 if (mem.startsWith(u8, arg, "-")) {
55 if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) {
56 const stdout = io.getStdOut().writer();
57 try stdout.writeAll(usage);
58 process.exit(0);
59 } else if (mem.eql(u8, arg, "--zig")) {
60 if (args_it.next()) |param| {
61 zig_exe = param;
62 } else {
63 fatal("expected parameter after --zig", .{});
64 }
65 } else if (mem.eql(u8, arg, "--zig-lib-dir")) {
66 if (args_it.next()) |param| {
67 // Convert relative to absolute because this will be passed
68 // to a child process with a different cwd.
69 opt_zig_lib_dir = try fs.realpathAlloc(allocator, param);
70 } else {
71 fatal("expected parameter after --zig-lib-dir", .{});
72 }
73 } else if (mem.eql(u8, arg, "--skip-code-tests")) {
74 do_code_tests = false;
75 } else {
76 fatal("unrecognized option: '{s}'", .{arg});
77 }
78 } else {
79 if (i > 1) {
80 fatal("too many arguments", .{});
81 }
82 files[i] = arg;
83 i += 1;
84 }
85 }
86 if (i < 2) {
87 fatal("not enough arguments", .{});
88 }
89
90 var in_file = try fs.cwd().openFile(files[0], .{ .mode = .read_only });
91 defer in_file.close();
92
93 var out_file = try fs.cwd().createFile(files[1], .{});
94 defer out_file.close();
95
96 const input_file_bytes = try in_file.reader().readAllAlloc(allocator, max_doc_file_size);
97
98 var buffered_writer = io.bufferedWriter(out_file.writer());
99
100 var tokenizer = Tokenizer.init(files[0], input_file_bytes);
101 var toc = try genToc(allocator, &tokenizer);
102
103 try fs.cwd().makePath(tmp_dir_name);
104 defer fs.cwd().deleteTree(tmp_dir_name) catch {};
105
106 try genHtml(allocator, &tokenizer, &toc, buffered_writer.writer(), zig_exe, opt_zig_lib_dir, do_code_tests);
107 try buffered_writer.flush();
108}
109
110const Token = struct {
111 id: Id,
112 start: usize,
113 end: usize,
114
115 const Id = enum {
116 invalid,
117 content,
118 bracket_open,
119 tag_content,
120 separator,
121 bracket_close,
122 eof,
123 };
124};
125
126const Tokenizer = struct {
127 buffer: []const u8,
128 index: usize,
129 state: State,
130 source_file_name: []const u8,
131 code_node_count: usize,
132
133 const State = enum {
134 start,
135 l_bracket,
136 hash,
137 tag_name,
138 eof,
139 };
140
141 fn init(source_file_name: []const u8, buffer: []const u8) Tokenizer {
142 return Tokenizer{
143 .buffer = buffer,
144 .index = 0,
145 .state = .start,
146 .source_file_name = source_file_name,
147 .code_node_count = 0,
148 };
149 }
150
151 fn next(self: *Tokenizer) Token {
152 var result = Token{
153 .id = .eof,
154 .start = self.index,
155 .end = undefined,
156 };
157 while (self.index < self.buffer.len) : (self.index += 1) {
158 const c = self.buffer[self.index];
159 switch (self.state) {
160 .start => switch (c) {
161 '{' => {
162 self.state = .l_bracket;
163 },
164 else => {
165 result.id = .content;
166 },
167 },
168 .l_bracket => switch (c) {
169 '#' => {
170 if (result.id != .eof) {
171 self.index -= 1;
172 self.state = .start;
173 break;
174 } else {
175 result.id = .bracket_open;
176 self.index += 1;
177 self.state = .tag_name;
178 break;
179 }
180 },
181 else => {
182 result.id = .content;
183 self.state = .start;
184 },
185 },
186 .tag_name => switch (c) {
187 '|' => {
188 if (result.id != .eof) {
189 break;
190 } else {
191 result.id = .separator;
192 self.index += 1;
193 break;
194 }
195 },
196 '#' => {
197 self.state = .hash;
198 },
199 else => {
200 result.id = .tag_content;
201 },
202 },
203 .hash => switch (c) {
204 '}' => {
205 if (result.id != .eof) {
206 self.index -= 1;
207 self.state = .tag_name;
208 break;
209 } else {
210 result.id = .bracket_close;
211 self.index += 1;
212 self.state = .start;
213 break;
214 }
215 },
216 else => {
217 result.id = .tag_content;
218 self.state = .tag_name;
219 },
220 },
221 .eof => unreachable,
222 }
223 } else {
224 switch (self.state) {
225 .start, .l_bracket, .eof => {},
226 else => {
227 result.id = .invalid;
228 },
229 }
230 self.state = .eof;
231 }
232 result.end = self.index;
233 return result;
234 }
235
236 const Location = struct {
237 line: usize,
238 column: usize,
239 line_start: usize,
240 line_end: usize,
241 };
242
243 fn getTokenLocation(self: *Tokenizer, token: Token) Location {
244 var loc = Location{
245 .line = 0,
246 .column = 0,
247 .line_start = 0,
248 .line_end = 0,
249 };
250 for (self.buffer, 0..) |c, i| {
251 if (i == token.start) {
252 loc.line_end = i;
253 while (loc.line_end < self.buffer.len and self.buffer[loc.line_end] != '\n') : (loc.line_end += 1) {}
254 return loc;
255 }
256 if (c == '\n') {
257 loc.line += 1;
258 loc.column = 0;
259 loc.line_start = i + 1;
260 } else {
261 loc.column += 1;
262 }
263 }
264 return loc;
265 }
266};
267
268fn parseError(tokenizer: *Tokenizer, token: Token, comptime fmt: []const u8, args: anytype) anyerror {
269 const loc = tokenizer.getTokenLocation(token);
270 const args_prefix = .{ tokenizer.source_file_name, loc.line + 1, loc.column + 1 };
271 print("{s}:{d}:{d}: error: " ++ fmt ++ "\n", args_prefix ++ args);
272 if (loc.line_start <= loc.line_end) {
273 print("{s}\n", .{tokenizer.buffer[loc.line_start..loc.line_end]});
274 {
275 var i: usize = 0;
276 while (i < loc.column) : (i += 1) {
277 print(" ", .{});
278 }
279 }
280 {
281 const caret_count = @min(token.end, loc.line_end) - token.start;
282 var i: usize = 0;
283 while (i < caret_count) : (i += 1) {
284 print("~", .{});
285 }
286 }
287 print("\n", .{});
288 }
289 return error.ParseError;
290}
291
292fn assertToken(tokenizer: *Tokenizer, token: Token, id: Token.Id) !void {
293 if (token.id != id) {
294 return parseError(tokenizer, token, "expected {s}, found {s}", .{ @tagName(id), @tagName(token.id) });
295 }
296}
297
298fn eatToken(tokenizer: *Tokenizer, id: Token.Id) !Token {
299 const token = tokenizer.next();
300 try assertToken(tokenizer, token, id);
301 return token;
302}
303
304const HeaderOpen = struct {
305 name: []const u8,
306 url: []const u8,
307 n: usize,
308};
309
310const SeeAlsoItem = struct {
311 name: []const u8,
312 token: Token,
313};
314
315const ExpectedOutcome = enum {
316 succeed,
317 fail,
318 build_fail,
319};
320
321const Code = struct {
322 id: Id,
323 name: []const u8,
324 source_token: Token,
325 just_check_syntax: bool,
326 mode: std.builtin.Mode,
327 link_objects: []const []const u8,
328 target_str: ?[]const u8,
329 link_libc: bool,
330 link_mode: ?std.builtin.LinkMode,
331 disable_cache: bool,
332 verbose_cimport: bool,
333 additional_options: []const []const u8,
334
335 const Id = union(enum) {
336 @"test",
337 test_error: []const u8,
338 test_safety: []const u8,
339 exe: ExpectedOutcome,
340 obj: ?[]const u8,
341 lib,
342 };
343};
344
345const Link = struct {
346 url: []const u8,
347 name: []const u8,
348 token: Token,
349};
350
351const SyntaxBlock = struct {
352 source_type: SourceType,
353 name: []const u8,
354 source_token: Token,
355
356 const SourceType = enum {
357 zig,
358 c,
359 peg,
360 javascript,
361 };
362};
363
364const Node = union(enum) {
365 Content: []const u8,
366 Nav,
367 Builtin: Token,
368 HeaderOpen: HeaderOpen,
369 SeeAlso: []const SeeAlsoItem,
370 Code: Code,
371 Link: Link,
372 InlineSyntax: Token,
373 Shell: Token,
374 SyntaxBlock: SyntaxBlock,
375};
376
377const Toc = struct {
378 nodes: []Node,
379 toc: []u8,
380 urls: std.StringHashMap(Token),
381};
382
383const Action = enum {
384 open,
385 close,
386};
387
388fn genToc(allocator: Allocator, tokenizer: *Tokenizer) !Toc {
389 var urls = std.StringHashMap(Token).init(allocator);
390 errdefer urls.deinit();
391
392 var header_stack_size: usize = 0;
393 var last_action: Action = .open;
394 var last_columns: ?u8 = null;
395
396 var toc_buf = std.ArrayList(u8).init(allocator);
397 defer toc_buf.deinit();
398
399 var toc = toc_buf.writer();
400
401 var nodes = std.ArrayList(Node).init(allocator);
402 defer nodes.deinit();
403
404 try toc.writeByte('\n');
405
406 while (true) {
407 const token = tokenizer.next();
408 switch (token.id) {
409 .eof => {
410 if (header_stack_size != 0) {
411 return parseError(tokenizer, token, "unbalanced headers", .{});
412 }
413 try toc.writeAll(" </ul>\n");
414 break;
415 },
416 .content => {
417 try nodes.append(Node{ .Content = tokenizer.buffer[token.start..token.end] });
418 },
419 .bracket_open => {
420 const tag_token = try eatToken(tokenizer, .tag_content);
421 const tag_name = tokenizer.buffer[tag_token.start..tag_token.end];
422
423 if (mem.eql(u8, tag_name, "nav")) {
424 _ = try eatToken(tokenizer, .bracket_close);
425
426 try nodes.append(Node.Nav);
427 } else if (mem.eql(u8, tag_name, "builtin")) {
428 _ = try eatToken(tokenizer, .bracket_close);
429 try nodes.append(Node{ .Builtin = tag_token });
430 } else if (mem.eql(u8, tag_name, "header_open")) {
431 _ = try eatToken(tokenizer, .separator);
432 const content_token = try eatToken(tokenizer, .tag_content);
433 const content = tokenizer.buffer[content_token.start..content_token.end];
434 var columns: ?u8 = null;
435 while (true) {
436 const bracket_tok = tokenizer.next();
437 switch (bracket_tok.id) {
438 .bracket_close => break,
439 .separator => continue,
440 .tag_content => {
441 const param = tokenizer.buffer[bracket_tok.start..bracket_tok.end];
442 if (mem.eql(u8, param, "2col")) {
443 columns = 2;
444 } else {
445 return parseError(
446 tokenizer,
447 bracket_tok,
448 "unrecognized header_open param: {s}",
449 .{param},
450 );
451 }
452 },
453 else => return parseError(tokenizer, bracket_tok, "invalid header_open token", .{}),
454 }
455 }
456
457 header_stack_size += 1;
458
459 const urlized = try urlize(allocator, content);
460 try nodes.append(Node{
461 .HeaderOpen = HeaderOpen{
462 .name = content,
463 .url = urlized,
464 .n = header_stack_size + 1, // highest-level section headers start at h2
465 },
466 });
467 if (try urls.fetchPut(urlized, tag_token)) |kv| {
468 parseError(tokenizer, tag_token, "duplicate header url: #{s}", .{urlized}) catch {};
469 parseError(tokenizer, kv.value, "other tag here", .{}) catch {};
470 return error.ParseError;
471 }
472 if (last_action == .open) {
473 try toc.writeByte('\n');
474 try toc.writeByteNTimes(' ', header_stack_size * 4);
475 if (last_columns) |n| {
476 try toc.print("<ul style=\"columns: {}\">\n", .{n});
477 } else {
478 try toc.writeAll("<ul>\n");
479 }
480 } else {
481 last_action = .open;
482 }
483 last_columns = columns;
484 try toc.writeByteNTimes(' ', 4 + header_stack_size * 4);
485 try toc.print("<li><a id=\"toc-{s}\" href=\"#{s}\">{s}</a>", .{ urlized, urlized, content });
486 } else if (mem.eql(u8, tag_name, "header_close")) {
487 if (header_stack_size == 0) {
488 return parseError(tokenizer, tag_token, "unbalanced close header", .{});
489 }
490 header_stack_size -= 1;
491 _ = try eatToken(tokenizer, .bracket_close);
492
493 if (last_action == .close) {
494 try toc.writeByteNTimes(' ', 8 + header_stack_size * 4);
495 try toc.writeAll("</ul></li>\n");
496 } else {
497 try toc.writeAll("</li>\n");
498 last_action = .close;
499 }
500 } else if (mem.eql(u8, tag_name, "see_also")) {
501 var list = std.ArrayList(SeeAlsoItem).init(allocator);
502 errdefer list.deinit();
503
504 while (true) {
505 const see_also_tok = tokenizer.next();
506 switch (see_also_tok.id) {
507 .tag_content => {
508 const content = tokenizer.buffer[see_also_tok.start..see_also_tok.end];
509 try list.append(SeeAlsoItem{
510 .name = content,
511 .token = see_also_tok,
512 });
513 },
514 .separator => {},
515 .bracket_close => {
516 try nodes.append(Node{ .SeeAlso = try list.toOwnedSlice() });
517 break;
518 },
519 else => return parseError(tokenizer, see_also_tok, "invalid see_also token", .{}),
520 }
521 }
522 } else if (mem.eql(u8, tag_name, "link")) {
523 _ = try eatToken(tokenizer, .separator);
524 const name_tok = try eatToken(tokenizer, .tag_content);
525 const name = tokenizer.buffer[name_tok.start..name_tok.end];
526
527 const url_name = blk: {
528 const tok = tokenizer.next();
529 switch (tok.id) {
530 .bracket_close => break :blk name,
531 .separator => {
532 const explicit_text = try eatToken(tokenizer, .tag_content);
533 _ = try eatToken(tokenizer, .bracket_close);
534 break :blk tokenizer.buffer[explicit_text.start..explicit_text.end];
535 },
536 else => return parseError(tokenizer, tok, "invalid link token", .{}),
537 }
538 };
539
540 try nodes.append(Node{
541 .Link = Link{
542 .url = try urlize(allocator, url_name),
543 .name = name,
544 .token = name_tok,
545 },
546 });
547 } else if (mem.eql(u8, tag_name, "code_begin")) {
548 _ = try eatToken(tokenizer, .separator);
549 const code_kind_tok = try eatToken(tokenizer, .tag_content);
550 _ = try eatToken(tokenizer, .separator);
551 const name_tok = try eatToken(tokenizer, .tag_content);
552 const name = tokenizer.buffer[name_tok.start..name_tok.end];
553 var error_str: []const u8 = "";
554 const maybe_sep = tokenizer.next();
555 switch (maybe_sep.id) {
556 .separator => {
557 const error_tok = try eatToken(tokenizer, .tag_content);
558 error_str = tokenizer.buffer[error_tok.start..error_tok.end];
559 _ = try eatToken(tokenizer, .bracket_close);
560 },
561 .bracket_close => {},
562 else => return parseError(tokenizer, token, "invalid token", .{}),
563 }
564 const code_kind_str = tokenizer.buffer[code_kind_tok.start..code_kind_tok.end];
565 var code_kind_id: Code.Id = undefined;
566 var just_check_syntax = false;
567 if (mem.eql(u8, code_kind_str, "exe")) {
568 code_kind_id = Code.Id{ .exe = .succeed };
569 } else if (mem.eql(u8, code_kind_str, "exe_err")) {
570 code_kind_id = Code.Id{ .exe = .fail };
571 } else if (mem.eql(u8, code_kind_str, "exe_build_err")) {
572 code_kind_id = Code.Id{ .exe = .build_fail };
573 } else if (mem.eql(u8, code_kind_str, "test")) {
574 code_kind_id = .@"test";
575 } else if (mem.eql(u8, code_kind_str, "test_err")) {
576 code_kind_id = Code.Id{ .test_error = error_str };
577 } else if (mem.eql(u8, code_kind_str, "test_safety")) {
578 code_kind_id = Code.Id{ .test_safety = error_str };
579 } else if (mem.eql(u8, code_kind_str, "obj")) {
580 code_kind_id = Code.Id{ .obj = null };
581 } else if (mem.eql(u8, code_kind_str, "obj_err")) {
582 code_kind_id = Code.Id{ .obj = error_str };
583 } else if (mem.eql(u8, code_kind_str, "lib")) {
584 code_kind_id = Code.Id.lib;
585 } else if (mem.eql(u8, code_kind_str, "syntax")) {
586 code_kind_id = Code.Id{ .obj = null };
587 just_check_syntax = true;
588 } else {
589 return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {s}", .{code_kind_str});
590 }
591
592 var mode: std.builtin.Mode = .Debug;
593 var link_objects = std.ArrayList([]const u8).init(allocator);
594 defer link_objects.deinit();
595 var target_str: ?[]const u8 = null;
596 var link_libc = false;
597 var link_mode: ?std.builtin.LinkMode = null;
598 var disable_cache = false;
599 var verbose_cimport = false;
600 var additional_options = std.ArrayList([]const u8).init(allocator);
601 defer additional_options.deinit();
602
603 const source_token = while (true) {
604 const content_tok = try eatToken(tokenizer, .content);
605 _ = try eatToken(tokenizer, .bracket_open);
606 const end_code_tag = try eatToken(tokenizer, .tag_content);
607 const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end];
608 if (mem.eql(u8, end_tag_name, "code_release_fast")) {
609 mode = .ReleaseFast;
610 } else if (mem.eql(u8, end_tag_name, "code_release_safe")) {
611 mode = .ReleaseSafe;
612 } else if (mem.eql(u8, end_tag_name, "code_disable_cache")) {
613 disable_cache = true;
614 } else if (mem.eql(u8, end_tag_name, "code_verbose_cimport")) {
615 verbose_cimport = true;
616 } else if (mem.eql(u8, end_tag_name, "code_link_object")) {
617 _ = try eatToken(tokenizer, .separator);
618 const obj_tok = try eatToken(tokenizer, .tag_content);
619 try link_objects.append(tokenizer.buffer[obj_tok.start..obj_tok.end]);
620 } else if (mem.eql(u8, end_tag_name, "target_windows")) {
621 target_str = "x86_64-windows";
622 } else if (mem.eql(u8, end_tag_name, "target_linux_x86_64")) {
623 target_str = "x86_64-linux";
624 } else if (mem.eql(u8, end_tag_name, "target_linux_riscv64")) {
625 target_str = "riscv64-linux";
626 } else if (mem.eql(u8, end_tag_name, "target_wasm")) {
627 target_str = "wasm32-freestanding";
628 } else if (mem.eql(u8, end_tag_name, "target_wasi")) {
629 target_str = "wasm32-wasi";
630 } else if (mem.eql(u8, end_tag_name, "link_libc")) {
631 link_libc = true;
632 } else if (mem.eql(u8, end_tag_name, "link_mode_dynamic")) {
633 link_mode = .Dynamic;
634 } else if (mem.eql(u8, end_tag_name, "additonal_option")) {
635 _ = try eatToken(tokenizer, .separator);
636 const option = try eatToken(tokenizer, .tag_content);
637 try additional_options.append(tokenizer.buffer[option.start..option.end]);
638 } else if (mem.eql(u8, end_tag_name, "code_end")) {
639 _ = try eatToken(tokenizer, .bracket_close);
640 break content_tok;
641 } else {
642 return parseError(
643 tokenizer,
644 end_code_tag,
645 "invalid token inside code_begin: {s}",
646 .{end_tag_name},
647 );
648 }
649 _ = try eatToken(tokenizer, .bracket_close);
650 } else unreachable; // TODO issue #707
651 try nodes.append(Node{
652 .Code = Code{
653 .id = code_kind_id,
654 .name = name,
655 .source_token = source_token,
656 .just_check_syntax = just_check_syntax,
657 .mode = mode,
658 .link_objects = try link_objects.toOwnedSlice(),
659 .target_str = target_str,
660 .link_libc = link_libc,
661 .link_mode = link_mode,
662 .disable_cache = disable_cache,
663 .verbose_cimport = verbose_cimport,
664 .additional_options = try additional_options.toOwnedSlice(),
665 },
666 });
667 tokenizer.code_node_count += 1;
668 } else if (mem.eql(u8, tag_name, "syntax")) {
669 _ = try eatToken(tokenizer, .bracket_close);
670 const content_tok = try eatToken(tokenizer, .content);
671 _ = try eatToken(tokenizer, .bracket_open);
672 const end_syntax_tag = try eatToken(tokenizer, .tag_content);
673 const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end];
674 if (!mem.eql(u8, end_tag_name, "endsyntax")) {
675 return parseError(
676 tokenizer,
677 end_syntax_tag,
678 "invalid token inside syntax: {s}",
679 .{end_tag_name},
680 );
681 }
682 _ = try eatToken(tokenizer, .bracket_close);
683 try nodes.append(Node{ .InlineSyntax = content_tok });
684 } else if (mem.eql(u8, tag_name, "shell_samp")) {
685 _ = try eatToken(tokenizer, .bracket_close);
686 const content_tok = try eatToken(tokenizer, .content);
687 _ = try eatToken(tokenizer, .bracket_open);
688 const end_syntax_tag = try eatToken(tokenizer, .tag_content);
689 const end_tag_name = tokenizer.buffer[end_syntax_tag.start..end_syntax_tag.end];
690 if (!mem.eql(u8, end_tag_name, "end_shell_samp")) {
691 return parseError(
692 tokenizer,
693 end_syntax_tag,
694 "invalid token inside syntax: {s}",
695 .{end_tag_name},
696 );
697 }
698 _ = try eatToken(tokenizer, .bracket_close);
699 try nodes.append(Node{ .Shell = content_tok });
700 } else if (mem.eql(u8, tag_name, "syntax_block")) {
701 _ = try eatToken(tokenizer, .separator);
702 const source_type_tok = try eatToken(tokenizer, .tag_content);
703 var name: []const u8 = "sample_code";
704 const maybe_sep = tokenizer.next();
705 switch (maybe_sep.id) {
706 .separator => {
707 const name_tok = try eatToken(tokenizer, .tag_content);
708 name = tokenizer.buffer[name_tok.start..name_tok.end];
709 _ = try eatToken(tokenizer, .bracket_close);
710 },
711 .bracket_close => {},
712 else => return parseError(tokenizer, token, "invalid token", .{}),
713 }
714 const source_type_str = tokenizer.buffer[source_type_tok.start..source_type_tok.end];
715 var source_type: SyntaxBlock.SourceType = undefined;
716 if (mem.eql(u8, source_type_str, "zig")) {
717 source_type = SyntaxBlock.SourceType.zig;
718 } else if (mem.eql(u8, source_type_str, "c")) {
719 source_type = SyntaxBlock.SourceType.c;
720 } else if (mem.eql(u8, source_type_str, "peg")) {
721 source_type = SyntaxBlock.SourceType.peg;
722 } else if (mem.eql(u8, source_type_str, "javascript")) {
723 source_type = SyntaxBlock.SourceType.javascript;
724 } else {
725 return parseError(tokenizer, source_type_tok, "unrecognized code kind: {s}", .{source_type_str});
726 }
727 const source_token = while (true) {
728 const content_tok = try eatToken(tokenizer, .content);
729 _ = try eatToken(tokenizer, .bracket_open);
730 const end_code_tag = try eatToken(tokenizer, .tag_content);
731 const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end];
732 if (mem.eql(u8, end_tag_name, "end_syntax_block")) {
733 _ = try eatToken(tokenizer, .bracket_close);
734 break content_tok;
735 } else {
736 return parseError(
737 tokenizer,
738 end_code_tag,
739 "invalid token inside code_begin: {s}",
740 .{end_tag_name},
741 );
742 }
743 _ = try eatToken(tokenizer, .bracket_close);
744 };
745 try nodes.append(Node{ .SyntaxBlock = SyntaxBlock{ .source_type = source_type, .name = name, .source_token = source_token } });
746 } else {
747 return parseError(tokenizer, tag_token, "unrecognized tag name: {s}", .{tag_name});
748 }
749 },
750 else => return parseError(tokenizer, token, "invalid token", .{}),
751 }
752 }
753
754 return Toc{
755 .nodes = try nodes.toOwnedSlice(),
756 .toc = try toc_buf.toOwnedSlice(),
757 .urls = urls,
758 };
759}
760
761fn urlize(allocator: Allocator, input: []const u8) ![]u8 {
762 var buf = std.ArrayList(u8).init(allocator);
763 defer buf.deinit();
764
765 const out = buf.writer();
766 for (input) |c| {
767 switch (c) {
768 'a'...'z', 'A'...'Z', '_', '-', '0'...'9' => {
769 try out.writeByte(c);
770 },
771 ' ' => {
772 try out.writeByte('-');
773 },
774 else => {},
775 }
776 }
777 return try buf.toOwnedSlice();
778}
779
780fn escapeHtml(allocator: Allocator, input: []const u8) ![]u8 {
781 var buf = std.ArrayList(u8).init(allocator);
782 defer buf.deinit();
783
784 const out = buf.writer();
785 try writeEscaped(out, input);
786 return try buf.toOwnedSlice();
787}
788
789fn writeEscaped(out: anytype, input: []const u8) !void {
790 for (input) |c| {
791 try switch (c) {
792 '&' => out.writeAll("&amp;"),
793 '<' => out.writeAll("&lt;"),
794 '>' => out.writeAll("&gt;"),
795 '"' => out.writeAll("&quot;"),
796 else => out.writeByte(c),
797 };
798 }
799}
800
801// Returns true if number is in slice.
802fn in(slice: []const u8, number: u8) bool {
803 for (slice) |n| {
804 if (number == n) return true;
805 }
806 return false;
807}
808
809fn termColor(allocator: Allocator, input: []const u8) ![]u8 {
810 // The SRG sequences generates by the Zig compiler are in the format:
811 // ESC [ <foreground-color> ; <n> m
812 // or
813 // ESC [ <n> m
814 //
815 // where
816 // foreground-color is 31 (red), 32 (green), 36 (cyan)
817 // n is 0 (reset), 1 (bold), 2 (dim)
818 //
819 // Note that 37 (white) is currently not used by the compiler.
820 //
821 // See std.debug.TTY.Color.
822 const supported_sgr_colors = [_]u8{ 31, 32, 36 };
823 const supported_sgr_numbers = [_]u8{ 0, 1, 2 };
824
825 var buf = std.ArrayList(u8).init(allocator);
826 defer buf.deinit();
827
828 var out = buf.writer();
829 var sgr_param_start_index: usize = undefined;
830 var sgr_num: u8 = undefined;
831 var sgr_color: u8 = undefined;
832 var i: usize = 0;
833 var state: enum {
834 start,
835 escape,
836 lbracket,
837 number,
838 after_number,
839 arg,
840 arg_number,
841 expect_end,
842 } = .start;
843 var last_new_line: usize = 0;
844 var open_span_count: usize = 0;
845 while (i < input.len) : (i += 1) {
846 const c = input[i];
847 switch (state) {
848 .start => switch (c) {
849 '\x1b' => state = .escape,
850 '\n' => {
851 try out.writeByte(c);
852 last_new_line = buf.items.len;
853 },
854 else => try out.writeByte(c),
855 },
856 .escape => switch (c) {
857 '[' => state = .lbracket,
858 else => return error.UnsupportedEscape,
859 },
860 .lbracket => switch (c) {
861 '0'...'9' => {
862 sgr_param_start_index = i;
863 state = .number;
864 },
865 else => return error.UnsupportedEscape,
866 },
867 .number => switch (c) {
868 '0'...'9' => {},
869 else => {
870 sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10);
871 sgr_color = 0;
872 state = .after_number;
873 i -= 1;
874 },
875 },
876 .after_number => switch (c) {
877 ';' => state = .arg,
878 'D' => state = .start,
879 'K' => {
880 buf.items.len = last_new_line;
881 state = .start;
882 },
883 else => {
884 state = .expect_end;
885 i -= 1;
886 },
887 },
888 .arg => switch (c) {
889 '0'...'9' => {
890 sgr_param_start_index = i;
891 state = .arg_number;
892 },
893 else => return error.UnsupportedEscape,
894 },
895 .arg_number => switch (c) {
896 '0'...'9' => {},
897 else => {
898 // Keep the sequence consistent, foreground color first.
899 // 32;1m is equivalent to 1;32m, but the latter will
900 // generate an incorrect HTML class without notice.
901 sgr_color = sgr_num;
902 if (!in(&supported_sgr_colors, sgr_color)) return error.UnsupportedForegroundColor;
903
904 sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10);
905 if (!in(&supported_sgr_numbers, sgr_num)) return error.UnsupportedNumber;
906
907 state = .expect_end;
908 i -= 1;
909 },
910 },
911 .expect_end => switch (c) {
912 'm' => {
913 state = .start;
914 while (open_span_count != 0) : (open_span_count -= 1) {
915 try out.writeAll("</span>");
916 }
917 if (sgr_num == 0) {
918 if (sgr_color != 0) return error.UnsupportedColor;
919 continue;
920 }
921 if (sgr_color != 0) {
922 try out.print("<span class=\"sgr-{d}_{d}m\">", .{ sgr_color, sgr_num });
923 } else {
924 try out.print("<span class=\"sgr-{d}m\">", .{sgr_num});
925 }
926 open_span_count += 1;
927 },
928 else => return error.UnsupportedEscape,
929 },
930 }
931 }
932 return try buf.toOwnedSlice();
933}
934
935const builtin_types = [_][]const u8{
936 "f16", "f32", "f64", "f80", "f128",
937 "c_longdouble", "c_short", "c_ushort", "c_int", "c_uint",
938 "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_char",
939 "anyopaque", "void", "bool", "isize", "usize",
940 "noreturn", "type", "anyerror", "comptime_int", "comptime_float",
941};
942
943fn isType(name: []const u8) bool {
944 for (builtin_types) |t| {
945 if (mem.eql(u8, t, name))
946 return true;
947 }
948 return false;
949}
950
951const start_line = "<span class=\"line\">";
952const end_line = "</span>";
953
954fn writeEscapedLines(out: anytype, text: []const u8) !void {
955 for (text) |char| {
956 if (char == '\n') {
957 try out.writeAll(end_line);
958 try out.writeAll("\n");
959 try out.writeAll(start_line);
960 } else {
961 try writeEscaped(out, &[_]u8{char});
962 }
963 }
964}
965
966fn tokenizeAndPrintRaw(
967 allocator: Allocator,
968 docgen_tokenizer: *Tokenizer,
969 out: anytype,
970 source_token: Token,
971 raw_src: []const u8,
972) !void {
973 const src_non_terminated = mem.trim(u8, raw_src, " \n");
974 const src = try allocator.dupeZ(u8, src_non_terminated);
975
976 try out.writeAll("<code>" ++ start_line);
977 var tokenizer = std.zig.Tokenizer.init(src);
978 var index: usize = 0;
979 var next_tok_is_fn = false;
980 while (true) {
981 const prev_tok_was_fn = next_tok_is_fn;
982 next_tok_is_fn = false;
983
984 const token = tokenizer.next();
985 if (mem.indexOf(u8, src[index..token.loc.start], "//")) |comment_start_off| {
986 // render one comment
987 const comment_start = index + comment_start_off;
988 const comment_end_off = mem.indexOf(u8, src[comment_start..token.loc.start], "\n");
989 const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start;
990
991 try writeEscapedLines(out, src[index..comment_start]);
992 try out.writeAll("<span class=\"tok-comment\">");
993 try writeEscaped(out, src[comment_start..comment_end]);
994 try out.writeAll("</span>");
995 index = comment_end;
996 tokenizer.index = index;
997 continue;
998 }
999
1000 try writeEscapedLines(out, src[index..token.loc.start]);
1001 switch (token.tag) {
1002 .eof => break,
1003
1004 .keyword_addrspace,
1005 .keyword_align,
1006 .keyword_and,
1007 .keyword_asm,
1008 .keyword_async,
1009 .keyword_await,
1010 .keyword_break,
1011 .keyword_catch,
1012 .keyword_comptime,
1013 .keyword_const,
1014 .keyword_continue,
1015 .keyword_defer,
1016 .keyword_else,
1017 .keyword_enum,
1018 .keyword_errdefer,
1019 .keyword_error,
1020 .keyword_export,
1021 .keyword_extern,
1022 .keyword_for,
1023 .keyword_if,
1024 .keyword_inline,
1025 .keyword_noalias,
1026 .keyword_noinline,
1027 .keyword_nosuspend,
1028 .keyword_opaque,
1029 .keyword_or,
1030 .keyword_orelse,
1031 .keyword_packed,
1032 .keyword_anyframe,
1033 .keyword_pub,
1034 .keyword_resume,
1035 .keyword_return,
1036 .keyword_linksection,
1037 .keyword_callconv,
1038 .keyword_struct,
1039 .keyword_suspend,
1040 .keyword_switch,
1041 .keyword_test,
1042 .keyword_threadlocal,
1043 .keyword_try,
1044 .keyword_union,
1045 .keyword_unreachable,
1046 .keyword_usingnamespace,
1047 .keyword_var,
1048 .keyword_volatile,
1049 .keyword_allowzero,
1050 .keyword_while,
1051 .keyword_anytype,
1052 => {
1053 try out.writeAll("<span class=\"tok-kw\">");
1054 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1055 try out.writeAll("</span>");
1056 },
1057
1058 .keyword_fn => {
1059 try out.writeAll("<span class=\"tok-kw\">");
1060 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1061 try out.writeAll("</span>");
1062 next_tok_is_fn = true;
1063 },
1064
1065 .string_literal,
1066 .char_literal,
1067 => {
1068 try out.writeAll("<span class=\"tok-str\">");
1069 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1070 try out.writeAll("</span>");
1071 },
1072
1073 .multiline_string_literal_line => {
1074 if (src[token.loc.end - 1] == '\n') {
1075 try out.writeAll("<span class=\"tok-str\">");
1076 try writeEscaped(out, src[token.loc.start .. token.loc.end - 1]);
1077 try out.writeAll("</span>" ++ end_line ++ "\n" ++ start_line);
1078 } else {
1079 try out.writeAll("<span class=\"tok-str\">");
1080 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1081 try out.writeAll("</span>");
1082 }
1083 },
1084
1085 .builtin => {
1086 try out.writeAll("<span class=\"tok-builtin\">");
1087 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1088 try out.writeAll("</span>");
1089 },
1090
1091 .doc_comment,
1092 .container_doc_comment,
1093 => {
1094 try out.writeAll("<span class=\"tok-comment\">");
1095 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1096 try out.writeAll("</span>");
1097 },
1098
1099 .identifier => {
1100 const tok_bytes = src[token.loc.start..token.loc.end];
1101 if (mem.eql(u8, tok_bytes, "undefined") or
1102 mem.eql(u8, tok_bytes, "null") or
1103 mem.eql(u8, tok_bytes, "true") or
1104 mem.eql(u8, tok_bytes, "false"))
1105 {
1106 try out.writeAll("<span class=\"tok-null\">");
1107 try writeEscaped(out, tok_bytes);
1108 try out.writeAll("</span>");
1109 } else if (prev_tok_was_fn) {
1110 try out.writeAll("<span class=\"tok-fn\">");
1111 try writeEscaped(out, tok_bytes);
1112 try out.writeAll("</span>");
1113 } else {
1114 const is_int = blk: {
1115 if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u')
1116 break :blk false;
1117 var i = token.loc.start + 1;
1118 if (i == token.loc.end)
1119 break :blk false;
1120 while (i != token.loc.end) : (i += 1) {
1121 if (src[i] < '0' or src[i] > '9')
1122 break :blk false;
1123 }
1124 break :blk true;
1125 };
1126 if (is_int or isType(tok_bytes)) {
1127 try out.writeAll("<span class=\"tok-type\">");
1128 try writeEscaped(out, tok_bytes);
1129 try out.writeAll("</span>");
1130 } else {
1131 try writeEscaped(out, tok_bytes);
1132 }
1133 }
1134 },
1135
1136 .number_literal => {
1137 try out.writeAll("<span class=\"tok-number\">");
1138 try writeEscaped(out, src[token.loc.start..token.loc.end]);
1139 try out.writeAll("</span>");
1140 },
1141
1142 .bang,
1143 .pipe,
1144 .pipe_pipe,
1145 .pipe_equal,
1146 .equal,
1147 .equal_equal,
1148 .equal_angle_bracket_right,
1149 .bang_equal,
1150 .l_paren,
1151 .r_paren,
1152 .semicolon,
1153 .percent,
1154 .percent_equal,
1155 .l_brace,
1156 .r_brace,
1157 .l_bracket,
1158 .r_bracket,
1159 .period,
1160 .period_asterisk,
1161 .ellipsis2,
1162 .ellipsis3,
1163 .caret,
1164 .caret_equal,
1165 .plus,
1166 .plus_plus,
1167 .plus_equal,
1168 .plus_percent,
1169 .plus_percent_equal,
1170 .plus_pipe,
1171 .plus_pipe_equal,
1172 .minus,
1173 .minus_equal,
1174 .minus_percent,
1175 .minus_percent_equal,
1176 .minus_pipe,
1177 .minus_pipe_equal,
1178 .asterisk,
1179 .asterisk_equal,
1180 .asterisk_asterisk,
1181 .asterisk_percent,
1182 .asterisk_percent_equal,
1183 .asterisk_pipe,
1184 .asterisk_pipe_equal,
1185 .arrow,
1186 .colon,
1187 .slash,
1188 .slash_equal,
1189 .comma,
1190 .ampersand,
1191 .ampersand_equal,
1192 .question_mark,
1193 .angle_bracket_left,
1194 .angle_bracket_left_equal,
1195 .angle_bracket_angle_bracket_left,
1196 .angle_bracket_angle_bracket_left_equal,
1197 .angle_bracket_angle_bracket_left_pipe,
1198 .angle_bracket_angle_bracket_left_pipe_equal,
1199 .angle_bracket_right,
1200 .angle_bracket_right_equal,
1201 .angle_bracket_angle_bracket_right,
1202 .angle_bracket_angle_bracket_right_equal,
1203 .tilde,
1204 => try writeEscaped(out, src[token.loc.start..token.loc.end]),
1205
1206 .invalid, .invalid_periodasterisks => return parseError(
1207 docgen_tokenizer,
1208 source_token,
1209 "syntax error",
1210 .{},
1211 ),
1212 }
1213 index = token.loc.end;
1214 }
1215 try out.writeAll(end_line ++ "</code>");
1216}
1217
1218fn tokenizeAndPrint(
1219 allocator: Allocator,
1220 docgen_tokenizer: *Tokenizer,
1221 out: anytype,
1222 source_token: Token,
1223) !void {
1224 const raw_src = docgen_tokenizer.buffer[source_token.start..source_token.end];
1225 return tokenizeAndPrintRaw(allocator, docgen_tokenizer, out, source_token, raw_src);
1226}
1227
1228fn printSourceBlock(allocator: Allocator, docgen_tokenizer: *Tokenizer, out: anytype, syntax_block: SyntaxBlock) !void {
1229 const source_type = @tagName(syntax_block.source_type);
1230
1231 try out.print("<figure><figcaption class=\"{s}-cap\"><cite class=\"file\">{s}</cite></figcaption><pre>", .{ source_type, syntax_block.name });
1232 switch (syntax_block.source_type) {
1233 .zig => try tokenizeAndPrint(allocator, docgen_tokenizer, out, syntax_block.source_token),
1234 else => {
1235 const raw_source = docgen_tokenizer.buffer[syntax_block.source_token.start..syntax_block.source_token.end];
1236 const trimmed_raw_source = mem.trim(u8, raw_source, " \n");
1237
1238 try out.writeAll("<code>" ++ start_line);
1239 try writeEscapedLines(out, trimmed_raw_source);
1240 try out.writeAll(end_line ++ "</code>");
1241 },
1242 }
1243 try out.writeAll("</pre></figure>");
1244}
1245
1246fn printShell(out: anytype, shell_content: []const u8, escape: bool) !void {
1247 const trimmed_shell_content = mem.trim(u8, shell_content, " \n");
1248 try out.writeAll("<figure><figcaption class=\"shell-cap\">Shell</figcaption><pre><samp>");
1249 var cmd_cont: bool = false;
1250 var iter = std.mem.splitScalar(u8, trimmed_shell_content, '\n');
1251 while (iter.next()) |orig_line| {
1252 const line = mem.trimRight(u8, orig_line, " ");
1253 if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] != '\\') {
1254 try out.writeAll("$ <kbd>");
1255 const s = std.mem.trimLeft(u8, line[1..], " ");
1256 if (escape) {
1257 try writeEscaped(out, s);
1258 } else {
1259 try out.writeAll(s);
1260 }
1261 try out.writeAll("</kbd>" ++ "\n");
1262 } else if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] == '\\') {
1263 try out.writeAll("$ <kbd>");
1264 const s = std.mem.trimLeft(u8, line[1..], " ");
1265 if (escape) {
1266 try writeEscaped(out, s);
1267 } else {
1268 try out.writeAll(s);
1269 }
1270 try out.writeAll("\n");
1271 cmd_cont = true;
1272 } else if (line.len > 0 and line[line.len - 1] != '\\' and cmd_cont) {
1273 if (escape) {
1274 try writeEscaped(out, line);
1275 } else {
1276 try out.writeAll(line);
1277 }
1278 try out.writeAll("</kbd>" ++ "\n");
1279 cmd_cont = false;
1280 } else {
1281 if (escape) {
1282 try writeEscaped(out, line);
1283 } else {
1284 try out.writeAll(line);
1285 }
1286 try out.writeAll("\n");
1287 }
1288 }
1289
1290 try out.writeAll("</samp></pre></figure>");
1291}
1292
1293// Override this to skip to later tests
1294const debug_start_line = 0;
1295
1296fn genHtml(
1297 allocator: Allocator,
1298 tokenizer: *Tokenizer,
1299 toc: *Toc,
1300 out: anytype,
1301 zig_exe: []const u8,
1302 opt_zig_lib_dir: ?[]const u8,
1303 do_code_tests: bool,
1304) !void {
1305 var progress = Progress{ .dont_print_on_dumb = true };
1306 const root_node = progress.start("Generating docgen examples", toc.nodes.len);
1307 defer root_node.end();
1308
1309 var env_map = try process.getEnvMap(allocator);
1310 try env_map.put("YES_COLOR", "1");
1311
1312 const host = try std.zig.system.NativeTargetInfo.detect(.{});
1313 const builtin_code = try getBuiltinCode(allocator, &env_map, zig_exe, opt_zig_lib_dir);
1314
1315 for (toc.nodes) |node| {
1316 defer root_node.completeOne();
1317 switch (node) {
1318 .Content => |data| {
1319 try out.writeAll(data);
1320 },
1321 .Link => |info| {
1322 if (!toc.urls.contains(info.url)) {
1323 return parseError(tokenizer, info.token, "url not found: {s}", .{info.url});
1324 }
1325 try out.print("<a href=\"#{s}\">{s}</a>", .{ info.url, info.name });
1326 },
1327 .Nav => {
1328 try out.writeAll(toc.toc);
1329 },
1330 .Builtin => |tok| {
1331 try out.writeAll("<figure><figcaption class=\"zig-cap\"><cite>@import(\"builtin\")</cite></figcaption><pre>");
1332 try tokenizeAndPrintRaw(allocator, tokenizer, out, tok, builtin_code);
1333 try out.writeAll("</pre></figure>");
1334 },
1335 .HeaderOpen => |info| {
1336 try out.print(
1337 "<h{d} id=\"{s}\"><a href=\"#toc-{s}\">{s}</a> <a class=\"hdr\" href=\"#{s}\">§</a></h{d}>\n",
1338 .{ info.n, info.url, info.url, info.name, info.url, info.n },
1339 );
1340 },
1341 .SeeAlso => |items| {
1342 try out.writeAll("<p>See also:</p><ul>\n");
1343 for (items) |item| {
1344 const url = try urlize(allocator, item.name);
1345 if (!toc.urls.contains(url)) {
1346 return parseError(tokenizer, item.token, "url not found: {s}", .{url});
1347 }
1348 try out.print("<li><a href=\"#{s}\">{s}</a></li>\n", .{ url, item.name });
1349 }
1350 try out.writeAll("</ul>\n");
1351 },
1352 .InlineSyntax => |content_tok| {
1353 try tokenizeAndPrint(allocator, tokenizer, out, content_tok);
1354 },
1355 .Shell => |content_tok| {
1356 const raw_shell_content = tokenizer.buffer[content_tok.start..content_tok.end];
1357 try printShell(out, raw_shell_content, true);
1358 },
1359 .SyntaxBlock => |syntax_block| {
1360 try printSourceBlock(allocator, tokenizer, out, syntax_block);
1361 },
1362 .Code => |code| {
1363 const name_plus_ext = try std.fmt.allocPrint(allocator, "{s}.zig", .{code.name});
1364 const syntax_block = SyntaxBlock{
1365 .source_type = .zig,
1366 .name = name_plus_ext,
1367 .source_token = code.source_token,
1368 };
1369
1370 try printSourceBlock(allocator, tokenizer, out, syntax_block);
1371
1372 if (!do_code_tests) {
1373 continue;
1374 }
1375
1376 if (debug_start_line > 0) {
1377 const loc = tokenizer.getTokenLocation(code.source_token);
1378 if (debug_start_line > loc.line) {
1379 continue;
1380 }
1381 }
1382
1383 const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end];
1384 const trimmed_raw_source = mem.trim(u8, raw_source, " \n");
1385 const tmp_source_file_name = try fs.path.join(
1386 allocator,
1387 &[_][]const u8{ tmp_dir_name, name_plus_ext },
1388 );
1389 try fs.cwd().writeFile(tmp_source_file_name, trimmed_raw_source);
1390
1391 var shell_buffer = std.ArrayList(u8).init(allocator);
1392 defer shell_buffer.deinit();
1393 var shell_out = shell_buffer.writer();
1394
1395 switch (code.id) {
1396 .exe => |expected_outcome| code_block: {
1397 var build_args = std.ArrayList([]const u8).init(allocator);
1398 defer build_args.deinit();
1399 try build_args.appendSlice(&[_][]const u8{
1400 zig_exe, "build-exe",
1401 "--name", code.name,
1402 "--color", "on",
1403 name_plus_ext,
1404 });
1405 if (opt_zig_lib_dir) |zig_lib_dir| {
1406 try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1407 }
1408
1409 try shell_out.print("$ zig build-exe {s} ", .{name_plus_ext});
1410
1411 switch (code.mode) {
1412 .Debug => {},
1413 else => {
1414 try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1415 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1416 },
1417 }
1418 for (code.link_objects) |link_object| {
1419 const name_with_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ link_object, obj_ext });
1420 try build_args.append(name_with_ext);
1421 try shell_out.print("{s} ", .{name_with_ext});
1422 }
1423 if (code.link_libc) {
1424 try build_args.append("-lc");
1425 try shell_out.print("-lc ", .{});
1426 }
1427 const target = try std.zig.CrossTarget.parse(.{
1428 .arch_os_abi = code.target_str orelse "native",
1429 });
1430 if (code.target_str) |triple| {
1431 try build_args.appendSlice(&[_][]const u8{ "-target", triple });
1432 try shell_out.print("-target {s} ", .{triple});
1433 }
1434 if (code.verbose_cimport) {
1435 try build_args.append("--verbose-cimport");
1436 try shell_out.print("--verbose-cimport ", .{});
1437 }
1438 for (code.additional_options) |option| {
1439 try build_args.append(option);
1440 try shell_out.print("{s} ", .{option});
1441 }
1442
1443 try shell_out.print("\n", .{});
1444
1445 if (expected_outcome == .build_fail) {
1446 const result = try ChildProcess.exec(.{
1447 .allocator = allocator,
1448 .argv = build_args.items,
1449 .cwd = tmp_dir_name,
1450 .env_map = &env_map,
1451 .max_output_bytes = max_doc_file_size,
1452 });
1453 switch (result.term) {
1454 .Exited => |exit_code| {
1455 if (exit_code == 0) {
1456 progress.log("", .{});
1457 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1458 dumpArgs(build_args.items);
1459 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1460 }
1461 },
1462 else => {
1463 progress.log("", .{});
1464 print("{s}\nThe following command crashed:\n", .{result.stderr});
1465 dumpArgs(build_args.items);
1466 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1467 },
1468 }
1469 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1470 const colored_stderr = try termColor(allocator, escaped_stderr);
1471 try shell_out.writeAll(colored_stderr);
1472 break :code_block;
1473 }
1474 const exec_result = exec(allocator, &env_map, tmp_dir_name, build_args.items) catch
1475 return parseError(tokenizer, code.source_token, "example failed to compile", .{});
1476
1477 if (code.verbose_cimport) {
1478 const escaped_build_stderr = try escapeHtml(allocator, exec_result.stderr);
1479 try shell_out.writeAll(escaped_build_stderr);
1480 }
1481
1482 if (code.target_str) |triple| {
1483 if (mem.startsWith(u8, triple, "wasm32") or
1484 mem.startsWith(u8, triple, "riscv64-linux") or
1485 (mem.startsWith(u8, triple, "x86_64-linux") and
1486 builtin.os.tag != .linux or builtin.cpu.arch != .x86_64))
1487 {
1488 // skip execution
1489 break :code_block;
1490 }
1491 }
1492
1493 const path_to_exe = try std.fmt.allocPrint(allocator, "./{s}{s}", .{
1494 code.name,
1495 target.exeFileExt(),
1496 });
1497 const run_args = &[_][]const u8{path_to_exe};
1498
1499 var exited_with_signal = false;
1500
1501 const result = if (expected_outcome == .fail) blk: {
1502 const result = try ChildProcess.exec(.{
1503 .allocator = allocator,
1504 .argv = run_args,
1505 .env_map = &env_map,
1506 .cwd = tmp_dir_name,
1507 .max_output_bytes = max_doc_file_size,
1508 });
1509 switch (result.term) {
1510 .Exited => |exit_code| {
1511 if (exit_code == 0) {
1512 progress.log("", .{});
1513 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1514 dumpArgs(run_args);
1515 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1516 }
1517 },
1518 .Signal => exited_with_signal = true,
1519 else => {},
1520 }
1521 break :blk result;
1522 } else blk: {
1523 break :blk exec(allocator, &env_map, tmp_dir_name, run_args) catch return parseError(tokenizer, code.source_token, "example crashed", .{});
1524 };
1525
1526 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1527 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1528
1529 const colored_stderr = try termColor(allocator, escaped_stderr);
1530 const colored_stdout = try termColor(allocator, escaped_stdout);
1531
1532 try shell_out.print("$ ./{s}\n{s}{s}", .{ code.name, colored_stdout, colored_stderr });
1533 if (exited_with_signal) {
1534 try shell_out.print("(process terminated by signal)", .{});
1535 }
1536 try shell_out.writeAll("\n");
1537 },
1538 .@"test" => {
1539 var test_args = std.ArrayList([]const u8).init(allocator);
1540 defer test_args.deinit();
1541
1542 try test_args.appendSlice(&[_][]const u8{
1543 zig_exe, "test",
1544 tmp_source_file_name,
1545 });
1546 if (opt_zig_lib_dir) |zig_lib_dir| {
1547 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1548 }
1549 try shell_out.print("$ zig test {s}.zig ", .{code.name});
1550
1551 switch (code.mode) {
1552 .Debug => {},
1553 else => {
1554 try test_args.appendSlice(&[_][]const u8{
1555 "-O", @tagName(code.mode),
1556 });
1557 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1558 },
1559 }
1560 if (code.link_libc) {
1561 try test_args.append("-lc");
1562 try shell_out.print("-lc ", .{});
1563 }
1564 if (code.target_str) |triple| {
1565 try test_args.appendSlice(&[_][]const u8{ "-target", triple });
1566 try shell_out.print("-target {s} ", .{triple});
1567
1568 const cross_target = try std.zig.CrossTarget.parse(.{
1569 .arch_os_abi = triple,
1570 });
1571 const target_info = try std.zig.system.NativeTargetInfo.detect(
1572 cross_target,
1573 );
1574 switch (host.getExternalExecutor(target_info, .{
1575 .link_libc = code.link_libc,
1576 })) {
1577 .native => {},
1578 else => {
1579 try test_args.appendSlice(&[_][]const u8{"--test-no-exec"});
1580 try shell_out.writeAll("--test-no-exec");
1581 },
1582 }
1583 }
1584 const result = exec(allocator, &env_map, null, test_args.items) catch
1585 return parseError(tokenizer, code.source_token, "test failed", .{});
1586 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1587 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1588 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
1589 },
1590 .test_error => |error_match| {
1591 var test_args = std.ArrayList([]const u8).init(allocator);
1592 defer test_args.deinit();
1593
1594 try test_args.appendSlice(&[_][]const u8{
1595 zig_exe, "test",
1596 "--color", "on",
1597 tmp_source_file_name,
1598 });
1599 if (opt_zig_lib_dir) |zig_lib_dir| {
1600 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1601 }
1602 try shell_out.print("$ zig test {s}.zig ", .{code.name});
1603
1604 switch (code.mode) {
1605 .Debug => {},
1606 else => {
1607 try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1608 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1609 },
1610 }
1611 if (code.link_libc) {
1612 try test_args.append("-lc");
1613 try shell_out.print("-lc ", .{});
1614 }
1615 const result = try ChildProcess.exec(.{
1616 .allocator = allocator,
1617 .argv = test_args.items,
1618 .env_map = &env_map,
1619 .max_output_bytes = max_doc_file_size,
1620 });
1621 switch (result.term) {
1622 .Exited => |exit_code| {
1623 if (exit_code == 0) {
1624 progress.log("", .{});
1625 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1626 dumpArgs(test_args.items);
1627 return parseError(tokenizer, code.source_token, "example incorrectly compiled", .{});
1628 }
1629 },
1630 else => {
1631 progress.log("", .{});
1632 print("{s}\nThe following command crashed:\n", .{result.stderr});
1633 dumpArgs(test_args.items);
1634 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1635 },
1636 }
1637 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1638 progress.log("", .{});
1639 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1640 return parseError(tokenizer, code.source_token, "example did not have expected compile error", .{});
1641 }
1642 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1643 const colored_stderr = try termColor(allocator, escaped_stderr);
1644 try shell_out.print("\n{s}\n", .{colored_stderr});
1645 },
1646 .test_safety => |error_match| {
1647 var test_args = std.ArrayList([]const u8).init(allocator);
1648 defer test_args.deinit();
1649
1650 try test_args.appendSlice(&[_][]const u8{
1651 zig_exe, "test",
1652 tmp_source_file_name,
1653 });
1654 if (opt_zig_lib_dir) |zig_lib_dir| {
1655 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1656 }
1657 var mode_arg: []const u8 = "";
1658 switch (code.mode) {
1659 .Debug => {},
1660 .ReleaseSafe => {
1661 try test_args.append("-OReleaseSafe");
1662 mode_arg = "-OReleaseSafe";
1663 },
1664 .ReleaseFast => {
1665 try test_args.append("-OReleaseFast");
1666 mode_arg = "-OReleaseFast";
1667 },
1668 .ReleaseSmall => {
1669 try test_args.append("-OReleaseSmall");
1670 mode_arg = "-OReleaseSmall";
1671 },
1672 }
1673
1674 const result = try ChildProcess.exec(.{
1675 .allocator = allocator,
1676 .argv = test_args.items,
1677 .env_map = &env_map,
1678 .max_output_bytes = max_doc_file_size,
1679 });
1680 switch (result.term) {
1681 .Exited => |exit_code| {
1682 if (exit_code == 0) {
1683 progress.log("", .{});
1684 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1685 dumpArgs(test_args.items);
1686 return parseError(tokenizer, code.source_token, "example test incorrectly succeeded", .{});
1687 }
1688 },
1689 else => {
1690 progress.log("", .{});
1691 print("{s}\nThe following command crashed:\n", .{result.stderr});
1692 dumpArgs(test_args.items);
1693 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1694 },
1695 }
1696 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1697 progress.log("", .{});
1698 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1699 return parseError(tokenizer, code.source_token, "example did not have expected runtime safety error message", .{});
1700 }
1701 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1702 const colored_stderr = try termColor(allocator, escaped_stderr);
1703 try shell_out.print("$ zig test {s}.zig {s}\n{s}\n", .{
1704 code.name,
1705 mode_arg,
1706 colored_stderr,
1707 });
1708 },
1709 .obj => |maybe_error_match| {
1710 const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{s}{s}", .{ code.name, obj_ext });
1711 var build_args = std.ArrayList([]const u8).init(allocator);
1712 defer build_args.deinit();
1713
1714 try build_args.appendSlice(&[_][]const u8{
1715 zig_exe, "build-obj",
1716 "--color", "on",
1717 "--name", code.name,
1718 tmp_source_file_name,
1719 try std.fmt.allocPrint(allocator, "-femit-bin={s}{c}{s}", .{
1720 tmp_dir_name, fs.path.sep, name_plus_obj_ext,
1721 }),
1722 });
1723 if (opt_zig_lib_dir) |zig_lib_dir| {
1724 try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1725 }
1726
1727 try shell_out.print("$ zig build-obj {s}.zig ", .{code.name});
1728
1729 switch (code.mode) {
1730 .Debug => {},
1731 else => {
1732 try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1733 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1734 },
1735 }
1736
1737 if (code.target_str) |triple| {
1738 try build_args.appendSlice(&[_][]const u8{ "-target", triple });
1739 try shell_out.print("-target {s} ", .{triple});
1740 }
1741 for (code.additional_options) |option| {
1742 try build_args.append(option);
1743 try shell_out.print("{s} ", .{option});
1744 }
1745
1746 if (maybe_error_match) |error_match| {
1747 const result = try ChildProcess.exec(.{
1748 .allocator = allocator,
1749 .argv = build_args.items,
1750 .env_map = &env_map,
1751 .max_output_bytes = max_doc_file_size,
1752 });
1753 switch (result.term) {
1754 .Exited => |exit_code| {
1755 if (exit_code == 0) {
1756 progress.log("", .{});
1757 print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr});
1758 dumpArgs(build_args.items);
1759 return parseError(tokenizer, code.source_token, "example build incorrectly succeeded", .{});
1760 }
1761 },
1762 else => {
1763 progress.log("", .{});
1764 print("{s}\nThe following command crashed:\n", .{result.stderr});
1765 dumpArgs(build_args.items);
1766 return parseError(tokenizer, code.source_token, "example compile crashed", .{});
1767 },
1768 }
1769 if (mem.indexOf(u8, result.stderr, error_match) == null) {
1770 progress.log("", .{});
1771 print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match });
1772 return parseError(tokenizer, code.source_token, "example did not have expected compile error message", .{});
1773 }
1774 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1775 const colored_stderr = try termColor(allocator, escaped_stderr);
1776 try shell_out.print("\n{s} ", .{colored_stderr});
1777 } else {
1778 _ = exec(allocator, &env_map, null, build_args.items) catch return parseError(tokenizer, code.source_token, "example failed to compile", .{});
1779 }
1780 try shell_out.writeAll("\n");
1781 },
1782 .lib => {
1783 const bin_basename = try std.zig.binNameAlloc(allocator, .{
1784 .root_name = code.name,
1785 .target = builtin.target,
1786 .output_mode = .Lib,
1787 });
1788
1789 var test_args = std.ArrayList([]const u8).init(allocator);
1790 defer test_args.deinit();
1791
1792 try test_args.appendSlice(&[_][]const u8{
1793 zig_exe, "build-lib",
1794 tmp_source_file_name,
1795 try std.fmt.allocPrint(allocator, "-femit-bin={s}{s}{s}", .{
1796 tmp_dir_name, fs.path.sep_str, bin_basename,
1797 }),
1798 });
1799 if (opt_zig_lib_dir) |zig_lib_dir| {
1800 try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir });
1801 }
1802 try shell_out.print("$ zig build-lib {s}.zig ", .{code.name});
1803
1804 switch (code.mode) {
1805 .Debug => {},
1806 else => {
1807 try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) });
1808 try shell_out.print("-O {s} ", .{@tagName(code.mode)});
1809 },
1810 }
1811 if (code.target_str) |triple| {
1812 try test_args.appendSlice(&[_][]const u8{ "-target", triple });
1813 try shell_out.print("-target {s} ", .{triple});
1814 }
1815 if (code.link_mode) |link_mode| {
1816 switch (link_mode) {
1817 .Static => {
1818 try test_args.append("-static");
1819 try shell_out.print("-static ", .{});
1820 },
1821 .Dynamic => {
1822 try test_args.append("-dynamic");
1823 try shell_out.print("-dynamic ", .{});
1824 },
1825 }
1826 }
1827 for (code.additional_options) |option| {
1828 try test_args.append(option);
1829 try shell_out.print("{s} ", .{option});
1830 }
1831 const result = exec(allocator, &env_map, null, test_args.items) catch return parseError(tokenizer, code.source_token, "test failed", .{});
1832 const escaped_stderr = try escapeHtml(allocator, result.stderr);
1833 const escaped_stdout = try escapeHtml(allocator, result.stdout);
1834 try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout });
1835 },
1836 }
1837
1838 if (!code.just_check_syntax) {
1839 try printShell(out, shell_buffer.items, false);
1840 }
1841 },
1842 }
1843 }
1844}
1845
1846fn exec(
1847 allocator: Allocator,
1848 env_map: *process.EnvMap,
1849 cwd: ?[]const u8,
1850 args: []const []const u8,
1851) !ChildProcess.ExecResult {
1852 const result = try ChildProcess.exec(.{
1853 .allocator = allocator,
1854 .argv = args,
1855 .env_map = env_map,
1856 .cwd = cwd,
1857 .max_output_bytes = max_doc_file_size,
1858 });
1859 switch (result.term) {
1860 .Exited => |exit_code| {
1861 if (exit_code != 0) {
1862 print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code });
1863 dumpArgs(args);
1864 return error.ChildExitError;
1865 }
1866 },
1867 else => {
1868 print("{s}\nThe following command crashed:\n", .{result.stderr});
1869 dumpArgs(args);
1870 return error.ChildCrashed;
1871 },
1872 }
1873 return result;
1874}
1875
1876fn getBuiltinCode(
1877 allocator: Allocator,
1878 env_map: *process.EnvMap,
1879 zig_exe: []const u8,
1880 opt_zig_lib_dir: ?[]const u8,
1881) ![]const u8 {
1882 if (opt_zig_lib_dir) |zig_lib_dir| {
1883 const result = try exec(allocator, env_map, null, &.{
1884 zig_exe, "build-obj", "--show-builtin", "--zig-lib-dir", zig_lib_dir,
1885 });
1886 return result.stdout;
1887 } else {
1888 const result = try exec(allocator, env_map, null, &.{
1889 zig_exe, "build-obj", "--show-builtin",
1890 });
1891 return result.stdout;
1892 }
1893}
1894
1895fn dumpArgs(args: []const []const u8) void {
1896 for (args) |arg|
1897 print("{s} ", .{arg})
1898 else
1899 print("\n", .{});
1900}
1901
1902test "term supported colors" {
1903 const test_allocator = testing.allocator;
1904
1905 {
1906 const input = "A\x1b[31;1mred\x1b[0mB";
1907 const expect = "A<span class=\"sgr-31_1m\">red</span>B";
1908
1909 const result = try termColor(test_allocator, input);
1910 defer test_allocator.free(result);
1911 try testing.expectEqualSlices(u8, expect, result);
1912 }
1913
1914 {
1915 const input = "A\x1b[32;1mgreen\x1b[0mB";
1916 const expect = "A<span class=\"sgr-32_1m\">green</span>B";
1917
1918 const result = try termColor(test_allocator, input);
1919 defer test_allocator.free(result);
1920 try testing.expectEqualSlices(u8, expect, result);
1921 }
1922
1923 {
1924 const input = "A\x1b[36;1mcyan\x1b[0mB";
1925 const expect = "A<span class=\"sgr-36_1m\">cyan</span>B";
1926
1927 const result = try termColor(test_allocator, input);
1928 defer test_allocator.free(result);
1929 try testing.expectEqualSlices(u8, expect, result);
1930 }
1931
1932 {
1933 const input = "A\x1b[1mbold\x1b[0mB";
1934 const expect = "A<span class=\"sgr-1m\">bold</span>B";
1935
1936 const result = try termColor(test_allocator, input);
1937 defer test_allocator.free(result);
1938 try testing.expectEqualSlices(u8, expect, result);
1939 }
1940
1941 {
1942 const input = "A\x1b[2mdim\x1b[0mB";
1943 const expect = "A<span class=\"sgr-2m\">dim</span>B";
1944
1945 const result = try termColor(test_allocator, input);
1946 defer test_allocator.free(result);
1947 try testing.expectEqualSlices(u8, expect, result);
1948 }
1949}
1950
1951test "term output from zig" {
1952 // Use data generated by https://github.com/perillo/zig-tty-test-data,
1953 // with zig version 0.11.0-dev.1898+36d47dd19.
1954 const test_allocator = testing.allocator;
1955
1956 {
1957 // 1.1-with-build-progress.out
1958 const input = "Semantic Analysis [1324] \x1b[25D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLD Link... \x1b[12D\x1b[0K";
1959 const expect = "";
1960
1961 const result = try termColor(test_allocator, input);
1962 defer test_allocator.free(result);
1963 try testing.expectEqualSlices(u8, expect, result);
1964 }
1965
1966 {
1967 // 2.1-with-reference-traces.out
1968 const input = "\x1b[1msrc/2.1-with-reference-traces.zig:3:7: \x1b[31;1merror: \x1b[0m\x1b[1mcannot assign to constant\n\x1b[0m x += 1;\n \x1b[32;1m~~^~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.1-with-reference-traces.zig:7:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m";
1969 const expect =
1970 \\<span class="sgr-1m">src/2.1-with-reference-traces.zig:3:7: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">cannot assign to constant
1971 \\</span> x += 1;
1972 \\ <span class="sgr-32_1m">~~^~~~
1973 \\</span><span class="sgr-2m">referenced by:
1974 \\ main: src/2.1-with-reference-traces.zig:7:5
1975 \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17
1976 \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces
1977 \\
1978 \\</span>
1979 ;
1980
1981 const result = try termColor(test_allocator, input);
1982 defer test_allocator.free(result);
1983 try testing.expectEqualSlices(u8, expect, result);
1984 }
1985
1986 {
1987 // 2.2-without-reference-traces.out
1988 const input = "\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: \x1b[31;1merror: \x1b[0m\x1b[1minvalid type given to fixedBufferStream\n\x1b[0m else => @compileError(\"invalid type given to fixedBufferStream\"),\n \x1b[32;1m^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\x1b[0m\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: \x1b[36;1mnote: \x1b[0m\x1b[1mcalled from here\n\x1b[0mpub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {\n; \x1b[32;1m~~~~~^~~~~~~~~~~~~~~~~\n\x1b[0m";
1989 const expect =
1990 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">invalid type given to fixedBufferStream
1991 \\</span> else => @compileError("invalid type given to fixedBufferStream"),
1992 \\ <span class="sgr-32_1m">^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1993 \\</span><span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">called from here
1994 \\</span>pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
1995 \\; <span class="sgr-32_1m">~~~~~^~~~~~~~~~~~~~~~~
1996 \\</span>
1997 ;
1998
1999 const result = try termColor(test_allocator, input);
2000 defer test_allocator.free(result);
2001 try testing.expectEqualSlices(u8, expect, result);
2002 }
2003
2004 {
2005 // 2.3-with-notes.out
2006 const input = "\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[31;1merror: \x1b[0m\x1b[1mexpected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'\n\x1b[0m bar(w);\n \x1b[32;1m^\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[36;1mnote: \x1b[0m\x1b[1mpointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:2:13: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Wat = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:1:14: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Derp = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:4:18: \x1b[36;1mnote: \x1b[0m\x1b[1mparameter type declared here\n\x1b[0mextern fn bar(d: *Derp) void;\n \x1b[32;1m^~~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.3-with-notes.zig:10:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m";
2007 const expect =
2008 \\<span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">expected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'
2009 \\</span> bar(w);
2010 \\ <span class="sgr-32_1m">^
2011 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">pointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'
2012 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:2:13: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here
2013 \\</span>const Wat = opaque {};
2014 \\ <span class="sgr-32_1m">^~~~~~~~~
2015 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:1:14: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here
2016 \\</span>const Derp = opaque {};
2017 \\ <span class="sgr-32_1m">^~~~~~~~~
2018 \\</span><span class="sgr-1m">src/2.3-with-notes.zig:4:18: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">parameter type declared here
2019 \\</span>extern fn bar(d: *Derp) void;
2020 \\ <span class="sgr-32_1m">^~~~~
2021 \\</span><span class="sgr-2m">referenced by:
2022 \\ main: src/2.3-with-notes.zig:10:5
2023 \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17
2024 \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces
2025 \\
2026 \\</span>
2027 ;
2028
2029 const result = try termColor(test_allocator, input);
2030 defer test_allocator.free(result);
2031 try testing.expectEqualSlices(u8, expect, result);
2032 }
2033
2034 {
2035 // 3.1-with-error-return-traces.out
2036
2037 const input = "error: Error\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:5:5\x1b[0m: \x1b[2m0x20b008 in callee (3.1-with-error-return-traces)\x1b[0m\n return error.Error;\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:9:5\x1b[0m: \x1b[2m0x20b113 in caller (3.1-with-error-return-traces)\x1b[0m\n try callee();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:13:5\x1b[0m: \x1b[2m0x20b153 in main (3.1-with-error-return-traces)\x1b[0m\n try caller();\n \x1b[32;1m^\x1b[0m\n";
2038 const expect =
2039 \\error: Error
2040 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:5:5</span>: <span class="sgr-2m">0x20b008 in callee (3.1-with-error-return-traces)</span>
2041 \\ return error.Error;
2042 \\ <span class="sgr-32_1m">^</span>
2043 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:9:5</span>: <span class="sgr-2m">0x20b113 in caller (3.1-with-error-return-traces)</span>
2044 \\ try callee();
2045 \\ <span class="sgr-32_1m">^</span>
2046 \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:13:5</span>: <span class="sgr-2m">0x20b153 in main (3.1-with-error-return-traces)</span>
2047 \\ try caller();
2048 \\ <span class="sgr-32_1m">^</span>
2049 \\
2050 ;
2051
2052 const result = try termColor(test_allocator, input);
2053 defer test_allocator.free(result);
2054 try testing.expectEqualSlices(u8, expect, result);
2055 }
2056
2057 {
2058 // 3.2-with-stack-trace.out
2059 const input = "\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:561:19\x1b[0m: \x1b[2m0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)\x1b[0m\n while (it.next()) |return_address| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:157:80\x1b[0m: \x1b[2m0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)\x1b[0m\n writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:5:36\x1b[0m: \x1b[2m0x20d3b2 in foo (3.2-with-stack-trace)\x1b[0m\n std.debug.dumpCurrentStackTrace(null);\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:9:8\x1b[0m: \x1b[2m0x20b458 in main (3.2-with-stack-trace)\x1b[0m\n foo();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:607:22\x1b[0m: \x1b[2m0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)\x1b[0m\n root.main();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:376:5\x1b[0m: \x1b[2m0x20a411 in _start (3.2-with-stack-trace)\x1b[0m\n @call(.never_inline, posixCallMainAndExit, .{});\n \x1b[32;1m^\x1b[0m\n";
2060 const expect =
2061 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:561:19</span>: <span class="sgr-2m">0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)</span>
2062 \\ while (it.next()) |return_address| {
2063 \\ <span class="sgr-32_1m">^</span>
2064 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:157:80</span>: <span class="sgr-2m">0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)</span>
2065 \\ writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {
2066 \\ <span class="sgr-32_1m">^</span>
2067 \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:5:36</span>: <span class="sgr-2m">0x20d3b2 in foo (3.2-with-stack-trace)</span>
2068 \\ std.debug.dumpCurrentStackTrace(null);
2069 \\ <span class="sgr-32_1m">^</span>
2070 \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:9:8</span>: <span class="sgr-2m">0x20b458 in main (3.2-with-stack-trace)</span>
2071 \\ foo();
2072 \\ <span class="sgr-32_1m">^</span>
2073 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:607:22</span>: <span class="sgr-2m">0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)</span>
2074 \\ root.main();
2075 \\ <span class="sgr-32_1m">^</span>
2076 \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:376:5</span>: <span class="sgr-2m">0x20a411 in _start (3.2-with-stack-trace)</span>
2077 \\ @call(.never_inline, posixCallMainAndExit, .{});
2078 \\ <span class="sgr-32_1m">^</span>
2079 \\
2080 ;
2081
2082 const result = try termColor(test_allocator, input);
2083 defer test_allocator.free(result);
2084 try testing.expectEqualSlices(u8, expect, result);
2085 }
2086}
2087
2088test "printShell" {
2089 const test_allocator = std.testing.allocator;
2090
2091 {
2092 const shell_out =
2093 \\$ zig build test.zig
2094 ;
2095 const expected =
2096 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2097 \\</samp></pre></figure>
2098 ;
2099
2100 var buffer = std.ArrayList(u8).init(test_allocator);
2101 defer buffer.deinit();
2102
2103 try printShell(buffer.writer(), shell_out, false);
2104 try testing.expectEqualSlices(u8, expected, buffer.items);
2105 }
2106 {
2107 const shell_out =
2108 \\$ zig build test.zig
2109 \\build output
2110 ;
2111 const expected =
2112 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2113 \\build output
2114 \\</samp></pre></figure>
2115 ;
2116
2117 var buffer = std.ArrayList(u8).init(test_allocator);
2118 defer buffer.deinit();
2119
2120 try printShell(buffer.writer(), shell_out, false);
2121 try testing.expectEqualSlices(u8, expected, buffer.items);
2122 }
2123 {
2124 const shell_out =
2125 \\$ zig build test.zig
2126 \\build output
2127 \\$ ./test
2128 ;
2129 const expected =
2130 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2131 \\build output
2132 \\$ <kbd>./test</kbd>
2133 \\</samp></pre></figure>
2134 ;
2135
2136 var buffer = std.ArrayList(u8).init(test_allocator);
2137 defer buffer.deinit();
2138
2139 try printShell(buffer.writer(), shell_out, false);
2140 try testing.expectEqualSlices(u8, expected, buffer.items);
2141 }
2142 {
2143 const shell_out =
2144 \\$ zig build test.zig
2145 \\
2146 \\$ ./test
2147 \\output
2148 ;
2149 const expected =
2150 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2151 \\
2152 \\$ <kbd>./test</kbd>
2153 \\output
2154 \\</samp></pre></figure>
2155 ;
2156
2157 var buffer = std.ArrayList(u8).init(test_allocator);
2158 defer buffer.deinit();
2159
2160 try printShell(buffer.writer(), shell_out, false);
2161 try testing.expectEqualSlices(u8, expected, buffer.items);
2162 }
2163 {
2164 const shell_out =
2165 \\$ zig build test.zig
2166 \\$ ./test
2167 \\output
2168 ;
2169 const expected =
2170 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2171 \\$ <kbd>./test</kbd>
2172 \\output
2173 \\</samp></pre></figure>
2174 ;
2175
2176 var buffer = std.ArrayList(u8).init(test_allocator);
2177 defer buffer.deinit();
2178
2179 try printShell(buffer.writer(), shell_out, false);
2180 try testing.expectEqualSlices(u8, expected, buffer.items);
2181 }
2182 {
2183 const shell_out =
2184 \\$ zig build test.zig \
2185 \\ --build-option
2186 \\build output
2187 \\$ ./test
2188 \\output
2189 ;
2190 const expected =
2191 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2192 \\ --build-option</kbd>
2193 \\build output
2194 \\$ <kbd>./test</kbd>
2195 \\output
2196 \\</samp></pre></figure>
2197 ;
2198
2199 var buffer = std.ArrayList(u8).init(test_allocator);
2200 defer buffer.deinit();
2201
2202 try printShell(buffer.writer(), shell_out, false);
2203 try testing.expectEqualSlices(u8, expected, buffer.items);
2204 }
2205 {
2206 // intentional space after "--build-option1 \"
2207 const shell_out =
2208 \\$ zig build test.zig \
2209 \\ --build-option1 \
2210 \\ --build-option2
2211 \\$ ./test
2212 ;
2213 const expected =
2214 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2215 \\ --build-option1 \
2216 \\ --build-option2</kbd>
2217 \\$ <kbd>./test</kbd>
2218 \\</samp></pre></figure>
2219 ;
2220
2221 var buffer = std.ArrayList(u8).init(test_allocator);
2222 defer buffer.deinit();
2223
2224 try printShell(buffer.writer(), shell_out, false);
2225 try testing.expectEqualSlices(u8, expected, buffer.items);
2226 }
2227 {
2228 const shell_out =
2229 \\$ zig build test.zig \
2230 \\$ ./test
2231 ;
2232 const expected =
2233 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \
2234 \\$ ./test</kbd>
2235 \\</samp></pre></figure>
2236 ;
2237
2238 var buffer = std.ArrayList(u8).init(test_allocator);
2239 defer buffer.deinit();
2240
2241 try printShell(buffer.writer(), shell_out, false);
2242 try testing.expectEqualSlices(u8, expected, buffer.items);
2243 }
2244 {
2245 const shell_out =
2246 \\$ zig build test.zig
2247 \\$ ./test
2248 \\$1
2249 ;
2250 const expected =
2251 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd>
2252 \\$ <kbd>./test</kbd>
2253 \\$1
2254 \\</samp></pre></figure>
2255 ;
2256
2257 var buffer = std.ArrayList(u8).init(test_allocator);
2258 defer buffer.deinit();
2259
2260 try printShell(buffer.writer(), shell_out, false);
2261 try testing.expectEqualSlices(u8, expected, buffer.items);
2262 }
2263 {
2264 const shell_out =
2265 \\$zig build test.zig
2266 ;
2267 const expected =
2268 \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$zig build test.zig
2269 \\</samp></pre></figure>
2270 ;
2271
2272 var buffer = std.ArrayList(u8).init(test_allocator);
2273 defer buffer.deinit();
2274
2275 try printShell(buffer.writer(), shell_out, false);
2276 try testing.expectEqualSlices(u8, expected, buffer.items);
2277 }
2278}