Commit 2d6520d5d4

Andrew Kelley <andrew@ziglang.org>
2019-04-27 02:41:37
zig fmt is built directly into stage1 rather than child process
Previously, `zig fmt` on the stage1 compiler (which is what we currently ship) would perform what equates to `zig run std/special/fmt_runner.zig` Now, `zig fmt` is implemented with the hybrid zig/C++ strategy outlined by #1964. This means Zig no longer has to ship some of the stage2 .zig files, and there is no longer a delay when running `zig fmt` for the first time.
1 parent ac39466
Changed files (6)
src/main.cpp
@@ -588,37 +588,7 @@ int main(int argc, char **argv) {
         }
         return (term.how == TerminationIdClean) ? term.code : -1;
     } else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
-        init_all_targets();
-        ZigTarget target;
-        get_native_target(&target);
-        Buf *zig_lib_dir = (override_lib_dir == nullptr) ? get_zig_lib_dir() : override_lib_dir;
-        Buf *fmt_runner_path = buf_alloc();
-        os_path_join(get_zig_special_dir(zig_lib_dir), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
-        Buf *cache_dir_buf = buf_create_from_str(cache_dir ? cache_dir : default_zig_cache_name);
-        CodeGen *g = codegen_create(main_pkg_path, fmt_runner_path, &target, OutTypeExe,
-                BuildModeDebug, zig_lib_dir, nullptr, nullptr, cache_dir_buf);
-        g->valgrind_support = valgrind_support;
-        g->want_single_threaded = true;
-        codegen_set_out_name(g, buf_create_from_str("fmt"));
-        g->enable_cache = true;
-
-        codegen_build_and_link(g);
-
-        // TODO standardize os.cpp so that the args are supposed to have the exe
-        ZigList<const char*> args_with_exe = {0};
-        ZigList<const char*> args_without_exe = {0};
-        const char *exec_path = buf_ptr(&g->output_file_path);
-        args_with_exe.append(exec_path);
-        for (int i = 2; i < argc; i += 1) {
-            args_with_exe.append(argv[i]);
-            args_without_exe.append(argv[i]);
-        }
-        args_with_exe.append(nullptr);
-        os_execv(exec_path, args_with_exe.items);
-
-        Termination term;
-        os_spawn_process(exec_path, args_without_exe, &term);
-        return term.code;
+        return stage2_fmt(argc, argv);
     }
 
     for (int i = 1; i < argc; i += 1) {
src/userland.cpp
@@ -37,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) {
     const char *msg = "stage0 called stage2_render_ast";
     stage2_panic(msg, strlen(msg));
 }
+
+int stage2_fmt(int argc, char **argv) {
+    const char *msg = "stage0 called stage2_fmt";
+    stage2_panic(msg, strlen(msg));
+}
src/userland.h
@@ -114,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len);
 // ABI warning
 ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len);
 
+// ABI warning
+ZIG_EXTERN_C int stage2_fmt(int argc, char **argv);
+
 #endif
src-self-hosted/stage1.zig
@@ -2,6 +2,7 @@
 // The prototypes in src/userland.h must match these definitions.
 
 const std = @import("std");
+const builtin = @import("builtin");
 
 // ABI warning
 export fn stage2_zen(ptr: *[*]const u8, len: *usize) void {
@@ -119,3 +120,277 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
     };
     return Error.None;
 }
