Skip to content

Commit 11e7971

Browse files
committed
make zig compiler processes live across rebuilds
Changes the `make` function signature to take an options struct, which additionally includes `watch: bool`. I intentionally am not exposing this information to configure phase logic. Closes #20600
1 parent d404d8a commit 11e7971

23 files changed

+178
-84
lines changed

lib/compiler/build_runner.zig

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,11 @@ fn workerMakeOneStep(
10311031
const sub_prog_node = prog_node.start(s.name, 0);
10321032
defer sub_prog_node.end();
10331033

1034-
const make_result = s.make(sub_prog_node);
1034+
const make_result = s.make(.{
1035+
.progress_node = sub_prog_node,
1036+
.thread_pool = thread_pool,
1037+
.watch = run.watch,
1038+
});
10351039

10361040
// No matter the result, we want to display error/warning messages.
10371041
const show_compile_errors = !run.prominent_compile_errors and

lib/std/Build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,8 +1078,8 @@ pub fn getUninstallStep(b: *Build) *Step {
10781078
return &b.uninstall_tls.step;
10791079
}
10801080

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

lib/std/Build/Step.zig

Lines changed: 112 additions & 45 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,25 @@ 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+
365+
pub const StreamEnum = enum { stdout, stderr };
366+
};
367+
355368
/// Assumes that argv contains `--listen=-` and that the process being spawned
356369
/// is the zig compiler - the same version that compiled the build runner.
357370
pub fn evalZigProcess(
358371
s: *Step,
359372
argv: []const []const u8,
360373
prog_node: std.Progress.Node,
374+
watch: bool,
361375
) !?[]const u8 {
376+
if (s.getZigProcess()) |zp| {
377+
assert(watch);
378+
return zigProcessUpdate(s, zp, watch);
379+
}
362380
assert(argv.len != 0);
363381
const b = s.owner;
364382
const arena = b.allocator;
@@ -378,29 +396,76 @@ pub fn evalZigProcess(
378396
child.spawn() catch |err| return s.fail("unable to spawn {s}: {s}", .{
379397
argv[0], @errorName(err),
380398
});
381-
var timer = try std.time.Timer.start();
382399

383-
var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
384-
.stdout = child.stdout.?,
385-
.stderr = child.stderr.?,
386-
});
387-
defer poller.deinit();
400+
const zp = try arena.create(ZigProcess);
401+
zp.* = .{
402+
.child = child,
403+
.poller = std.io.poll(gpa, ZigProcess.StreamEnum, .{
404+
.stdout = child.stdout.?,
405+
.stderr = child.stderr.?,
406+
}),
407+
};
408+
if (watch) s.setZigProcess(zp);
409+
defer if (!watch) zp.poller.deinit();
410+
411+
const result = try zigProcessUpdate(s, zp, watch);
412+
413+
if (!watch) {
414+
// Send EOF to stdin.
415+
zp.child.stdin.?.close();
416+
zp.child.stdin = null;
417+
418+
const term = zp.child.wait() catch |err| {
419+
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
420+
};
421+
s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0;
422+
423+
// Special handling for Compile step that is expecting compile errors.
424+
if (s.cast(Compile)) |compile| switch (term) {
425+
.Exited => {
426+
// Note that the exit code may be 0 in this case due to the
427+
// compiler server protocol.
428+
if (compile.expect_errors != null) {
429+
return error.NeedCompileErrorCheck;
430+
}
431+
},
432+
else => {},
433+
};
434+
435+
try handleChildProcessTerm(s, term, null, argv);
436+
}
437+
438+
if (s.result_error_bundle.errorMessageCount() > 0) {
439+
return s.fail("the following command failed with {d} compilation errors:\n{s}", .{
440+
s.result_error_bundle.errorMessageCount(),
441+
try allocPrintCmd(arena, null, argv),
442+
});
443+
}
444+
445+
return result;
446+
}
388447

