aboutsummaryrefslogtreecommitdiff
path: root/lib/std/Build/Step/Fmt.zig
blob: a364dfa6f47805d8421e73793ffab187bc76599d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! This step has two modes:
//! * Modify mode: directly modify source files, formatting them in place.
//! * Check mode: fail the step if a non-conforming file is found.
const std = @import("std");
const Step = std.Build.Step;
const Fmt = @This();

step: Step,
paths: []const []const u8,
exclude_paths: []const []const u8,
check: bool,

pub const base_id: Step.Id = .fmt;

pub const Options = struct {
    paths: []const []const u8 = &.{},
    exclude_paths: []const []const u8 = &.{},
    /// If true, fails the build step when any non-conforming files are encountered.
    check: bool = false,
};

pub fn create(owner: *std.Build, options: Options) *Fmt {
    const fmt = owner.allocator.create(Fmt) catch @panic("OOM");
    const name = if (options.check) "zig fmt --check" else "zig fmt";
    fmt.* = .{
        .step = Step.init(.{
            .id = base_id,
            .name = name,
            .owner = owner,
            .makeFn = make,
        }),
        .paths = owner.dupeStrings(options.paths),
        .exclude_paths = owner.dupeStrings(options.exclude_paths),
        .check = options.check,
    };
    return fmt;
}

fn make(step: *Step, options: Step.MakeOptions) !void {
    const prog_node = options.progress_node;

    // TODO: if check=false, this means we are modifying source files in place, which
    // is an operation that could race against other operations also modifying source files
    // in place. In this case, this step should obtain a write lock while making those
    // modifications.

    const b = step.owner;
    const arena = b.allocator;
    const fmt: *Fmt = @fieldParentPtr("step", step);

    var argv: std.ArrayListUnmanaged([]const u8) = .empty;
    try argv.ensureUnusedCapacity(arena, 2 + 1 + fmt.paths.len + 2 * fmt.exclude_paths.len);

    argv.appendAssumeCapacity(b.graph.zig_exe);
    argv.appendAssumeCapacity("fmt");

    if (fmt.check) {
        argv.appendAssumeCapacity("--check");
    }

    for (fmt.paths) |p| {
        argv.appendAssumeCapacity(b.pathFromRoot(p));
    }

    for (fmt.exclude_paths) |p| {
        argv.appendAssumeCapacity("--exclude");
        argv.appendAssumeCapacity(b.pathFromRoot(p));
    }

    const run_result = try step.captureChildProcess(prog_node, argv.items);
    if (fmt.check) switch (run_result.term) {
        .Exited => |code| if (code != 0 and run_result.stdout.len != 0) {
            var it = std.mem.tokenizeScalar(u8, run_result.stdout, '\n');
            while (it.next()) |bad_file_name| {
                try step.addError("{s}: non-conforming formatting", .{bad_file_name});
            }
        },
        else => {},
    };
    try step.handleChildProcessTerm(run_result.term, null, argv.items);
}