Skip to content

Commit 2d6520d

Browse files
committed
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 commit 2d6520d

File tree

6 files changed

+284
-301
lines changed

6 files changed

+284
-301
lines changed

CMakeLists.txt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,6 @@ set(ZIG_STD_FILES
685685
"special/compiler_rt/udivmodti4.zig"
686686
"special/compiler_rt/udivti3.zig"
687687
"special/compiler_rt/umodti3.zig"
688-
"special/fmt_runner.zig"
689688
"special/init-exe/build.zig"
690689
"special/init-exe/src/main.zig"
691690
"special/init-lib/build.zig"
@@ -6744,7 +6743,3 @@ foreach(file ${ZIG_LIBCXX_FILES})
67446743
get_filename_component(file_dir "${LIBCXX_FILES_DEST}/${file}" DIRECTORY)
67456744
install(FILES "${CMAKE_SOURCE_DIR}/libcxx/${file}" DESTINATION "${file_dir}")
67466745
endforeach()
6747-
6748-
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/arg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
6749-
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/main.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")
6750-
install(FILES "${CMAKE_SOURCE_DIR}/src-self-hosted/errmsg.zig" DESTINATION "${ZIG_STD_DEST}/special/fmt/")

src-self-hosted/stage1.zig

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The prototypes in src/userland.h must match these definitions.
33

44
const std = @import("std");
5+
const builtin = @import("builtin");
56

67
// ABI warning
78
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 {
119120
};
120121
return Error.None;
121122
}
123+
124+
// TODO: just use the actual self-hosted zig fmt. Until the coroutine rewrite, we use a blocking implementation.
125+
export fn stage2_fmt(argc: c_int, argv: [*]const [*]const u8) c_int {
126+
if (std.debug.runtime_safety) {
127+
fmtMain(argc, argv) catch unreachable;
128+
} else {
129+
fmtMain(argc, argv) catch |e| {
130+
std.debug.warn("{}\n", @errorName(e));
131+
return -1;
132+
}
133+
}
134+
return 0;
135+
}
136+
137+
fn fmtMain(argc: c_int, argv: [*]const [*]const u8) !void {
138+
const allocator = std.heap.c_allocator;
139+
var args_list = std.ArrayList([]const u8).init(allocator);
140+
const argc_usize = @intCast(usize, argc);
141+
var arg_i: usize = 0;
142+
while (arg_i < argc_usize) : (arg_i += 1) {
143+
try args_list.append(std.mem.toSliceConst(u8, argv[arg_i]));
144+
}
145+
146+
var stdout_file = try std.io.getStdOut();
147+
var stdout_out_stream = stdout_file.outStream();
148+
stdout = &stdout_out_stream.stream;
149+
150+
stderr_file = try std.io.getStdErr();
151+
var stderr_out_stream = stderr_file.outStream();
152+
stderr = &stderr_out_stream.stream;
153+
154+
const args = args_list.toSliceConst();
155+
var flags = try Args.parse(allocator, self_hosted_main.args_fmt_spec, args[2..]);
156+
defer flags.deinit();
157+
158+
if (flags.present("help")) {
159+
try stdout.write(self_hosted_main.usage_fmt);
160+
os.exit(0);
161+
}
162+
163+
const color = blk: {
164+
if (flags.single("color")) |color_flag| {
165+
if (mem.eql(u8, color_flag, "auto")) {
166+
break :blk errmsg.Color.Auto;
167+
} else if (mem.eql(u8, color_flag, "on")) {
168+
break :blk errmsg.Color.On;
169+
} else if (mem.eql(u8, color_flag, "off")) {
170+
break :blk errmsg.Color.Off;
171+
} else unreachable;
172+
} else {
173+
break :blk errmsg.Color.Auto;
174+
}
175+
};
176+
177+
if (flags.present("stdin")) {
178+
if (flags.positionals.len != 0) {
179+
try stderr.write("cannot use --stdin with positional arguments\n");
180+
os.exit(1);
181+
}
182+
183+
var stdin_file = try io.getStdIn();
184+
var stdin = stdin_file.inStream();
185+
186+
const source_code = try stdin.stream.readAllAlloc(allocator, self_hosted_main.max_src_size);
187+
defer allocator.free(source_code);
188+
189+
const tree = std.zig.parse(allocator, source_code) catch |err| {
190+
try stderr.print("error parsing stdin: {}\n", err);
191+
os.exit(1);
192+
};
193+
defer tree.deinit();
194+
195+
var error_it = tree.errors.iterator(0);
196+
while (error_it.next()) |parse_error| {
197+
try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
198+
}
199+
if (tree.errors.len != 0) {
200+
os.exit(1);
201+
}
202+
if (flags.present("check")) {
203+
const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
204+
const code = if (anything_changed) u8(1) else u8(0);
205+
os.exit(code);
206+
}
207+
208+
_ = try std.zig.render(allocator, stdout, tree);
209+
return;
210+
}
211+
212+
if (flags.positionals.len == 0) {
213+
try stderr.write("expected at least one source file argument\n");
214+
os.exit(1);
215+
}
216+
217+
var fmt = Fmt{
218+
.seen = Fmt.SeenMap.init(allocator),
219+
.any_error = false,
220+
.color = color,
221+
.allocator = allocator,
222+
};
223+
224+
const check_mode = flags.present("check");
225+
226+
for (flags.positionals.toSliceConst()) |file_path| {
227+
try fmtPath(&fmt, file_path, check_mode);
228+
}
229+
if (fmt.any_error) {
230+
os.exit(1);
231+
}
232+
}
233+
234+
const FmtError = error{
235+
SystemResources,
236+
OperationAborted,
237+
IoPending,
238+
BrokenPipe,
239+
Unexpected,
240+
WouldBlock,
241+
FileClosed,
242+
DestinationAddressRequired,
243+
DiskQuota,
244+
FileTooBig,
245+
InputOutput,
246+
NoSpaceLeft,
247+
AccessDenied,
248+
OutOfMemory,
249+
RenameAcrossMountPoints,
250+
ReadOnlyFileSystem,
251+
LinkQuotaExceeded,
252+
FileBusy,
253+
} || os.File.OpenError;
254+
255+
fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtError!void {
256+
const file_path = try std.mem.dupe(fmt.allocator, u8, file_path_ref);
257+
defer fmt.allocator.free(file_path);
258+
259+
if (try fmt.seen.put(file_path, {})) |_| return;
260+
261+
const source_code = io.readFileAlloc(fmt.allocator, file_path) catch |err| switch (err) {
262+
error.IsDir, error.AccessDenied => {
263+
// TODO make event based (and dir.next())
264+
var dir = try std.os.Dir.open(fmt.allocator, file_path);
265+
defer dir.close();
266+
267+
while (try dir.next()) |entry| {
268+
if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) {
269+
const full_path = try os.path.join(fmt.allocator, [][]const u8{ file_path, entry.name });
270+
try fmtPath(fmt, full_path, check_mode);
271+
}
272+
}
273+
return;
274+
},
275+
else => {
276+
// TODO lock stderr printing
277+
try stderr.print("unable to open '{}': {}\n", file_path, err);
278+
fmt.any_error = true;
279+
return;
280+
},
281+
};
282+
defer fmt.allocator.free(source_code);
283+
284+
const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
285+
try stderr.print("error parsing file '{}': {}\n", file_path, err);
286+
fmt.any_error = true;
287+
return;
288+
};
289+
defer tree.deinit();
290+
291+
var error_it = tree.errors.iterator(0);
292+
while (error_it.next()) |parse_error| {
293+
try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
294+
}
295+
if (tree.errors.len != 0) {
296+
fmt.any_error = true;
297+
return;
298+
}
299+
300+
if (check_mode) {
301+
const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
302+
if (anything_changed) {
303+
try stderr.print("{}\n", file_path);
304+
fmt.any_error = true;
305+
}
306+
} else {
307+
const baf = try io.BufferedAtomicFile.create(fmt.allocator, file_path);
308+
defer baf.destroy();
309+
310+
const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
311+
if (anything_changed) {
312+
try stderr.print("{}\n", file_path);
313+
try baf.finish();
314+
}
315+
}
316+
}
317+
318+
const Fmt = struct {
319+
seen: SeenMap,
320+
any_error: bool,
321+
color: errmsg.Color,
322+
allocator: *mem.Allocator,
323+
324+
const SeenMap = std.HashMap([]const u8, void, mem.hash_slice_u8, mem.eql_slice_u8);
325+
};
326+
327+
fn printErrMsgToFile(
328+
allocator: *mem.Allocator,
329+
parse_error: *const ast.Error,
330+
tree: *ast.Tree,
331+
path: []const u8,
332+
file: os.File,
333+
color: errmsg.Color,
334+
) !void {
335+
const color_on = switch (color) {
336+
errmsg.Color.Auto => file.isTty(),
337+
errmsg.Color.On => true,
338+
errmsg.Color.Off => false,
339+
};
340+
const lok_token = parse_error.loc();
341+
const span = errmsg.Span{
342+
.first = lok_token,
343+
.last = lok_token,
344+
};
345+
346+
const first_token = tree.tokens.at(span.first);
347+
const last_token = tree.tokens.at(span.last);
348+
const start_loc = tree.tokenLocationPtr(0, first_token);
349+
const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
350+
351+
var text_buf = try std.Buffer.initSize(allocator, 0);
352+
var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
353+
try parse_error.render(&tree.tokens, out_stream);
354+
const text = text_buf.toOwnedSlice();
355+
356+
const stream = &file.outStream().stream;
357+
if (!color_on) {
358+
try stream.print(
359+
"{}:{}:{}: error: {}\n",
360+
path,
361+
start_loc.line + 1,
362+
start_loc.column + 1,
363+
text,
364+
);
365+
return;
366+
}
367+
368+
try stream.print(
369+
"{}:{}:{}: error: {}\n{}\n",
370+
path,
371+
start_loc.line + 1,
372+
start_loc.column + 1,
373+
text,
374+
tree.source[start_loc.line_start..start_loc.line_end],
375+
);
376+
try stream.writeByteNTimes(' ', start_loc.column);
377+
try stream.writeByteNTimes('~', last_token.end - first_token.start);
378+
try stream.write("\n");
379+
}
380+
381+
const os = std.os;
382+
const io = std.io;
383+
const mem = std.mem;
384+
const Allocator = mem.Allocator;
385+
const ArrayList = std.ArrayList;
386+
const Buffer = std.Buffer;
387+
388+
const arg = @import("arg.zig");
389+
const self_hosted_main = @import("main.zig");
390+
const Args = arg.Args;
391+
const Flag = arg.Flag;
392+
const errmsg = @import("errmsg.zig");
393+
394+
var stderr_file: os.File = undefined;
395+
var stderr: *io.OutStream(os.File.WriteError) = undefined;
396+
var stdout: *io.OutStream(os.File.WriteError) = undefined;

