zig/lib/std/Build/Step.zig
Andrew Kelley 986a30e373 integrate the build runner and the compiler server
The compiler now provides a server protocol for an interactive session
with another process. The build runner uses this protocol to communicate
compilation errors semantically from zig compiler subprocesses to the
build runner.

The protocol is exposed via stdin/stdout, or on a network socket,
depending on whether the CLI flag `--listen=-` or e.g.
`--listen=127.0.0.1:1337` is used.

Additionally:

 * add the zig version string to the build runner cache prefix

 * remove --prominent-compile-errors CLI flag because it no longer does
   anything. Compilation errors are now unconditionally displayed at the
   bottom of the build summary output when using the terminal-based
   build runner.

 * Remove the color field from std.Build. The build steps are no longer
   supposed to interact with stderr directly. Instead they communicate
   semantically back to the build runner, which has its own logic about
   TTY configuration.

 * Use the cleanExit() pattern in the build runner.

 * Build steps can now use error.MakeFailed when they have already
   properly reported an error, or they can fail with any other error
   code in which case the build runner will create a simple message
   based on this error code.
2023-03-15 10:48:13 -07:00

169 lines
5.1 KiB
Zig

id: Id,
name: []const u8,
makeFn: *const fn (self: *Step) anyerror!void,
dependencies: std.ArrayList(*Step),
/// This field is empty during execution of the user's build script, and
/// then populated during dependency loop checking in the build runner.
dependants: std.ArrayListUnmanaged(*Step),
state: State,
/// The return addresss associated with creation of this step that can be useful
/// to print along with debugging messages.
debug_stack_trace: [n_debug_stack_frames]usize,
result_error_msgs: std.ArrayListUnmanaged([]const u8),
result_error_bundle: std.zig.ErrorBundle,
const n_debug_stack_frames = 4;
pub const State = enum {
precheck_unstarted,
precheck_started,
precheck_done,
running,
dependency_failure,
success,
failure,
};
pub const Id = enum {
top_level,
compile,
install_artifact,
install_file,
install_dir,
log,
remove_dir,
fmt,
translate_c,
write_file,
run,
emulatable_run,
check_file,
check_object,
config_header,
objcopy,
options,
custom,
pub fn Type(comptime id: Id) type {
return switch (id) {
.top_level => Build.TopLevelStep,
.compile => Build.CompileStep,
.install_artifact => Build.InstallArtifactStep,
.install_file => Build.InstallFileStep,
.install_dir => Build.InstallDirStep,
.log => Build.LogStep,
.remove_dir => Build.RemoveDirStep,
.fmt => Build.FmtStep,
.translate_c => Build.TranslateCStep,
.write_file => Build.WriteFileStep,
.run => Build.RunStep,
.emulatable_run => Build.EmulatableRunStep,
.check_file => Build.CheckFileStep,
.check_object => Build.CheckObjectStep,
.config_header => Build.ConfigHeaderStep,
.objcopy => Build.ObjCopyStep,
.options => Build.OptionsStep,
.custom => @compileError("no type available for custom step"),
};
}
};
pub const Options = struct {
id: Id,
name: []const u8,
makeFn: *const fn (self: *Step) anyerror!void = makeNoOp,
first_ret_addr: ?usize = null,
};
pub fn init(allocator: Allocator, options: Options) Step {
var addresses = [1]usize{0} ** n_debug_stack_frames;
const first_ret_addr = options.first_ret_addr orelse @returnAddress();
var stack_trace = std.builtin.StackTrace{
.instruction_addresses = &addresses,
.index = 0,
};
std.debug.captureStackTrace(first_ret_addr, &stack_trace);
return .{
.id = options.id,
.name = allocator.dupe(u8, options.name) catch @panic("OOM"),
.makeFn = options.makeFn,
.dependencies = std.ArrayList(*Step).init(allocator),
.dependants = .{},
.state = .precheck_unstarted,
.debug_stack_trace = addresses,
.result_error_msgs = .{},
.result_error_bundle = std.zig.ErrorBundle.empty,
};
}
/// If the Step's `make` function reports `error.MakeFailed`, it indicates they
/// have already reported the error. Otherwise, we add a simple error report
/// here.
pub fn make(s: *Step) error{MakeFailed}!void {
return s.makeFn(s) catch |err| {
if (err != error.MakeFailed) {
const gpa = s.dependencies.allocator;
s.result_error_msgs.append(gpa, std.fmt.allocPrint(gpa, "{s} failed: {s}", .{
s.name, @errorName(err),
}) catch @panic("OOM")) catch @panic("OOM");
}
return error.MakeFailed;
};
}
pub fn dependOn(self: *Step, other: *Step) void {
self.dependencies.append(other) catch @panic("OOM");
}
pub fn getStackTrace(s: *Step) std.builtin.StackTrace {
const stack_addresses = &s.debug_stack_trace;
var len: usize = 0;
while (len < n_debug_stack_frames and stack_addresses[len] != 0) {
len += 1;
}
return .{
.instruction_addresses = stack_addresses,
.index = len,
};
}
fn makeNoOp(self: *Step) anyerror!void {
_ = self;
}
pub fn cast(step: *Step, comptime T: type) ?*T {
if (step.id == T.base_id) {
return @fieldParentPtr(T, "step", step);
}
return null;
}
/// For debugging purposes, prints identifying information about this Step.
pub fn dump(step: *Step) void {
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const stderr = std.io.getStdErr();
const w = stderr.writer();
const tty_config = std.debug.detectTTYConfig(stderr);
const debug_info = std.debug.getSelfDebugInfo() catch |err| {
w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
@errorName(err),
}) catch {};
return;
};
const ally = debug_info.allocator;
w.print("name: '{s}'. creation stack trace:\n", .{step.name}) catch {};
std.debug.writeStackTrace(step.getStackTrace(), w, ally, debug_info, tty_config) catch |err| {
stderr.writer().print("Unable to dump stack trace: {s}\n", .{@errorName(err)}) catch {};
return;
};
}
const Step = @This();
const std = @import("../std.zig");
const Build = std.Build;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;