master
1//! This step has two modes:
2//! * Modify mode: directly modify source files, formatting them in place.
3//! * Check mode: fail the step if a non-conforming file is found.
4const std = @import("std");
5const Step = std.Build.Step;
6const Fmt = @This();
7
8step: Step,
9paths: []const []const u8,
10exclude_paths: []const []const u8,
11check: bool,
12
13pub const base_id: Step.Id = .fmt;
14
15pub const Options = struct {
16 paths: []const []const u8 = &.{},
17 exclude_paths: []const []const u8 = &.{},
18 /// If true, fails the build step when any non-conforming files are encountered.
19 check: bool = false,
20};
21
22pub fn create(owner: *std.Build, options: Options) *Fmt {
23 const fmt = owner.allocator.create(Fmt) catch @panic("OOM");
24 const name = if (options.check) "zig fmt --check" else "zig fmt";
25 fmt.* = .{
26 .step = Step.init(.{
27 .id = base_id,
28 .name = name,
29 .owner = owner,
30 .makeFn = make,
31 }),
32 .paths = owner.dupeStrings(options.paths),
33 .exclude_paths = owner.dupeStrings(options.exclude_paths),
34 .check = options.check,
35 };
36 return fmt;
37}
38
39fn make(step: *Step, options: Step.MakeOptions) !void {
40 const prog_node = options.progress_node;
41
42 // TODO: if check=false, this means we are modifying source files in place, which
43 // is an operation that could race against other operations also modifying source files
44 // in place. In this case, this step should obtain a write lock while making those
45 // modifications.
46
47 const b = step.owner;
48 const arena = b.allocator;
49 const fmt: *Fmt = @fieldParentPtr("step", step);
50
51 var argv: std.ArrayList([]const u8) = .empty;
52 try argv.ensureUnusedCapacity(arena, 2 + 1 + fmt.paths.len + 2 * fmt.exclude_paths.len);
53
54 argv.appendAssumeCapacity(b.graph.zig_exe);
55 argv.appendAssumeCapacity("fmt");
56
57 if (fmt.check) {
58 argv.appendAssumeCapacity("--check");
59 }
60
61 for (fmt.paths) |p| {
62 argv.appendAssumeCapacity(b.pathFromRoot(p));
63 }
64
65 for (fmt.exclude_paths) |p| {
66 argv.appendAssumeCapacity("--exclude");
67 argv.appendAssumeCapacity(b.pathFromRoot(p));
68 }
69
70 const run_result = try step.captureChildProcess(options.gpa, prog_node, argv.items);
71 if (fmt.check) switch (run_result.term) {
72 .Exited => |code| if (code != 0 and run_result.stdout.len != 0) {
73 var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
74 while (it.next()) |bad_file_name| {
75 try step.addError("{s}: non-conforming formatting", .{bad_file_name});
76 }
77 },
78 else => {},
79 };
80 try step.handleChildProcessTerm(run_result.term);
81}