src/main.cpp

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -588,37 +588,7 @@ int main(int argc, char **argv) {
588588
}
589589
return (term.how == TerminationIdClean) ? term.code : -1;
590590
} else if (argc >= 2 && strcmp(argv[1], "fmt") == 0) {
591-
init_all_targets();
592-
ZigTarget target;
593-
get_native_target(&target);
594-
Buf *zig_lib_dir = (override_lib_dir == nullptr) ? get_zig_lib_dir() : override_lib_dir;
595-
Buf *fmt_runner_path = buf_alloc();
596-
os_path_join(get_zig_special_dir(zig_lib_dir), buf_create_from_str("fmt_runner.zig"), fmt_runner_path);
597-
Buf *cache_dir_buf = buf_create_from_str(cache_dir ? cache_dir : default_zig_cache_name);
598-
CodeGen *g = codegen_create(main_pkg_path, fmt_runner_path, &target, OutTypeExe,
599-
BuildModeDebug, zig_lib_dir, nullptr, nullptr, cache_dir_buf);
600-
g->valgrind_support = valgrind_support;
601-
g->want_single_threaded = true;
602-
codegen_set_out_name(g, buf_create_from_str("fmt"));
603-
g->enable_cache = true;
604-
605-
codegen_build_and_link(g);
606-
607-
// TODO standardize os.cpp so that the args are supposed to have the exe
608-
ZigList<const char*> args_with_exe = {0};
609-
ZigList<const char*> args_without_exe = {0};
610-
const char *exec_path = buf_ptr(&g->output_file_path);
611-
args_with_exe.append(exec_path);
612-
for (int i = 2; i < argc; i += 1) {
613-
args_with_exe.append(argv[i]);
614-
args_without_exe.append(argv[i]);
615-
}
616-
args_with_exe.append(nullptr);
617-
os_execv(exec_path, args_with_exe.items);
618-
619-
Termination term;
620-
os_spawn_process(exec_path, args_without_exe, &term);
621-
return term.code;
591+
return stage2_fmt(argc, argv);
622592
}
623593

624594
for (int i = 1; i < argc; i += 1) {

src/userland.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,8 @@ void stage2_render_ast(struct Stage2Ast *ast, FILE *output_file) {
3737
const char *msg = "stage0 called stage2_render_ast";
3838
stage2_panic(msg, strlen(msg));
3939
}
40+
41+
int stage2_fmt(int argc, char **argv) {
42+
const char *msg = "stage0 called stage2_fmt";
43+
stage2_panic(msg, strlen(msg));
44+
}

src/userland.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,7 @@ ZIG_EXTERN_C void stage2_zen(const char **ptr, size_t *len);
114114
// ABI warning
115115
ZIG_EXTERN_C ZIG_ATTRIBUTE_NORETURN void stage2_panic(const char *ptr, size_t len);
116116

117+
// ABI warning
118+
ZIG_EXTERN_C int stage2_fmt(int argc, char **argv);
119+
117120
#endif

0 commit comments

Comments
 (0)