389-
try sendMessage(child.stdin.?, .update);
390-
try sendMessage(child.stdin.?, .exit);
448+
fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?[]const u8 {
449+
const b = s.owner;
450+
const arena = b.allocator;
451+
452+
var timer = try std.time.Timer.start();
453+
454+
try sendMessage(zp.child.stdin.?, .update);
455+
if (!watch) try sendMessage(zp.child.stdin.?, .exit);
391456

392457
const Header = std.zig.Server.Message.Header;
393458
var result: ?[]const u8 = null;
394459

395-
const stdout = poller.fifo(.stdout);
460+
const stdout = zp.poller.fifo(.stdout);
396461

397462
poll: while (true) {
398463
while (stdout.readableLength() < @sizeOf(Header)) {
399-
if (!(try poller.poll())) break :poll;
464+
if (!(try zp.poller.poll())) break :poll;
400465
}
401466
const header = stdout.reader().readStruct(Header) catch unreachable;
402467
while (stdout.readableLength() < header.bytes_len) {
403-
if (!(try poller.poll())) break :poll;
468+
if (!(try zp.poller.poll())) break :poll;
404469
}
405470
const body = stdout.readableSliceOfLen(header.bytes_len);
406471

@@ -428,12 +493,22 @@ pub fn evalZigProcess(
428493
.string_bytes = try arena.dupe(u8, string_bytes),
429494
.extra = extra_array,
430495
};
496+
if (watch) {
497+
// This message indicates the end of the update.
498+
stdout.discard(body.len);
499+
break;
500+
}
431501
},
432502
.emit_bin_path => {
433503
const EbpHdr = std.zig.Server.Message.EmitBinPath;
434504
const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
435505
s.result_cached = ebp_hdr.flags.cache_hit;
436506
result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
507+
if (watch) {
508+
// This message indicates the end of the update.
509+
stdout.discard(body.len);
510+
break;
511+
}
437512
},
438513
.file_system_inputs => {
439514
s.clearWatchInputs();
@@ -470,6 +545,13 @@ pub fn evalZigProcess(
470545
};
471546
try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
472547
},
548+
.global_cache => {
549+
const path: Build.Cache.Path = .{
550+
.root_dir = s.owner.graph.global_cache_root,
551+
.sub_path = sub_path_dirname,
552+
};
553+
try addWatchInputFromPath(s, path, std.fs.path.basename(sub_path));
554+
},
473555
}
474556
}
475557
},
@@ -479,43 +561,28 @@ pub fn evalZigProcess(
479561
stdout.discard(body.len);
480562
}
481563

482-
const stderr = poller.fifo(.stderr);
564+
s.result_duration_ns = timer.read();
565+
566+
const stderr = zp.poller.fifo(.stderr);
483567
if (stderr.readableLength() > 0) {
484568
try s.result_error_msgs.append(arena, try stderr.toOwnedSlice());
485569
}
486570

487-
// Send EOF to stdin.
488-
child.stdin.?.close();
489-
child.stdin = null;
571+
return result;
572+
}
490573

491-
const term = child.wait() catch |err| {
492-
return s.fail("unable to wait for {s}: {s}", .{ argv[0], @errorName(err) });
574+
fn getZigProcess(s: *Step) ?*ZigProcess {
575+
return switch (s.id) {
576+
.compile => s.cast(Compile).?.zig_process,
577+
else => null,
493578
};
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 => {},
507-
};
508-
509-
try handleChildProcessTerm(s, term, null, argv);
579+
}
510580

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-
});
581+
fn setZigProcess(s: *Step, zp: *ZigProcess) void {
582+
switch (s.id) {
583+
.compile => s.cast(Compile).?.zig_process = zp,
584+
else => unreachable,
516585
}
517-
518-
return result;
519586
}
520587

521588
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);

lib/std/Build/Step/Compile.zig

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ is_linking_libcpp: bool = false,
213213

214214
no_builtin: bool = false,
215215