+
+// TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation.
+export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int {
+    if (std.debug.runtime_safety) {
+        fmtMain(argc, argv) catch unreachable;
+    } else {
+        fmtMain(argc, argv) catch |e| {
+            std.debug.warn("{}\n", @errorName(e));
+            return -1;
+        }
+    }
+    return 0;
+}
+
+fn fmtMain(argc: c_int, argv: [*]const [*]const u8) !void {
+    const allocator = std.heap.c_allocator;
+    var args_list = std.ArrayList([]const u8).init(allocator);
+    const argc_usize = @intCast(usize, argc);
+    var arg_i: usize = 0;
+    while (arg_i < argc_usize) : (arg_i += 1) {
+        try args_list.append(std.mem.toSliceConst(u8, argv[arg_i]));
+    }
+
+    var stdout_file = try std.io.getStdOut();
+    var stdout_out_stream = stdout_file.outStream();
+    stdout = &stdout_out_stream.stream;
+
+    stderr_file = try std.io.getStdErr();
+    var stderr_out_stream = stderr_file.outStream();
+    stderr = &stderr_out_stream.stream;
+
+    const args = args_list.toSliceConst();
+    var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[2..]);
+    defer flags.deinit();
+
+    if (flags.present("help")) {
+        try stdout.write(self_hosted_main.usage_fmt);
+        os.exit(0);
+    }
+
+    const color = blk: {
+        if (flags.single("color")) |color_flag| {
+            if (mem.eql(u8, color_flag, "auto")) {
+                break :blk errmsg.Color.Auto;
+            } else if (mem.eql(u8, color_flag, "on")) {
+                break :blk errmsg.Color.On;
+            } else if (mem.eql(u8, color_flag, "off")) {
+                break :blk errmsg.Color.Off;
+            } else unreachable;
+        } else {
+            break :blk errmsg.Color.Auto;
+        }
+    };
+
+    if (flags.present("stdin")) {
+        if (flags.positionals.len != 0) {
+            try stderr.write("cannot use --stdin with positional arguments\n");
+            os.exit(1);
+        }
+
+        var stdin_file = try io.getStdIn();
+        var stdin = stdin_file.inStream();
+
+        const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
+        defer allocator.free(source_code);
+
+        const tree = std.zig.parse(allocator, source_code) catch |err| {
+            try stderr.print("error parsing stdin: {}\n", err);
+            os.exit(1);
+        };
+        defer tree.deinit();
+
+        var error_it = tree.errors.iterator(0);
+        while (error_it.next()) |parse_error| {
+            try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
+        }
+        if (tree.errors.len != 0) {
+            os.exit(1);
+        }
+        if (flags.present("check")) {
+            const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
+            const code = if (anything_changed) u8(1) else u8(0);
+            os.exit(code);
+        }
+
+        _ = try std.zig.render(allocator, stdout, tree);
+        return;
+    }
+
+    if (flags.positionals.len == 0) {
+        try stderr.write("expected at least one source file argument\n");
+        os.exit(1);
+    }
+
+    var fmt = Fmt{
+        .seen = Fmt.SeenMap.init(allocator),
+        .any_error = false,
+        .color = color,
+        .allocator = allocator,
+    };
+
+    const check_mode = flags.present("check");
+
+    for (flags.positionals.toSliceConst()) |file_path| {
+        try fmtPath(&fmt, file_path, check_mode);
+    }
+    if (fmt.any_error) {
+        os.exit(1);
+    }
+}
+
+const FmtError = error{
+    SystemResources,
+    OperationAborted,
+    IoPending,
+    BrokenPipe,
+    Unexpected,
+    WouldBlock,
+    FileClosed,
+    DestinationAddressRequired,
+    DiskQuota,
+    FileTooBig,
+    InputOutput,
+    NoSpaceLeft,
+    AccessDenied,
+    OutOfMemory,
+    RenameAcrossMountPoints,
+    ReadOnlyFileSystem,
+    LinkQuotaExceeded,
+    FileBusy,
+} || os.File.OpenError;
+
+fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
+    const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
+    defer fmt.allocator.free(file_path);
+
+    if (try fmt.seen.put(file_path, {})) |_| return;
+
+    const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
+        error.IsDir, error.AccessDenied => {
+            // TODO make event based (and dir.next())
+            var dir = try std.os.Dir.open(fmt.allocator, file_path);
+            defer dir.close();
+
+            while (try dir.next()) |entry| {
+                if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
+                    const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
+                    try fmtPath(fmt, full_path, check_mode);
+                }
+            }
+            return;
+        },
+        else => {
+            // TODO lock stderr printing
+            try stderr.print("unable to open '{}': {}\n", file_path, err);
+            fmt.any_error = true;
+            return;
+        },
+    };
+    defer fmt.allocator.free(source_code);
+
+    const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
+        try stderr.print("error parsing file '{}': {}\n", file_path, err);
+        fmt.any_error = true;
+        return;
+    };
+    defer tree.deinit();
+
+    var error_it = tree.errors.iterator(0);
+    while (error_it.next()) |parse_error| {
+        try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
+    }
+    if (tree.errors.len != 0) {
+        fmt.any_error = true;
+        return;
+    }
+
+    if (check_mode) {
+        const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
+        if (anything_changed) {
+            try stderr.print("{}\n", file_path);
+            fmt.any_error = true;
+        }
+    } else {
+        const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
+        defer baf.destroy();
+
+        const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
+        if (anything_changed) {
+            try stderr.print("{}\n", file_path);
+            try baf.finish();
+        }
+    }
+}
+
+const Fmt = struct {
+    seen: SeenMap,
+    any_error: bool,
+    color: errmsg.Color,
+    allocator: *mem.Allocator,
+
+    const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
+};
+
+fn printErrMsgToFile(
+    allocator: *mem.Allocator,
+    parse_error: *const ast.Error,
+    tree: *ast.Tree,
+    path: []const u8,
+    file: os.File,
+    color: errmsg.Color,
+) !void {
+    const color_on = switch (color) {
+        errmsg.Color.Auto => file.isTty(),
+        errmsg.Color.On => true,
+        errmsg.Color.Off => false,
+    };
+    const lok_token = parse_error.loc();
+    const span = errmsg.Span{
+        .first = lok_token,
+        .last = lok_token,
+    };
+
+    const first_token = tree.tokens.at(span.first);
+    const last_token = tree.tokens.at(span.last);
+    const start_loc = tree.tokenLocationPtr(0, first_token);
+    const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
+
+    var text_buf = try std.Buffer.initSize(allocator, 0);
+    var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
+    try parse_error.render(&tree.tokens, out_stream);
+    const text = text_buf.toOwnedSlice();
+
+    const stream = &file.outStream().stream;
+    if (!color_on) {
+        try stream.print(
+            "{}:{}:{}: error: {}\n",
+            path,
+            start_loc.line + 1,
+            start_loc.column + 1,
+            text,
+        );
+        return;
+    }
+
+    try stream.print(
+        "{}:{}:{}: error: {}\n{}\n",
+        path,
+        start_loc.line + 1,
+        start_loc.column + 1,
+        text,
+        tree.source[start_loc.line_start..start_loc.line_end],
+    );
+    try stream.writeByteNTimes(' ', start_loc.column);
+    try stream.writeByteNTimes('~', last_token.end - first_token.start);
+    try stream.write("\n");
+}
+
+const os = std.os;
+const io = std.io;
+const mem = std.mem;
+const Allocator = mem.Allocator;
+const ArrayList = std.ArrayList;
+const Buffer = std.Buffer;
+
+const arg = @import("arg.zig");
+const self_hosted_main = @import("main.zig");
+const Args = arg.Args;
+const Flag = arg.Flag;
+const errmsg = @import("errmsg.zig");
+
+var stderr_file: os.File = undefined;
+var stderr: *io.OutStream(os.File.WriteError) = undefined;
+var stdout: *io.OutStream(os.File.WriteError) = undefined;
std/special/fmt_runner.zig
@@ -1,265 +0,0 @@
-const std = @import("std");
-const builtin = @import("builtin");
-
-const os = std.os;
-const io = std.io;
-const mem = std.mem;
-const Allocator = mem.Allocator;
-const ArrayList = std.ArrayList;
-const Buffer = std.Buffer;
-const ast = std.zig.ast;
-
-const arg = @import("fmt/arg.zig");
-const self_hosted_main = @import("fmt/main.zig");
-const Args = arg.Args;
-const Flag = arg.Flag;
-const errmsg = @import("fmt/errmsg.zig");
-
-var stderr_file: os.File = undefined;
-var stderr: *io.OutStream(os.File.WriteError) = undefined;
-var stdout: *io.OutStream(os.File.WriteError) = undefined;
-
-// This brings `zig fmt` to stage 1.
-pub fn main() !void {
-    // Here we use an ArenaAllocator backed by a DirectAllocator because `zig fmt` is a short-lived,
-    // one shot program. We don't need to waste time freeing memory and finding places to squish
-    // bytes into. So we free everything all at once at the very end.
-    var direct_allocator = std.heap.DirectAllocator.init();
-    var arena = std.heap.ArenaAllocator.init(&direct_allocator.allocator);
-    const allocator = &arena.allocator;
-
-    var stdout_file = try std.io.getStdOut();
-    var stdout_out_stream = stdout_file.outStream();
-    stdout = &stdout_out_stream.stream;
-
-    stderr_file = try std.io.getStdErr();
-    var stderr_out_stream = stderr_file.outStream();
-    stderr = &stderr_out_stream.stream;
-    const args = try std.os.argsAlloc(allocator);
-
-    var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[1..]);
-    defer flags.deinit();
-
-    if (flags.present("help")) {
-        try stdout.write(self_hosted_main.usage_fmt);
-        os.exit(0);
-    }
-
-    const color = blk: {
-        if (flags.single("color")) |color_flag| {
-            if (mem.eql(u8, color_flag, "auto")) {
-                break :blk errmsg.Color.Auto;
-            } else if (mem.eql(u8, color_flag, "on")) {
-                break :blk errmsg.Color.On;
-            } else if (mem.eql(u8, color_flag, "off")) {
-                break :blk errmsg.Color.Off;
-            } else unreachable;
-        } else {
-            break :blk errmsg.Color.Auto;
-        }
-    };
-
-    if (flags.present("stdin")) {
-        if (flags.positionals.len != 0) {
-            try stderr.write("cannot use --stdin with positional arguments\n");
-            os.exit(1);
-        }
-
-        var stdin_file = try io.getStdIn();
-        var stdin = stdin_file.inStream();
-
-        const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
-        defer allocator.free(source_code);
-
-        const tree = std.zig.parse(allocator, source_code) catch |err| {
-            try stderr.print("error parsing stdin: {}\n", err);
-            os.exit(1);
-        };
-        defer tree.deinit();
-
-        var error_it = tree.errors.iterator(0);
-        while (error_it.next()) |parse_error| {
-            try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
-        }
-        if (tree.errors.len != 0) {
-            os.exit(1);
-        }
-        if (flags.present("check")) {
-            const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
-            const code = if (anything_changed) u8(1) else u8(0);
-            os.exit(code);
-        }
-
-        _ = try std.zig.render(allocator, stdout, tree);
-        return;
-    }
-
-    if (flags.positionals.len == 0) {
-        try stderr.write("expected at least one source file argument\n");
-        os.exit(1);
-    }
-
-    var fmt = Fmt{
-        .seen = Fmt.SeenMap.init(allocator),
-        .any_error = false,
-        .color = color,
-        .allocator = allocator,
-    };
-
-    const check_mode = flags.present("check");
-
-    for (flags.positionals.toSliceConst()) |file_path| {
-        try fmtPath(&fmt, file_path, check_mode);
-    }
-    if (fmt.any_error) {
-        os.exit(1);
-    }
-}
-
-const FmtError = error{
-    SystemResources,
-    OperationAborted,
-    IoPending,
-    BrokenPipe,
-    Unexpected,
-    WouldBlock,
-    FileClosed,
-    DestinationAddressRequired,
-    DiskQuota,
-    FileTooBig,
-    InputOutput,
-    NoSpaceLeft,
-    AccessDenied,
-    OutOfMemory,
-    RenameAcrossMountPoints,
-    ReadOnlyFileSystem,
-    LinkQuotaExceeded,
-    FileBusy,
-} || os.File.OpenError;
-
-fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
-    const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
-    defer fmt.allocator.free(file_path);
-
-    if (try fmt.seen.put(file_path, {})) |_| return;
-
-    const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
-        error.IsDir, error.AccessDenied => {
-            // TODO make event based (and dir.next())
-            var dir = try std.os.Dir.open(fmt.allocator, file_path);
-            defer dir.close();
-
-            while (try dir.next()) |entry| {
-                if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
-                    const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
-                    try fmtPath(fmt, full_path, check_mode);
-                }
-            }
-            return;
-        },
-        else => {
-            // TODO lock stderr printing
-            try stderr.print("unable to open '{}': {}\n", file_path, err);
-            fmt.any_error = true;
-            return;
-        },
-    };
-    defer fmt.allocator.free(source_code);
-
-    const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
-        try stderr.print("error parsing file '{}': {}\n", file_path, err);
-        fmt.any_error = true;
-        return;
-    };
-    defer tree.deinit();
-
-    var error_it = tree.errors.iterator(0);
-    while (error_it.next()) |parse_error| {
-        try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
-    }
-    if (tree.errors.len != 0) {
-        fmt.any_error = true;
-        return;
-    }
-
-    if (check_mode) {
-        const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
-        if (anything_changed) {
-            try stderr.print("{}\n", file_path);
-            fmt.any_error = true;
-        }
-    } else {
-        // TODO make this evented
-        const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
-        defer baf.destroy();
-
-        const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
-        if (anything_changed) {
-            try stderr.print("{}\n", file_path);
-            try baf.finish();
-        }
-    }
-}
-
-const Fmt = struct {
-    seen: SeenMap,
-    any_error: bool,
-    color: errmsg.Color,
-    allocator: *mem.Allocator,
-
-    const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
-};
-
-fn printErrMsgToFile(
-    allocator: *mem.Allocator,
-    parse_error: *const ast.Error,
-    tree: *ast.Tree,
-    path: []const u8,
-    file: os.File,
-    color: errmsg.Color,
-) !void {
-    const color_on = switch (color) {
-        errmsg.Color.Auto => file.isTty(),
-        errmsg.Color.On => true,
-        errmsg.Color.Off => false,
-    };
-    const lok_token = parse_error.loc();
-    const span = errmsg.Span{
-        .first = lok_token,
-        .last = lok_token,
-    };
-
-    const first_token = tree.tokens.at(span.first);
-    const last_token = tree.tokens.at(span.last);
-    const start_loc = tree.tokenLocationPtr(0, first_token);
-    const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
-
-    var text_buf = try std.Buffer.initSize(allocator, 0);
-    var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
-    try parse_error.render(&tree.tokens, out_stream);
-    const text = text_buf.toOwnedSlice();
-
-    const stream = &file.outStream().stream;
-    if (!color_on) {
-        try stream.print(
-            "{}:{}:{}: error: {}\n",
-            path,
-            start_loc.line + 1,
-            start_loc.column + 1,
-            text,
-        );
-        return;
-    }
-
-    try stream.print(
-        "{}:{}:{}: error: {}\n{}\n",
-        path,
-        start_loc.line + 1,
-        start_loc.column + 1,
-        text,
-        tree.source[start_loc.line_start..start_loc.line_end],
-    );
-    try stream.writeByteNTimes(' ', start_loc.column);
-    try stream.writeByteNTimes('~', last_token.end - first_token.start);
-    try stream.write("\n");
-}
CMakeLists.txt
@@ -685,7 +685,6 @@ set(ZIG_STD_FILES
     "special/compiler_rt/udivmodti4.zig"
     "special/compiler_rt/udivti3.zig"
     "special/compiler_rt/umodti3.zig"
-    "special/fmt_runner.zig"
     "special/init-exe/build.zig"
     "special/init-exe/src/main.zig"
     "special/init-lib/build.zig"
@@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES})
     get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY)
     install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}")
 endforeach()
-
-install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
-install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
-install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")