From 2d6520d5d4cd912cdd20db6cfa155e871efc6f59 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 26 Apr 2019 20:41:37 -0400 Subject: [PATCH] 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. --- CMakeLists.txt | 5 - src-self-hosted/stage1.zig | 275 +++++++++++++++++++++++++++++++++++++ src/main.cpp | 32 +---- src/userland.cpp | 5 + src/userland.h | 3 + std/special/fmt_runner.zig | 265 ----------------------------------- 6 files changed, 284 insertions(+), 301 deletions(-) delete mode 100644 std/special/fmt_runner.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index f3911699f..39892e6c2 100644 --- a/CMakeLists.txt +++ b/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/") diff --git a/src-self-hosted/stage1.zig b/src-self-hosted/stage1.zig index d05ee4e16..e05918b61 100644 --- a/src-self-hosted/stage1.zig +++ b/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, "", 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; diff --git a/src/main.cpp b/src/main.cpp index 2b65999e3..c803dfa17 100644 --- a/src/main.cpp +++ b/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 args_with_exe = {0}; - ZigList 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) { diff --git a/src/userland.cpp b/src/userland.cpp index fad1450f0..20740711e 100644 --- a/src/userland.cpp +++ b/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)); +} diff --git a/src/userland.h b/src/userland.h index ba797d8b2..e48b80d0c 100644 --- a/src/userland.h +++ b/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 diff --git a/std/special/fmt_runner.zig b/std/special/fmt_runner.zig deleted file mode 100644 index e63de4d01..000000000 --- a/std/special/fmt_runner.zig +++ /dev/null @@ -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, "", 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"); -}