master
1const std = @import("std");
2const builtin = @import("builtin");
3const fs = std.fs;
4const Step = std.Build.Step;
5const GeneratedFile = std.Build.GeneratedFile;
6const LazyPath = std.Build.LazyPath;
7
8const Options = @This();
9
10pub const base_id: Step.Id = .options;
11
12step: Step,
13generated_file: GeneratedFile,
14
15contents: std.ArrayList(u8),
16args: std.ArrayList(Arg),
17encountered_types: std.StringHashMapUnmanaged(void),
18
19pub fn create(owner: *std.Build) *Options {
20 const options = owner.allocator.create(Options) catch @panic("OOM");
21 options.* = .{
22 .step = .init(.{
23 .id = base_id,
24 .name = "options",
25 .owner = owner,
26 .makeFn = make,
27 }),
28 .generated_file = undefined,
29 .contents = .empty,
30 .args = .empty,
31 .encountered_types = .empty,
32 };
33 options.generated_file = .{ .step = &options.step };
34
35 return options;
36}
37
38pub fn addOption(options: *Options, comptime T: type, name: []const u8, value: T) void {
39 return addOptionFallible(options, T, name, value) catch @panic("unhandled error");
40}
41
42fn addOptionFallible(options: *Options, comptime T: type, name: []const u8, value: T) !void {
43 try printType(options, &options.contents, T, value, 0, name);
44}
45
46fn printType(
47 options: *Options,
48 out: *std.ArrayList(u8),
49 comptime T: type,
50 value: T,
51 indent: u8,
52 name: ?[]const u8,
53) !void {
54 const gpa = options.step.owner.allocator;
55 switch (T) {
56 []const []const u8 => {
57 if (name) |payload| {
58 try out.print(gpa, "pub const {f}: []const []const u8 = ", .{std.zig.fmtId(payload)});
59 }
60
61 try out.appendSlice(gpa, "&[_][]const u8{\n");
62
63 for (value) |slice| {
64 try out.appendNTimes(gpa, ' ', indent);
65 try out.print(gpa, " \"{f}\",\n", .{std.zig.fmtString(slice)});
66 }
67
68 if (name != null) {
69 try out.appendSlice(gpa, "};\n");
70 } else {
71 try out.appendSlice(gpa, "},\n");
72 }
73
74 return;
75 },
76 []const u8 => {
77 if (name) |some| {
78 try out.print(gpa, "pub const {f}: []const u8 = \"{f}\";", .{
79 std.zig.fmtId(some), std.zig.fmtString(value),
80 });
81 } else {
82 try out.print(gpa, "\"{f}\",", .{std.zig.fmtString(value)});
83 }
84 return out.appendSlice(gpa, "\n");
85 },
86 [:0]const u8 => {
87 if (name) |some| {
88 try out.print(gpa, "pub const {f}: [:0]const u8 = \"{f}\";", .{ std.zig.fmtId(some), std.zig.fmtString(value) });
89 } else {
90 try out.print(gpa, "\"{f}\",", .{std.zig.fmtString(value)});
91 }
92 return out.appendSlice(gpa, "\n");
93 },
94 ?[]const u8 => {
95 if (name) |some| {
96 try out.print(gpa, "pub const {f}: ?[]const u8 = ", .{std.zig.fmtId(some)});
97 }
98
99 if (value) |payload| {
100 try out.print(gpa, "\"{f}\"", .{std.zig.fmtString(payload)});
101 } else {
102 try out.appendSlice(gpa, "null");
103 }
104
105 if (name != null) {
106 try out.appendSlice(gpa, ";\n");
107 } else {
108 try out.appendSlice(gpa, ",\n");
109 }
110 return;
111 },
112 ?[:0]const u8 => {
113 if (name) |some| {
114 try out.print(gpa, "pub const {f}: ?[:0]const u8 = ", .{std.zig.fmtId(some)});
115 }
116
117 if (value) |payload| {
118 try out.print(gpa, "\"{f}\"", .{std.zig.fmtString(payload)});
119 } else {
120 try out.appendSlice(gpa, "null");
121 }
122
123 if (name != null) {
124 try out.appendSlice(gpa, ";\n");
125 } else {
126 try out.appendSlice(gpa, ",\n");
127 }
128 return;
129 },
130 std.SemanticVersion => {
131 if (name) |some| {
132 try out.print(gpa, "pub const {f}: @import(\"std\").SemanticVersion = ", .{std.zig.fmtId(some)});
133 }
134
135 try out.appendSlice(gpa, ".{\n");
136 try out.appendNTimes(gpa, ' ', indent);
137 try out.print(gpa, " .major = {d},\n", .{value.major});
138 try out.appendNTimes(gpa, ' ', indent);
139 try out.print(gpa, " .minor = {d},\n", .{value.minor});
140 try out.appendNTimes(gpa, ' ', indent);
141 try out.print(gpa, " .patch = {d},\n", .{value.patch});
142
143 if (value.pre) |some| {
144 try out.appendNTimes(gpa, ' ', indent);
145 try out.print(gpa, " .pre = \"{f}\",\n", .{std.zig.fmtString(some)});
146 }
147 if (value.build) |some| {
148 try out.appendNTimes(gpa, ' ', indent);
149 try out.print(gpa, " .build = \"{f}\",\n", .{std.zig.fmtString(some)});
150 }
151
152 if (name != null) {
153 try out.appendSlice(gpa, "};\n");
154 } else {
155 try out.appendSlice(gpa, "},\n");
156 }
157 return;
158 },
159 else => {},
160 }
161
162 switch (@typeInfo(T)) {
163 .array => {
164 if (name) |some| {
165 try out.print(gpa, "pub const {f}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) });
166 }
167
168 try out.print(gpa, "{s} {{\n", .{@typeName(T)});
169 for (value) |item| {
170 try out.appendNTimes(gpa, ' ', indent + 4);
171 try printType(options, out, @TypeOf(item), item, indent + 4, null);
172 }
173 try out.appendNTimes(gpa, ' ', indent);
174 try out.appendSlice(gpa, "}");
175
176 if (name != null) {
177 try out.appendSlice(gpa, ";\n");
178 } else {
179 try out.appendSlice(gpa, ",\n");
180 }
181 return;
182 },
183 .pointer => |p| {
184 if (p.size != .slice) {
185 @compileError("Non-slice pointers are not yet supported in build options");
186 }
187
188 if (name) |some| {
189 try out.print(gpa, "pub const {f}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) });
190 }
191
192 try out.print(gpa, "&[_]{s} {{\n", .{@typeName(p.child)});
193 for (value) |item| {
194 try out.appendNTimes(gpa, ' ', indent + 4);
195 try printType(options, out, @TypeOf(item), item, indent + 4, null);
196 }
197 try out.appendNTimes(gpa, ' ', indent);
198 try out.appendSlice(gpa, "}");
199
200 if (name != null) {
201 try out.appendSlice(gpa, ";\n");
202 } else {
203 try out.appendSlice(gpa, ",\n");
204 }
205 return;
206 },
207 .optional => {
208 if (name) |some| {
209 try out.print(gpa, "pub const {f}: {s} = ", .{ std.zig.fmtId(some), @typeName(T) });
210 }
211
212 if (value) |inner| {
213 try printType(options, out, @TypeOf(inner), inner, indent + 4, null);
214 // Pop the '\n' and ',' chars
215 _ = options.contents.pop();
216 _ = options.contents.pop();
217 } else {
218 try out.appendSlice(gpa, "null");
219 }
220
221 if (name != null) {
222 try out.appendSlice(gpa, ";\n");
223 } else {
224 try out.appendSlice(gpa, ",\n");
225 }
226 return;
227 },
228 .void,
229 .bool,
230 .int,
231 .comptime_int,
232 .float,
233 .comptime_float,
234 .null,
235 => {
236 if (name) |some| {
237 try out.print(gpa, "pub const {f}: {s} = {any};\n", .{ std.zig.fmtId(some), @typeName(T), value });
238 } else {
239 try out.print(gpa, "{any},\n", .{value});
240 }
241 return;
242 },
243 .@"enum" => |info| {
244 try printEnum(options, out, T, info, indent);
245
246 if (name) |some| {
247 try out.print(gpa, "pub const {f}: {f} = .{f};\n", .{
248 std.zig.fmtId(some),
249 std.zig.fmtId(@typeName(T)),
250 std.zig.fmtIdFlags(@tagName(value), .{ .allow_underscore = true, .allow_primitive = true }),
251 });
252 }
253 return;
254 },
255 .@"struct" => |info| {
256 try printStruct(options, out, T, info, indent);
257
258 if (name) |some| {
259 try out.print(gpa, "pub const {f}: {f} = ", .{
260 std.zig.fmtId(some),
261 std.zig.fmtId(@typeName(T)),
262 });
263 try printStructValue(options, out, info, value, indent);
264 }
265 return;
266 },
267 else => @compileError(std.fmt.comptimePrint("`{s}` are not yet supported as build options", .{@tagName(@typeInfo(T))})),
268 }
269}
270
271fn printUserDefinedType(options: *Options, out: *std.ArrayList(u8), comptime T: type, indent: u8) !void {
272 switch (@typeInfo(T)) {
273 .@"enum" => |info| {
274 return try printEnum(options, out, T, info, indent);
275 },
276 .@"struct" => |info| {
277 return try printStruct(options, out, T, info, indent);
278 },
279 else => {},
280 }
281}
282
283fn printEnum(
284 options: *Options,
285 out: *std.ArrayList(u8),
286 comptime T: type,
287 comptime val: std.builtin.Type.Enum,
288 indent: u8,
289) !void {
290 const gpa = options.step.owner.allocator;
291 const gop = try options.encountered_types.getOrPut(gpa, @typeName(T));
292 if (gop.found_existing) return;
293
294 try out.appendNTimes(gpa, ' ', indent);
295 try out.print(gpa, "pub const {f} = enum ({s}) {{\n", .{ std.zig.fmtId(@typeName(T)), @typeName(val.tag_type) });
296
297 inline for (val.fields) |field| {
298 try out.appendNTimes(gpa, ' ', indent);
299 try out.print(gpa, " {f} = {d},\n", .{
300 std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true }), field.value,
301 });
302 }
303
304 if (!val.is_exhaustive) {
305 try out.appendNTimes(gpa, ' ', indent);
306 try out.appendSlice(gpa, " _,\n");
307 }
308
309 try out.appendNTimes(gpa, ' ', indent);
310 try out.appendSlice(gpa, "};\n");
311}
312
313fn printStruct(options: *Options, out: *std.ArrayList(u8), comptime T: type, comptime val: std.builtin.Type.Struct, indent: u8) !void {
314 const gpa = options.step.owner.allocator;
315 const gop = try options.encountered_types.getOrPut(gpa, @typeName(T));
316 if (gop.found_existing) return;
317
318 try out.appendNTimes(gpa, ' ', indent);
319 try out.print(gpa, "pub const {f} = ", .{std.zig.fmtId(@typeName(T))});
320
321 switch (val.layout) {
322 .@"extern" => try out.appendSlice(gpa, "extern struct"),
323 .@"packed" => try out.appendSlice(gpa, "packed struct"),
324 else => try out.appendSlice(gpa, "struct"),
325 }
326
327 try out.appendSlice(gpa, " {\n");
328
329 inline for (val.fields) |field| {
330 try out.appendNTimes(gpa, ' ', indent);
331
332 const type_name = @typeName(field.type);
333
334 // If the type name doesn't contains a '.' the type is from zig builtins.
335 if (std.mem.containsAtLeast(u8, type_name, 1, ".")) {
336 try out.print(gpa, " {f}: {f}", .{
337 std.zig.fmtIdFlags(field.name, .{ .allow_underscore = true, .allow_primitive = true }),
338 std.zig.fmtId(type_name),
339 });
340 } else {
341 try out.print(gpa, " {f}: {s}", .{
342 std.zig.fmtIdFlags(field.name, .{ .allow_underscore = true, .allow_primitive = true }),
343 type_name,
344 });
345 }
346
347 if (field.defaultValue()) |default_value| {
348 try out.appendSlice(gpa, " = ");
349 switch (@typeInfo(@TypeOf(default_value))) {
350 .@"enum" => try out.print(gpa, ".{s},\n", .{@tagName(default_value)}),
351 .@"struct" => |info| {
352 try printStructValue(options, out, info, default_value, indent + 4);
353 },
354 else => try printType(options, out, @TypeOf(default_value), default_value, indent, null),
355 }
356 } else {
357 try out.appendSlice(gpa, ",\n");
358 }
359 }
360
361 // TODO: write declarations
362
363 try out.appendNTimes(gpa, ' ', indent);
364 try out.appendSlice(gpa, "};\n");
365
366 inline for (val.fields) |field| {
367 try printUserDefinedType(options, out, field.type, 0);
368 }
369}
370
371fn printStructValue(
372 options: *Options,
373 out: *std.ArrayList(u8),
374 comptime struct_val: std.builtin.Type.Struct,
375 val: anytype,
376 indent: u8,
377) !void {
378 const gpa = options.step.owner.allocator;
379 try out.appendSlice(gpa, ".{\n");
380
381 if (struct_val.is_tuple) {
382 inline for (struct_val.fields) |field| {
383 try out.appendNTimes(gpa, ' ', indent);
384 try printType(options, out, @TypeOf(@field(val, field.name)), @field(val, field.name), indent, null);
385 }
386 } else {
387 inline for (struct_val.fields) |field| {
388 try out.appendNTimes(gpa, ' ', indent);
389 try out.print(gpa, " .{f} = ", .{
390 std.zig.fmtIdFlags(field.name, .{ .allow_primitive = true, .allow_underscore = true }),
391 });
392
393 const field_name = @field(val, field.name);
394 switch (@typeInfo(@TypeOf(field_name))) {
395 .@"enum" => try out.print(gpa, ".{s},\n", .{@tagName(field_name)}),
396 .@"struct" => |struct_info| {
397 try printStructValue(options, out, struct_info, field_name, indent + 4);
398 },
399 else => try printType(options, out, @TypeOf(field_name), field_name, indent, null),
400 }
401 }
402 }
403
404 if (indent == 0) {
405 try out.appendSlice(gpa, "};\n");
406 } else {
407 try out.appendNTimes(gpa, ' ', indent);
408 try out.appendSlice(gpa, "},\n");
409 }
410}
411
412/// The value is the path in the cache dir.
413/// Adds a dependency automatically.
414pub fn addOptionPath(
415 options: *Options,
416 name: []const u8,
417 path: LazyPath,
418) void {
419 const arena = options.step.owner.allocator;
420 options.args.append(arena, .{
421 .name = options.step.owner.dupe(name),
422 .path = path.dupe(options.step.owner),
423 }) catch @panic("OOM");
424 path.addStepDependencies(&options.step);
425}
426
427pub fn createModule(options: *Options) *std.Build.Module {
428 return options.step.owner.createModule(.{
429 .root_source_file = options.getOutput(),
430 });
431}
432
433/// Returns the main artifact of this Build Step which is a Zig source file
434/// generated from the key-value pairs of the Options.
435pub fn getOutput(options: *Options) LazyPath {
436 return .{ .generated = .{ .file = &options.generated_file } };
437}
438
439fn make(step: *Step, make_options: Step.MakeOptions) !void {
440 // This step completes so quickly that no progress reporting is necessary.
441 _ = make_options;
442
443 const b = step.owner;
444 const options: *Options = @fieldParentPtr("step", step);
445
446 for (options.args.items) |item| {
447 options.addOption(
448 []const u8,
449 item.name,
450 item.path.getPath2(b, step),
451 );
452 }
453 if (!step.inputs.populated()) for (options.args.items) |item| {
454 try step.addWatchInput(item.path);
455 };
456
457 const basename = "options.zig";
458
459 // Hash contents to file name.
460 var hash = b.graph.cache.hash;
461 // Random bytes to make unique. Refresh this with new random bytes when
462 // implementation is modified in a non-backwards-compatible way.
463 hash.add(@as(u32, 0xad95e922));
464 hash.addBytes(options.contents.items);
465 const sub_path = "c" ++ fs.path.sep_str ++ hash.final() ++ fs.path.sep_str ++ basename;
466
467 options.generated_file.path = try b.cache_root.join(b.allocator, &.{sub_path});
468
469 // Optimize for the hot path. Stat the file, and if it already exists,
470 // cache hit.
471 if (b.cache_root.handle.access(sub_path, .{})) |_| {
472 // This is the hot path, success.
473 step.result_cached = true;
474 return;
475 } else |outer_err| switch (outer_err) {
476 error.FileNotFound => {
477 const sub_dirname = fs.path.dirname(sub_path).?;
478 b.cache_root.handle.makePath(sub_dirname) catch |e| {
479 return step.fail("unable to make path '{f}{s}': {s}", .{
480 b.cache_root, sub_dirname, @errorName(e),
481 });
482 };
483
484 const rand_int = std.crypto.random.int(u64);
485 const tmp_sub_path = "tmp" ++ fs.path.sep_str ++
486 std.fmt.hex(rand_int) ++ fs.path.sep_str ++
487 basename;
488 const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?;
489
490 b.cache_root.handle.makePath(tmp_sub_path_dirname) catch |err| {
491 return step.fail("unable to make temporary directory '{f}{s}': {s}", .{
492 b.cache_root, tmp_sub_path_dirname, @errorName(err),
493 });
494 };
495
496 b.cache_root.handle.writeFile(.{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| {
497 return step.fail("unable to write options to '{f}{s}': {s}", .{
498 b.cache_root, tmp_sub_path, @errorName(err),
499 });
500 };
501
502 b.cache_root.handle.rename(tmp_sub_path, sub_path) catch |err| switch (err) {
503 error.PathAlreadyExists => {
504 // Other process beat us to it. Clean up the temp file.
505 b.cache_root.handle.deleteFile(tmp_sub_path) catch |e| {
506 try step.addError("warning: unable to delete temp file '{f}{s}': {s}", .{
507 b.cache_root, tmp_sub_path, @errorName(e),
508 });
509 };
510 step.result_cached = true;
511 return;
512 },
513 else => {
514 return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {s}", .{
515 b.cache_root, tmp_sub_path,
516 b.cache_root, sub_path,
517 @errorName(err),
518 });
519 },
520 };
521 },
522 else => |e| return step.fail("unable to access options file '{f}{s}': {s}", .{
523 b.cache_root, sub_path, @errorName(e),
524 }),
525 }
526}
527
528const Arg = struct {
529 name: []const u8,
530 path: LazyPath,
531};
532
533test Options {
534 if (builtin.os.tag == .wasi) return error.SkipZigTest;
535
536 const io = std.testing.io;
537
538 var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
539 defer arena.deinit();
540
541 var graph: std.Build.Graph = .{
542 .io = io,
543 .arena = arena.allocator(),
544 .cache = .{
545 .io = io,
546 .gpa = arena.allocator(),
547 .manifest_dir = std.fs.cwd(),
548 },
549 .zig_exe = "test",
550 .env_map = std.process.EnvMap.init(arena.allocator()),
551 .global_cache_root = .{ .path = "test", .handle = std.fs.cwd() },
552 .host = .{
553 .query = .{},
554 .result = try std.zig.system.resolveTargetQuery(io, .{}),
555 },
556 .zig_lib_directory = std.Build.Cache.Directory.cwd(),
557 .time_report = false,
558 };
559
560 var builder = try std.Build.create(
561 &graph,
562 .{ .path = "test", .handle = std.fs.cwd() },
563 .{ .path = "test", .handle = std.fs.cwd() },
564 &.{},
565 );
566
567 const options = builder.addOptions();
568
569 const KeywordEnum = enum {
570 @"0.8.1",
571 };
572
573 const NormalEnum = enum {
574 foo,
575 bar,
576 };
577
578 const nested_array = [2][2]u16{
579 [2]u16{ 300, 200 },
580 [2]u16{ 300, 200 },
581 };
582 const nested_slice: []const []const u16 = &[_][]const u16{ &nested_array[0], &nested_array[1] };
583
584 const NormalStruct = struct {
585 hello: ?[]const u8,
586 world: bool = true,
587 };
588
589 const NestedStruct = struct {
590 normal_struct: NormalStruct,
591 normal_enum: NormalEnum = .foo,
592 };
593
594 options.addOption(usize, "option1", 1);
595 options.addOption(?usize, "option2", null);
596 options.addOption(?usize, "option3", 3);
597 options.addOption(comptime_int, "option4", 4);
598 options.addOption(comptime_float, "option5", 5.01);
599 options.addOption([]const u8, "string", "zigisthebest");
600 options.addOption(?[]const u8, "optional_string", null);
601 options.addOption([2][2]u16, "nested_array", nested_array);
602 options.addOption([]const []const u16, "nested_slice", nested_slice);
603 options.addOption(KeywordEnum, "keyword_enum", .@"0.8.1");
604 options.addOption(std.SemanticVersion, "semantic_version", try std.SemanticVersion.parse("0.1.2-foo+bar"));
605 options.addOption(NormalEnum, "normal1_enum", NormalEnum.foo);
606 options.addOption(NormalEnum, "normal2_enum", NormalEnum.bar);
607 options.addOption(NormalStruct, "normal1_struct", NormalStruct{
608 .hello = "foo",
609 });
610 options.addOption(NormalStruct, "normal2_struct", NormalStruct{
611 .hello = null,
612 .world = false,
613 });
614 options.addOption(NestedStruct, "nested_struct", NestedStruct{
615 .normal_struct = .{ .hello = "bar" },
616 });
617
618 try std.testing.expectEqualStrings(
619 \\pub const option1: usize = 1;
620 \\pub const option2: ?usize = null;
621 \\pub const option3: ?usize = 3;
622 \\pub const option4: comptime_int = 4;
623 \\pub const option5: comptime_float = 5.01;
624 \\pub const string: []const u8 = "zigisthebest";
625 \\pub const optional_string: ?[]const u8 = null;
626 \\pub const nested_array: [2][2]u16 = [2][2]u16 {
627 \\ [2]u16 {
628 \\ 300,
629 \\ 200,
630 \\ },
631 \\ [2]u16 {
632 \\ 300,
633 \\ 200,
634 \\ },
635 \\};
636 \\pub const nested_slice: []const []const u16 = &[_][]const u16 {
637 \\ &[_]u16 {
638 \\ 300,
639 \\ 200,
640 \\ },
641 \\ &[_]u16 {
642 \\ 300,
643 \\ 200,
644 \\ },
645 \\};
646 \\pub const @"Build.Step.Options.decltest.Options.KeywordEnum" = enum (u0) {
647 \\ @"0.8.1" = 0,
648 \\};
649 \\pub const keyword_enum: @"Build.Step.Options.decltest.Options.KeywordEnum" = .@"0.8.1";
650 \\pub const semantic_version: @import("std").SemanticVersion = .{
651 \\ .major = 0,
652 \\ .minor = 1,
653 \\ .patch = 2,
654 \\ .pre = "foo",
655 \\ .build = "bar",
656 \\};
657 \\pub const @"Build.Step.Options.decltest.Options.NormalEnum" = enum (u1) {
658 \\ foo = 0,
659 \\ bar = 1,
660 \\};
661 \\pub const normal1_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo;
662 \\pub const normal2_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .bar;
663 \\pub const @"Build.Step.Options.decltest.Options.NormalStruct" = struct {
664 \\ hello: ?[]const u8,
665 \\ world: bool = true,
666 \\};
667 \\pub const normal1_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{
668 \\ .hello = "foo",
669 \\ .world = true,
670 \\};
671 \\pub const normal2_struct: @"Build.Step.Options.decltest.Options.NormalStruct" = .{
672 \\ .hello = null,
673 \\ .world = false,
674 \\};
675 \\pub const @"Build.Step.Options.decltest.Options.NestedStruct" = struct {
676 \\ normal_struct: @"Build.Step.Options.decltest.Options.NormalStruct",
677 \\ normal_enum: @"Build.Step.Options.decltest.Options.NormalEnum" = .foo,
678 \\};
679 \\pub const nested_struct: @"Build.Step.Options.decltest.Options.NestedStruct" = .{
680 \\ .normal_struct = .{
681 \\ .hello = "bar",
682 \\ .world = true,
683 \\ },
684 \\ .normal_enum = .foo,
685 \\};
686 \\
687 , options.contents.items);
688
689 _ = try std.zig.Ast.parse(arena.allocator(), try options.contents.toOwnedSliceSentinel(arena.allocator(), 0), .zig);
690}