216+
/// Populated during the make phase when there is a long-lived compiler process.
217+
/// Managed by the build runner, not user build script.
218+
zig_process: ?*Step.ZigProcess,
219+
216220
pub const ExpectedCompileErrors = union(enum) {
217221
contains: []const u8,
218222
exact: []const []const u8,
@@ -398,6 +402,8 @@ pub fn create(owner: *std.Build, options: Options) *Compile {
398402

399403
.use_llvm = options.use_llvm,
400404
.use_lld = options.use_lld,
405+
406+
.zig_process = null,
401407
};
402408

403409
compile.root_module.init(owner, options.root_module, compile);
@@ -1735,13 +1741,17 @@ fn getZigArgs(compile: *Compile) ![][]const u8 {
17351741
return try zig_args.toOwnedSlice();
17361742
}
17371743

1738-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
1744+
fn make(step: *Step, options: Step.MakeOptions) !void {
17391745
const b = step.owner;
17401746
const compile: *Compile = @fieldParentPtr("step", step);
17411747

17421748
const zig_args = try getZigArgs(compile);
17431749

1744-
const maybe_output_bin_path = step.evalZigProcess(zig_args, prog_node) catch |err| switch (err) {
1750+
const maybe_output_bin_path = step.evalZigProcess(
1751+
zig_args,
1752+
options.progress_node,
1753+
options.watch,
1754+
) catch |err| switch (err) {
17451755
error.NeedCompileErrorCheck => {
17461756
assert(compile.expect_errors != null);
17471757
try checkCompileErrors(compile);

lib/std/Build/Step/ConfigHeader.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ fn putValue(config_header: *ConfigHeader, field_name: []const u8, comptime T: ty
164164
}
165165
}
166166

167-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
168-
_ = prog_node;
167+
fn make(step: *Step, options: Step.MakeOptions) !void {
168+
_ = options;
169169
const b = step.owner;
170170
const config_header: *ConfigHeader = @fieldParentPtr("step", step);
171171
if (config_header.style.getPath()) |lp| try step.singleUnchangingWatchInput(lp);

lib/std/Build/Step/Fail.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ pub fn create(owner: *std.Build, error_msg: []const u8) *Fail {
2424
return fail;
2525
}
2626

27-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
28-
_ = prog_node; // No progress to report.
27+
fn make(step: *Step, options: Step.MakeOptions) !void {
28+
_ = options; // No progress to report.
2929

3030
const fail: *Fail = @fieldParentPtr("step", step);
3131

lib/std/Build/Step/Fmt.zig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ pub fn create(owner: *std.Build, options: Options) *Fmt {
3636
return fmt;
3737
}
3838

39-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
39+
fn make(step: *Step, options: Step.MakeOptions) !void {
40+
const prog_node = options.progress_node;
41+
4042
// TODO: if check=false, this means we are modifying source files in place, which
4143
// is an operation that could race against other operations also modifying source files
4244
// in place. In this case, this step should obtain a write lock while making those

lib/std/Build/Step/InstallArtifact.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ pub fn create(owner: *std.Build, artifact: *Step.Compile, options: Options) *Ins
115115
return install_artifact;
116116
}
117117

118-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
119-
_ = prog_node;
118+
fn make(step: *Step, options: Step.MakeOptions) !void {
119+
_ = options;
120120
const install_artifact: *InstallArtifact = @fieldParentPtr("step", step);
121121
const b = step.owner;
122122
const cwd = fs.cwd();

lib/std/Build/Step/InstallDir.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ pub fn create(owner: *std.Build, options: Options) *InstallDir {
5555
return install_dir;
5656
}
5757

58-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
59-
_ = prog_node;
58+
fn make(step: *Step, options: Step.MakeOptions) !void {
59+
_ = options;
6060
const b = step.owner;
6161
const install_dir: *InstallDir = @fieldParentPtr("step", step);
6262
step.clearWatchInputs();

lib/std/Build/Step/InstallFile.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ pub fn create(
3535
return install_file;
3636
}
3737

38-
fn make(step: *Step, prog_node: std.Progress.Node) !void {
39-
_ = prog_node;
38+
fn make(step: *Step, options: Step.MakeOptions) !void {
39+
_ = options;
4040
const b = step.owner;
4141
const install_file: *InstallFile = @fieldParentPtr("step", step);
4242
try step.singleUnchangingWatchInput(install_file.source);

0 commit comments

Comments
 (0)