Skip to content

Commit 9d38e82

Browse files
authored
Merge pull request #20633 from ziglang/long-live-zig
make zig compiler processes live across rebuilds
2 parents 583e698 + 445bd7a commit 9d38e82

27 files changed

+285
-105
lines changed

lib/compiler/build_runner.zig

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ pub fn main() !void {
235235
prominent_compile_errors = true;
236236
} else if (mem.eql(u8, arg, "--watch")) {
237237
watch = true;
238+
} else if (mem.eql(u8, arg, "-fincremental")) {
239+
graph.incremental = true;
240+
} else if (mem.eql(u8, arg, "-fno-incremental")) {
241+
graph.incremental = false;
238242
} else if (mem.eql(u8, arg, "-fwine")) {
239243
builder.enable_wine = true;
240244
} else if (mem.eql(u8, arg, "-fno-wine")) {
@@ -406,8 +410,8 @@ pub fn main() !void {
406410
// trigger a rebuild on all steps with modified inputs, as well as their
407411
// recursive dependants.
408412
var caption_buf: [std.Progress.Node.max_name_len]u8 = undefined;
409-
const caption = std.fmt.bufPrint(&caption_buf, "Watching {d} Directories", .{
410-
w.dir_table.entries.len,
413+
const caption = std.fmt.bufPrint(&caption_buf, "watching {d} directories, {d} processes", .{
414+
w.dir_table.entries.len, countSubProcesses(run.step_stack.keys()),
411415
}) catch &caption_buf;
412416
var debouncing_node = main_progress_node.start(caption, 0);
413417
var debounce_timeout: Watch.Timeout = .none;
@@ -440,6 +444,14 @@ fn markFailedStepsDirty(gpa: Allocator, all_steps: []const *Step) void {
440444
};
441445
}
442446

447+
fn countSubProcesses(all_steps: []const *Step) usize {
448+
var count: usize = 0;
449+
for (all_steps) |s| {
450+
count += @intFromBool(s.getZigProcess() != null);
451+
}
452+
return count;
453+
}
454+
443455
const Run = struct {
444456
max_rss: u64,
445457
max_rss_is_default: bool,
@@ -1031,7 +1043,11 @@ fn workerMakeOneStep(
10311043
const sub_prog_node = prog_node.start(s.name, 0);
10321044
defer sub_prog_node.end();
10331045

1034-
const make_result = s.make(sub_prog_node);
1046+
const make_result = s.make(.{
1047+
.progress_node = sub_prog_node,
1048+
.thread_pool = thread_pool,
1049+
.watch = run.watch,
1050+
});
10351051

10361052
// No matter the result, we want to display error/warning messages.
10371053
const show_compile_errors = !run.prominent_compile_errors and
@@ -1212,6 +1228,8 @@ fn usage(b: *std.Build, out_stream: anytype) !void {
12121228
\\ --fetch Exit after fetching dependency tree
12131229
\\ --watch Continuously rebuild when source files are modified
12141230
\\ --debounce <ms> Delay before rebuilding after changed file detected
1231+
\\ -fincremental Enable incremental compilation
1232+
\\ -fno-incremental Disable incremental compilation
12151233
\\
12161234
\\Project-Specific Options:
12171235
\\

lib/std/Build.zig

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub const Graph = struct {
120120
needed_lazy_dependencies: std.StringArrayHashMapUnmanaged(void) = .{},
121121
/// Information about the native target. Computed before build() is invoked.
122122
host: ResolvedTarget,
123+
incremental: ?bool = null,
123124
};
124125

125126
const AvailableDeps = []const struct { []const u8, []const u8 };
@@ -1078,8 +1079,8 @@ pub fn getUninstallStep(b: *Build) *Step {
10781079
return &b.uninstall_tls.step;
10791080
}
10801081

1081-
fn makeUninstall(uninstall_step: *Step, prog_node: std.Progress.Node) anyerror!void {
1082-
_ = prog_node;
1082+
fn makeUninstall(uninstall_step: *Step, options: Step.MakeOptions) anyerror!void {
1083+
_ = options;
10831084
const uninstall_tls: *TopLevelStep = @fieldParentPtr("step", uninstall_step);
10841085
const b: *Build = @fieldParentPtr("uninstall_tls", uninstall_tls);
10851086

lib/std/Build/Step.zig

Lines changed: 157 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ pub const TestResults = struct {
6868
}
6969
};
7070

71-
pub const MakeFn = *const fn (step: *Step, prog_node: std.Progress.Node) anyerror!void;
71+
pub const MakeOptions = struct {
72+
progress_node: std.Progress.Node,
73+
thread_pool: *std.Thread.Pool,
74+
watch: bool,
75+
};
76+
77+
pub const MakeFn = *const fn (step: *Step, options: MakeOptions) anyerror!void;
7278

7379
pub const State = enum {
7480
precheck_unstarted,
@@ -219,10 +225,10 @@ pub fn init(options: StepOptions) Step {
219225
/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
220226
/// have already reported the error. Otherwise, we add a simple error report
221227
/// here.
222-
pub fn make(s: *Step, prog_node: std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
228+
pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void {
223229
const arena = s.owner.allocator;
224230

225-
s.makeFn(s, prog_node) catch |err| switch (err) {
231+
s.makeFn(s, options) catch |err| switch (err) {
226232
error.MakeFailed => return error.MakeFailed,
227233
error.MakeSkipped => return error.MakeSkipped,
228234
else => {
@@ -260,8 +266,8 @@ pub fn getStackTrace(s: *Step) ?std.builtin.StackTrace {
260266
};
261267
}
262268

263-
fn makeNoOp(step: *Step, prog_node: std.Progress.Node) anyerror!void {
264-
_ = prog_node;
269+
fn makeNoOp(step: *Step, options: MakeOptions) anyerror!void {
270+
_ = options;
265271

266272
var all_cached = true;
267273

@@ -352,13 +358,54 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
352358
try step.result_error_msgs.append(arena, msg);
353359
}
354360

361+
pub const ZigProcess = struct {
362+
child: std.process.Child,
363+
poller: std.io.Poller(StreamEnum),
364+
progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
365+
366+
pub const StreamEnum = enum { stdout, stderr };
367+
};
368+
355369
/// Assumes that argv contains `--listen=-` and that the process being spawned
356370
/// is the zig compiler - the same version that compiled the build runner.
357371
pub fn evalZigProcess(
358372
s: *Step,
359373
argv: []const []const u8,
360374
prog_node: std.Progress.Node,
375+
watch: bool,
361376
) !?[]const u8 {
377+
if (s.getZigProcess()) |zp| update: {
378+
assert(watch);
379+
if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
380+
const result = zigProcessUpdate(s, zp, watch) catch |err| switch (err) {
381+
error.BrokenPipe => {
382+
// Process restart required.
383+
const term = zp.child.wait() catch |e| {
384+
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(e) });
385+
};
386+
_ = term;
387+
s.clearZigProcess();
388+
break :update;
389+
},
390+
else => |e| return e,
391+
};
392+
393+
if (s.result_error_bundle.errorMessageCount() > 0)
394+
return s.fail("{d} compilation errors", .{s.result_error_bundle.errorMessageCount()});
395+
396+
if (s.result_error_msgs.items.len > 0 and result == null) {
397+
// Crash detected.
398+
const term = zp.child.wait() catch |e| {
399+
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(e) });
400+
};
401+
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
402+
s.clearZigProcess();
403+
try handleChildProcessTerm(s, term, null, argv);
404+
return error.MakeFailed;
405+
}
406+
407+
return result;
408+
}
362409
assert(argv.len != 0);
363410
const b = s.owner;
364411
const arena = b.allocator;
@@ -378,29 +425,79 @@ pub fn evalZigProcess(
378425
child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
379426
argv[0], @errorName(err),
380427
});
381-
var timer = try std.time.Timer.start();
382428

383-
var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
384-
.stdout = child.stdout.?,
385-
.stderr = child.stderr.?,
386-
});
387-
defer poller.deinit();
429+
const zp = try gpa.create(ZigProcess);
430+
zp.* = .{
431+
.child = child,
432+
.poller = std.io.poll(gpa, ZigProcess.StreamEnum, .{
433+
.stdout = child.stdout.?,
434+
.stderr = child.stderr.?,
435+
}),
436+
.progress_ipc_fd = if (std.Progress.have_ipc) child.progress_node.getIpcFd() else {},
437+
};
438+
if (watch) s.setZigProcess(zp);
439+
defer if (!watch) zp.poller.deinit();
440+
441+
const result = try zigProcessUpdate(s, zp, watch);
442+
443+
if (!watch) {
444+
// Send EOF to stdin.
445+
zp.child.stdin.?.close();
446+
zp.child.stdin = null;
447+
448+
const term = zp.child.wait() catch |err| {
449+
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
450+
};
451+
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
452+
453+
// Special handling for Compile step that is expecting compile errors.
454+
if (s.cast(Compile)) |compile| switch (term) {
455+
.Exited => {
456+
// Note that the exit code may be 0 in this case due to the
457+
// compiler server protocol.
458+
if (compile.expect_errors != null) {
459+
return error.NeedCompileErrorCheck;
460+
}
461+
},
462+
else => {},
463+
};
464+
465+
try handleChildProcessTerm(s, term, null, argv);
466+
}
467+
468+
// This is intentionally printed for failure on the first build but not for
469+
// subsequent rebuilds.
470+
if (s.result_error_bundle.errorMessageCount() > 0) {
471+
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
472+
s.result_error_bundle.errorMessageCount(),
473+
try allocPrintCmd(arena, null, argv),
474+
});
475+
}
388476

389-
try sendMessage(child.stdin.?, .update);
390-
try sendMessage(child.stdin.?, .exit);
477+
return result;
478+
}
479+
480+
fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 {
481+
const b = s.owner;
482+
const arena = b.allocator;
483+
484+
var timer = try std.time.Timer.start();
485+
486+
try sendMessage(zp.child.stdin.?, .update);
487+
if (!watch) try sendMessage(zp.child.stdin.?, .exit);
391488

392489
const Header = std.zig.Server.Message.Header;
393490
var result: ?[]const u8 = null;
394491

395-
const stdout = poller.fifo(.stdout);
492+
const stdout = zp.poller.fifo(.stdout);
396493

397494
poll: while (true) {
398495
while (stdout.readableLength() < @sizeOf(Header)) {
399-
if (!(try poller.poll())) break :poll;
496+
if (!(try zp.poller.poll())) break :poll;
400497
}
401498
const header = stdout.reader().readStruct(Header) catch unreachable;
402499
while (stdout.readableLength() < header.bytes_len) {
403-
if (!(try poller.poll())) break :poll;
500+
if (!(try zp.poller.poll())) break :poll;
404501
}
405502
const body = stdout.readableSliceOfLen(header.bytes_len);
406503

@@ -428,12 +525,22 @@ pub fn evalZigProcess(
428525
.string_bytes = try arena.dupe(u8, string_bytes),
429526
.extra = extra_array,
430527
};
528+
if (watch) {
529+
// This message indicates the end of the update.
530+
stdout.discard(body.len);
531+
break;
532+
}
431533
},
432534
.emit_bin_path => {
433535
const EbpHdr = std.zig.Server.Message.EmitBinPath;
434536
const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
435537
s.result_cached = ebp_hdr.flags.cache_hit;
436538
result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
539+
if (watch) {
540+
// This message indicates the end of the update.
541+
stdout.discard(body.len);
542+
break;
543+
}
437544
},
438545
.file_system_inputs => {
439546
s.clearWatchInputs();
@@ -470,6 +577,13 @@ pub fn evalZigProcess(
470577
};
471578
try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
472579
},
580+
.global_cache => {
581+
const path: Build.Cache.Path = .{
582+
.root_dir = s.owner.graph.global_cache_root,
583+
.sub_path = sub_path_dirname,
584+
};
585+
try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
586+
},
473587
}
474588
}
475589
},
@@ -479,43 +593,42 @@ pub fn evalZigProcess(
479593
stdout.discard(body.len);
480594
}
481595

482-
const stderr = poller.fifo(.stderr);
596+
s.result_duration_ns = timer.read();
597+
598+
const stderr = zp.poller.fifo(.stderr);
483599
if (stderr.readableLength() > 0) {
484600
try s.result_error_msgs.append(arena, try stderr.toOwnedSlice());
485601
}
486602

487-
// Send EOF to stdin.
488-
child.stdin.?.close();
489-
child.stdin = null;
603+
return result;
604+
}
490605

491-
const term = child.wait() catch |err| {
492-
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
493-
};
494-
s.result_duration_ns = timer.read();
495-
s.result_peak_rss = child.resource_usage_statistics.getMaxRss() orelse 0;
496-
497-
// Special handling for Compile step that is expecting compile errors.
498-
if (s.cast(Compile)) |compile| switch (term) {
499-
.Exited => {
500-
// Note that the exit code may be 0 in this case due to the
501-
// compiler server protocol.
502-
if (compile.expect_errors != null) {
503-
return error.NeedCompileErrorCheck;
504-
}
505-
},
506-
else => {},
606+
pub fn getZigProcess(s: *Step) ?*ZigProcess {
607+
return switch (s.id) {
608+
.compile => s.cast(Compile).?.zig_process,
609+
else => null,
507610
};
611+
}
508612

509-
try handleChildProcessTerm(s, term, null, argv);
510-
511-
if (s.result_error_bundle.errorMessageCount() > 0) {
512-
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
513-
s.result_error_bundle.errorMessageCount(),
514-
try allocPrintCmd(arena, null, argv),
515-
});
613+
fn setZigProcess(s: *Step, zp: *ZigProcess) void {
614+
switch (s.id) {
615+
.compile => s.cast(Compile).?.zig_process = zp,
616+
else => unreachable,
516617
}
618+
}
517619

518-
return result;
620+
fn clearZigProcess(s: *Step) void {
621+
const gpa = s.owner.allocator;
622+
switch (s.id) {
623+
.compile => {
624+
const compile = s.cast(Compile).?;
625+
if (compile.zig_process) |zp| {
626+
gpa.destroy(zp);
627+
compile.zig_process = null;
628+
}
629+
},
630+
else => unreachable,
631+
}
519632
}
520633

521634
fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {

lib/std/Build/Step/CheckFile.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ pub fn setName(check_file: *CheckFile, name: []const u8) void {
4646
check_file.step.name = name;
4747
}
4848

49-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
50-
_ = prog_node;
49+
fn make(step: *Step, options: Step.MakeOptions) !void {
50+
_ = options;
5151
const b = step.owner;
5252
const check_file: *CheckFile = @fieldParentPtr("step", step);
5353
try step.singleUnchangingWatchInput(check_file.source);

lib/std/Build/Step/CheckObject.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,8 @@ pub fn checkComputeCompare(
550550
check_object.checks.append(check) catch @panic("OOM");
551551
}
552552

553-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
554-
_ = prog_node;
553+
fn make(step: *Step, make_options: Step.MakeOptions) !void {
554+
_ = make_options;
555555
const b = step.owner;
556556
const gpa = b.allocator;
557557
const check_object: *CheckObject = @fieldParentPtr("step", step);

0 commit comments

Comments
 (0)